diff --git a/server/bindata.go b/server/bindata.go index bf3c03639259752681498fc2a8dff7a64584feee..7e9bbcceea385cad1ec65a89c5ae34a86407b9f2 100644 --- a/server/bindata.go +++ b/server/bindata.go @@ -74,7 +74,7 @@ func staticCssBootstrapMinCss() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "static/css/bootstrap.min.css", size: 140936, mode: os.FileMode(420), modTime: time.Unix(1560696660, 0)} + info := bindataFileInfo{name: "static/css/bootstrap.min.css", size: 140936, mode: os.FileMode(420), modTime: time.Unix(1550305824, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -147,7 +147,7 @@ func staticCssSigninCss() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "static/css/signin.css", size: 1009, mode: os.FileMode(420), modTime: time.Unix(1560696660, 0)} + info := bindataFileInfo{name: "static/css/signin.css", size: 1009, mode: os.FileMode(420), modTime: time.Unix(1535013418, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -170,7 +170,7 @@ func staticJsBootstrap413MinJs() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "static/js/bootstrap-4.1.3.min.js", size: 51039, mode: os.FileMode(420), modTime: time.Unix(1560696660, 0)} + info := bindataFileInfo{name: "static/js/bootstrap-4.1.3.min.js", size: 51039, mode: os.FileMode(420), modTime: time.Unix(1550305766, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -189,7 +189,7 @@ func staticJsJquery331MinJs() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "static/js/jquery-3.3.1.min.js", size: 86927, mode: os.FileMode(420), modTime: time.Unix(1560696660, 0)} + info := bindataFileInfo{name: "static/js/jquery-3.3.1.min.js", size: 86927, mode: os.FileMode(420), modTime: time.Unix(1516469204, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -243,7 +243,7 @@ func staticJsLogoutJs() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "static/js/logout.js", size: 1005, mode: os.FileMode(420), modTime: time.Unix(1560696660, 0)} + info := bindataFileInfo{name: "static/js/logout.js", size: 1005, mode: os.FileMode(420), modTime: time.Unix(1535013418, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -265,7 +265,7 @@ func staticJsPopper1143MinJs() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "static/js/popper-1.14.3.min.js", size: 20337, mode: os.FileMode(420), modTime: time.Unix(1560696660, 0)} + info := bindataFileInfo{name: "static/js/popper-1.14.3.min.js", size: 20337, mode: os.FileMode(420), modTime: time.Unix(1526549114, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1030,7 +1030,7 @@ func staticJsU2fApiJs() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "static/js/u2f-api.js", size: 20880, mode: os.FileMode(420), modTime: time.Unix(1512325237, 0)} + info := bindataFileInfo{name: "static/js/u2f-api.js", size: 20880, mode: os.FileMode(420), modTime: time.Unix(1535013418, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1099,7 +1099,7 @@ func staticJsU2fJs() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "static/js/u2f.js", size: 1274, mode: os.FileMode(420), modTime: time.Unix(1560696660, 0)} + info := bindataFileInfo{name: "static/js/u2f.js", size: 1274, mode: os.FileMode(420), modTime: time.Unix(1541228751, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1154,7 +1154,7 @@ func templatesLogin_otpHtml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "templates/login_otp.html", size: 956, mode: os.FileMode(420), modTime: time.Unix(1561757406, 0)} + info := bindataFileInfo{name: "templates/login_otp.html", size: 956, mode: os.FileMode(420), modTime: time.Unix(1561884470, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1225,7 +1225,7 @@ func templatesLogin_passwordHtml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "templates/login_password.html", size: 1432, mode: os.FileMode(420), modTime: time.Unix(1561757427, 0)} + info := bindataFileInfo{name: "templates/login_password.html", size: 1432, mode: os.FileMode(420), modTime: time.Unix(1561884470, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1282,7 +1282,7 @@ func templatesLogin_u2fHtml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "templates/login_u2f.html", size: 908, mode: os.FileMode(420), modTime: time.Unix(1561757448, 0)} + info := bindataFileInfo{name: "templates/login_u2f.html", size: 908, mode: os.FileMode(420), modTime: time.Unix(1561884470, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1342,7 +1342,7 @@ func templatesLogoutHtml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "templates/logout.html", size: 1063, mode: os.FileMode(420), modTime: time.Unix(1561757528, 0)} + info := bindataFileInfo{name: "templates/logout.html", size: 1063, mode: os.FileMode(420), modTime: time.Unix(1548600535, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1353,8 +1353,8 @@ var _templatesPageHtml = []byte(`{{define "header"}}<!DOCTYPE html> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no"> {{if .U2FSignRequest}}<meta name="u2f_request" value="{{json .U2FSignRequest}}">{{end}} - <link rel="stylesheet" href="{{.URLPrefix}}/static/css/bootstrap.min.css" integrity="sha384-MCw98/SFnGE8fJT3GXwEOngsV7Zt27NXFoaoApmYm81iuXoPkFOJwJ8ERdknLPMO"> - <link rel="stylesheet" href="{{.URLPrefix}}/static/css/signin.css" integrity="sha384-9Y3UkAyM3svAuamEoaXIxe+1MqBKJdZtL8S1FZjvE1XqkICDH7DTXNavnFV8Uk2o"> + <link rel="stylesheet" href="{{.URLPrefix}}/static/css/bootstrap.min.css"{{SRI "/static/css/bootstrap.min.css"}}> + <link rel="stylesheet" href="{{.URLPrefix}}/static/css/signin.css"{{SRI "/static/css/signin.css"}}> {{if .SiteFavicon}}<link rel="icon" type="image/x-icon" href="{{.URLPrefix}}/favicon.ico">{{end}} <title>{{if .SiteName}}{{.SiteName}} - {{end}}Sign In</title> </head> @@ -1366,15 +1366,15 @@ var _templatesPageHtml = []byte(`{{define "header"}}<!DOCTYPE html> {{define "footer"}} </div> - <script src="{{.URLPrefix}}/static/js/jquery-3.3.1.min.js" integrity="sha384-tsQFqpEReu7ZLhBV2VZlAu7zcOV+rXbYlF2cqB8txI/8aZajjp4Bqd+V6D5IgvKT"></script> - <script src="{{.URLPrefix}}/static/js/popper-1.14.3.min.js" integrity="sha384-ZMP7rVo3mIykV+2+9J3UJ46jBk0WLaUAdn689aCwoqbBJiSnjAK/l8WvCWPIPm49"></script> - <script src="{{.URLPrefix}}/static/js/bootstrap-4.1.3.min.js" integrity="sha384-ChfqqxuZUCnJSK3+MXmPNIyE6ZbWh2IMqE241rYiqJxyMiZ6OW/JmZQ5stwEULTy"></script> + <script src="{{.URLPrefix}}/static/js/jquery-3.3.1.min.js"{{SRI "/static/js/jquery-3.3.1.min.js"}}></script> + <script src="{{.URLPrefix}}/static/js/popper-1.14.3.min.js"{{SRI "/static/js/popper-1.14.3.min.js"}}></script> + <script src="{{.URLPrefix}}/static/js/bootstrap-4.1.3.min.js"{{SRI "/static/js/bootstrap-4.1.3.min.js"}}></script> {{if .U2FSignRequest}} - <script src="{{.URLPrefix}}/static/js/u2f-api.js" integrity="sha384-9ChevE6pp8ArGK03HgolnFjZbF3webZQtYkwcabzbcI28Lx1/2x2j2fbaAWD4cgR"></script> - <script src="{{.URLPrefix}}/static/js/u2f.js" integrity="sha384-7zZy25ajTABErGlCQgcyRDpQDS9QVZv9o+95IfvCjWftQe20f411F1a39Ge5xmCe"></script> + <script src="{{.URLPrefix}}/static/js/u2f-api.js"{{SRI "/static/js/u2f-api.js"}}></script> + <script src="{{.URLPrefix}}/static/js/u2f.js"{{SRI "/static/js/u2f.js"}}></script> {{end}} {{if .IncludeLogoutScripts}} - <script src="{{.URLPrefix}}/static/js/logout.js" integrity="sha384-lChVngGLNFXetIJTSxc+scDpi1vsBL+7Xa4r2uZpQFP/6Y2z9eCDXe/Y4IUdklRD"></script> + <script src="{{.URLPrefix}}/static/js/logout.js"{{SRI "/static/js/logout.js"}}></script> {{end}} </body> </html> @@ -1391,7 +1391,7 @@ func templatesPageHtml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "templates/page.html", size: 1865, mode: os.FileMode(420), modTime: time.Unix(1561757493, 0)} + info := bindataFileInfo{name: "templates/page.html", size: 1476, mode: os.FileMode(420), modTime: time.Unix(1576422396, 0)} a := &asset{bytes: bytes, info: info} return a, nil } diff --git a/server/http.go b/server/http.go index f4412f1a9bb762ab1b90cb4cccb1b9cd1bef9587..cc299c23683548e6e0f71ebab900e77f1d385bf4 100644 --- a/server/http.go +++ b/server/http.go @@ -1,6 +1,6 @@ package server -//go:generate python sri.py templates/*.html +//go:generate go run scripts/sri.go --package server --output sri_map.go static //go:generate go-bindata --nocompress --pkg server static/... templates/... import ( @@ -362,6 +362,7 @@ func (h *Server) Handler() http.Handler { func parseEmbeddedTemplates() *template.Template { root := template.New("").Funcs(template.FuncMap{ "json": toJSON, + "SRI": sriIntegrity, }) files, err := AssetDir("templates") if err != nil { @@ -422,3 +423,13 @@ func intersectGroups(a, b []string) []string { } return out } + +// Return an integrity= attribute for the given URI (which should be +// supplied without an eventual prefix). +func sriIntegrity(uri string) template.HTML { + sri, ok := sriMap[uri] + if !ok { + return template.HTML("") + } + return template.HTML(fmt.Sprintf(" integrity=\"%s\"", sri)) +} diff --git a/server/scripts/sri.go b/server/scripts/sri.go new file mode 100644 index 0000000000000000000000000000000000000000..251ddddb768a534d2a4e09d27d751b8234a28bc0 --- /dev/null +++ b/server/scripts/sri.go @@ -0,0 +1,94 @@ +package main + +import ( + "crypto/sha512" + "encoding/base64" + "flag" + "fmt" + "io" + "io/ioutil" + "log" + "os" + "path/filepath" + "strings" +) + +var ( + outputPath = flag.String("output", "", "output `file` name") + packageName = flag.String("package", "", "`name` of the package for generated code") + stripPrefix = flag.String("strip", "", "prefix to strip from `path`s to generate URLs") +) + +func computeChecksum(path string) (string, error) { + data, err := ioutil.ReadFile(path) + if err != nil { + return "", err + } + sha := sha512.Sum384(data) + return "sha384-" + base64.StdEncoding.EncodeToString(sha[:]), nil +} + +func urlForPath(path string) string { + path = strings.TrimPrefix(path, *stripPrefix) + if !strings.HasPrefix(path, "/") { + path = "/" + path + } + return path +} + +func mkSRIMap(m map[string]string, dir string) error { + return filepath.Walk(dir, func(path string, info os.FileInfo, err error) error { + // Only match files with the desired extensions. + if info.IsDir() || !match(info.Name()) { + return nil + } + + if cksum, err := computeChecksum(path); err == nil { + m[urlForPath(path)] = cksum + } + return nil + }) +} + +func match(name string) bool { + switch filepath.Ext(name) { + case ".js", ".json", ".css": + return true + default: + return false + } +} + +// nolint: errcheck +func codegen(w io.Writer, m map[string]string) { + fmt.Fprintf(w, "package %s\n", *packageName) + io.WriteString(w, ` +var sriMap = map[string]string{ +`) + for k, v := range m { + fmt.Fprintf(w, "\t%q: %q,\n", k, v) + } + io.WriteString(w, "}\n") +} + +func main() { + log.SetFlags(0) + flag.Parse() + + m := make(map[string]string) + for _, path := range flag.Args() { + if err := mkSRIMap(m, path); err != nil { + log.Println(err) + } + } + + var w io.Writer = os.Stdout + if *outputPath != "" { + var err error + w, err = os.Create(*outputPath) + if err != nil { + log.Fatal(err) + } + } + codegen(w, m) +} diff --git a/server/sri.py b/server/sri.py deleted file mode 100755 index 725dd07c6a5ffdbdcd04743aa1d41289130d94f4..0000000000000000000000000000000000000000 --- a/server/sri.py +++ /dev/null @@ -1,54 +0,0 @@ -#!/usr/bin/python -# -# Automatically fix Subresource Integrity links in the HTML templates. -# -# Pass templates as command-line arguments. Expects to be run from the -# base resource directory. -# - -import glob -import re -import sys -from hashlib import sha384 - - -script_rx = re.compile(r'<(?:script|link rel="stylesheet")[^>]*(?:src|href)="(?:{{.URLPrefix}})?([^"]+)"[^>]*>') -integrity_rx = re.compile(r' +integrity="[^"]*"') - - -def compute_checksum(src): - if src[0] == '/': - src = src[1:] - with open(src) as fd: - return 'sha384-' + sha384(fd.read()).digest().encode('base64').strip() - - -def replace_checksum(m): - src = m.group(1) - checksum = compute_checksum(src) - - script = m.group(0) - script = integrity_rx.sub('', script) - script = '%s integrity="%s">' % (script[:-1], checksum) - - return script - - -def fix_sri(path): - with open(path) as fd: - data = fd.read() - result = script_rx.sub(replace_checksum, data) - if result != data: - print >>sys.stderr, 'updating %s' % path - with open(path, 'w') as fd: - fd.write(result) - - -if __name__ == '__main__': - for arg in sys.argv[1:]: - for path in glob.glob(arg): - try: - fix_sri(path) - except Exception as e: - print >>sys.stderr, "Error fixing %s: %s" % (path, e) - diff --git a/server/sri_map.go b/server/sri_map.go new file mode 100644 index 0000000000000000000000000000000000000000..9fb8c6dcd4649f467d4982b55009d78647142196 --- /dev/null +++ b/server/sri_map.go @@ -0,0 +1,12 @@ +package server + +var sriMap = map[string]string{ + "/static/css/bootstrap.min.css": "sha384-MCw98/SFnGE8fJT3GXwEOngsV7Zt27NXFoaoApmYm81iuXoPkFOJwJ8ERdknLPMO", + "/static/css/signin.css": "sha384-9Y3UkAyM3svAuamEoaXIxe+1MqBKJdZtL8S1FZjvE1XqkICDH7DTXNavnFV8Uk2o", + "/static/js/bootstrap-4.1.3.min.js": "sha384-ChfqqxuZUCnJSK3+MXmPNIyE6ZbWh2IMqE241rYiqJxyMiZ6OW/JmZQ5stwEULTy", + "/static/js/jquery-3.3.1.min.js": "sha384-tsQFqpEReu7ZLhBV2VZlAu7zcOV+rXbYlF2cqB8txI/8aZajjp4Bqd+V6D5IgvKT", + "/static/js/logout.js": "sha384-lChVngGLNFXetIJTSxc+scDpi1vsBL+7Xa4r2uZpQFP/6Y2z9eCDXe/Y4IUdklRD", + "/static/js/popper-1.14.3.min.js": "sha384-ZMP7rVo3mIykV+2+9J3UJ46jBk0WLaUAdn689aCwoqbBJiSnjAK/l8WvCWPIPm49", + "/static/js/u2f-api.js": "sha384-9ChevE6pp8ArGK03HgolnFjZbF3webZQtYkwcabzbcI28Lx1/2x2j2fbaAWD4cgR", + "/static/js/u2f.js": "sha384-7zZy25ajTABErGlCQgcyRDpQDS9QVZv9o+95IfvCjWftQe20f411F1a39Ge5xmCe", +} diff --git a/server/templates/page.html b/server/templates/page.html index 18401f5c983b6ae622554e8a5fa17452f16ab396..e1ec50fe7730f505471cfa1da9285590208fd647 100644 --- a/server/templates/page.html +++ b/server/templates/page.html @@ -4,8 +4,8 @@ <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no"> {{if .U2FSignRequest}}<meta name="u2f_request" value="{{json .U2FSignRequest}}">{{end}} - <link rel="stylesheet" href="{{.URLPrefix}}/static/css/bootstrap.min.css" integrity="sha384-MCw98/SFnGE8fJT3GXwEOngsV7Zt27NXFoaoApmYm81iuXoPkFOJwJ8ERdknLPMO"> - <link rel="stylesheet" href="{{.URLPrefix}}/static/css/signin.css" integrity="sha384-9Y3UkAyM3svAuamEoaXIxe+1MqBKJdZtL8S1FZjvE1XqkICDH7DTXNavnFV8Uk2o"> + <link rel="stylesheet" href="{{.URLPrefix}}/static/css/bootstrap.min.css"{{SRI "/static/css/bootstrap.min.css"}}> + <link rel="stylesheet" href="{{.URLPrefix}}/static/css/signin.css"{{SRI "/static/css/signin.css"}}> {{if .SiteFavicon}}<link rel="icon" type="image/x-icon" href="{{.URLPrefix}}/favicon.ico">{{end}} <title>{{if .SiteName}}{{.SiteName}} - {{end}}Sign In</title> </head> @@ -17,15 +17,15 @@ {{define "footer"}} </div> - <script src="{{.URLPrefix}}/static/js/jquery-3.3.1.min.js" integrity="sha384-tsQFqpEReu7ZLhBV2VZlAu7zcOV+rXbYlF2cqB8txI/8aZajjp4Bqd+V6D5IgvKT"></script> - <script src="{{.URLPrefix}}/static/js/popper-1.14.3.min.js" integrity="sha384-ZMP7rVo3mIykV+2+9J3UJ46jBk0WLaUAdn689aCwoqbBJiSnjAK/l8WvCWPIPm49"></script> - <script src="{{.URLPrefix}}/static/js/bootstrap-4.1.3.min.js" integrity="sha384-ChfqqxuZUCnJSK3+MXmPNIyE6ZbWh2IMqE241rYiqJxyMiZ6OW/JmZQ5stwEULTy"></script> + <script src="{{.URLPrefix}}/static/js/jquery-3.3.1.min.js"{{SRI "/static/js/jquery-3.3.1.min.js"}}></script> + <script src="{{.URLPrefix}}/static/js/popper-1.14.3.min.js"{{SRI "/static/js/popper-1.14.3.min.js"}}></script> + <script src="{{.URLPrefix}}/static/js/bootstrap-4.1.3.min.js"{{SRI "/static/js/bootstrap-4.1.3.min.js"}}></script> {{if .U2FSignRequest}} - <script src="{{.URLPrefix}}/static/js/u2f-api.js" integrity="sha384-9ChevE6pp8ArGK03HgolnFjZbF3webZQtYkwcabzbcI28Lx1/2x2j2fbaAWD4cgR"></script> - <script src="{{.URLPrefix}}/static/js/u2f.js" integrity="sha384-7zZy25ajTABErGlCQgcyRDpQDS9QVZv9o+95IfvCjWftQe20f411F1a39Ge5xmCe"></script> + <script src="{{.URLPrefix}}/static/js/u2f-api.js"{{SRI "/static/js/u2f-api.js"}}></script> + <script src="{{.URLPrefix}}/static/js/u2f.js"{{SRI "/static/js/u2f.js"}}></script> {{end}} {{if .IncludeLogoutScripts}} - <script src="{{.URLPrefix}}/static/js/logout.js" integrity="sha384-lChVngGLNFXetIJTSxc+scDpi1vsBL+7Xa4r2uZpQFP/6Y2z9eCDXe/Y4IUdklRD"></script> + <script src="{{.URLPrefix}}/static/js/logout.js"{{SRI "/static/js/logout.js"}}></script> {{end}} </body> </html>