Skip to content
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
21 changes: 19 additions & 2 deletions Godeps/Godeps.json

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

21 changes: 20 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ or to specify a set of rules to explicitly exclude using the '-exclude=' flag.
- G301: Poor file permissions used when creating a directory
- G302: Poor file permisions used with chmod
- G303: Creating tempfile using a predictable path
- G304: File path provided as taint input
- G401: Detect the usage of DES, RC4, or MD5
- G402: Look for bad TLS connection settings
- G403: Ensure minimum RSA key length of 2048 bits
Expand Down Expand Up @@ -105,7 +106,7 @@ $ gas -nosec=true ./...

### Output formats

Gas currently supports text, json, csv and JUnit XML output formats. By default
Gas currently supports text, json, yaml, csv and JUnit XML output formats. By default
results will be reported to stdout, but can also be written to an output
file. The output format is controlled by the '-fmt' flag, and the output file is controlled by the '-out' flag as follows:

Expand All @@ -114,3 +115,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.
3 changes: 2 additions & 1 deletion cmd/gas/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ var (
flagIgnoreNoSec = flag.Bool("nosec", false, "Ignores #nosec comments when set")

// format output
flagFormat = flag.String("fmt", "text", "Set output format. Valid options are: json, csv, junit-xml, html, or text")
flagFormat = flag.String("fmt", "text", "Set output format. Valid options are: json, yaml, csv, junit-xml, html, or text")

// output file
flagOutput = flag.String("out", "", "Set output file for results")
Expand Down Expand Up @@ -110,6 +110,7 @@ func usage() {
func loadConfig(configFile string) (gas.Config, error) {
config := gas.NewConfig()
if configFile != "" {
// #nosec
file, err := os.Open(configFile)
if err != nil {
return nil, err
Expand Down
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)
}
}
1 change: 1 addition & 0 deletions issue.go
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,7 @@ func NewIssue(ctx *Context, node ast.Node, desc string, severity Score, confiden
line = fmt.Sprintf("%d-%d", start, end)
}

// #nosec
if file, err := os.Open(fobj.Name()); err == nil {
defer file.Close()
s := (int64)(fobj.Position(node.Pos()).Offset) // Go bug, should be int64
Expand Down
12 changes: 12 additions & 0 deletions output/formatter.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import (
plainTemplate "text/template"

"github.com/GoASTScanner/gas"
"gopkg.in/yaml.v2"
)

// ReportFormat enumrates the output format for reported issues
Expand Down Expand Up @@ -72,6 +73,8 @@ func CreateReport(w io.Writer, format string, issues []*gas.Issue, metrics *gas.
switch format {
case "json":
err = reportJSON(w, data)
case "yaml":
err = reportYAML(w, data)
case "csv":
err = reportCSV(w, data)
case "junit-xml":
Expand Down Expand Up @@ -99,6 +102,15 @@ func reportJSON(w io.Writer, data *reportInfo) error {
return err
}

func reportYAML(w io.Writer, data *reportInfo) error {
raw, err := yaml.Marshal(data)
if err != nil {
return err
}
_, err = w.Write(raw)
return err
}

func reportCSV(w io.Writer, data *reportInfo) error {
out := csv.NewWriter(w)
defer out.Flush()
Expand Down
Loading