Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add a tool to generate the TLS configuration form Mozilla's ciphers recommendation #178

Merged
merged 5 commits into from
Feb 21, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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