support gas

This commit is contained in:
golangci 2018-05-07 14:02:27 +03:00
parent 0af6cacb28
commit 9d95267977
61 changed files with 5119 additions and 2 deletions

30
Gopkg.lock generated
View File

@ -1,6 +1,16 @@
# This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'.
[[projects]]
branch = "master"
name = "github.com/GoASTScanner/gas"
packages = [
".",
"rules"
]
revision = "a9de4d6c1589158e002cc336c495bf11fbf3ea06"
source = "github.com/golangci/gas"
[[projects]]
name = "github.com/bradleyfalzon/revgrep"
packages = ["."]
@ -75,10 +85,10 @@
revision = "f557f368b7f3d00a54ed44eb57b9eca59e85cee1"
[[projects]]
branch = "master"
name = "github.com/golangci/goconst"
packages = ["."]
revision = "b67b9035d29a7561902d87eb3757dd490b31c1b1"
branch = "master"
[[projects]]
branch = "master"
@ -183,6 +193,22 @@
revision = "53b9af5a45362a6f2896cfa39cc17d17ba9667ea"
source = "github.com/golangci/dupl"
[[projects]]
name = "github.com/nbutton23/zxcvbn-go"
packages = [
".",
"adjacency",
"data",
"entropy",
"frequency",
"match",
"matching",
"scoring",
"utils/math"
]
revision = "eafdab6b0663b4b528c35975c8b0e78be6e25261"
version = "v0.1"
[[projects]]
name = "github.com/pmezard/go-difflib"
packages = ["difflib"]
@ -306,6 +332,6 @@
[solve-meta]
analyzer-name = "dep"
analyzer-version = 1
inputs-digest = "33466d605f73dcfcecf89a0d3a46aaa719366433cdd6080d00f8476fe77e747f"
inputs-digest = "c5c664dfe96bbba11f6249112ed2f5252ec99b832a4a5d70b00ed41c64ac3b6b"
solver-name = "gps-cdcl"
solver-version = 1

View File

@ -60,6 +60,11 @@
branch = "master"
source = "github.com/golangci/interfacer"
[[constraint]]
name = "github.com/GoASTScanner/gas"
branch = "master"
source = "github.com/golangci/gas"
[[override]]
name = "mvdan.cc/lint"
branch = "master"

View File

@ -63,6 +63,7 @@ func GetAllSupportedLinterConfigs() []LinterConfig {
enabledByDefault(golinters.Errcheck{}, "Errcheck is a program for checking for unchecked errors in go programs. These unchecked errors can be critical bugs in some cases", true, false),
enabledByDefault(golinters.Golint{}, "Golint differs from gofmt. Gofmt reformats Go source code, whereas golint prints out style mistakes", false, false),
enabledByDefault(golinters.Megacheck{}, "Megacheck: 3 sub-linters in one: staticcheck, gosimple and unused", true, true),
enabledByDefault(golinters.Gas{}, "Inspects source code for security problems", true, false),
enabledByDefault(golinters.Structcheck{}, "Finds unused struct fields", true, false),
enabledByDefault(golinters.Varcheck{}, "Finds unused global variables and constants", true, false),
enabledByDefault(golinters.Interfacer{}, "Linter that suggests narrower interface types", true, true),

44
pkg/golinters/gas.go Normal file
View File

@ -0,0 +1,44 @@
package golinters
import (
"context"
"fmt"
"io/ioutil"
"log"
"strconv"
"github.com/GoASTScanner/gas"
"github.com/GoASTScanner/gas/rules"
"github.com/golangci/golangci-lint/pkg/result"
)
type Gas struct{}
func (Gas) Name() string {
return "gas"
}
func (lint Gas) Run(ctx context.Context, lintCtx *Context) (*result.Result, error) {
gasConfig := gas.NewConfig()
enabledRules := rules.Generate(rules.NewRuleFilter(true, "G104")) // disable what errcheck does: it reports on Close etc
logger := log.New(ioutil.Discard, "", 0)
analyzer := gas.NewAnalyzer(gasConfig, logger)
analyzer.LoadRules(enabledRules.Builders())
analyzer.ProcessProgram(lintCtx.Program)
issues, _ := analyzer.Report()
res := &result.Result{}
for _, i := range issues {
text := fmt.Sprintf("%s: %s", i.RuleID, i.What) // TODO: use severity and confidence
line, _ := strconv.Atoi(i.Line)
res.Issues = append(res.Issues, result.Issue{
File: i.File,
LineNumber: line,
Text: text,
FromLinter: lint.Name(),
})
}
return res, nil
}

11
pkg/golinters/testdata/gas.go vendored Normal file
View File

@ -0,0 +1,11 @@
package testdata
import (
"crypto/md5" // ERROR "G501: Blacklisted import crypto/md5: weak cryptographic primitive"
"log"
)
func Gas() {
h := md5.New() // ERROR "G401: Use of weak cryptographic primitive"
log.Print(h)
}

30
vendor/github.com/GoASTScanner/gas/.gitignore generated vendored Normal file
View File

@ -0,0 +1,30 @@
# Compiled Object files, Static and Dynamic libs (Shared Objects)
*.o
*.a
*.so
*.swp
# Folders
_obj
_test
vendor
# Architecture specific extensions/prefixes
*.[568vq]
[568vq].out
*.cgo1.go
*.cgo2.c
_cgo_defun.c
_cgo_gotypes.go
_cgo_export.*
_testmain.go
*.exe
*.test
*.prof
.DS_Store
.vscode

20
vendor/github.com/GoASTScanner/gas/.travis.yml generated vendored Normal file
View File

@ -0,0 +1,20 @@
language: go
go:
- 1.8
- 1.9
- "1.10"
- tip
install:
- go get -u github.com/golang/dep/cmd/dep
- go get -u github.com/golang/lint/golint
- go get -u github.com/onsi/ginkgo/ginkgo
- go get -u github.com/onsi/gomega
- go get -u golang.org/x/crypto/ssh
- go get -u github.com/GoASTScanner/gas/cmd/gas/...
- go get -v -t ./...
- export PATH=$PATH:$HOME/gopath/bin
script: make test

8
vendor/github.com/GoASTScanner/gas/Dockerfile generated vendored Normal file
View File

@ -0,0 +1,8 @@
FROM golang:1.9.4-alpine3.7
ENV BIN=gas
COPY build/*-linux-amd64 /go/bin/$BIN
COPY docker-entrypoint.sh /usr/local/bin
ENTRYPOINT ["docker-entrypoint.sh"]

134
vendor/github.com/GoASTScanner/gas/Gopkg.lock generated vendored Normal file
View File

@ -0,0 +1,134 @@
# This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'.
[[projects]]
name = "github.com/kisielk/gotool"
packages = ["."]
revision = "0de1eaf82fa3f583ce21fde859f1e7e0c5e9b220"
[[projects]]
branch = "master"
name = "github.com/mozilla/tls-observatory"
packages = ["constants"]
revision = "8791a200eb40f8625a152bfb8336171305f5f35c"
[[projects]]
name = "github.com/nbutton23/zxcvbn-go"
packages = [
".",
"adjacency",
"data",
"entropy",
"frequency",
"match",
"matching",
"scoring",
"utils/math"
]
revision = "a22cb81b2ecdde8b68e9ffb8824731cbf88e1de4"
[[projects]]
name = "github.com/onsi/ginkgo"
packages = [
".",
"config",
"internal/codelocation",
"internal/containernode",
"internal/failer",
"internal/leafnodes",
"internal/remote",
"internal/spec",
"internal/spec_iterator",
"internal/specrunner",
"internal/suite",
"internal/testingtproxy",
"internal/writer",
"reporters",
"reporters/stenographer",
"reporters/stenographer/support/go-colorable",
"reporters/stenographer/support/go-isatty",
"types"
]
revision = "11459a886d9cd66b319dac7ef1e917ee221372c9"
[[projects]]
name = "github.com/onsi/gomega"
packages = [
".",
"format",
"internal/assertion",
"internal/asyncassertion",
"internal/oraclematcher",
"internal/testingtsupport",
"matchers",
"matchers/support/goraph/bipartitegraph",
"matchers/support/goraph/edge",
"matchers/support/goraph/node",
"matchers/support/goraph/util",
"types"
]
revision = "dcabb60a477c2b6f456df65037cb6708210fbb02"
[[projects]]
branch = "master"
name = "github.com/ryanuber/go-glob"
packages = ["."]
revision = "256dc444b735e061061cf46c809487313d5b0065"
[[projects]]
name = "golang.org/x/net"
packages = [
"html",
"html/atom",
"html/charset"
]
revision = "8351a756f30f1297fe94bbf4b767ec589c6ea6d0"
[[projects]]
name = "golang.org/x/sys"
packages = ["unix"]
revision = "164713f0dfcec4e80be8b53e1f0811f5f0d84578"
[[projects]]
name = "golang.org/x/text"
packages = [
"encoding",
"encoding/charmap",
"encoding/htmlindex",
"encoding/internal",
"encoding/internal/identifier",
"encoding/japanese",
"encoding/korean",
"encoding/simplifiedchinese",
"encoding/traditionalchinese",
"encoding/unicode",
"internal/gen",
"internal/tag",
"internal/utf8internal",
"language",
"runes",
"transform",
"unicode/cldr"
]
revision = "1cbadb444a806fd9430d14ad08967ed91da4fa0a"
[[projects]]
name = "golang.org/x/tools"
packages = [
"go/ast/astutil",
"go/buildutil",
"go/loader"
]
revision = "e531a2a1c15f94033f6fa87666caeb19a688175f"
[[projects]]
name = "gopkg.in/yaml.v2"
packages = ["."]
revision = "eb3733d160e74a9c7e442f435eb3bea458e1d19f"
[solve-meta]
analyzer-name = "dep"
analyzer-version = 1
inputs-digest = "706e049cd8b8db8705af09e7a375a999d01373a409beadc850c80d64de9849fd"
solver-name = "gps-cdcl"
solver-version = 1

38
vendor/github.com/GoASTScanner/gas/Gopkg.toml generated vendored Normal file
View File

@ -0,0 +1,38 @@
# Gopkg.toml example
#
# Refer to https://golang.github.io/dep/docs/Gopkg.toml.html
# for detailed Gopkg.toml documentation.
#
# required = ["github.com/user/thing/cmd/thing"]
# ignored = ["github.com/user/project/pkgX", "bitbucket.org/user/project/pkgA/pkgY"]
#
# [[constraint]]
# name = "github.com/user/project"
# version = "1.0.0"
#
# [[constraint]]
# name = "github.com/user/project2"
# branch = "dev"
# source = "github.com/myfork/project2"
#
# [[override]]
# name = "github.com/x/y"
# version = "2.4.0"
#
# [prune]
# non-go = false
# go-tests = true
# unused-packages = true
[[constraint]]
branch = "master"
name = "github.com/mozilla/tls-observatory"
[[constraint]]
branch = "master"
name = "github.com/ryanuber/go-glob"
[prune]
go-tests = true
unused-packages = true

154
vendor/github.com/GoASTScanner/gas/LICENSE.txt generated vendored Normal file
View File

@ -0,0 +1,154 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction, and
distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by the copyright
owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all other entities
that control, are controlled by, or are under common control with that entity.
For the purposes of this definition, "control" means (i) the power, direct or
indirect, to cause the direction or management of such entity, whether by
contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity exercising
permissions granted by this License.
"Source" form shall mean the preferred form for making modifications, including
but not limited to software source code, documentation source, and configuration
files.
"Object" form shall mean any form resulting from mechanical transformation or
translation of a Source form, including but not limited to compiled object code,
generated documentation, and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or Object form, made
available under the License, as indicated by a copyright notice that is included
in or attached to the work (an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object form, that
is based on (or derived from) the Work and for which the editorial revisions,
annotations, elaborations, or other modifications represent, as a whole, an
original work of authorship. For the purposes of this License, Derivative Works
shall not include works that remain separable from, or merely link (or bind by
name) to the interfaces of, the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including the original version
of the Work and any modifications or additions to that Work or Derivative Works
thereof, that is intentionally submitted to Licensor for inclusion in the Work
by the copyright owner or by an individual or Legal Entity authorized to submit
on behalf of the copyright owner. For the purposes of this definition,
"submitted" means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems, and
issue tracking systems that are managed by, or on behalf of, the Licensor for
the purpose of discussing and improving the Work, but excluding communication
that is conspicuously marked or otherwise designated in writing by the copyright
owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity on behalf
of whom a Contribution has been received by Licensor and subsequently
incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of this
License, each Contributor hereby grants to You a perpetual, worldwide,
non-exclusive, no-charge, royalty-free, irrevocable copyright license to
reproduce, prepare Derivative Works of, publicly display, publicly perform,
sublicense, and distribute the Work and such Derivative Works in Source or
Object form.
3. Grant of Patent License. Subject to the terms and conditions of this License,
each Contributor hereby grants to You a perpetual, worldwide, non-exclusive,
no-charge, royalty-free, irrevocable (except as stated in this section) patent
license to make, have made, use, offer to sell, sell, import, and otherwise
transfer the Work, where such license applies only to those patent claims
licensable by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s) with the Work
to which such Contribution(s) was submitted. If You institute patent litigation
against any entity (including a cross-claim or counterclaim in a lawsuit)
alleging that the Work or a Contribution incorporated within the Work
constitutes direct or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate as of the date
such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the Work or
Derivative Works thereof in any medium, with or without modifications, and in
Source or Object form, provided that You meet the following conditions:
You must give any other recipients of the Work or Derivative Works a copy of
this License; and You must cause any modified files to carry prominent notices
stating that You changed the files; and You must retain, in the Source form of
any Derivative Works that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work, excluding those notices
that do not pertain to any part of the Derivative Works; and If the Work
includes a "NOTICE" text file as part of its distribution, then any Derivative
Works that You distribute must include a readable copy of the attribution
notices contained within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one of the following
places: within a NOTICE text file distributed as part of the Derivative Works;
within the Source form or documentation, if provided along with the Derivative
Works; or, within a display generated by the Derivative Works, if and wherever
such third-party notices normally appear. The contents of the NOTICE file are
for informational purposes only and do not modify the License. You may add Your
own attribution notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided that such
additional attribution notices cannot be construed as modifying the License.
You may add Your own copyright statement to Your modifications and may provide
additional or different license terms and conditions for use, reproduction, or
distribution of Your modifications, or for any such Derivative Works as a whole,
provided Your use, reproduction, and distribution of the Work otherwise complies
with the conditions stated in this License. 5. Submission of Contributions.
Unless You explicitly state otherwise, any Contribution intentionally submitted
for inclusion in the Work by You to the Licensor shall be under the terms and
conditions of this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify the terms of
any separate license agreement you may have executed with Licensor regarding
such Contributions.
6. Trademarks. This License does not grant permission to use the trade names,
trademarks, service marks, or product names of the Licensor, except as required
for reasonable and customary use in describing the origin of the Work and
reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or agreed to in
writing, Licensor provides the Work (and each Contributor provides its
Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied, including, without limitation, any warranties
or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any risks
associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory, whether in
tort (including negligence), contract, or otherwise, unless required by
applicable law (such as deliberate and grossly negligent acts) or agreed to in
writing, shall any Contributor be liable to You for damages, including any
direct, indirect, special, incidental, or consequential damages of any character
arising as a result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill, work stoppage,
computer failure or malfunction, or any and all other commercial damages or
losses), even if such Contributor has been advised of the possibility of such
damages.
9. Accepting Warranty or Additional Liability. While redistributing the Work or
Derivative Works thereof, You may choose to offer, and charge a fee for,
acceptance of support, warranty, indemnity, or other liability obligations
and/or rights consistent with this License. However, in accepting such
obligations, You may act only on Your own behalf and on Your sole
responsibility, not on behalf of any other Contributor, and only if You agree to
indemnify, defend, and hold each Contributor harmless for any liability incurred
by, or claims asserted against, such Contributor by reason of your accepting any
such warranty or additional liability.
END OF TERMS AND CONDITIONS

48
vendor/github.com/GoASTScanner/gas/Makefile generated vendored Normal file
View File

@ -0,0 +1,48 @@
GIT_TAG?= $(shell git describe --always --tags)
BUILD_DATE = $(shell date +%Y-%m-%d)
BIN = gas
BUILD_CMD = go build -ldflags "-X main.Version=${VERSION} -X main.GitTag=${GIT_TAG} -X main.BuildDate=${BUILD_DATE}" -o build/$(BIN)-$(VERSION)-$${GOOS}-$${GOARCH} ./cmd/gas/ &
FMT_CMD = $(gofmt -s -l -w $(find . -type f -name '*.go' -not -path './vendor/*') | tee /dev/stderr)
IMAGE_REPO = docker.io
default:
$(MAKE) bootstrap
$(MAKE) build
test: bootstrap
test -z '$(FMT_CMD)'
go vet $(go list ./... | grep -v /vendor/)
golint -set_exit_status $(shell go list ./... | grep -v vendor)
gas ./...
ginkgo -r -v
bootstrap:
dep ensure
build:
go build -o $(BIN) ./cmd/gas/
clean:
rm -rf build vendor
rm -f release image bootstrap $(BIN)
release: bootstrap
ifndef VERSION
$(error VERSION flag is not set. Run 'make release VERSION=<YOUR VERSION>'.)
endif
@echo "Running build command..."
bash -c '\
export GOOS=linux; export GOARCH=amd64; export CGO_ENABLED=0; $(BUILD_CMD) \
wait \
'
touch release
image: release
@echo "Building the Docker image..."
docker build -t $(IMAGE_REPO)/$(BIN):$(VERSION) .
docker tag $(IMAGE_REPO)/$(BIN):$(VERSION) $(IMAGE_REPO)/$(BIN):latest
touch image
image-push: image
@echo "Pushing the Docker image..."
docker push $(IMAGE_REPO)/$(BIN):$(VERSION)
docker push $(IMAGE_REPO)/$(BIN):latest
.PHONY: test build clean image-push

199
vendor/github.com/GoASTScanner/gas/README.md generated vendored Normal file
View File

@ -0,0 +1,199 @@
## GAS - Go AST Scanner
Inspects source code for security problems by scanning the Go AST.
### License
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License [here](http://www.apache.org/licenses/LICENSE-2.0).
### Project status
[![Build Status](https://travis-ci.org/GoASTScanner/gas.svg?branch=master)](https://travis-ci.org/GoASTScanner/gas)
[![GoDoc](https://godoc.org/github.com/GoASTScanner/gas?status.svg)](https://godoc.org/github.com/GoASTScanner/gas)
Gas is still in alpha and accepting feedback from early adopters. We do
not consider it production ready at this time.
### Install
`$ go get github.com/GoASTScanner/gas/cmd/gas/...`
### Usage
Gas can be configured to only run a subset of rules, to exclude certain file
paths, and produce reports in different formats. By default all rules will be
run against the supplied input files. To recursively scan from the current
directory you can supply './...' as the input argument.
#### Selecting rules
By default Gas will run all rules against the supplied file paths. It is however possible to select a subset of rules to run via the '-include=' flag,
or to specify a set of rules to explicitly exclude using the '-exclude=' flag.
##### Available rules
- G101: Look for hardcoded credentials
- G102: Bind to all interfaces
- G103: Audit the use of unsafe block
- G104: Audit errors not checked
- G105: Audit the use of math/big.Int.Exp
- G106: Audit the use of ssh.InsecureIgnoreHostKey
- G201: SQL query construction using format string
- G202: SQL query construction using string concatenation
- G203: Use of unescaped data in HTML templates
- G204: Audit use of command execution
- 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
- G404: Insecure random number source (rand)
- G501: Import blacklist: crypto/md5
- G502: Import blacklist: crypto/des
- G503: Import blacklist: crypto/rc4
- G504: Import blacklist: net/http/cgi
```
# Run a specific set of rules
$ gas -include=G101,G203,G401 ./...
# Run everything except for rule G303
$ gas -exclude=G303 ./...
```
#### Excluding files:
Gas will ignore dependencies in your vendor directory any files
that are not considered build artifacts by the compiler (so test files).
#### Annotating code
As with all automated detection tools there will be cases of false positives. In cases where Gas reports a failure that has been manually verified as being safe it is possible to annotate the code with a '#nosec' comment.
The annotation causes Gas to stop processing any further nodes within the
AST so can apply to a whole block or more granularly to a single expression.
```go
import "md5" // #nosec
func main(){
/* #nosec */
if x > y {
h := md5.New() // this will also be ignored
}
}
```
In some cases you may also want to revisit places where #nosec annotations
have been used. To run the scanner and ignore any #nosec annotations you
can do the following:
```
$ gas -nosec=true ./...
```
#### Build tags
Gas is able to pass your [Go build tags](https://golang.org/pkg/go/build/) to the analyzer.
They can be provided as a comma separated list as follows:
```
$ gas -tag debug,ignore ./...
```
### Output formats
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:
```
# Write output in json format to results.json
$ gas -fmt=json -out=results.json *.go
```
### Development
#### Prerequisites
Install dep according to the instructions here: https://github.com/golang/dep
Install the latest version of golint: https://github.com/golang/lint
#### Build
```
make
```
#### Tests
```
make test
```
#### Release Build
Gas can be released as follows:
```bash
make release VERSION=2.0.0
```
The released version of the tool is available in the `build` folder. The build information should be displayed in the usage text.
```
./build/gas-2.0.0-linux-amd64 -h
GAS - Go AST Scanner
Gas analyzes Go source code to look for common programming mistakes that
can lead to security problems.
VERSION: 2.0.0
GIT TAG: 96489ff
BUILD DATE: 2018-02-21
```
#### Docker image
You can execute a release and build the docker image as follows:
```
make image VERSION=2.0.0
```
Now you can run the gas tool in a container against your local workspace:
```
docker run -it -v <YOUR LOCAL WORKSPACE>:/workspace gas /workspace
```
#### 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.

255
vendor/github.com/GoASTScanner/gas/analyzer.go generated vendored Normal file
View File

@ -0,0 +1,255 @@
// (c) Copyright 2016 Hewlett Packard Enterprise Development LP
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// Modifications copyright (C) 2018 GolangCI
// Package gas holds the central scanning logic used by GAS
package gas
import (
"go/ast"
"go/build"
"go/parser"
"go/token"
"go/types"
"log"
"os"
"path"
"reflect"
"regexp"
"strings"
"path/filepath"
"golang.org/x/tools/go/loader"
)
// The Context is populated with data parsed from the source code as it is scanned.
// It is passed through to all rule functions as they are called. Rules may use
// this data in conjunction withe the encoutered AST node.
type Context struct {
FileSet *token.FileSet
Comments ast.CommentMap
Info *types.Info
Pkg *types.Package
Root *ast.File
Config map[string]interface{}
Imports *ImportTracker
Ignores []map[string]bool
}
// Metrics used when reporting information about a scanning run.
type Metrics struct {
NumFiles int `json:"files"`
NumLines int `json:"lines"`
NumNosec int `json:"nosec"`
NumFound int `json:"found"`
}
// Analyzer object is the main object of GAS. It has methods traverse an AST
// and invoke the correct checking rules as on each node as required.
type Analyzer struct {
ignoreNosec bool
ruleset RuleSet
context *Context
config Config
logger *log.Logger
issues []*Issue
stats *Metrics
}
// NewAnalyzer builds a new anaylzer.
func NewAnalyzer(conf Config, logger *log.Logger) *Analyzer {
ignoreNoSec := false
if setting, err := conf.GetGlobal("nosec"); err == nil {
ignoreNoSec = setting == "true" || setting == "enabled"
}
if logger == nil {
logger = log.New(os.Stderr, "[gas]", log.LstdFlags)
}
return &Analyzer{
ignoreNosec: ignoreNoSec,
ruleset: make(RuleSet),
context: &Context{},
config: conf,
logger: logger,
issues: make([]*Issue, 0, 16),
stats: &Metrics{},
}
}
// LoadRules instantiates all the rules to be used when analyzing source
// packages
func (gas *Analyzer) LoadRules(ruleDefinitions map[string]RuleBuilder) {
for id, def := range ruleDefinitions {
r, nodes := def(id, gas.config)
gas.ruleset.Register(r, nodes...)
}
}
// Process kicks off the analysis process for a given package
func (gas *Analyzer) Process(buildTags []string, packagePaths ...string) error {
ctx := build.Default
ctx.BuildTags = append(ctx.BuildTags, buildTags...)
packageConfig := loader.Config{
Build: &ctx,
ParserMode: parser.ParseComments,
AllowErrors: true,
}
for _, packagePath := range packagePaths {
abspath, err := filepath.Abs(packagePath)
if err != nil {
return err
}
if _, err := os.Stat(abspath); os.IsNotExist(err) {
gas.logger.Printf("Skipping: %s. Path doesn't exist.", abspath)
continue
}
gas.logger.Println("Searching directory:", abspath)
basePackage, err := build.Default.ImportDir(packagePath, build.ImportComment)
if err != nil {
return err
}
var packageFiles []string
for _, filename := range basePackage.GoFiles {
packageFiles = append(packageFiles, path.Join(packagePath, filename))
}
packageConfig.CreateFromFilenames(basePackage.Name, packageFiles...)
}
builtPackage, err := packageConfig.Load()
if err != nil {
return err
}
gas.ProcessProgram(builtPackage)
return nil
}
// ProcessProgram kicks off the analysis process for a given program
func (gas *Analyzer) ProcessProgram(builtPackage *loader.Program) {
for _, pkg := range builtPackage.InitialPackages() {
gas.logger.Println("Checking package:", pkg.String())
for _, file := range pkg.Files {
gas.logger.Println("Checking file:", builtPackage.Fset.File(file.Pos()).Name())
gas.context.FileSet = builtPackage.Fset
gas.context.Config = gas.config
gas.context.Comments = ast.NewCommentMap(gas.context.FileSet, file, file.Comments)
gas.context.Root = file
gas.context.Info = &pkg.Info
gas.context.Pkg = pkg.Pkg
gas.context.Imports = NewImportTracker()
gas.context.Imports.TrackPackages(gas.context.Pkg.Imports()...)
ast.Walk(gas, file)
gas.stats.NumFiles++
gas.stats.NumLines += builtPackage.Fset.File(file.Pos()).LineCount()
}
}
}
// ignore a node (and sub-tree) if it is tagged with a "#nosec" comment
func (gas *Analyzer) ignore(n ast.Node) ([]string, bool) {
if groups, ok := gas.context.Comments[n]; ok && !gas.ignoreNosec {
for _, group := range groups {
if strings.Contains(group.Text(), "#nosec") {
gas.stats.NumNosec++
// Pull out the specific rules that are listed to be ignored.
re := regexp.MustCompile("(G\\d{3})")
matches := re.FindAllStringSubmatch(group.Text(), -1)
// If no specific rules were given, ignore everything.
if matches == nil || len(matches) == 0 {
return nil, true
}
// Find the rule IDs to ignore.
var ignores []string
for _, v := range matches {
ignores = append(ignores, v[1])
}
return ignores, false
}
}
}
return nil, false
}
// Visit runs the GAS visitor logic over an AST created by parsing go code.
// Rule methods added with AddRule will be invoked as necessary.
func (gas *Analyzer) Visit(n ast.Node) ast.Visitor {
// If we've reached the end of this branch, pop off the ignores stack.
if n == nil {
if len(gas.context.Ignores) > 0 {
gas.context.Ignores = gas.context.Ignores[1:]
}
return gas
}
// Get any new rule exclusions.
ignoredRules, ignoreAll := gas.ignore(n)
if ignoreAll {
return nil
}
// Now create the union of exclusions.
ignores := make(map[string]bool, 0)
if len(gas.context.Ignores) > 0 {
for k, v := range gas.context.Ignores[0] {
ignores[k] = v
}
}
for _, v := range ignoredRules {
ignores[v] = true
}
// Push the new set onto the stack.
gas.context.Ignores = append([]map[string]bool{ignores}, gas.context.Ignores...)
// Track aliased and initialization imports
gas.context.Imports.TrackImport(n)
for _, rule := range gas.ruleset.RegisteredFor(n) {
if _, ok := ignores[rule.ID()]; ok {
continue
}
issue, err := rule.Match(n, gas.context)
if err != nil {
file, line := GetLocation(n, gas.context)
file = path.Base(file)
gas.logger.Printf("Rule error: %v => %s (%s:%d)\n", reflect.TypeOf(rule), err, file, line)
}
if issue != nil {
gas.issues = append(gas.issues, issue)
gas.stats.NumFound++
}
}
return gas
}
// Report returns the current issues discovered and the metrics about the scan
func (gas *Analyzer) Report() ([]*Issue, *Metrics) {
return gas.issues, gas.stats
}
// Reset clears state such as context, issues and metrics from the configured analyzer
func (gas *Analyzer) Reset() {
gas.context = &Context{}
gas.issues = make([]*Issue, 0, 16)
gas.stats = &Metrics{}
}

78
vendor/github.com/GoASTScanner/gas/call_list.go generated vendored Normal file
View File

@ -0,0 +1,78 @@
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package gas
import (
"go/ast"
)
type set map[string]bool
// CallList is used to check for usage of specific packages
// and functions.
type CallList map[string]set
// NewCallList creates a new empty CallList
func NewCallList() CallList {
return make(CallList)
}
// AddAll will add several calls to the call list at once
func (c CallList) AddAll(selector string, idents ...string) {
for _, ident := range idents {
c.Add(selector, ident)
}
}
// Add a selector and call to the call list
func (c CallList) Add(selector, ident string) {
if _, ok := c[selector]; !ok {
c[selector] = make(set)
}
c[selector][ident] = true
}
// Contains returns true if the package and function are
/// members of this call list.
func (c CallList) Contains(selector, ident string) bool {
if idents, ok := c[selector]; ok {
_, found := idents[ident]
return found
}
return false
}
// ContainsCallExpr resolves the call expression name and type
/// or package and determines if it exists within the CallList
func (c CallList) ContainsCallExpr(n ast.Node, ctx *Context) *ast.CallExpr {
selector, ident, err := GetCallInfo(n, ctx)
if err != nil {
return nil
}
// Use only explicit path to reduce conflicts
if path, ok := GetImportPath(selector, ctx); ok && c.Contains(path, ident) {
return n.(*ast.CallExpr)
}
/*
// Try direct resolution
if c.Contains(selector, ident) {
log.Printf("c.Contains == true, %s, %s.", selector, ident)
return n.(*ast.CallExpr)
}
*/
return nil
}

88
vendor/github.com/GoASTScanner/gas/config.go generated vendored Normal file
View File

@ -0,0 +1,88 @@
package gas
import (
"bytes"
"encoding/json"
"fmt"
"io"
"io/ioutil"
)
const (
// Globals are applicable to all rules and used for general
// configuration settings for gas.
Globals = "global"
)
// Config is used to provide configuration and customization to each of the rules.
type Config map[string]interface{}
// NewConfig initializes a new configuration instance. The configuration data then
// needs to be loaded via c.ReadFrom(strings.NewReader("config data"))
// or from a *os.File.
func NewConfig() Config {
cfg := make(Config)
cfg[Globals] = make(map[string]string)
return cfg
}
// ReadFrom implements the io.ReaderFrom interface. This
// should be used with io.Reader to load configuration from
//file or from string etc.
func (c Config) ReadFrom(r io.Reader) (int64, error) {
data, err := ioutil.ReadAll(r)
if err != nil {
return int64(len(data)), err
}
if err = json.Unmarshal(data, &c); err != nil {
return int64(len(data)), err
}
return int64(len(data)), nil
}
// WriteTo implements the io.WriteTo interface. This should
// be used to save or print out the configuration information.
func (c Config) WriteTo(w io.Writer) (int64, error) {
data, err := json.Marshal(c)
if err != nil {
return int64(len(data)), err
}
return io.Copy(w, bytes.NewReader(data))
}
// Get returns the configuration section for the supplied key
func (c Config) Get(section string) (interface{}, error) {
settings, found := c[section]
if !found {
return nil, fmt.Errorf("Section %s not in configuration", section)
}
return settings, nil
}
// Set section in the configuration to specified value
func (c Config) Set(section string, value interface{}) {
c[section] = value
}
// GetGlobal returns value associated with global configuration option
func (c Config) GetGlobal(option string) (string, error) {
if globals, ok := c[Globals]; ok {
if settings, ok := globals.(map[string]string); ok {
if value, ok := settings[option]; ok {
return value, nil
}
return "", fmt.Errorf("global setting for %s not found", option)
}
}
return "", fmt.Errorf("no global config options found")
}
// SetGlobal associates a value with a global configuration ooption
func (c Config) SetGlobal(option, value string) {
if globals, ok := c[Globals]; ok {
if settings, ok := globals.(map[string]string); ok {
settings[option] = value
}
}
}

2
vendor/github.com/GoASTScanner/gas/docker-entrypoint.sh generated vendored Executable file
View File

@ -0,0 +1,2 @@
#!/usr/bin/env sh
${BIN} "$@"

195
vendor/github.com/GoASTScanner/gas/helpers.go generated vendored Normal file
View File

@ -0,0 +1,195 @@
// (c) Copyright 2016 Hewlett Packard Enterprise Development LP
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package gas
import (
"fmt"
"go/ast"
"go/token"
"go/types"
"strconv"
)
// MatchCallByPackage ensures that the specified package is imported,
// adjusts the name for any aliases and ignores cases that are
// initialization only imports.
//
// Usage:
// node, matched := MatchCallByPackage(n, ctx, "math/rand", "Read")
//
func MatchCallByPackage(n ast.Node, c *Context, pkg string, names ...string) (*ast.CallExpr, bool) {
importedName, found := GetImportedName(pkg, c)
if !found {
return nil, false
}
if callExpr, ok := n.(*ast.CallExpr); ok {
packageName, callName, err := GetCallInfo(callExpr, c)
if err != nil {
return nil, false
}
if packageName == importedName {
for _, name := range names {
if callName == name {
return callExpr, true
}
}
}
}
return nil, false
}
// MatchCallByType ensures that the node is a call expression to a
// specific object type.
//
// Usage:
// node, matched := MatchCallByType(n, ctx, "bytes.Buffer", "WriteTo", "Write")
//
func MatchCallByType(n ast.Node, ctx *Context, requiredType string, calls ...string) (*ast.CallExpr, bool) {
if callExpr, ok := n.(*ast.CallExpr); ok {
typeName, callName, err := GetCallInfo(callExpr, ctx)
if err != nil {
return nil, false
}
if typeName == requiredType {
for _, call := range calls {
if call == callName {
return callExpr, true
}
}
}
}
return nil, false
}
// MatchCompLit will match an ast.CompositeLit based on the supplied type
func MatchCompLit(n ast.Node, ctx *Context, required string) *ast.CompositeLit {
if complit, ok := n.(*ast.CompositeLit); ok {
typeOf := ctx.Info.TypeOf(complit)
if typeOf.String() == required {
return complit
}
}
return nil
}
// GetInt will read and return an integer value from an ast.BasicLit
func GetInt(n ast.Node) (int64, error) {
if node, ok := n.(*ast.BasicLit); ok && node.Kind == token.INT {
return strconv.ParseInt(node.Value, 0, 64)
}
return 0, fmt.Errorf("Unexpected AST node type: %T", n)
}
// GetFloat will read and return a float value from an ast.BasicLit
func GetFloat(n ast.Node) (float64, error) {
if node, ok := n.(*ast.BasicLit); ok && node.Kind == token.FLOAT {
return strconv.ParseFloat(node.Value, 64)
}
return 0.0, fmt.Errorf("Unexpected AST node type: %T", n)
}
// GetChar will read and return a char value from an ast.BasicLit
func GetChar(n ast.Node) (byte, error) {
if node, ok := n.(*ast.BasicLit); ok && node.Kind == token.CHAR {
return node.Value[0], nil
}
return 0, fmt.Errorf("Unexpected AST node type: %T", n)
}
// GetString will read and return a string value from an ast.BasicLit
func GetString(n ast.Node) (string, error) {
if node, ok := n.(*ast.BasicLit); ok && node.Kind == token.STRING {
return strconv.Unquote(node.Value)
}
return "", fmt.Errorf("Unexpected AST node type: %T", n)
}
// GetCallObject returns the object and call expression and associated
// object for a given AST node. nil, nil will be returned if the
// object cannot be resolved.
func GetCallObject(n ast.Node, ctx *Context) (*ast.CallExpr, types.Object) {
switch node := n.(type) {
case *ast.CallExpr:
switch fn := node.Fun.(type) {
case *ast.Ident:
return node, ctx.Info.Uses[fn]
case *ast.SelectorExpr:
return node, ctx.Info.Uses[fn.Sel]
}
}
return nil, nil
}
// GetCallInfo returns the package or type and name associated with a
// call expression.
func GetCallInfo(n ast.Node, ctx *Context) (string, string, error) {
switch node := n.(type) {
case *ast.CallExpr:
switch fn := node.Fun.(type) {
case *ast.SelectorExpr:
switch expr := fn.X.(type) {
case *ast.Ident:
if expr.Obj != nil && expr.Obj.Kind == ast.Var {
t := ctx.Info.TypeOf(expr)
if t != nil {
return t.String(), fn.Sel.Name, nil
}
return "undefined", fn.Sel.Name, fmt.Errorf("missing type info")
}
return expr.Name, fn.Sel.Name, nil
}
case *ast.Ident:
return ctx.Pkg.Name(), fn.Name, nil
}
}
return "", "", fmt.Errorf("unable to determine call info")
}
// GetImportedName returns the name used for the package within the
// code. It will resolve aliases and ignores initalization only imports.
func GetImportedName(path string, ctx *Context) (string, bool) {
importName, imported := ctx.Imports.Imported[path]
if !imported {
return "", false
}
if _, initonly := ctx.Imports.InitOnly[path]; initonly {
return "", false
}
if alias, ok := ctx.Imports.Aliased[path]; ok {
importName = alias
}
return importName, true
}
// GetImportPath resolves the full import path of an identifer based on
// the imports in the current context.
func GetImportPath(name string, ctx *Context) (string, bool) {
for path := range ctx.Imports.Imported {
if imported, ok := GetImportedName(path, ctx); ok && imported == name {
return path, true
}
}
return "", false
}
// GetLocation returns the filename and line number of an ast.Node
func GetLocation(n ast.Node, ctx *Context) (string, int) {
fobj := ctx.FileSet.File(n.Pos())
return fobj.Name(), fobj.Line(n.Pos())
}

67
vendor/github.com/GoASTScanner/gas/import_tracker.go generated vendored Normal file
View File

@ -0,0 +1,67 @@
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package gas
import (
"go/ast"
"go/types"
"strings"
)
// ImportTracker is used to normalize the packages that have been imported
// by a source file. It is able to differentiate between plain imports, aliased
// imports and init only imports.
type ImportTracker struct {
Imported map[string]string
Aliased map[string]string
InitOnly map[string]bool
}
// NewImportTracker creates an empty Import tracker instance
func NewImportTracker() *ImportTracker {
return &ImportTracker{
make(map[string]string),
make(map[string]string),
make(map[string]bool),
}
}
// TrackPackages tracks all the imports used by the supplied packages
func (t *ImportTracker) TrackPackages(pkgs ...*types.Package) {
for _, pkg := range pkgs {
t.Imported[pkg.Path()] = pkg.Name()
// Transient imports
//for _, imp := range pkg.Imports() {
// t.Imported[imp.Path()] = imp.Name()
//}
}
}
// TrackImport tracks imports and handles the 'unsafe' import
func (t *ImportTracker) TrackImport(n ast.Node) {
if imported, ok := n.(*ast.ImportSpec); ok {
path := strings.Trim(imported.Path.Value, `"`)
if imported.Name != nil {
if imported.Name.Name == "_" {
// Initialization only import
t.InitOnly[path] = true
} else {
// Aliased import
t.Aliased[path] = imported.Name.Name
}
}
if path == "unsafe" {
t.Imported[path] = path
}
}
}

122
vendor/github.com/GoASTScanner/gas/issue.go generated vendored Normal file
View File

@ -0,0 +1,122 @@
// (c) Copyright 2016 Hewlett Packard Enterprise Development LP
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package gas
import (
"encoding/json"
"fmt"
"go/ast"
"os"
"strconv"
)
// Score type used by severity and confidence values
type Score int
const (
// Low severity or confidence
Low Score = iota
// Medium severity or confidence
Medium
// High severity or confidence
High
)
// Issue is returnd by a GAS rule if it discovers an issue with the scanned code.
type Issue struct {
Severity Score `json:"severity"` // issue severity (how problematic it is)
Confidence Score `json:"confidence"` // issue confidence (how sure we are we found it)
RuleID string `json:"rule_id"` // Human readable explanation
What string `json:"details"` // Human readable explanation
File string `json:"file"` // File name we found it in
Code string `json:"code"` // Impacted code line
Line string `json:"line"` // Line number in file
}
// MetaData is embedded in all GAS rules. The Severity, Confidence and What message
// will be passed tbhrough to reported issues.
type MetaData struct {
ID string
Severity Score
Confidence Score
What string
}
// MarshalJSON is used convert a Score object into a JSON representation
func (c Score) MarshalJSON() ([]byte, error) {
return json.Marshal(c.String())
}
// String converts a Score into a string
func (c Score) String() string {
switch c {
case High:
return "HIGH"
case Medium:
return "MEDIUM"
case Low:
return "LOW"
}
return "UNDEFINED"
}
func codeSnippet(file *os.File, start int64, end int64, n ast.Node) (string, error) {
if n == nil {
return "", fmt.Errorf("Invalid AST node provided")
}
size := (int)(end - start) // Go bug, os.File.Read should return int64 ...
file.Seek(start, 0) // #nosec
buf := make([]byte, size)
if nread, err := file.Read(buf); err != nil || nread != size {
return "", fmt.Errorf("Unable to read code")
}
return string(buf), nil
}
// NewIssue creates a new Issue
func NewIssue(ctx *Context, node ast.Node, ruleID, desc string, severity Score, confidence Score) *Issue {
var code string
fobj := ctx.FileSet.File(node.Pos())
name := fobj.Name()
start, end := fobj.Line(node.Pos()), fobj.Line(node.End())
line := strconv.Itoa(start)
if start != end {
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
e := (int64)(fobj.Position(node.End()).Offset) // Go bug, should be int64
code, err = codeSnippet(file, s, e, node)
if err != nil {
code = err.Error()
}
}
return &Issue{
File: name,
Line: line,
RuleID: ruleID,
What: desc,
Confidence: confidence,
Severity: severity,
Code: code,
}
}

82
vendor/github.com/GoASTScanner/gas/resolve.go generated vendored Normal file
View File

@ -0,0 +1,82 @@
// (c) Copyright 2016 Hewlett Packard Enterprise Development LP
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package gas
import "go/ast"
func resolveIdent(n *ast.Ident, c *Context) bool {
if n.Obj == nil || n.Obj.Kind != ast.Var {
return true
}
if node, ok := n.Obj.Decl.(ast.Node); ok {
return TryResolve(node, c)
}
return false
}
func resolveAssign(n *ast.AssignStmt, c *Context) bool {
for _, arg := range n.Rhs {
if !TryResolve(arg, c) {
return false
}
}
return true
}
func resolveCompLit(n *ast.CompositeLit, c *Context) bool {
for _, arg := range n.Elts {
if !TryResolve(arg, c) {
return false
}
}
return true
}
func resolveBinExpr(n *ast.BinaryExpr, c *Context) bool {
return (TryResolve(n.X, c) && TryResolve(n.Y, c))
}
func resolveCallExpr(n *ast.CallExpr, c *Context) bool {
// TODO(tkelsey): next step, full function resolution
return false
}
// TryResolve will attempt, given a subtree starting at some ATS node, to resolve
// all values contained within to a known constant. It is used to check for any
// unkown values in compound expressions.
func TryResolve(n ast.Node, c *Context) bool {
switch node := n.(type) {
case *ast.BasicLit:
return true
case *ast.CompositeLit:
return resolveCompLit(node, c)
case *ast.Ident:
return resolveIdent(node, c)
case *ast.AssignStmt:
return resolveAssign(node, c)
case *ast.CallExpr:
return resolveCallExpr(node, c)
case *ast.BinaryExpr:
return resolveBinExpr(node, c)
}
return false
}

59
vendor/github.com/GoASTScanner/gas/rule.go generated vendored Normal file
View File

@ -0,0 +1,59 @@
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package gas
import (
"go/ast"
"reflect"
)
// The Rule interface used by all rules supported by GAS.
type Rule interface {
ID() string
Match(ast.Node, *Context) (*Issue, error)
}
// RuleBuilder is used to register a rule definition with the analyzer
type RuleBuilder func(id string, c Config) (Rule, []ast.Node)
// A RuleSet maps lists of rules to the type of AST node they should be run on.
// The anaylzer will only invoke rules contained in the list associated with the
// type of AST node it is currently visiting.
type RuleSet map[reflect.Type][]Rule
// NewRuleSet constructs a new RuleSet
func NewRuleSet() RuleSet {
return make(RuleSet)
}
// Register adds a trigger for the supplied rule for the the
// specified ast nodes.
func (r RuleSet) Register(rule Rule, nodes ...ast.Node) {
for _, n := range nodes {
t := reflect.TypeOf(n)
if rules, ok := r[t]; ok {
r[t] = append(rules, rule)
} else {
r[t] = []Rule{rule}
}
}
}
// RegisteredFor will return all rules that are registered for a
// specified ast node.
func (r RuleSet) RegisteredFor(n ast.Node) []Rule {
if rules, found := r[reflect.TypeOf(n)]; found {
return rules
}
return []Rule{}
}

52
vendor/github.com/GoASTScanner/gas/rules/big.go generated vendored Normal file
View File

@ -0,0 +1,52 @@
// (c) Copyright 2016 Hewlett Packard Enterprise Development LP
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package rules
import (
"go/ast"
"github.com/GoASTScanner/gas"
)
type usingBigExp struct {
gas.MetaData
pkg string
calls []string
}
func (r *usingBigExp) ID() string {
return r.MetaData.ID
}
func (r *usingBigExp) Match(n ast.Node, c *gas.Context) (gi *gas.Issue, err error) {
if _, matched := gas.MatchCallByType(n, c, r.pkg, r.calls...); matched {
return gas.NewIssue(c, n, r.ID(), r.What, r.Severity, r.Confidence), nil
}
return nil, nil
}
// NewUsingBigExp detects issues with modulus == 0 for Bignum
func NewUsingBigExp(id string, conf gas.Config) (gas.Rule, []ast.Node) {
return &usingBigExp{
pkg: "*math/big.Int",
calls: []string{"Exp"},
MetaData: gas.MetaData{
ID: id,
What: "Use of math/big.Int.Exp function should be audited for modulus == 0",
Severity: gas.Low,
Confidence: gas.High,
},
}, []ast.Node{(*ast.CallExpr)(nil)}
}

64
vendor/github.com/GoASTScanner/gas/rules/bind.go generated vendored Normal file
View File

@ -0,0 +1,64 @@
// (c) Copyright 2016 Hewlett Packard Enterprise Development LP
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package rules
import (
"go/ast"
"regexp"
"github.com/GoASTScanner/gas"
)
// Looks for net.Listen("0.0.0.0") or net.Listen(":8080")
type bindsToAllNetworkInterfaces struct {
gas.MetaData
calls gas.CallList
pattern *regexp.Regexp
}
func (r *bindsToAllNetworkInterfaces) ID() string {
return r.MetaData.ID
}
func (r *bindsToAllNetworkInterfaces) Match(n ast.Node, c *gas.Context) (*gas.Issue, error) {
callExpr := r.calls.ContainsCallExpr(n, c)
if callExpr == nil {
return nil, nil
}
if arg, err := gas.GetString(callExpr.Args[1]); err == nil {
if r.pattern.MatchString(arg) {
return gas.NewIssue(c, n, r.ID(), r.What, r.Severity, r.Confidence), nil
}
}
return nil, nil
}
// NewBindsToAllNetworkInterfaces detects socket connections that are setup to
// listen on all network interfaces.
func NewBindsToAllNetworkInterfaces(id string, conf gas.Config) (gas.Rule, []ast.Node) {
calls := gas.NewCallList()
calls.Add("net", "Listen")
calls.Add("crypto/tls", "Listen")
return &bindsToAllNetworkInterfaces{
calls: calls,
pattern: regexp.MustCompile(`^(0.0.0.0|:).*$`),
MetaData: gas.MetaData{
ID: id,
Severity: gas.Medium,
Confidence: gas.High,
What: "Binds to all network interfaces",
},
}, []ast.Node{(*ast.CallExpr)(nil)}
}

87
vendor/github.com/GoASTScanner/gas/rules/blacklist.go generated vendored Normal file
View File

@ -0,0 +1,87 @@
// (c) Copyright 2016 Hewlett Packard Enterprise Development LP
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package rules
import (
"go/ast"
"strings"
"github.com/GoASTScanner/gas"
)
type blacklistedImport struct {
gas.MetaData
Blacklisted map[string]string
}
func unquote(original string) string {
copy := strings.TrimSpace(original)
copy = strings.TrimLeft(copy, `"`)
return strings.TrimRight(copy, `"`)
}
func (r *blacklistedImport) ID() string {
return r.MetaData.ID
}
func (r *blacklistedImport) Match(n ast.Node, c *gas.Context) (*gas.Issue, error) {
if node, ok := n.(*ast.ImportSpec); ok {
if description, ok := r.Blacklisted[unquote(node.Path.Value)]; ok {
return gas.NewIssue(c, node, r.ID(), description, r.Severity, r.Confidence), nil
}
}
return nil, nil
}
// NewBlacklistedImports reports when a blacklisted import is being used.
// Typically when a deprecated technology is being used.
func NewBlacklistedImports(id string, conf gas.Config, blacklist map[string]string) (gas.Rule, []ast.Node) {
return &blacklistedImport{
MetaData: gas.MetaData{
ID: id,
Severity: gas.Medium,
Confidence: gas.High,
},
Blacklisted: blacklist,
}, []ast.Node{(*ast.ImportSpec)(nil)}
}
// NewBlacklistedImportMD5 fails if MD5 is imported
func NewBlacklistedImportMD5(id string, conf gas.Config) (gas.Rule, []ast.Node) {
return NewBlacklistedImports(id, conf, map[string]string{
"crypto/md5": "Blacklisted import crypto/md5: weak cryptographic primitive",
})
}
// NewBlacklistedImportDES fails if DES is imported
func NewBlacklistedImportDES(id string, conf gas.Config) (gas.Rule, []ast.Node) {
return NewBlacklistedImports(id, conf, map[string]string{
"crypto/des": "Blacklisted import crypto/des: weak cryptographic primitive",
})
}
// NewBlacklistedImportRC4 fails if DES is imported
func NewBlacklistedImportRC4(id string, conf gas.Config) (gas.Rule, []ast.Node) {
return NewBlacklistedImports(id, conf, map[string]string{
"crypto/rc4": "Blacklisted import crypto/rc4: weak cryptographic primitive",
})
}
// NewBlacklistedImportCGI fails if CGI is imported
func NewBlacklistedImportCGI(id string, conf gas.Config) (gas.Rule, []ast.Node) {
return NewBlacklistedImports(id, conf, map[string]string{
"net/http/cgi": "Blacklisted import net/http/cgi: Go versions < 1.6.3 are vulnerable to Httpoxy attack: (CVE-2016-5386)",
})
}

102
vendor/github.com/GoASTScanner/gas/rules/errors.go generated vendored Normal file
View File

@ -0,0 +1,102 @@
// (c) Copyright 2016 Hewlett Packard Enterprise Development LP
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package rules
import (
"go/ast"
"go/types"
"github.com/GoASTScanner/gas"
)
type noErrorCheck struct {
gas.MetaData
whitelist gas.CallList
}
func (r *noErrorCheck) ID() string {
return r.MetaData.ID
}
func returnsError(callExpr *ast.CallExpr, ctx *gas.Context) int {
if tv := ctx.Info.TypeOf(callExpr); tv != nil {
switch t := tv.(type) {
case *types.Tuple:
for pos := 0; pos < t.Len(); pos++ {
variable := t.At(pos)
if variable != nil && variable.Type().String() == "error" {
return pos
}
}
case *types.Named:
if t.String() == "error" {
return 0
}
}
}
return -1
}
func (r *noErrorCheck) Match(n ast.Node, ctx *gas.Context) (*gas.Issue, error) {
switch stmt := n.(type) {
case *ast.AssignStmt:
for _, expr := range stmt.Rhs {
if callExpr, ok := expr.(*ast.CallExpr); ok && r.whitelist.ContainsCallExpr(expr, ctx) == nil {
pos := returnsError(callExpr, ctx)
if pos < 0 || pos >= len(stmt.Lhs) {
return nil, nil
}
if id, ok := stmt.Lhs[pos].(*ast.Ident); ok && id.Name == "_" {
return gas.NewIssue(ctx, n, r.ID(), r.What, r.Severity, r.Confidence), nil
}
}
}
case *ast.ExprStmt:
if callExpr, ok := stmt.X.(*ast.CallExpr); ok && r.whitelist.ContainsCallExpr(stmt.X, ctx) == nil {
pos := returnsError(callExpr, ctx)
if pos >= 0 {
return gas.NewIssue(ctx, n, r.ID(), r.What, r.Severity, r.Confidence), nil
}
}
}
return nil, nil
}
// NewNoErrorCheck detects if the returned error is unchecked
func NewNoErrorCheck(id string, conf gas.Config) (gas.Rule, []ast.Node) {
// TODO(gm) Come up with sensible defaults here. Or flip it to use a
// black list instead.
whitelist := gas.NewCallList()
whitelist.AddAll("bytes.Buffer", "Write", "WriteByte", "WriteRune", "WriteString")
whitelist.AddAll("fmt", "Print", "Printf", "Println", "Fprint", "Fprintf", "Fprintln")
whitelist.Add("io.PipeWriter", "CloseWithError")
if configured, ok := conf["G104"]; ok {
if whitelisted, ok := configured.(map[string][]string); ok {
for key, val := range whitelisted {
whitelist.AddAll(key, val...)
}
}
}
return &noErrorCheck{
MetaData: gas.MetaData{
ID: id,
Severity: gas.Low,
Confidence: gas.High,
What: "Errors unhandled.",
},
whitelist: whitelist,
}, []ast.Node{(*ast.AssignStmt)(nil), (*ast.ExprStmt)(nil)}
}

95
vendor/github.com/GoASTScanner/gas/rules/fileperms.go generated vendored Normal file
View File

@ -0,0 +1,95 @@
// (c) Copyright 2016 Hewlett Packard Enterprise Development LP
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package rules
import (
"fmt"
"go/ast"
"strconv"
"github.com/GoASTScanner/gas"
)
type filePermissions struct {
gas.MetaData
mode int64
pkg string
calls []string
}
func (r *filePermissions) ID() string {
return r.MetaData.ID
}
func getConfiguredMode(conf map[string]interface{}, configKey string, defaultMode int64) int64 {
var mode = defaultMode
if value, ok := conf[configKey]; ok {
switch value.(type) {
case int64:
mode = value.(int64)
case string:
if m, e := strconv.ParseInt(value.(string), 0, 64); e != nil {
mode = defaultMode
} else {
mode = m
}
}
}
return mode
}
func (r *filePermissions) Match(n ast.Node, c *gas.Context) (*gas.Issue, error) {
if callexpr, matched := gas.MatchCallByPackage(n, c, r.pkg, r.calls...); matched {
modeArg := callexpr.Args[len(callexpr.Args)-1]
if mode, err := gas.GetInt(modeArg); err == nil && mode > r.mode {
return gas.NewIssue(c, n, r.ID(), r.What, r.Severity, r.Confidence), nil
}
}
return nil, nil
}
// NewFilePerms creates a rule to detect file creation with a more permissive than configured
// permission mask.
func NewFilePerms(id string, conf gas.Config) (gas.Rule, []ast.Node) {
mode := getConfiguredMode(conf, "G302", 0600)
return &filePermissions{
mode: mode,
pkg: "os",
calls: []string{"OpenFile", "Chmod"},
MetaData: gas.MetaData{
ID: id,
Severity: gas.Medium,
Confidence: gas.High,
What: fmt.Sprintf("Expect file permissions to be %#o or less", mode),
},
}, []ast.Node{(*ast.CallExpr)(nil)}
}
// NewMkdirPerms creates a rule to detect directory creation with more permissive than
// configured permission mask.
func NewMkdirPerms(id string, conf gas.Config) (gas.Rule, []ast.Node) {
mode := getConfiguredMode(conf, "G301", 0750)
return &filePermissions{
mode: mode,
pkg: "os",
calls: []string{"Mkdir", "MkdirAll"},
MetaData: gas.MetaData{
ID: id,
Severity: gas.Medium,
Confidence: gas.High,
What: fmt.Sprintf("Expect directory permissions to be %#o or less", mode),
},
}, []ast.Node{(*ast.CallExpr)(nil)}
}

View File

@ -0,0 +1,147 @@
// (c) Copyright 2016 Hewlett Packard Enterprise Development LP
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package rules
import (
"go/ast"
"regexp"
"strconv"
"github.com/GoASTScanner/gas"
"github.com/nbutton23/zxcvbn-go"
)
type credentials struct {
gas.MetaData
pattern *regexp.Regexp
entropyThreshold float64
perCharThreshold float64
truncate int
ignoreEntropy bool
}
func (r *credentials) ID() string {
return r.MetaData.ID
}
func truncate(s string, n int) string {
if n > len(s) {
return s
}
return s[:n]
}
func (r *credentials) isHighEntropyString(str string) bool {
s := truncate(str, r.truncate)
info := zxcvbn.PasswordStrength(s, []string{})
entropyPerChar := info.Entropy / float64(len(s))
return (info.Entropy >= r.entropyThreshold ||
(info.Entropy >= (r.entropyThreshold/2) &&
entropyPerChar >= r.perCharThreshold))
}
func (r *credentials) Match(n ast.Node, ctx *gas.Context) (*gas.Issue, error) {
switch node := n.(type) {
case *ast.AssignStmt:
return r.matchAssign(node, ctx)
case *ast.ValueSpec:
return r.matchValueSpec(node, ctx)
}
return nil, nil
}
func (r *credentials) matchAssign(assign *ast.AssignStmt, ctx *gas.Context) (*gas.Issue, error) {
for _, i := range assign.Lhs {
if ident, ok := i.(*ast.Ident); ok {
if r.pattern.MatchString(ident.Name) {
for _, e := range assign.Rhs {
if val, err := gas.GetString(e); err == nil {
if r.ignoreEntropy || (!r.ignoreEntropy && r.isHighEntropyString(val)) {
return gas.NewIssue(ctx, assign, r.ID(), r.What, r.Severity, r.Confidence), nil
}
}
}
}
}
}
return nil, nil
}
func (r *credentials) matchValueSpec(valueSpec *ast.ValueSpec, ctx *gas.Context) (*gas.Issue, error) {
for index, ident := range valueSpec.Names {
if r.pattern.MatchString(ident.Name) && valueSpec.Values != nil {
// const foo, bar = "same value"
if len(valueSpec.Values) <= index {
index = len(valueSpec.Values) - 1
}
if val, err := gas.GetString(valueSpec.Values[index]); err == nil {
if r.ignoreEntropy || (!r.ignoreEntropy && r.isHighEntropyString(val)) {
return gas.NewIssue(ctx, valueSpec, r.ID(), r.What, r.Severity, r.Confidence), nil
}
}
}
}
return nil, nil
}
// NewHardcodedCredentials attempts to find high entropy string constants being
// assigned to variables that appear to be related to credentials.
func NewHardcodedCredentials(id string, conf gas.Config) (gas.Rule, []ast.Node) {
pattern := `(?i)passwd|pass|password|pwd|secret|token`
entropyThreshold := 80.0
perCharThreshold := 3.0
ignoreEntropy := false
var truncateString = 16
if val, ok := conf["G101"]; ok {
conf := val.(map[string]string)
if configPattern, ok := conf["pattern"]; ok {
pattern = configPattern
}
if configIgnoreEntropy, ok := conf["ignore_entropy"]; ok {
if parsedBool, err := strconv.ParseBool(configIgnoreEntropy); err == nil {
ignoreEntropy = parsedBool
}
}
if configEntropyThreshold, ok := conf["entropy_threshold"]; ok {
if parsedNum, err := strconv.ParseFloat(configEntropyThreshold, 64); err == nil {
entropyThreshold = parsedNum
}
}
if configCharThreshold, ok := conf["per_char_threshold"]; ok {
if parsedNum, err := strconv.ParseFloat(configCharThreshold, 64); err == nil {
perCharThreshold = parsedNum
}
}
if configTruncate, ok := conf["truncate"]; ok {
if parsedInt, err := strconv.Atoi(configTruncate); err == nil {
truncateString = parsedInt
}
}
}
return &credentials{
pattern: regexp.MustCompile(pattern),
entropyThreshold: entropyThreshold,
perCharThreshold: perCharThreshold,
ignoreEntropy: ignoreEntropy,
truncate: truncateString,
MetaData: gas.MetaData{
ID: id,
What: "Potential hardcoded credentials",
Confidence: gas.Low,
Severity: gas.High,
},
}, []ast.Node{(*ast.AssignStmt)(nil), (*ast.ValueSpec)(nil)}
}

55
vendor/github.com/GoASTScanner/gas/rules/rand.go generated vendored Normal file
View File

@ -0,0 +1,55 @@
// (c) Copyright 2016 Hewlett Packard Enterprise Development LP
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package rules
import (
"go/ast"
"github.com/GoASTScanner/gas"
)
type weakRand struct {
gas.MetaData
funcNames []string
packagePath string
}
func (w *weakRand) ID() string {
return w.MetaData.ID
}
func (w *weakRand) Match(n ast.Node, c *gas.Context) (*gas.Issue, error) {
for _, funcName := range w.funcNames {
if _, matched := gas.MatchCallByPackage(n, c, w.packagePath, funcName); matched {
return gas.NewIssue(c, n, w.ID(), w.What, w.Severity, w.Confidence), nil
}
}
return nil, nil
}
// NewWeakRandCheck detects the use of random number generator that isn't cryptographically secure
func NewWeakRandCheck(id string, conf gas.Config) (gas.Rule, []ast.Node) {
return &weakRand{
funcNames: []string{"Read", "Int"},
packagePath: "math/rand",
MetaData: gas.MetaData{
ID: id,
Severity: gas.High,
Confidence: gas.Medium,
What: "Use of weak random number generator (math/rand instead of crypto/rand)",
},
}, []ast.Node{(*ast.CallExpr)(nil)}
}

63
vendor/github.com/GoASTScanner/gas/rules/readfile.go generated vendored Normal file
View File

@ -0,0 +1,63 @@
// (c) Copyright 2016 Hewlett Packard Enterprise Development LP
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package rules
import (
"go/ast"
"go/types"
"github.com/GoASTScanner/gas"
)
type readfile struct {
gas.MetaData
gas.CallList
}
// ID returns the identifier for this rule
func (r *readfile) ID() string {
return r.MetaData.ID
}
// Match inspects AST nodes to determine if the match the methods `os.Open` or `ioutil.ReadFile`
func (r *readfile) Match(n ast.Node, c *gas.Context) (*gas.Issue, error) {
if node := r.ContainsCallExpr(n, c); node != nil {
for _, arg := range node.Args {
if ident, ok := arg.(*ast.Ident); ok {
obj := c.Info.ObjectOf(ident)
if _, ok := obj.(*types.Var); ok && !gas.TryResolve(ident, c) {
return gas.NewIssue(c, n, r.ID(), r.What, r.Severity, r.Confidence), nil
}
}
}
}
return nil, nil
}
// NewReadFile detects cases where we read files
func NewReadFile(id string, conf gas.Config) (gas.Rule, []ast.Node) {
rule := &readfile{
CallList: gas.NewCallList(),
MetaData: gas.MetaData{
ID: id,
What: "Potential file inclusion via variable",
Severity: gas.Medium,
Confidence: gas.High,
},
}
rule.Add("io/ioutil", "ReadFile")
rule.Add("os", "Open")
return rule, []ast.Node{(*ast.CallExpr)(nil)}
}

58
vendor/github.com/GoASTScanner/gas/rules/rsa.go generated vendored Normal file
View File

@ -0,0 +1,58 @@
// (c) Copyright 2016 Hewlett Packard Enterprise Development LP
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package rules
import (
"fmt"
"go/ast"
"github.com/GoASTScanner/gas"
)
type weakKeyStrength struct {
gas.MetaData
calls gas.CallList
bits int
}
func (w *weakKeyStrength) ID() string {
return w.MetaData.ID
}
func (w *weakKeyStrength) Match(n ast.Node, c *gas.Context) (*gas.Issue, error) {
if callExpr := w.calls.ContainsCallExpr(n, c); callExpr != nil {
if bits, err := gas.GetInt(callExpr.Args[1]); err == nil && bits < (int64)(w.bits) {
return gas.NewIssue(c, n, w.ID(), w.What, w.Severity, w.Confidence), nil
}
}
return nil, nil
}
// NewWeakKeyStrength builds a rule that detects RSA keys < 2048 bits
func NewWeakKeyStrength(id string, conf gas.Config) (gas.Rule, []ast.Node) {
calls := gas.NewCallList()
calls.Add("crypto/rsa", "GenerateKey")
bits := 2048
return &weakKeyStrength{
calls: calls,
bits: bits,
MetaData: gas.MetaData{
ID: id,
Severity: gas.Medium,
Confidence: gas.High,
What: fmt.Sprintf("RSA keys should be at least %d bits", bits),
},
}, []ast.Node{(*ast.CallExpr)(nil)}
}

108
vendor/github.com/GoASTScanner/gas/rules/rulelist.go generated vendored Normal file
View File

@ -0,0 +1,108 @@
// (c) Copyright 2016 Hewlett Packard Enterprise Development LP
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package rules
import (
"github.com/GoASTScanner/gas"
)
// RuleDefinition contains the description of a rule and a mechanism to
// create it.
type RuleDefinition struct {
ID string
Description string
Create gas.RuleBuilder
}
// RuleList is a mapping of rule ID's to rule definitions
type RuleList map[string]RuleDefinition
// Builders returns all the create methods for a given rule list
func (rl RuleList) Builders() map[string]gas.RuleBuilder {
builders := make(map[string]gas.RuleBuilder)
for _, def := range rl {
builders[def.ID] = def.Create
}
return builders
}
// RuleFilter can be used to include or exclude a rule depending on the return
// value of the function
type RuleFilter func(string) bool
// NewRuleFilter is a closure that will include/exclude the rule ID's based on
// the supplied boolean value.
func NewRuleFilter(action bool, ruleIDs ...string) RuleFilter {
rulelist := make(map[string]bool)
for _, rule := range ruleIDs {
rulelist[rule] = true
}
return func(rule string) bool {
if _, found := rulelist[rule]; found {
return action
}
return !action
}
}
// Generate the list of rules to use
func Generate(filters ...RuleFilter) RuleList {
rules := []RuleDefinition{
// misc
{"G101", "Look for hardcoded credentials", NewHardcodedCredentials},
{"G102", "Bind to all interfaces", NewBindsToAllNetworkInterfaces},
{"G103", "Audit the use of unsafe block", NewUsingUnsafe},
{"G104", "Audit errors not checked", NewNoErrorCheck},
{"G105", "Audit the use of big.Exp function", NewUsingBigExp},
{"G106", "Audit the use of ssh.InsecureIgnoreHostKey function", NewSSHHostKey},
// injection
{"G201", "SQL query construction using format string", NewSQLStrFormat},
{"G202", "SQL query construction using string concatenation", NewSQLStrConcat},
{"G203", "Use of unescaped data in HTML templates", NewTemplateCheck},
{"G204", "Audit use of command execution", NewSubproc},
// filesystem
{"G301", "Poor file permissions used when creating a directory", NewMkdirPerms},
{"G302", "Poor file permisions used when creation file or using chmod", NewFilePerms},
{"G303", "Creating tempfile using a predictable path", NewBadTempFile},
{"G304", "File path provided as taint input", NewReadFile},
// crypto
{"G401", "Detect the usage of DES, RC4, or MD5", NewUsesWeakCryptography},
{"G402", "Look for bad TLS connection settings", NewIntermediateTLSCheck},
{"G403", "Ensure minimum RSA key length of 2048 bits", NewWeakKeyStrength},
{"G404", "Insecure random number source (rand)", NewWeakRandCheck},
// blacklist
{"G501", "Import blacklist: crypto/md5", NewBlacklistedImportMD5},
{"G502", "Import blacklist: crypto/des", NewBlacklistedImportDES},
{"G503", "Import blacklist: crypto/rc4", NewBlacklistedImportRC4},
{"G504", "Import blacklist: net/http/cgi", NewBlacklistedImportCGI},
}
ruleMap := make(map[string]RuleDefinition)
RULES:
for _, rule := range rules {
for _, filter := range filters {
if filter(rule.ID) {
continue RULES
}
}
ruleMap[rule.ID] = rule
}
return ruleMap
}

135
vendor/github.com/GoASTScanner/gas/rules/sql.go generated vendored Normal file
View File

@ -0,0 +1,135 @@
// (c) Copyright 2016 Hewlett Packard Enterprise Development LP
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package rules
import (
"go/ast"
"regexp"
"github.com/GoASTScanner/gas"
)
type sqlStatement struct {
gas.MetaData
// Contains a list of patterns which must all match for the rule to match.
patterns []*regexp.Regexp
}
func (s *sqlStatement) ID() string {
return s.MetaData.ID
}
// See if the string matches the patterns for the statement.
func (s *sqlStatement) MatchPatterns(str string) bool {
for _, pattern := range s.patterns {
if !pattern.MatchString(str) {
return false
}
}
return true
}
type sqlStrConcat struct {
sqlStatement
}
func (s *sqlStrConcat) ID() string {
return s.MetaData.ID
}
// see if we can figure out what it is
func (s *sqlStrConcat) checkObject(n *ast.Ident) bool {
if n.Obj != nil {
return n.Obj.Kind != ast.Var && n.Obj.Kind != ast.Fun
}
return false
}
// Look for "SELECT * FROM table WHERE " + " ' OR 1=1"
func (s *sqlStrConcat) Match(n ast.Node, c *gas.Context) (*gas.Issue, error) {
if node, ok := n.(*ast.BinaryExpr); ok {
if start, ok := node.X.(*ast.BasicLit); ok {
if str, e := gas.GetString(start); e == nil {
if !s.MatchPatterns(str) {
return nil, nil
}
if _, ok := node.Y.(*ast.BasicLit); ok {
return nil, nil // string cat OK
}
if second, ok := node.Y.(*ast.Ident); ok && s.checkObject(second) {
return nil, nil
}
return gas.NewIssue(c, n, s.ID(), s.What, s.Severity, s.Confidence), nil
}
}
}
return nil, nil
}
// NewSQLStrConcat looks for cases where we are building SQL strings via concatenation
func NewSQLStrConcat(id string, conf gas.Config) (gas.Rule, []ast.Node) {
return &sqlStrConcat{
sqlStatement: sqlStatement{
patterns: []*regexp.Regexp{
regexp.MustCompile(`(?)(SELECT|DELETE|INSERT|UPDATE|INTO|FROM|WHERE) `),
},
MetaData: gas.MetaData{
ID: id,
Severity: gas.Medium,
Confidence: gas.High,
What: "SQL string concatenation",
},
},
}, []ast.Node{(*ast.BinaryExpr)(nil)}
}
type sqlStrFormat struct {
sqlStatement
calls gas.CallList
}
// Looks for "fmt.Sprintf("SELECT * FROM foo where '%s', userInput)"
func (s *sqlStrFormat) Match(n ast.Node, c *gas.Context) (*gas.Issue, error) {
// TODO(gm) improve confidence if database/sql is being used
if node := s.calls.ContainsCallExpr(n, c); node != nil {
if arg, e := gas.GetString(node.Args[0]); s.MatchPatterns(arg) && e == nil {
return gas.NewIssue(c, n, s.ID(), s.What, s.Severity, s.Confidence), nil
}
}
return nil, nil
}
// NewSQLStrFormat looks for cases where we're building SQL query strings using format strings
func NewSQLStrFormat(id string, conf gas.Config) (gas.Rule, []ast.Node) {
rule := &sqlStrFormat{
calls: gas.NewCallList(),
sqlStatement: sqlStatement{
patterns: []*regexp.Regexp{
regexp.MustCompile("(?)(SELECT|DELETE|INSERT|UPDATE|INTO|FROM|WHERE) "),
regexp.MustCompile("%[^bdoxXfFp]"),
},
MetaData: gas.MetaData{
ID: id,
Severity: gas.Medium,
Confidence: gas.High,
What: "SQL string formatting",
},
},
}
rule.calls.AddAll("fmt", "Sprint", "Sprintf", "Sprintln")
return rule, []ast.Node{(*ast.CallExpr)(nil)}
}

38
vendor/github.com/GoASTScanner/gas/rules/ssh.go generated vendored Normal file
View File

@ -0,0 +1,38 @@
package rules
import (
"go/ast"
"github.com/GoASTScanner/gas"
)
type sshHostKey struct {
gas.MetaData
pkg string
calls []string
}
func (r *sshHostKey) ID() string {
return r.MetaData.ID
}
func (r *sshHostKey) Match(n ast.Node, c *gas.Context) (gi *gas.Issue, err error) {
if _, matches := gas.MatchCallByPackage(n, c, r.pkg, r.calls...); matches {
return gas.NewIssue(c, n, r.ID(), r.What, r.Severity, r.Confidence), nil
}
return nil, nil
}
// NewSSHHostKey rule detects the use of insecure ssh HostKeyCallback.
func NewSSHHostKey(id string, conf gas.Config) (gas.Rule, []ast.Node) {
return &sshHostKey{
pkg: "golang.org/x/crypto/ssh",
calls: []string{"InsecureIgnoreHostKey"},
MetaData: gas.MetaData{
ID: id,
What: "Use of ssh InsecureIgnoreHostKey should be audited",
Severity: gas.Medium,
Confidence: gas.High,
},
}, []ast.Node{(*ast.CallExpr)(nil)}
}

63
vendor/github.com/GoASTScanner/gas/rules/subproc.go generated vendored Normal file
View File

@ -0,0 +1,63 @@
// (c) Copyright 2016 Hewlett Packard Enterprise Development LP
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package rules
import (
"go/ast"
"go/types"
"github.com/GoASTScanner/gas"
)
type subprocess struct {
gas.MetaData
gas.CallList
}
func (r *subprocess) ID() string {
return r.MetaData.ID
}
// TODO(gm) The only real potential for command injection with a Go project
// is something like this:
//
// syscall.Exec("/bin/sh", []string{"-c", tainted})
//
// E.g. Input is correctly escaped but the execution context being used
// is unsafe. For example:
//
// syscall.Exec("echo", "foobar" + tainted)
func (r *subprocess) Match(n ast.Node, c *gas.Context) (*gas.Issue, error) {
if node := r.ContainsCallExpr(n, c); node != nil {
for _, arg := range node.Args {
if ident, ok := arg.(*ast.Ident); ok {
obj := c.Info.ObjectOf(ident)
if _, ok := obj.(*types.Var); ok && !gas.TryResolve(ident, c) {
return gas.NewIssue(c, n, r.ID(), "Subprocess launched with variable", gas.Medium, gas.High), nil
}
}
}
return gas.NewIssue(c, n, r.ID(), "Subprocess launching should be audited", gas.Low, gas.High), nil
}
return nil, nil
}
// NewSubproc detects cases where we are forking out to an external process
func NewSubproc(id string, conf gas.Config) (gas.Rule, []ast.Node) {
rule := &subprocess{gas.MetaData{ID: id}, gas.NewCallList()}
rule.Add("os/exec", "Command")
rule.Add("syscall", "Exec")
return rule, []ast.Node{(*ast.CallExpr)(nil)}
}

58
vendor/github.com/GoASTScanner/gas/rules/tempfiles.go generated vendored Normal file
View File

@ -0,0 +1,58 @@
// (c) Copyright 2016 Hewlett Packard Enterprise Development LP
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package rules
import (
"go/ast"
"regexp"
"github.com/GoASTScanner/gas"
)
type badTempFile struct {
gas.MetaData
calls gas.CallList
args *regexp.Regexp
}
func (t *badTempFile) ID() string {
return t.MetaData.ID
}
func (t *badTempFile) Match(n ast.Node, c *gas.Context) (gi *gas.Issue, err error) {
if node := t.calls.ContainsCallExpr(n, c); node != nil {
if arg, e := gas.GetString(node.Args[0]); t.args.MatchString(arg) && e == nil {
return gas.NewIssue(c, n, t.ID(), t.What, t.Severity, t.Confidence), nil
}
}
return nil, nil
}
// NewBadTempFile detects direct writes to predictable path in temporary directory
func NewBadTempFile(id string, conf gas.Config) (gas.Rule, []ast.Node) {
calls := gas.NewCallList()
calls.Add("io/ioutil", "WriteFile")
calls.Add("os", "Create")
return &badTempFile{
calls: calls,
args: regexp.MustCompile(`^/tmp/.*$|^/var/tmp/.*$`),
MetaData: gas.MetaData{
ID: id,
Severity: gas.Medium,
Confidence: gas.High,
What: "File creation in shared tmp directory without using ioutil.Tempfile",
},
}, []ast.Node{(*ast.CallExpr)(nil)}
}

61
vendor/github.com/GoASTScanner/gas/rules/templates.go generated vendored Normal file
View File

@ -0,0 +1,61 @@
// (c) Copyright 2016 Hewlett Packard Enterprise Development LP
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package rules
import (
"go/ast"
"github.com/GoASTScanner/gas"
)
type templateCheck struct {
gas.MetaData
calls gas.CallList
}
func (t *templateCheck) ID() string {
return t.MetaData.ID
}
func (t *templateCheck) Match(n ast.Node, c *gas.Context) (*gas.Issue, error) {
if node := t.calls.ContainsCallExpr(n, c); node != nil {
for _, arg := range node.Args {
if _, ok := arg.(*ast.BasicLit); !ok { // basic lits are safe
return gas.NewIssue(c, n, t.ID(), t.What, t.Severity, t.Confidence), nil
}
}
}
return nil, nil
}
// NewTemplateCheck constructs the template check rule. This rule is used to
// find use of tempaltes where HTML/JS escaping is not being used
func NewTemplateCheck(id string, conf gas.Config) (gas.Rule, []ast.Node) {
calls := gas.NewCallList()
calls.Add("html/template", "HTML")
calls.Add("html/template", "HTMLAttr")
calls.Add("html/template", "JS")
calls.Add("html/template", "URL")
return &templateCheck{
calls: calls,
MetaData: gas.MetaData{
ID: id,
Severity: gas.Medium,
Confidence: gas.Low,
What: "this method will not auto-escape HTML. Verify data is well formed.",
},
}, []ast.Node{(*ast.CallExpr)(nil)}
}

130
vendor/github.com/GoASTScanner/gas/rules/tls.go generated vendored Normal file
View File

@ -0,0 +1,130 @@
// (c) Copyright 2016 Hewlett Packard Enterprise Development LP
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//go:generate tlsconfig
package rules
import (
"fmt"
"go/ast"
"github.com/GoASTScanner/gas"
)
type insecureConfigTLS struct {
gas.MetaData
MinVersion int16
MaxVersion int16
requiredType string
goodCiphers []string
}
func (t *insecureConfigTLS) ID() string {
return t.MetaData.ID
}
func stringInSlice(a string, list []string) bool {
for _, b := range list {
if b == a {
return true
}
}
return false
}
func (t *insecureConfigTLS) processTLSCipherSuites(n ast.Node, c *gas.Context) *gas.Issue {
if ciphers, ok := n.(*ast.CompositeLit); ok {
for _, cipher := range ciphers.Elts {
if ident, ok := cipher.(*ast.SelectorExpr); ok {
if !stringInSlice(ident.Sel.Name, t.goodCiphers) {
err := fmt.Sprintf("TLS Bad Cipher Suite: %s", ident.Sel.Name)
return gas.NewIssue(c, ident, t.ID(), err, gas.High, gas.High)
}
}
}
}
return nil
}
func (t *insecureConfigTLS) processTLSConfVal(n *ast.KeyValueExpr, c *gas.Context) *gas.Issue {
if ident, ok := n.Key.(*ast.Ident); ok {
switch ident.Name {
case "InsecureSkipVerify":
if node, ok := n.Value.(*ast.Ident); ok {
if node.Name != "false" {
return gas.NewIssue(c, n, t.ID(), "TLS InsecureSkipVerify set true.", gas.High, gas.High)
}
} else {
// TODO(tk): symbol tab look up to get the actual value
return gas.NewIssue(c, n, t.ID(), "TLS InsecureSkipVerify may be true.", gas.High, gas.Low)
}
case "PreferServerCipherSuites":
if node, ok := n.Value.(*ast.Ident); ok {
if node.Name == "false" {
return gas.NewIssue(c, n, t.ID(), "TLS PreferServerCipherSuites set false.", gas.Medium, gas.High)
}
} else {
// TODO(tk): symbol tab look up to get the actual value
return gas.NewIssue(c, n, t.ID(), "TLS PreferServerCipherSuites may be false.", gas.Medium, gas.Low)
}
case "MinVersion":
if ival, ierr := gas.GetInt(n.Value); ierr == nil {
if (int16)(ival) < t.MinVersion {
return gas.NewIssue(c, n, t.ID(), "TLS MinVersion too low.", gas.High, gas.High)
}
// TODO(tk): symbol tab look up to get the actual value
return gas.NewIssue(c, n, t.ID(), "TLS MinVersion may be too low.", gas.High, gas.Low)
}
case "MaxVersion":
if ival, ierr := gas.GetInt(n.Value); ierr == nil {
if (int16)(ival) < t.MaxVersion {
return gas.NewIssue(c, n, t.ID(), "TLS MaxVersion too low.", gas.High, gas.High)
}
// TODO(tk): symbol tab look up to get the actual value
return gas.NewIssue(c, n, t.ID(), "TLS MaxVersion may be too low.", gas.High, gas.Low)
}
case "CipherSuites":
if ret := t.processTLSCipherSuites(n.Value, c); ret != nil {
return ret
}
}
}
return nil
}
func (t *insecureConfigTLS) Match(n ast.Node, c *gas.Context) (*gas.Issue, error) {
if complit, ok := n.(*ast.CompositeLit); ok && complit.Type != nil {
actualType := c.Info.TypeOf(complit.Type)
if actualType != nil && actualType.String() == t.requiredType {
for _, elt := range complit.Elts {
if kve, ok := elt.(*ast.KeyValueExpr); ok {
issue := t.processTLSConfVal(kve, c)
if issue != nil {
return issue, nil
}
}
}
}
}
return nil, nil
}

141
vendor/github.com/GoASTScanner/gas/rules/tls_config.go generated vendored Normal file
View File

@ -0,0 +1,141 @@
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(id string, conf gas.Config) (gas.Rule, []ast.Node) {
return &insecureConfigTLS{
MetaData: gas.MetaData{ID: id},
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_CHACHA20_POLY1305",
"TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305",
"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(id string, conf gas.Config) (gas.Rule, []ast.Node) {
return &insecureConfigTLS{
MetaData: gas.MetaData{ID: id},
requiredType: "crypto/tls.Config",
MinVersion: 0x0301,
MaxVersion: 0x0303,
goodCiphers: []string{
"TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305",
"TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305",
"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(id string, conf gas.Config) (gas.Rule, []ast.Node) {
return &insecureConfigTLS{
MetaData: gas.MetaData{ID: id},
requiredType: "crypto/tls.Config",
MinVersion: 0x0300,
MaxVersion: 0x0303,
goodCiphers: []string{
"TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305",
"TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305",
"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)}
}

53
vendor/github.com/GoASTScanner/gas/rules/unsafe.go generated vendored Normal file
View File

@ -0,0 +1,53 @@
// (c) Copyright 2016 Hewlett Packard Enterprise Development LP
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package rules
import (
"go/ast"
"github.com/GoASTScanner/gas"
)
type usingUnsafe struct {
gas.MetaData
pkg string
calls []string
}
func (r *usingUnsafe) ID() string {
return r.MetaData.ID
}
func (r *usingUnsafe) Match(n ast.Node, c *gas.Context) (gi *gas.Issue, err error) {
if _, matches := gas.MatchCallByPackage(n, c, r.pkg, r.calls...); matches {
return gas.NewIssue(c, n, r.ID(), r.What, r.Severity, r.Confidence), nil
}
return nil, nil
}
// NewUsingUnsafe rule detects the use of the unsafe package. This is only
// really useful for auditing purposes.
func NewUsingUnsafe(id string, conf gas.Config) (gas.Rule, []ast.Node) {
return &usingUnsafe{
pkg: "unsafe",
calls: []string{"Alignof", "Offsetof", "Sizeof", "Pointer"},
MetaData: gas.MetaData{
ID: id,
What: "Use of unsafe calls should be audited",
Severity: gas.Low,
Confidence: gas.High,
},
}, []ast.Node{(*ast.CallExpr)(nil)}
}

57
vendor/github.com/GoASTScanner/gas/rules/weakcrypto.go generated vendored Normal file
View File

@ -0,0 +1,57 @@
// (c) Copyright 2016 Hewlett Packard Enterprise Development LP
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package rules
import (
"go/ast"
"github.com/GoASTScanner/gas"
)
type usesWeakCryptography struct {
gas.MetaData
blacklist map[string][]string
}
func (r *usesWeakCryptography) ID() string {
return r.MetaData.ID
}
func (r *usesWeakCryptography) Match(n ast.Node, c *gas.Context) (*gas.Issue, error) {
for pkg, funcs := range r.blacklist {
if _, matched := gas.MatchCallByPackage(n, c, pkg, funcs...); matched {
return gas.NewIssue(c, n, r.ID(), r.What, r.Severity, r.Confidence), nil
}
}
return nil, nil
}
// NewUsesWeakCryptography detects uses of des.* md5.* or rc4.*
func NewUsesWeakCryptography(id string, conf gas.Config) (gas.Rule, []ast.Node) {
calls := make(map[string][]string)
calls["crypto/des"] = []string{"NewCipher", "NewTripleDESCipher"}
calls["crypto/md5"] = []string{"New", "Sum"}
calls["crypto/rc4"] = []string{"NewCipher"}
rule := &usesWeakCryptography{
blacklist: calls,
MetaData: gas.MetaData{
ID: id,
Severity: gas.Medium,
Confidence: gas.High,
What: "Use of weak cryptographic primitive",
},
}
return rule, []ast.Node{(*ast.CallExpr)(nil)}
}

2
vendor/github.com/nbutton23/zxcvbn-go/.gitignore generated vendored Normal file
View File

@ -0,0 +1,2 @@
zxcvbn
debug.test

27
vendor/github.com/nbutton23/zxcvbn-go/Gopkg.lock generated vendored Normal file
View File

@ -0,0 +1,27 @@
# This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'.
[[projects]]
name = "github.com/davecgh/go-spew"
packages = ["spew"]
revision = "346938d642f2ec3594ed81d874461961cd0faa76"
version = "v1.1.0"
[[projects]]
name = "github.com/pmezard/go-difflib"
packages = ["difflib"]
revision = "792786c7400a136282c1664665ae0a8db921c6c2"
version = "v1.0.0"
[[projects]]
name = "github.com/stretchr/testify"
packages = ["assert"]
revision = "69483b4bd14f5845b5a1e55bca19e954e827f1d0"
version = "v1.1.4"
[solve-meta]
analyzer-name = "dep"
analyzer-version = 1
inputs-digest = "b750880cdc8ce044e6f9bf3b331d8a392471c328107b8c3d42e3e11022d76858"
solver-name = "gps-cdcl"
solver-version = 1

26
vendor/github.com/nbutton23/zxcvbn-go/Gopkg.toml generated vendored Normal file
View File

@ -0,0 +1,26 @@
# Gopkg.toml example
#
# Refer to https://github.com/golang/dep/blob/master/docs/Gopkg.toml.md
# for detailed Gopkg.toml documentation.
#
# required = ["github.com/user/thing/cmd/thing"]
# ignored = ["github.com/user/project/pkgX", "bitbucket.org/user/project/pkgA/pkgY"]
#
# [[constraint]]
# name = "github.com/user/project"
# version = "1.0.0"
#
# [[constraint]]
# name = "github.com/user/project2"
# branch = "dev"
# source = "github.com/myfork/project2"
#
# [[override]]
# name = "github.com/x/y"
# version = "2.4.0"
[[constraint]]
name = "github.com/stretchr/testify"
version = "1.1.4"

20
vendor/github.com/nbutton23/zxcvbn-go/LICENSE.txt generated vendored Normal file
View File

@ -0,0 +1,20 @@
Copyright (c) Nathan Button
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

78
vendor/github.com/nbutton23/zxcvbn-go/README.md generated vendored Normal file
View File

@ -0,0 +1,78 @@
This is a goLang port of python-zxcvbn and [zxcvbn](https://github.com/dropbox/zxcvbn), which are python and JavaScript password strength
generators. zxcvbn attempts to give sound password advice through pattern
matching and conservative entropy calculations. It finds 10k common passwords,
common American names and surnames, common English words, and common patterns
like dates, repeats (aaa), sequences (abcd), and QWERTY patterns.
Please refer to http://tech.dropbox.com/?p=165 for the full details and
motivation behind zxcbvn. The source code for the original JavaScript (well,
actually CoffeeScript) implementation can be found at:
https://github.com/lowe/zxcvbn
Python at:
https://github.com/dropbox/python-zxcvbn
For full motivation, see:
http://tech.dropbox.com/?p=165
------------------------------------------------------------------------
Use
------------------------------------------------------------------------
The zxcvbn module has the public method PasswordStrength() function. Import zxcvbn, and
call PasswordStrength(password string, userInputs []string). The function will return a
result dictionary with the following keys:
Entropy # bits
CrackTime # estimation of actual crack time, in seconds.
CrackTimeDisplay # same crack time, as a friendlier string:
# "instant", "6 minutes", "centuries", etc.
Score # [0,1,2,3,4] if crack time is less than
# [10^2, 10^4, 10^6, 10^8, Infinity].
# (useful for implementing a strength bar.)
MatchSequence # the list of patterns that zxcvbn based the
# entropy calculation on.
CalcTime # how long it took to calculate an answer,
# in milliseconds. usually only a few ms.
The userInputs argument is an splice of strings that zxcvbn
will add to its internal dictionary. This can be whatever list of
strings you like, but is meant for user inputs from other fields of the
form, like name and email. That way a password that includes the user's
personal info can be heavily penalized. This list is also good for
site-specific vocabulary.
Bug reports and pull requests welcome!
------------------------------------------------------------------------
Project Status
------------------------------------------------------------------------
Use zxcvbn_test.go to check how close to feature parity the project is.
------------------------------------------------------------------------
Acknowledgment
------------------------------------------------------------------------
Thanks to Dan Wheeler (https://github.com/lowe) for the CoffeeScript implementation
(see above.) To repeat his outside acknowledgements (which remain useful, as always):
Many thanks to Mark Burnett for releasing his 10k top passwords list:
http://xato.net/passwords/more-top-worst-passwords
and for his 2006 book,
"Perfect Passwords: Selection, Protection, Authentication"
Huge thanks to Wiktionary contributors for building a frequency list
of English as used in television and movies:
http://en.wiktionary.org/wiki/Wiktionary:Frequency_lists
Last but not least, big thanks to xkcd :)
https://xkcd.com/936/

View File

@ -0,0 +1,96 @@
package adjacency
import (
"encoding/json"
"log"
// "fmt"
"github.com/nbutton23/zxcvbn-go/data"
)
type AdjacencyGraph struct {
Graph map[string][]string
averageDegree float64
Name string
}
var AdjacencyGph = make(map[string]AdjacencyGraph)
func init() {
AdjacencyGph["qwerty"] = BuildQwerty()
AdjacencyGph["dvorak"] = BuildDvorak()
AdjacencyGph["keypad"] = BuildKeypad()
AdjacencyGph["macKeypad"] = BuildMacKeypad()
AdjacencyGph["l33t"] = BuildLeet()
}
func BuildQwerty() AdjacencyGraph {
data, err := zxcvbn_data.Asset("data/Qwerty.json")
if err != nil {
panic("Can't find asset")
}
return GetAdjancencyGraphFromFile(data, "qwerty")
}
func BuildDvorak() AdjacencyGraph {
data, err := zxcvbn_data.Asset("data/Dvorak.json")
if err != nil {
panic("Can't find asset")
}
return GetAdjancencyGraphFromFile(data, "dvorak")
}
func BuildKeypad() AdjacencyGraph {
data, err := zxcvbn_data.Asset("data/Keypad.json")
if err != nil {
panic("Can't find asset")
}
return GetAdjancencyGraphFromFile(data, "keypad")
}
func BuildMacKeypad() AdjacencyGraph {
data, err := zxcvbn_data.Asset("data/MacKeypad.json")
if err != nil {
panic("Can't find asset")
}
return GetAdjancencyGraphFromFile(data, "mac_keypad")
}
func BuildLeet() AdjacencyGraph {
data, err := zxcvbn_data.Asset("data/L33t.json")
if err != nil {
panic("Can't find asset")
}
return GetAdjancencyGraphFromFile(data, "keypad")
}
func GetAdjancencyGraphFromFile(data []byte, name string) AdjacencyGraph {
var graph AdjacencyGraph
err := json.Unmarshal(data, &graph)
if err != nil {
log.Fatal(err)
}
graph.Name = name
return graph
}
//on qwerty, 'g' has degree 6, being adjacent to 'ftyhbv'. '\' has degree 1.
//this calculates the average over all keys.
//TODO double check that i ported this correctly scoring.coffee ln 5
func (adjGrp AdjacencyGraph) CalculateAvgDegree() float64 {
if adjGrp.averageDegree != float64(0) {
return adjGrp.averageDegree
}
var avg float64
var count float64
for _, value := range adjGrp.Graph {
for _, char := range value {
if char != "" || char != " " {
avg += float64(len(char))
count++
}
}
}
adjGrp.averageDegree = avg / count
return adjGrp.averageDegree
}

444
vendor/github.com/nbutton23/zxcvbn-go/data/bindata.go generated vendored Normal file

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,215 @@
package entropy
import (
"github.com/nbutton23/zxcvbn-go/adjacency"
"github.com/nbutton23/zxcvbn-go/match"
"github.com/nbutton23/zxcvbn-go/utils/math"
"math"
"regexp"
"unicode"
)
const (
START_UPPER string = `^[A-Z][^A-Z]+$`
END_UPPER string = `^[^A-Z]+[A-Z]$'`
ALL_UPPER string = `^[A-Z]+$`
NUM_YEARS = float64(119) // years match against 1900 - 2019
NUM_MONTHS = float64(12)
NUM_DAYS = float64(31)
)
var (
KEYPAD_STARTING_POSITIONS = len(adjacency.AdjacencyGph["keypad"].Graph)
KEYPAD_AVG_DEGREE = adjacency.AdjacencyGph["keypad"].CalculateAvgDegree()
)
func DictionaryEntropy(match match.Match, rank float64) float64 {
baseEntropy := math.Log2(rank)
upperCaseEntropy := extraUpperCaseEntropy(match)
//TODO: L33t
return baseEntropy + upperCaseEntropy
}
func extraUpperCaseEntropy(match match.Match) float64 {
word := match.Token
allLower := true
for _, char := range word {
if unicode.IsUpper(char) {
allLower = false
break
}
}
if allLower {
return float64(0)
}
//a capitalized word is the most common capitalization scheme,
//so it only doubles the search space (uncapitalized + capitalized): 1 extra bit of entropy.
//allcaps and end-capitalized are common enough too, underestimate as 1 extra bit to be safe.
for _, regex := range []string{START_UPPER, END_UPPER, ALL_UPPER} {
matcher := regexp.MustCompile(regex)
if matcher.MatchString(word) {
return float64(1)
}
}
//Otherwise calculate the number of ways to capitalize U+L uppercase+lowercase letters with U uppercase letters or
//less. Or, if there's more uppercase than lower (for e.g. PASSwORD), the number of ways to lowercase U+L letters
//with L lowercase letters or less.
countUpper, countLower := float64(0), float64(0)
for _, char := range word {
if unicode.IsUpper(char) {
countUpper++
} else if unicode.IsLower(char) {
countLower++
}
}
totalLenght := countLower + countUpper
var possibililities float64
for i := float64(0); i <= math.Min(countUpper, countLower); i++ {
possibililities += float64(zxcvbn_math.NChoseK(totalLenght, i))
}
if possibililities < 1 {
return float64(1)
}
return float64(math.Log2(possibililities))
}
func SpatialEntropy(match match.Match, turns int, shiftCount int) float64 {
var s, d float64
if match.DictionaryName == "qwerty" || match.DictionaryName == "dvorak" {
//todo: verify qwerty and dvorak have the same length and degree
s = float64(len(adjacency.BuildQwerty().Graph))
d = adjacency.BuildQwerty().CalculateAvgDegree()
} else {
s = float64(KEYPAD_STARTING_POSITIONS)
d = KEYPAD_AVG_DEGREE
}
possibilities := float64(0)
length := float64(len(match.Token))
//TODO: Should this be <= or just < ?
//Estimate the number of possible patterns w/ length L or less with t turns or less
for i := float64(2); i <= length+1; i++ {
possibleTurns := math.Min(float64(turns), i-1)
for j := float64(1); j <= possibleTurns+1; j++ {
x := zxcvbn_math.NChoseK(i-1, j-1) * s * math.Pow(d, j)
possibilities += x
}
}
entropy := math.Log2(possibilities)
//add extra entropu for shifted keys. ( % instead of 5 A instead of a)
//Math is similar to extra entropy for uppercase letters in dictionary matches.
if S := float64(shiftCount); S > float64(0) {
possibilities = float64(0)
U := length - S
for i := float64(0); i < math.Min(S, U)+1; i++ {
possibilities += zxcvbn_math.NChoseK(S+U, i)
}
entropy += math.Log2(possibilities)
}
return entropy
}
func RepeatEntropy(match match.Match) float64 {
cardinality := CalcBruteForceCardinality(match.Token)
entropy := math.Log2(cardinality * float64(len(match.Token)))
return entropy
}
//TODO: Validate against python
func CalcBruteForceCardinality(password string) float64 {
lower, upper, digits, symbols := float64(0), float64(0), float64(0), float64(0)
for _, char := range password {
if unicode.IsLower(char) {
lower = float64(26)
} else if unicode.IsDigit(char) {
digits = float64(10)
} else if unicode.IsUpper(char) {
upper = float64(26)
} else {
symbols = float64(33)
}
}
cardinality := lower + upper + digits + symbols
return cardinality
}
func SequenceEntropy(match match.Match, dictionaryLength int, ascending bool) float64 {
firstChar := match.Token[0]
baseEntropy := float64(0)
if string(firstChar) == "a" || string(firstChar) == "1" {
baseEntropy = float64(0)
} else {
baseEntropy = math.Log2(float64(dictionaryLength))
//TODO: should this be just the first or any char?
if unicode.IsUpper(rune(firstChar)) {
baseEntropy++
}
}
if !ascending {
baseEntropy++
}
return baseEntropy + math.Log2(float64(len(match.Token)))
}
func ExtraLeetEntropy(match match.Match, password string) float64 {
var subsitutions float64
var unsub float64
subPassword := password[match.I:match.J]
for index, char := range subPassword {
if string(char) != string(match.Token[index]) {
subsitutions++
} else {
//TODO: Make this only true for 1337 chars that are not subs?
unsub++
}
}
var possibilities float64
for i := float64(0); i <= math.Min(subsitutions, unsub)+1; i++ {
possibilities += zxcvbn_math.NChoseK(subsitutions+unsub, i)
}
if possibilities <= 1 {
return float64(1)
}
return math.Log2(possibilities)
}
func YearEntropy(dateMatch match.DateMatch) float64 {
return math.Log2(NUM_YEARS)
}
func DateEntropy(dateMatch match.DateMatch) float64 {
var entropy float64
if dateMatch.Year < 100 {
entropy = math.Log2(NUM_DAYS * NUM_MONTHS * 100)
} else {
entropy = math.Log2(NUM_DAYS * NUM_MONTHS * NUM_YEARS)
}
if dateMatch.Separator != "" {
entropy += 2 //add two bits for separator selection [/,-,.,etc]
}
return entropy
}

View File

@ -0,0 +1,47 @@
package frequency
import (
"encoding/json"
"github.com/nbutton23/zxcvbn-go/data"
"log"
)
type FrequencyList struct {
Name string
List []string
}
var FrequencyLists = make(map[string]FrequencyList)
func init() {
maleFilePath := getAsset("data/MaleNames.json")
femaleFilePath := getAsset("data/FemaleNames.json")
surnameFilePath := getAsset("data/Surnames.json")
englishFilePath := getAsset("data/English.json")
passwordsFilePath := getAsset("data/Passwords.json")
FrequencyLists["MaleNames"] = GetStringListFromAsset(maleFilePath, "MaleNames")
FrequencyLists["FemaleNames"] = GetStringListFromAsset(femaleFilePath, "FemaleNames")
FrequencyLists["Surname"] = GetStringListFromAsset(surnameFilePath, "Surname")
FrequencyLists["English"] = GetStringListFromAsset(englishFilePath, "English")
FrequencyLists["Passwords"] = GetStringListFromAsset(passwordsFilePath, "Passwords")
}
func getAsset(name string) []byte {
data, err := zxcvbn_data.Asset(name)
if err != nil {
panic("Error getting asset " + name)
}
return data
}
func GetStringListFromAsset(data []byte, name string) FrequencyList {
var tempList FrequencyList
err := json.Unmarshal(data, &tempList)
if err != nil {
log.Fatal(err)
}
tempList.Name = name
return tempList
}

40
vendor/github.com/nbutton23/zxcvbn-go/match/match.go generated vendored Normal file
View File

@ -0,0 +1,40 @@
package match
type Matches []Match
func (s Matches) Len() int {
return len(s)
}
func (s Matches) Swap(i, j int) {
s[i], s[j] = s[j], s[i]
}
func (s Matches) Less(i, j int) bool {
if s[i].I < s[j].I {
return true
} else if s[i].I == s[j].I {
return s[i].J < s[j].J
} else {
return false
}
}
type Match struct {
Pattern string
I, J int
Token string
DictionaryName string
Entropy float64
}
type DateMatch struct {
Pattern string
I, J int
Token string
Separator string
Day, Month, Year int64
}
type Matcher struct {
MatchingFunc func(password string) []Match
ID string
}

View File

@ -0,0 +1,204 @@
package matching
import (
"regexp"
"strconv"
"strings"
"github.com/nbutton23/zxcvbn-go/entropy"
"github.com/nbutton23/zxcvbn-go/match"
)
const (
DATESEP_MATCHER_NAME = "DATESEP"
DATEWITHOUTSEP_MATCHER_NAME = "DATEWITHOUT"
)
func FilterDateSepMatcher(m match.Matcher) bool {
return m.ID == DATESEP_MATCHER_NAME
}
func FilterDateWithoutSepMatcher(m match.Matcher) bool {
return m.ID == DATEWITHOUTSEP_MATCHER_NAME
}
func checkDate(day, month, year int64) (bool, int64, int64, int64) {
if (12 <= month && month <= 31) && day <= 12 {
day, month = month, day
}
if day > 31 || month > 12 {
return false, 0, 0, 0
}
if !((1900 <= year && year <= 2019) || (0 <= year && year <= 99)) {
return false, 0, 0, 0
}
return true, day, month, year
}
func dateSepMatcher(password string) []match.Match {
dateMatches := dateSepMatchHelper(password)
var matches []match.Match
for _, dateMatch := range dateMatches {
match := match.Match{
I: dateMatch.I,
J: dateMatch.J,
Entropy: entropy.DateEntropy(dateMatch),
DictionaryName: "date_match",
Token: dateMatch.Token,
}
matches = append(matches, match)
}
return matches
}
func dateSepMatchHelper(password string) []match.DateMatch {
var matches []match.DateMatch
matcher := regexp.MustCompile(DATE_RX_YEAR_SUFFIX)
for _, v := range matcher.FindAllString(password, len(password)) {
splitV := matcher.FindAllStringSubmatch(v, len(v))
i := strings.Index(password, v)
j := i + len(v)
day, _ := strconv.ParseInt(splitV[0][4], 10, 16)
month, _ := strconv.ParseInt(splitV[0][2], 10, 16)
year, _ := strconv.ParseInt(splitV[0][6], 10, 16)
match := match.DateMatch{Day: day, Month: month, Year: year, Separator: splitV[0][5], I: i, J: j, Token: password[i:j]}
matches = append(matches, match)
}
matcher = regexp.MustCompile(DATE_RX_YEAR_PREFIX)
for _, v := range matcher.FindAllString(password, len(password)) {
splitV := matcher.FindAllStringSubmatch(v, len(v))
i := strings.Index(password, v)
j := i + len(v)
day, _ := strconv.ParseInt(splitV[0][4], 10, 16)
month, _ := strconv.ParseInt(splitV[0][6], 10, 16)
year, _ := strconv.ParseInt(splitV[0][2], 10, 16)
match := match.DateMatch{Day: day, Month: month, Year: year, Separator: splitV[0][5], I: i, J: j, Token: password[i:j]}
matches = append(matches, match)
}
var out []match.DateMatch
for _, match := range matches {
if valid, day, month, year := checkDate(match.Day, match.Month, match.Year); valid {
match.Pattern = "date"
match.Day = day
match.Month = month
match.Year = year
out = append(out, match)
}
}
return out
}
type DateMatchCandidate struct {
DayMonth string
Year string
I, J int
}
type DateMatchCandidateTwo struct {
Day string
Month string
Year string
I, J int
}
func dateWithoutSepMatch(password string) []match.Match {
dateMatches := dateWithoutSepMatchHelper(password)
var matches []match.Match
for _, dateMatch := range dateMatches {
match := match.Match{
I: dateMatch.I,
J: dateMatch.J,
Entropy: entropy.DateEntropy(dateMatch),
DictionaryName: "date_match",
Token: dateMatch.Token,
}
matches = append(matches, match)
}
return matches
}
//TODO Has issues with 6 digit dates
func dateWithoutSepMatchHelper(password string) (matches []match.DateMatch) {
matcher := regexp.MustCompile(DATE_WITHOUT_SEP_MATCH)
for _, v := range matcher.FindAllString(password, len(password)) {
i := strings.Index(password, v)
j := i + len(v)
length := len(v)
lastIndex := length - 1
var candidatesRoundOne []DateMatchCandidate
if length <= 6 {
//2-digit year prefix
candidatesRoundOne = append(candidatesRoundOne, buildDateMatchCandidate(v[2:], v[0:2], i, j))
//2-digityear suffix
candidatesRoundOne = append(candidatesRoundOne, buildDateMatchCandidate(v[0:lastIndex-2], v[lastIndex-2:], i, j))
}
if length >= 6 {
//4-digit year prefix
candidatesRoundOne = append(candidatesRoundOne, buildDateMatchCandidate(v[4:], v[0:4], i, j))
//4-digit year sufix
candidatesRoundOne = append(candidatesRoundOne, buildDateMatchCandidate(v[0:lastIndex-3], v[lastIndex-3:], i, j))
}
var candidatesRoundTwo []DateMatchCandidateTwo
for _, c := range candidatesRoundOne {
if len(c.DayMonth) == 2 {
candidatesRoundTwo = append(candidatesRoundTwo, buildDateMatchCandidateTwo(c.DayMonth[0:0], c.DayMonth[1:1], c.Year, c.I, c.J))
} else if len(c.DayMonth) == 3 {
candidatesRoundTwo = append(candidatesRoundTwo, buildDateMatchCandidateTwo(c.DayMonth[0:2], c.DayMonth[2:2], c.Year, c.I, c.J))
candidatesRoundTwo = append(candidatesRoundTwo, buildDateMatchCandidateTwo(c.DayMonth[0:0], c.DayMonth[1:3], c.Year, c.I, c.J))
} else if len(c.DayMonth) == 4 {
candidatesRoundTwo = append(candidatesRoundTwo, buildDateMatchCandidateTwo(c.DayMonth[0:2], c.DayMonth[2:4], c.Year, c.I, c.J))
}
}
for _, candidate := range candidatesRoundTwo {
intDay, err := strconv.ParseInt(candidate.Day, 10, 16)
if err != nil {
continue
}
intMonth, err := strconv.ParseInt(candidate.Month, 10, 16)
if err != nil {
continue
}
intYear, err := strconv.ParseInt(candidate.Year, 10, 16)
if err != nil {
continue
}
if ok, _, _, _ := checkDate(intDay, intMonth, intYear); ok {
matches = append(matches, match.DateMatch{Token: password, Pattern: "date", Day: intDay, Month: intMonth, Year: intYear, I: i, J: j})
}
}
}
return matches
}
func buildDateMatchCandidate(dayMonth, year string, i, j int) DateMatchCandidate {
return DateMatchCandidate{DayMonth: dayMonth, Year: year, I: i, J: j}
}
func buildDateMatchCandidateTwo(day, month string, year string, i, j int) DateMatchCandidateTwo {
return DateMatchCandidateTwo{Day: day, Month: month, Year: year, I: i, J: j}
}

View File

@ -0,0 +1,54 @@
package matching
import (
"github.com/nbutton23/zxcvbn-go/entropy"
"github.com/nbutton23/zxcvbn-go/match"
"strings"
)
func buildDictMatcher(dictName string, rankedDict map[string]int) func(password string) []match.Match {
return func(password string) []match.Match {
matches := dictionaryMatch(password, dictName, rankedDict)
for _, v := range matches {
v.DictionaryName = dictName
}
return matches
}
}
func dictionaryMatch(password string, dictionaryName string, rankedDict map[string]int) []match.Match {
length := len(password)
var results []match.Match
pwLower := strings.ToLower(password)
for i := 0; i < length; i++ {
for j := i; j < length; j++ {
word := pwLower[i : j+1]
if val, ok := rankedDict[word]; ok {
matchDic := match.Match{Pattern: "dictionary",
DictionaryName: dictionaryName,
I: i,
J: j,
Token: password[i : j+1],
}
matchDic.Entropy = entropy.DictionaryEntropy(matchDic, float64(val))
results = append(results, matchDic)
}
}
}
return results
}
func buildRankedDict(unrankedList []string) map[string]int {
result := make(map[string]int)
for i, v := range unrankedList {
result[strings.ToLower(v)] = i + 1
}
return result
}

75
vendor/github.com/nbutton23/zxcvbn-go/matching/leet.go generated vendored Normal file
View File

@ -0,0 +1,75 @@
package matching
import (
"strings"
"github.com/nbutton23/zxcvbn-go/entropy"
"github.com/nbutton23/zxcvbn-go/match"
)
const L33T_MATCHER_NAME = "l33t"
func FilterL33tMatcher(m match.Matcher) bool {
return m.ID == L33T_MATCHER_NAME
}
func l33tMatch(password string) []match.Match {
substitutions := relevantL33tSubtable(password)
permutations := getAllPermutationsOfLeetSubstitutions(password, substitutions)
var matches []match.Match
for _, permutation := range permutations {
for _, mather := range DICTIONARY_MATCHERS {
matches = append(matches, mather.MatchingFunc(permutation)...)
}
}
for _, match := range matches {
match.Entropy += entropy.ExtraLeetEntropy(match, password)
match.DictionaryName = match.DictionaryName + "_3117"
}
return matches
}
func getAllPermutationsOfLeetSubstitutions(password string, substitutionsMap map[string][]string) []string {
var permutations []string
for index, char := range password {
for value, splice := range substitutionsMap {
for _, sub := range splice {
if string(char) == sub {
var permutation string
permutation = password[:index] + value + password[index+1:]
permutations = append(permutations, permutation)
if index < len(permutation) {
tempPermutations := getAllPermutationsOfLeetSubstitutions(permutation[index+1:], substitutionsMap)
for _, temp := range tempPermutations {
permutations = append(permutations, permutation[:index+1]+temp)
}
}
}
}
}
}
return permutations
}
func relevantL33tSubtable(password string) map[string][]string {
relevantSubs := make(map[string][]string)
for key, values := range L33T_TABLE.Graph {
for _, value := range values {
if strings.Contains(password, value) {
relevantSubs[key] = append(relevantSubs[key], value)
}
}
}
return relevantSubs
}

View File

@ -0,0 +1,87 @@
package matching
import (
"sort"
"github.com/nbutton23/zxcvbn-go/adjacency"
"github.com/nbutton23/zxcvbn-go/frequency"
"github.com/nbutton23/zxcvbn-go/match"
)
var (
DICTIONARY_MATCHERS []match.Matcher
MATCHERS []match.Matcher
ADJACENCY_GRAPHS []adjacency.AdjacencyGraph
L33T_TABLE adjacency.AdjacencyGraph
SEQUENCES map[string]string
)
const (
DATE_RX_YEAR_SUFFIX string = `((\d{1,2})(\s|-|\/|\\|_|\.)(\d{1,2})(\s|-|\/|\\|_|\.)(19\d{2}|200\d|201\d|\d{2}))`
DATE_RX_YEAR_PREFIX string = `((19\d{2}|200\d|201\d|\d{2})(\s|-|/|\\|_|\.)(\d{1,2})(\s|-|/|\\|_|\.)(\d{1,2}))`
DATE_WITHOUT_SEP_MATCH string = `\d{4,8}`
)
func init() {
loadFrequencyList()
}
func Omnimatch(password string, userInputs []string, filters ...func(match.Matcher) bool) (matches []match.Match) {
//Can I run into the issue where nil is not equal to nil?
if DICTIONARY_MATCHERS == nil || ADJACENCY_GRAPHS == nil {
loadFrequencyList()
}
if userInputs != nil {
userInputMatcher := buildDictMatcher("user_inputs", buildRankedDict(userInputs))
matches = userInputMatcher(password)
}
for _, matcher := range MATCHERS {
shouldBeFiltered := false
for i := range filters {
if filters[i](matcher) {
shouldBeFiltered = true
break
}
}
if !shouldBeFiltered {
matches = append(matches, matcher.MatchingFunc(password)...)
}
}
sort.Sort(match.Matches(matches))
return matches
}
func loadFrequencyList() {
for n, list := range frequency.FrequencyLists {
DICTIONARY_MATCHERS = append(DICTIONARY_MATCHERS, match.Matcher{MatchingFunc: buildDictMatcher(n, buildRankedDict(list.List)), ID: n})
}
L33T_TABLE = adjacency.AdjacencyGph["l33t"]
ADJACENCY_GRAPHS = append(ADJACENCY_GRAPHS, adjacency.AdjacencyGph["qwerty"])
ADJACENCY_GRAPHS = append(ADJACENCY_GRAPHS, adjacency.AdjacencyGph["dvorak"])
ADJACENCY_GRAPHS = append(ADJACENCY_GRAPHS, adjacency.AdjacencyGph["keypad"])
ADJACENCY_GRAPHS = append(ADJACENCY_GRAPHS, adjacency.AdjacencyGph["macKeypad"])
//l33tFilePath, _ := filepath.Abs("adjacency/L33t.json")
//L33T_TABLE = adjacency.GetAdjancencyGraphFromFile(l33tFilePath, "l33t")
SEQUENCES = make(map[string]string)
SEQUENCES["lower"] = "abcdefghijklmnopqrstuvwxyz"
SEQUENCES["upper"] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
SEQUENCES["digits"] = "0123456789"
MATCHERS = append(MATCHERS, DICTIONARY_MATCHERS...)
MATCHERS = append(MATCHERS, match.Matcher{MatchingFunc: spatialMatch, ID: SPATIAL_MATCHER_NAME})
MATCHERS = append(MATCHERS, match.Matcher{MatchingFunc: repeatMatch, ID: REPEAT_MATCHER_NAME})
MATCHERS = append(MATCHERS, match.Matcher{MatchingFunc: sequenceMatch, ID: SEQUENCE_MATCHER_NAME})
MATCHERS = append(MATCHERS, match.Matcher{MatchingFunc: l33tMatch, ID: L33T_MATCHER_NAME})
MATCHERS = append(MATCHERS, match.Matcher{MatchingFunc: dateSepMatcher, ID: DATESEP_MATCHER_NAME})
MATCHERS = append(MATCHERS, match.Matcher{MatchingFunc: dateWithoutSepMatch, ID: DATEWITHOUTSEP_MATCHER_NAME})
}

View File

@ -0,0 +1,66 @@
package matching
import (
"strings"
"github.com/nbutton23/zxcvbn-go/entropy"
"github.com/nbutton23/zxcvbn-go/match"
)
const REPEAT_MATCHER_NAME = "REPEAT"
func FilterRepeatMatcher(m match.Matcher) bool {
return m.ID == REPEAT_MATCHER_NAME
}
func repeatMatch(password string) []match.Match {
var matches []match.Match
//Loop through password. if current == prev currentStreak++ else if currentStreak > 2 {buildMatch; currentStreak = 1} prev = current
var current, prev string
currentStreak := 1
var i int
var char rune
for i, char = range password {
current = string(char)
if i == 0 {
prev = current
continue
}
if strings.ToLower(current) == strings.ToLower(prev) {
currentStreak++
} else if currentStreak > 2 {
iPos := i - currentStreak
jPos := i - 1
matchRepeat := match.Match{
Pattern: "repeat",
I: iPos,
J: jPos,
Token: password[iPos : jPos+1],
DictionaryName: prev}
matchRepeat.Entropy = entropy.RepeatEntropy(matchRepeat)
matches = append(matches, matchRepeat)
currentStreak = 1
} else {
currentStreak = 1
}
prev = current
}
if currentStreak > 2 {
iPos := i - currentStreak + 1
jPos := i
matchRepeat := match.Match{
Pattern: "repeat",
I: iPos,
J: jPos,
Token: password[iPos : jPos+1],
DictionaryName: prev}
matchRepeat.Entropy = entropy.RepeatEntropy(matchRepeat)
matches = append(matches, matchRepeat)
}
return matches
}

View File

@ -0,0 +1,75 @@
package matching
import (
"strings"
"github.com/nbutton23/zxcvbn-go/entropy"
"github.com/nbutton23/zxcvbn-go/match"
)
const SEQUENCE_MATCHER_NAME = "SEQ"
func FilterSequenceMatcher(m match.Matcher) bool {
return m.ID == SEQUENCE_MATCHER_NAME
}
func sequenceMatch(password string) []match.Match {
var matches []match.Match
for i := 0; i < len(password); {
j := i + 1
var seq string
var seqName string
seqDirection := 0
for seqCandidateName, seqCandidate := range SEQUENCES {
iN := strings.Index(seqCandidate, string(password[i]))
var jN int
if j < len(password) {
jN = strings.Index(seqCandidate, string(password[j]))
} else {
jN = -1
}
if iN > -1 && jN > -1 {
direction := jN - iN
if direction == 1 || direction == -1 {
seq = seqCandidate
seqName = seqCandidateName
seqDirection = direction
break
}
}
}
if seq != "" {
for {
var prevN, curN int
if j < len(password) {
prevChar, curChar := password[j-1], password[j]
prevN, curN = strings.Index(seq, string(prevChar)), strings.Index(seq, string(curChar))
}
if j == len(password) || curN-prevN != seqDirection {
if j-i > 2 {
matchSequence := match.Match{
Pattern: "sequence",
I: i,
J: j - 1,
Token: password[i:j],
DictionaryName: seqName,
}
matchSequence.Entropy = entropy.SequenceEntropy(matchSequence, len(seq), (seqDirection == 1))
matches = append(matches, matchSequence)
}
break
} else {
j += 1
}
}
}
i = j
}
return matches
}

View File

@ -0,0 +1,87 @@
package matching
import (
"strings"
"github.com/nbutton23/zxcvbn-go/adjacency"
"github.com/nbutton23/zxcvbn-go/entropy"
"github.com/nbutton23/zxcvbn-go/match"
)
const SPATIAL_MATCHER_NAME = "SPATIAL"
func FilterSpatialMatcher(m match.Matcher) bool {
return m.ID == SPATIAL_MATCHER_NAME
}
func spatialMatch(password string) (matches []match.Match) {
for _, graph := range ADJACENCY_GRAPHS {
if graph.Graph != nil {
matches = append(matches, spatialMatchHelper(password, graph)...)
}
}
return matches
}
func spatialMatchHelper(password string, graph adjacency.AdjacencyGraph) (matches []match.Match) {
for i := 0; i < len(password)-1; {
j := i + 1
lastDirection := -99 //an int that it should never be!
turns := 0
shiftedCount := 0
for {
prevChar := password[j-1]
found := false
foundDirection := -1
curDirection := -1
//My graphs seem to be wrong. . . and where the hell is qwerty
adjacents := graph.Graph[string(prevChar)]
//Consider growing pattern by one character if j hasn't gone over the edge
if j < len(password) {
curChar := password[j]
for _, adj := range adjacents {
curDirection += 1
if strings.Index(adj, string(curChar)) != -1 {
found = true
foundDirection = curDirection
if strings.Index(adj, string(curChar)) == 1 {
//index 1 in the adjacency means the key is shifted, 0 means unshifted: A vs a, % vs 5, etc.
//for example, 'q' is adjacent to the entry '2@'. @ is shifted w/ index 1, 2 is unshifted.
shiftedCount += 1
}
if lastDirection != foundDirection {
//adding a turn is correct even in the initial case when last_direction is null:
//every spatial pattern starts with a turn.
turns += 1
lastDirection = foundDirection
}
break
}
}
}
//if the current pattern continued, extend j and try to grow again
if found {
j += 1
} else {
//otherwise push the pattern discovered so far, if any...
//don't consider length 1 or 2 chains.
if j-i > 2 {
matchSpc := match.Match{Pattern: "spatial", I: i, J: j - 1, Token: password[i:j], DictionaryName: graph.Name}
matchSpc.Entropy = entropy.SpatialEntropy(matchSpc, turns, shiftedCount)
matches = append(matches, matchSpc)
}
//. . . and then start a new search from the rest of the password
i = j
break
}
}
}
return matches
}

View File

@ -0,0 +1,180 @@
package scoring
import (
"fmt"
"github.com/nbutton23/zxcvbn-go/entropy"
"github.com/nbutton23/zxcvbn-go/match"
"github.com/nbutton23/zxcvbn-go/utils/math"
"math"
"sort"
)
const (
START_UPPER string = `^[A-Z][^A-Z]+$`
END_UPPER string = `^[^A-Z]+[A-Z]$'`
ALL_UPPER string = `^[A-Z]+$`
//for a hash function like bcrypt/scrypt/PBKDF2, 10ms per guess is a safe lower bound.
//(usually a guess would take longer -- this assumes fast hardware and a small work factor.)
//adjust for your site accordingly if you use another hash function, possibly by
//several orders of magnitude!
SINGLE_GUESS float64 = 0.010
NUM_ATTACKERS float64 = 100 //Cores used to make guesses
SECONDS_PER_GUESS float64 = SINGLE_GUESS / NUM_ATTACKERS
)
type MinEntropyMatch struct {
Password string
Entropy float64
MatchSequence []match.Match
CrackTime float64
CrackTimeDisplay string
Score int
CalcTime float64
}
/*
Returns minimum entropy
Takes a list of overlapping matches, returns the non-overlapping sublist with
minimum entropy. O(nm) dp alg for length-n password with m candidate matches.
*/
func MinimumEntropyMatchSequence(password string, matches []match.Match) MinEntropyMatch {
bruteforceCardinality := float64(entropy.CalcBruteForceCardinality(password))
upToK := make([]float64, len(password))
backPointers := make([]match.Match, len(password))
for k := 0; k < len(password); k++ {
upToK[k] = get(upToK, k-1) + math.Log2(bruteforceCardinality)
for _, match := range matches {
if match.J != k {
continue
}
i, j := match.I, match.J
//see if best entropy up to i-1 + entropy of match is less that current min at j
upTo := get(upToK, i-1)
candidateEntropy := upTo + match.Entropy
if candidateEntropy < upToK[j] {
upToK[j] = candidateEntropy
match.Entropy = candidateEntropy
backPointers[j] = match
}
}
}
//walk backwards and decode the best sequence
var matchSequence []match.Match
passwordLen := len(password)
passwordLen--
for k := passwordLen; k >= 0; {
match := backPointers[k]
if match.Pattern != "" {
matchSequence = append(matchSequence, match)
k = match.I - 1
} else {
k--
}
}
sort.Sort(match.Matches(matchSequence))
makeBruteForceMatch := func(i, j int) match.Match {
return match.Match{Pattern: "bruteforce",
I: i,
J: j,
Token: password[i : j+1],
Entropy: math.Log2(math.Pow(bruteforceCardinality, float64(j-i)))}
}
k := 0
var matchSequenceCopy []match.Match
for _, match := range matchSequence {
i, j := match.I, match.J
if i-k > 0 {
matchSequenceCopy = append(matchSequenceCopy, makeBruteForceMatch(k, i-1))
}
k = j + 1
matchSequenceCopy = append(matchSequenceCopy, match)
}
if k < len(password) {
matchSequenceCopy = append(matchSequenceCopy, makeBruteForceMatch(k, len(password)-1))
}
var minEntropy float64
if len(password) == 0 {
minEntropy = float64(0)
} else {
minEntropy = upToK[len(password)-1]
}
crackTime := roundToXDigits(entropyToCrackTime(minEntropy), 3)
return MinEntropyMatch{Password: password,
Entropy: roundToXDigits(minEntropy, 3),
MatchSequence: matchSequenceCopy,
CrackTime: crackTime,
CrackTimeDisplay: displayTime(crackTime),
Score: crackTimeToScore(crackTime)}
}
func get(a []float64, i int) float64 {
if i < 0 || i >= len(a) {
return float64(0)
}
return a[i]
}
func entropyToCrackTime(entropy float64) float64 {
crackTime := (0.5 * math.Pow(float64(2), entropy)) * SECONDS_PER_GUESS
return crackTime
}
func roundToXDigits(number float64, digits int) float64 {
return zxcvbn_math.Round(number, .5, digits)
}
func displayTime(seconds float64) string {
formater := "%.1f %s"
minute := float64(60)
hour := minute * float64(60)
day := hour * float64(24)
month := day * float64(31)
year := month * float64(12)
century := year * float64(100)
if seconds < minute {
return "instant"
} else if seconds < hour {
return fmt.Sprintf(formater, (1 + math.Ceil(seconds/minute)), "minutes")
} else if seconds < day {
return fmt.Sprintf(formater, (1 + math.Ceil(seconds/hour)), "hours")
} else if seconds < month {
return fmt.Sprintf(formater, (1 + math.Ceil(seconds/day)), "days")
} else if seconds < year {
return fmt.Sprintf(formater, (1 + math.Ceil(seconds/month)), "months")
} else if seconds < century {
return fmt.Sprintf(formater, (1 + math.Ceil(seconds/century)), "years")
} else {
return "centuries"
}
}
func crackTimeToScore(seconds float64) int {
if seconds < math.Pow(10, 2) {
return 0
} else if seconds < math.Pow(10, 4) {
return 1
} else if seconds < math.Pow(10, 6) {
return 2
} else if seconds < math.Pow(10, 8) {
return 3
}
return 4
}

View File

@ -0,0 +1,40 @@
package zxcvbn_math
import "math"
/**
I am surprised that I have to define these. . . Maybe i just didn't look hard enough for a lib.
*/
//http://blog.plover.com/math/choose.html
func NChoseK(n, k float64) float64 {
if k > n {
return 0
} else if k == 0 {
return 1
}
var r float64 = 1
for d := float64(1); d <= k; d++ {
r *= n
r /= d
n--
}
return r
}
func Round(val float64, roundOn float64, places int) (newVal float64) {
var round float64
pow := math.Pow(10, float64(places))
digit := pow * val
_, div := math.Modf(digit)
if div >= roundOn {
round = math.Ceil(digit)
} else {
round = math.Floor(digit)
}
newVal = round / pow
return
}

21
vendor/github.com/nbutton23/zxcvbn-go/zxcvbn.go generated vendored Normal file
View File

@ -0,0 +1,21 @@
package zxcvbn
import (
"time"
"github.com/nbutton23/zxcvbn-go/match"
"github.com/nbutton23/zxcvbn-go/matching"
"github.com/nbutton23/zxcvbn-go/scoring"
"github.com/nbutton23/zxcvbn-go/utils/math"
)
func PasswordStrength(password string, userInputs []string, filters ...func(match.Matcher) bool) scoring.MinEntropyMatch {
start := time.Now()
matches := matching.Omnimatch(password, userInputs, filters...)
result := scoring.MinimumEntropyMatchSequence(password, matches)
end := time.Now()
calcTime := end.Nanosecond() - start.Nanosecond()
result.CalcTime = zxcvbn_math.Round(float64(calcTime)*time.Nanosecond.Seconds(), .5, 3)
return result
}