Skip to content

Commit

Permalink
Add a tool to generate the TLS configuration form Mozilla's ciphers r…
Browse files Browse the repository at this point in the history
…ecommendation (securego#178)

* Add a tool which generates the TLS rule configuration from Mozilla server side
TLS configuration

* Update README

* Remove trailing space in README

* Update dependencies

* Fix the commends of the generated functions
  • Loading branch information
cosmincojocar authored and gcmurphy committed Feb 21, 2018
1 parent 1c58cbd commit edb362f
Show file tree
Hide file tree
Showing 7 changed files with 406 additions and 72 deletions.
19 changes: 18 additions & 1 deletion Godeps/Godeps.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

18 changes: 18 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
13 changes: 13 additions & 0 deletions cmd/tlsconfig/header_template.go
Original file line number Diff line number Diff line change
@@ -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"
)
`))
19 changes: 19 additions & 0 deletions cmd/tlsconfig/rule_template.go
Original file line number Diff line number Diff line change
@@ -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)}
}
`))
204 changes: 204 additions & 0 deletions cmd/tlsconfig/tlsconfig.go
Original file line number Diff line number Diff line change
@@ -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)
}
}
73 changes: 2 additions & 71 deletions rules/tls.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@
// See the License for the specific language governing permissions and
// limitations under the License.

//go:generate tlsconfig

package rules

import (
Expand Down Expand Up @@ -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)}
}
Loading

0 comments on commit edb362f

Please sign in to comment.