diff --git a/Godeps/Godeps.json b/Godeps/Godeps.json index a7508b4..7763c31 100644 --- a/Godeps/Godeps.json +++ b/Godeps/Godeps.json @@ -1,8 +1,20 @@ { "ImportPath": "github.com/GoASTScanner/gas", "GoVersion": "go1.9", - "GodepVersion": "v79", + "GodepVersion": "v80", + "Packages": [ + "./..." + ], "Deps": [ + { + "ImportPath": "github.com/kisielk/gotool", + "Rev": "0de1eaf82fa3f583ce21fde859f1e7e0c5e9b220" + }, + { + "ImportPath": "github.com/mozilla/tls-observatory/constants", + "Comment": "1.2.32-17-g17e0ce4b", + "Rev": "17e0ce4bfc46eae3d57acf13a2d7c7517655d493" + }, { "ImportPath": "github.com/nbutton23/zxcvbn-go", "Rev": "a22cb81b2ecdde8b68e9ffb8824731cbf88e1de4" @@ -189,6 +201,11 @@ "Comment": "v1.2.0-2-gdcabb60", "Rev": "dcabb60a477c2b6f456df65037cb6708210fbb02" }, + { + "ImportPath": "github.com/ryanuber/go-glob", + "Comment": "v0.1-4-g256dc44", + "Rev": "256dc444b735e061061cf46c809487313d5b0065" + }, { "ImportPath": "golang.org/x/net/html", "Rev": "8351a756f30f1297fe94bbf4b767ec589c6ea6d0" diff --git a/README.md b/README.md index 1c9a1e4..7dace9e 100644 --- a/README.md +++ b/README.md @@ -114,3 +114,21 @@ file. The output format is controlled by the '-fmt' flag, and the output file is $ gas -fmt=json -out=results.json *.go ``` +### Generate TLS rule + +The configuration of TLS rule can be generated from [Mozilla's TLS ciphers recommendation](https://statics.tls.security.mozilla.org/server-side-tls-conf.json). + + +First you need to install the generator tool: + +``` +go get github.com/GoASTScanner/gas/cmd/tlsconfig/... +``` + +You can invoke now the `go generate` in the root of the project: + +``` +go generate ./... +``` + +This will generate the `rules/tls_config.go` file with will contain the current ciphers recommendation from Mozilla. diff --git a/cmd/tlsconfig/header_template.go b/cmd/tlsconfig/header_template.go new file mode 100644 index 0000000..618221b --- /dev/null +++ b/cmd/tlsconfig/header_template.go @@ -0,0 +1,13 @@ +package main + +import "text/template" + +var generatedHeaderTmpl = template.Must(template.New("generated").Parse(` +package {{.}} + +import ( + "go/ast" + + "github.com/GoASTScanner/gas" +) +`)) diff --git a/cmd/tlsconfig/rule_template.go b/cmd/tlsconfig/rule_template.go new file mode 100644 index 0000000..05ed831 --- /dev/null +++ b/cmd/tlsconfig/rule_template.go @@ -0,0 +1,19 @@ +package main + +import "text/template" + +var generatedRuleTmpl = template.Must(template.New("generated").Parse(` +// New{{.Name}}TLSCheck creates a check for {{.Name}} TLS ciphers +// DO NOT EDIT - generated by tlsconfig tool +func New{{.Name}}TLSCheck(conf gas.Config) (gas.Rule, []ast.Node) { + return &insecureConfigTLS{ + requiredType: "crypto/tls.Config", + MinVersion: {{ .MinVersion }}, + MaxVersion: {{ .MaxVersion }}, + goodCiphers: []string{ +{{range $cipherName := .Ciphers }} "{{$cipherName}}", +{{end}} + }, + }, []ast.Node{(*ast.CompositeLit)(nil)} +} +`)) diff --git a/cmd/tlsconfig/tlsconfig.go b/cmd/tlsconfig/tlsconfig.go new file mode 100644 index 0000000..90a324a --- /dev/null +++ b/cmd/tlsconfig/tlsconfig.go @@ -0,0 +1,204 @@ +package main + +import ( + "bytes" + "crypto/tls" + "encoding/json" + "errors" + "flag" + "fmt" + "go/format" + "io/ioutil" + "log" + "net/http" + "path/filepath" + "sort" + "strings" + + "github.com/mozilla/tls-observatory/constants" +) + +var ( + pkg = flag.String("pkg", "rules", "package name to be added to the output file") + outputFile = flag.String("outputFile", "tls_config.go", "name of the output file") +) + +// TLSConfURL url where Mozilla publishes the TLS ciphers recommendations +const TLSConfURL = "https://statics.tls.security.mozilla.org/server-side-tls-conf.json" + +// ServerSideTLSJson contains all the available configurations and the version of the current document. +type ServerSideTLSJson struct { + Configurations map[string]Configuration `json:"configurations"` + Version float64 `json:"version"` +} + +// Configuration represents configurations levels declared by the Mozilla server-side-tls +// see https://wiki.mozilla.org/Security/Server_Side_TLS +type Configuration struct { + OpenSSLCiphersuites string `json:"openssl_ciphersuites"` + Ciphersuites []string `json:"ciphersuites"` + TLSVersions []string `json:"tls_versions"` + TLSCurves []string `json:"tls_curves"` + CertificateTypes []string `json:"certificate_types"` + CertificateCurves []string `json:"certificate_curves"` + CertificateSignatures []string `json:"certificate_signatures"` + RsaKeySize float64 `json:"rsa_key_size"` + DHParamSize float64 `json:"dh_param_size"` + ECDHParamSize float64 `json:"ecdh_param_size"` + HstsMinAge float64 `json:"hsts_min_age"` + OldestClients []string `json:"oldest_clients"` +} + +type goCipherConfiguration struct { + Name string + Ciphers []string + MinVersion string + MaxVersion string +} + +type goTLSConfiguration struct { + cipherConfigs []goCipherConfiguration +} + +// getTLSConfFromURL retrieves the json containing the TLS configurations from the specified URL. +func getTLSConfFromURL(url string) (*ServerSideTLSJson, error) { + r, err := http.Get(url) + if err != nil { + return nil, err + } + defer r.Body.Close() + + var sstls ServerSideTLSJson + err = json.NewDecoder(r.Body).Decode(&sstls) + if err != nil { + return nil, err + } + + return &sstls, nil +} + +func getGoCipherConfig(name string, sstls ServerSideTLSJson) (goCipherConfiguration, error) { + cipherConf := goCipherConfiguration{Name: strings.Title(name)} + conf, ok := sstls.Configurations[name] + if !ok { + return cipherConf, fmt.Errorf("TLS configuration '%s' not found", name) + } + + for _, cipherName := range conf.Ciphersuites { + cipherSuite, ok := constants.CipherSuites[cipherName] + if !ok { + log.Printf("Warning: cannot map cipher '%s'\n", cipherName) + } + if len(cipherSuite.IANAName) > 0 { + cipherConf.Ciphers = append(cipherConf.Ciphers, cipherSuite.IANAName) + } + } + + versions := mapTLSVersions(conf.TLSVersions) + if len(versions) > 0 { + cipherConf.MinVersion = fmt.Sprintf("0x%04x", versions[0]) + cipherConf.MaxVersion = fmt.Sprintf("0x%04x", versions[len(versions)-1]) + } else { + return cipherConf, fmt.Errorf("No TLS versions found for configuration '%s'", name) + } + return cipherConf, nil +} + +func mapTLSVersions(tlsVersions []string) []int { + var versions []int + for _, tlsVersion := range tlsVersions { + switch tlsVersion { + case "TLSv1.2": + versions = append(versions, tls.VersionTLS12) + case "TLSv1.1": + versions = append(versions, tls.VersionTLS11) + case "TLSv1": + versions = append(versions, tls.VersionTLS10) + case "SSLv3": + versions = append(versions, tls.VersionSSL30) + default: + continue + } + } + sort.Ints(versions) + return versions +} + +func getGoTLSConf() (goTLSConfiguration, error) { + sstls, err := getTLSConfFromURL(TLSConfURL) + if err != nil || sstls == nil { + msg := fmt.Sprintf("Could not load the Server Side TLS configuration from Mozilla's website. Check the URL: %s. Error: %v\n", + TLSConfURL, err) + panic(msg) + } + + tlsConfg := goTLSConfiguration{} + + modern, err := getGoCipherConfig("modern", *sstls) + if err != nil { + return tlsConfg, err + } + tlsConfg.cipherConfigs = append(tlsConfg.cipherConfigs, modern) + + intermediate, err := getGoCipherConfig("intermediate", *sstls) + if err != nil { + return tlsConfg, err + } + tlsConfg.cipherConfigs = append(tlsConfg.cipherConfigs, intermediate) + + old, err := getGoCipherConfig("old", *sstls) + if err != nil { + return tlsConfg, err + } + tlsConfg.cipherConfigs = append(tlsConfg.cipherConfigs, old) + + return tlsConfg, nil +} + +func getCurrentDir() (string, error) { + dir := "." + if args := flag.Args(); len(args) == 1 { + dir = args[0] + } else if len(args) > 1 { + return "", errors.New("only one directory at a time") + } + dir, err := filepath.Abs(dir) + if err != nil { + return "", err + } + return dir, nil +} + +func main() { + dir, err := getCurrentDir() + if err != nil { + log.Fatalln(err) + } + tlsConfig, err := getGoTLSConf() + if err != nil { + log.Fatalln(err) + } + + var buf bytes.Buffer + err = generatedHeaderTmpl.Execute(&buf, *pkg) + if err != nil { + log.Fatalf("Failed to generate the header: %v", err) + } + for _, cipherConfig := range tlsConfig.cipherConfigs { + err := generatedRuleTmpl.Execute(&buf, cipherConfig) + if err != nil { + log.Fatalf("Failed to generated the cipher config: %v", err) + } + } + + src, err := format.Source(buf.Bytes()) + if err != nil { + log.Printf("warnings: Failed to format the code: %v", err) + src = buf.Bytes() + } + + outputPath := filepath.Join(dir, *outputFile) + if err := ioutil.WriteFile(outputPath, src, 0644); err != nil { + log.Fatalf("Writing output: %s", err) + } +} diff --git a/rules/tls.go b/rules/tls.go index e5a8914..f0eb2d8 100644 --- a/rules/tls.go +++ b/rules/tls.go @@ -12,6 +12,8 @@ // See the License for the specific language governing permissions and // limitations under the License. +//go:generate tlsconfig + package rules import ( @@ -118,74 +120,3 @@ func (t *insecureConfigTLS) Match(n ast.Node, c *gas.Context) (*gas.Issue, error } return nil, nil } - -// NewModernTLSCheck see: https://wiki.mozilla.org/Security/Server_Side_TLS#Modern_compatibility -func NewModernTLSCheck(conf gas.Config) (gas.Rule, []ast.Node) { - return &insecureConfigTLS{ - requiredType: "crypto/tls.Config", - MinVersion: 0x0303, // TLS 1.2 only - MaxVersion: 0x0303, - goodCiphers: []string{ - "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256", - "TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384", - "TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305", - "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256", - "TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384", - "TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305", - }, - }, []ast.Node{(*ast.CompositeLit)(nil)} -} - -// NewIntermediateTLSCheck see: https://wiki.mozilla.org/Security/Server_Side_TLS#Intermediate_compatibility_.28default.29 -func NewIntermediateTLSCheck(conf gas.Config) (gas.Rule, []ast.Node) { - return &insecureConfigTLS{ - requiredType: "crypto/tls.Config", - MinVersion: 0x0301, // TLS 1.2, 1.1, 1.0 - MaxVersion: 0x0303, - goodCiphers: []string{ - "TLS_RSA_WITH_AES_128_CBC_SHA", - "TLS_RSA_WITH_AES_256_CBC_SHA", - "TLS_RSA_WITH_AES_128_GCM_SHA256", - "TLS_RSA_WITH_AES_256_GCM_SHA384", - "TLS_ECDHE_ECDSA_WITH_RC4_128_SHA", - "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA", - "TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA", - "TLS_ECDHE_RSA_WITH_RC4_128_SHA", - "TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA", - "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA", - "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA", - "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256", - "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256", - "TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384", - "TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384", - }, - }, []ast.Node{(*ast.CompositeLit)(nil)} -} - -// NewCompatTLSCheck see: https://wiki.mozilla.org/Security/Server_Side_TLS#Old_compatibility_.28default.29 -func NewCompatTLSCheck(conf gas.Config) (gas.Rule, []ast.Node) { - return &insecureConfigTLS{ - requiredType: "crypto/tls.Config", - MinVersion: 0x0301, // TLS 1.2, 1.1, 1.0 - MaxVersion: 0x0303, - goodCiphers: []string{ - "TLS_RSA_WITH_RC4_128_SHA", - "TLS_RSA_WITH_3DES_EDE_CBC_SHA", - "TLS_RSA_WITH_AES_128_CBC_SHA", - "TLS_RSA_WITH_AES_256_CBC_SHA", - "TLS_RSA_WITH_AES_128_GCM_SHA256", - "TLS_RSA_WITH_AES_256_GCM_SHA384", - "TLS_ECDHE_ECDSA_WITH_RC4_128_SHA", - "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA", - "TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA", - "TLS_ECDHE_RSA_WITH_RC4_128_SHA", - "TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA", - "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA", - "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA", - "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256", - "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256", - "TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384", - "TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384", - }, - }, []ast.Node{(*ast.CompositeLit)(nil)} -} diff --git a/rules/tls_config.go b/rules/tls_config.go new file mode 100644 index 0000000..4f7afd3 --- /dev/null +++ b/rules/tls_config.go @@ -0,0 +1,132 @@ +package rules + +import ( + "go/ast" + + "github.com/GoASTScanner/gas" +) + +// NewModernTLSCheck creates a check for Modern TLS ciphers +// DO NOT EDIT - generated by tlsconfig tool +func NewModernTLSCheck(conf gas.Config) (gas.Rule, []ast.Node) { + return &insecureConfigTLS{ + requiredType: "crypto/tls.Config", + MinVersion: 0x0303, + MaxVersion: 0x0303, + goodCiphers: []string{ + "TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384", + "TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384", + "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256", + "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256", + "TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384", + "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384", + "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256", + "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256", + }, + }, []ast.Node{(*ast.CompositeLit)(nil)} +} + +// NewIntermediateTLSCheck creates a check for Intermediate TLS ciphers +// DO NOT EDIT - generated by tlsconfig tool +func NewIntermediateTLSCheck(conf gas.Config) (gas.Rule, []ast.Node) { + return &insecureConfigTLS{ + requiredType: "crypto/tls.Config", + MinVersion: 0x0301, + MaxVersion: 0x0303, + goodCiphers: []string{ + "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256", + "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256", + "TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384", + "TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384", + "TLS_DHE_RSA_WITH_AES_128_GCM_SHA256", + "TLS_DHE_RSA_WITH_AES_256_GCM_SHA384", + "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256", + "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256", + "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA", + "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384", + "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA", + "TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384", + "TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA", + "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA", + "TLS_DHE_RSA_WITH_AES_128_CBC_SHA256", + "TLS_DHE_RSA_WITH_AES_128_CBC_SHA", + "TLS_DHE_RSA_WITH_AES_256_CBC_SHA256", + "TLS_DHE_RSA_WITH_AES_256_CBC_SHA", + "TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA", + "TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA", + "TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA", + "TLS_RSA_WITH_AES_128_GCM_SHA256", + "TLS_RSA_WITH_AES_256_GCM_SHA384", + "TLS_RSA_WITH_AES_128_CBC_SHA256", + "TLS_RSA_WITH_AES_256_CBC_SHA256", + "TLS_RSA_WITH_AES_128_CBC_SHA", + "TLS_RSA_WITH_AES_256_CBC_SHA", + "TLS_RSA_WITH_3DES_EDE_CBC_SHA", + }, + }, []ast.Node{(*ast.CompositeLit)(nil)} +} + +// NewOldTLSCheck creates a check for Old TLS ciphers +// DO NOT EDIT - generated by tlsconfig tool +func NewOldTLSCheck(conf gas.Config) (gas.Rule, []ast.Node) { + return &insecureConfigTLS{ + requiredType: "crypto/tls.Config", + MinVersion: 0x0300, + MaxVersion: 0x0303, + goodCiphers: []string{ + "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256", + "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256", + "TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384", + "TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384", + "TLS_DHE_RSA_WITH_AES_128_GCM_SHA256", + "TLS_DHE_DSS_WITH_AES_128_GCM_SHA256", + "TLS_DHE_DSS_WITH_AES_256_GCM_SHA384", + "TLS_DHE_RSA_WITH_AES_256_GCM_SHA384", + "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256", + "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256", + "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA", + "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA", + "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384", + "TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384", + "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA", + "TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA", + "TLS_DHE_RSA_WITH_AES_128_CBC_SHA256", + "TLS_DHE_RSA_WITH_AES_128_CBC_SHA", + "TLS_DHE_DSS_WITH_AES_128_CBC_SHA256", + "TLS_DHE_RSA_WITH_AES_256_CBC_SHA256", + "TLS_DHE_DSS_WITH_AES_256_CBC_SHA", + "TLS_DHE_RSA_WITH_AES_256_CBC_SHA", + "TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA", + "TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA", + "TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA", + "TLS_RSA_WITH_AES_128_GCM_SHA256", + "TLS_RSA_WITH_AES_256_GCM_SHA384", + "TLS_RSA_WITH_AES_128_CBC_SHA256", + "TLS_RSA_WITH_AES_256_CBC_SHA256", + "TLS_RSA_WITH_AES_128_CBC_SHA", + "TLS_RSA_WITH_AES_256_CBC_SHA", + "TLS_DHE_DSS_WITH_AES_256_CBC_SHA256", + "TLS_DHE_DSS_WITH_AES_128_CBC_SHA", + "TLS_RSA_WITH_3DES_EDE_CBC_SHA", + "TLS_ECDHE_RSA_WITH_CAMELLIA_256_CBC_SHA384", + "TLS_ECDHE_ECDSA_WITH_CAMELLIA_256_CBC_SHA384", + "TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA256", + "TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA256", + "TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA", + "TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA", + "TLS_RSA_WITH_CAMELLIA_256_CBC_SHA256", + "TLS_RSA_WITH_CAMELLIA_256_CBC_SHA", + "TLS_ECDHE_RSA_WITH_CAMELLIA_128_CBC_SHA256", + "TLS_ECDHE_ECDSA_WITH_CAMELLIA_128_CBC_SHA256", + "TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA256", + "TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA256", + "TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA", + "TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA", + "TLS_RSA_WITH_CAMELLIA_128_CBC_SHA256", + "TLS_RSA_WITH_CAMELLIA_128_CBC_SHA", + "TLS_DHE_RSA_WITH_SEED_CBC_SHA", + "TLS_DHE_DSS_WITH_SEED_CBC_SHA", + "TLS_RSA_WITH_SEED_CBC_SHA", + }, + }, []ast.Node{(*ast.CompositeLit)(nil)} +}