initial commit

This commit is contained in:
2024-10-11 17:06:47 +02:00
commit aa59c865e3
9 changed files with 105317 additions and 0 deletions

39
Makefile Normal file
View File

@@ -0,0 +1,39 @@
# Copyright © 2023 Thomas von Dein
# This module is published under the terms of the BSD 3-Clause
# License. Please read the file LICENSE for details.
#
# no need to modify anything below
VERSION = $(shell grep VERSION handler.go | head -1 | cut -d '"' -f2)
all: buildlocal
buildlocal:
go build -o example/example example/example.go
clean:
rm -rf $(tool) coverage.out testdata t/out example/example
test: clean
go test $(ARGS)
singletest:
@echo "Call like this: make singletest TEST=TestName ARGS=-v"
go test -run $(TEST) $(ARGS)
cover-report:
go test -cover -coverprofile=coverage.out
go tool cover -html=coverage.out
goupdate:
go get -t -u=patch ./...
lint:
golangci-lint run -p bugs -p unused
release: buildlocal test
gh release create v$(VERSION) --generate-notes

68
example/test.go Normal file
View File

@@ -0,0 +1,68 @@
package main
import (
"bufio"
"fmt"
"log"
"os"
"github.com/tlinden/valpass"
)
const template string = `
Metric Random Threshhold Result
------------------------------------------------------------------
Compression rate 0%% min %d%% %d%%
Character distribution 100%% min %0.2f%% %0.2f%%
Character entropy 8.0 bits/char min %0.2f %0.2f bits/char
Character redundancy 0.0%% max %0.2f%% %0.2f%%
Dictionary match false false %t
------------------------------------------------------------------
Validation response %t
`
func main() {
opts := valpass.Options{
Compress: valpass.MIN_COMPRESS,
CharDistribution: valpass.MIN_DIST,
Entropy: valpass.MIN_ENTROPY,
Dictionary: &valpass.Dictionary{Words: ReadDict("t/american-english")},
UTF8: false,
}
res, err := valpass.Validate(os.Args[1], opts)
if err != nil {
log.Fatal(err)
}
fmt.Printf(
template,
opts.Compress,
res.Compress,
opts.CharDistribution,
res.CharDistribution,
opts.Entropy,
res.Entropy,
100-opts.CharDistribution,
100-res.CharDistribution,
res.DictionaryMatch,
res.Ok,
)
}
func ReadDict(path string) []string {
file, err := os.Open(path)
if err != nil {
panic(err)
}
defer file.Close()
var lines []string
scanner := bufio.NewScanner(file)
for scanner.Scan() {
lines = append(lines, scanner.Text())
}
return lines
}

5
go.mod Normal file
View File

@@ -0,0 +1,5 @@
module github.com/tlinden/valpass
go 1.22
require github.com/alecthomas/repr v0.4.0 // indirect

2
go.sum Normal file
View File

@@ -0,0 +1,2 @@
github.com/alecthomas/repr v0.4.0 h1:GhI2A8MACjfegCPVq9f1FLvIBS+DrQ2KQBFZP1iFzXc=
github.com/alecthomas/repr v0.4.0/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4=

285
lib.bak Normal file
View File

@@ -0,0 +1,285 @@
package valpass
import (
"bytes"
"compress/flate"
"fmt"
"math"
"strings"
)
/*
* Contains the raw dictionary data and some flags. Must be provided
* by the user
*/
type Dictionary struct {
Words []string // the actual dictionary
Submatch bool // if true 'foo' would match 'foobar'
}
/*
* Options define how to operate the validation
*/
type Options struct {
Compress int // minimum compression rate in percent
CharDistribution float64 // minimum char distribution in percent
Entropy float64 // minimum entropy value in bits/char
Dictionary []string // if set, lookup given dictionary, the caller provides it
UTF8 bool // if true work on unicode utf-8 space, not just bytes
}
/*
* Default validation config, a compromise of comfort and security, as always.
*/
const (
MIN_ENTROPY float64 = 3.0
MIN_COMPRESS int = 10
MIN_DICT bool = false
MIN_DIST float64 = 10.0
MAX_UTF8 int = 2164864 // max characters encodable with utf8
MAX_CHARS int = 95 // maximum printable US ASCII chars
MIN_DICT_LEN int = 5000
// we start our ascii arrays at char(32), so to have max 95
// elements in the slice, we subtract 32 from each ascii code
MIN_ASCII int = 32
)
type Result struct {
Ok bool
Options
}
func Validate(passphrase string, opts ...Options) (Result, error) {
result := Result{Ok: true}
options := Options{
MIN_COMPRESS,
MIN_DIST,
MIN_ENTROPY,
nil,
false,
}
if len(opts) == 1 {
options = opts[0]
}
if options.Entropy > 0 {
var entropy float64
var err error
switch options.UTF8 {
case true:
entropy, err = GetEntropyUTF8(passphrase)
if err != nil {
return result, err
}
default:
entropy, err = GetEntropyAscii(passphrase)
if err != nil {
return result, err
}
}
if entropy <= options.Entropy {
result.Ok = false
}
result.Entropy = entropy
}
if options.Compress > 0 {
compression, err := GetCompression([]byte(passphrase))
if err != nil {
return result, err
}
if compression >= options.Compress {
result.Ok = false
}
result.Compress = compression
}
if options.CharDistribution > 0 {
var dist float64
switch options.UTF8 {
case true:
dist = GetDistributionUTF8(passphrase)
default:
dist = GetDistributionAscii(passphrase)
}
if dist <= options.CharDistribution {
result.Ok = false
}
result.CharDistribution = dist
}
if len(options.Dictionary) > 0 {
}
return result, nil
}
/*
* we compress with Flate level 9 (max) and see if the result is
* smaller than the password, in which case it could be compressed and
* contains repeating characters; OR it is larger than the password,
* in which case it could NOT be compressed, which is what we want.
*/
func GetCompression(passphrase []byte) (int, error) {
var b bytes.Buffer
flater, _ := flate.NewWriter(&b, 9)
if _, err := flater.Write(passphrase); err != nil {
return 0, fmt.Errorf("failed to write to flate writer: %w", err)
}
if err := flater.Flush(); err != nil {
return 0, fmt.Errorf("failed to flush flate writer: %w", err)
}
if err := flater.Close(); err != nil {
return 0, fmt.Errorf("failed to close flate writer: %w", err)
}
// use floats to avoid division by zero panic
length := float32(len(passphrase))
compressed := float32(len(b.Bytes()))
if compressed >= length {
return 0, nil
}
percent := 100 - (compressed / (length / 100))
return int(percent), nil
}
/*
* Return the entropy as bits/rune, where rune is a unicode char in
* utf8 space.
*/
func GetEntropyUTF8(passphrase string) (float64, error) {
var entropy float64
length := len(passphrase)
wherechar := make([]int, MAX_UTF8)
hist := make([]int, length)
var histlen int
for i := 0; i < MAX_UTF8; i++ {
wherechar[i] = -1
}
for _, char := range passphrase {
if wherechar[char] == -1 {
wherechar[char] = histlen
histlen++
}
hist[wherechar[char]]++
}
for i := 0; i < histlen; i++ {
diff := float64(hist[i]) / float64(length)
entropy -= diff * math.Log2(diff)
}
return entropy, nil
}
/* same thing for us ascii */
func GetEntropyAscii(passphrase string) (float64, error) {
var entropy float64
length := len(passphrase)
wherechar := make([]int, MAX_CHARS)
hist := make([]int, length)
var histlen int
for i := 0; i < MAX_CHARS; i++ {
wherechar[i] = -1
}
for _, char := range []byte(passphrase) {
if char < MIN_ASCII || char > 126 {
return 0, fmt.Errorf("non-printable ASCII character encountered: %c", char)
}
if wherechar[char-MIN_ASCII] == -1 {
wherechar[char-MIN_ASCII] = histlen
histlen++
}
hist[wherechar[char-MIN_ASCII]]++
}
for i := 0; i < histlen; i++ {
diff := float64(hist[i]) / float64(length)
entropy -= diff * math.Log2(diff)
}
return entropy, nil
}
/*
* Return character distribution
*/
func GetDistributionUTF8(passphrase string) float64 {
hash := make([]int, MAX_UTF8)
var chars float64
for _, char := range passphrase {
hash[char]++
}
for i := 0; i < MAX_UTF8; i++ {
if hash[i] > 0 {
chars++
}
}
return chars / (float64(MAX_UTF8) / 100)
}
func GetDistributionAscii(passphrase string) float64 {
hash := make([]int, MAX_CHARS)
var chars float64
for _, char := range []byte(passphrase) {
hash[int(char)-MIN_ASCII]++
}
for i := 0; i < MAX_CHARS; i++ {
if hash[i] > 0 {
chars++
}
}
return chars / (float64(MAX_CHARS) / 100)
}
func GetDictMatch(passphrase string, dict *Dictionary) (bool, error) {
if len(dict.Words) < MIN_DICT_LEN {
return false, fmt.Errorf("provided dictionary is too small")
}
lcpass := strings.ToLower(passphrase)
if dict.Submatch {
for _, word := range dict.Words {
if strings.Contains(strings.ToLower(word), lcpass) {
return true, nil
}
}
} else {
for _, word := range dict.Words {
if lcpass == strings.ToLower(word) {
return true, nil
}
}
}
return false, nil
}

318
lib.go Normal file
View File

@@ -0,0 +1,318 @@
package valpass
import (
"bytes"
"compress/flate"
"fmt"
"math"
"strings"
)
/*
* Contains the raw dictionary data and some flags. Must be provided
* by the user
*/
type Dictionary struct {
Words []string // the actual dictionary
Submatch bool // if true 'foo' would match 'foobar'
}
/*
* Options define how to operate the validation
*/
type Options struct {
Compress int // minimum compression rate in percent
CharDistribution float64 // minimum char distribution in percent
Entropy float64 // minimum entropy value in bits/char
Dictionary *Dictionary // if set, lookup given dictionary, the caller provides it
UTF8 bool // if true work on unicode utf-8 space, not just bytes
}
/*
* Default validation config, a compromise of comfort and security, as always.
*/
const (
MIN_ENTROPY float64 = 3.0
MIN_COMPRESS int = 10
MIN_DICT bool = false
MIN_DIST float64 = 10.0
MAX_UTF8 int = 2164864 // max characters encodable with utf8
MAX_CHARS int = 95 // maximum printable US ASCII chars
MIN_DICT_LEN int = 5000
// we start our ascii arrays at char(32), so to have max 95
// elements in the slice, we subtract 32 from each ascii code
MIN_ASCII byte = 32
)
/*
Stores the results of all validations.
*/
type Result struct {
Ok bool // overall result
DictionaryMatch bool // true if the password matched a dictionary entry
Compress int // actual compression rate in percent
CharDistribution float64 // actual character distribution in percent
Entropy float64 // actual entropy value in bits/chars
}
/*
* Generic validation function. You should only call this function and
* tune it using the Options struct. However, options are optional,
* there are sensible defaults builtin
*/
func Validate(passphrase string, opts ...Options) (Result, error) {
result := Result{Ok: true}
// defaults, see above
options := Options{
MIN_COMPRESS,
MIN_DIST,
MIN_ENTROPY,
nil,
false,
}
if len(opts) == 1 {
options = opts[0]
}
// execute the actual validation checks
if options.Entropy > 0 {
var entropy float64
var err error
switch options.UTF8 {
case true:
entropy, err = GetEntropyUTF8(passphrase)
if err != nil {
return result, err
}
default:
entropy, err = GetEntropyAscii(passphrase)
if err != nil {
return result, err
}
}
if entropy <= options.Entropy {
result.Ok = false
}
result.Entropy = entropy
}
if options.Compress > 0 {
compression, err := GetCompression([]byte(passphrase))
if err != nil {
return result, err
}
if compression >= options.Compress {
result.Ok = false
}
result.Compress = compression
}
if options.CharDistribution > 0 {
var dist float64
switch options.UTF8 {
case true:
dist = GetDistributionUTF8(passphrase)
default:
dist = GetDistributionAscii(passphrase)
}
if dist <= options.CharDistribution {
result.Ok = false
}
result.CharDistribution = dist
}
if options.Dictionary != nil {
match, err := GetDictMatch(passphrase, options.Dictionary)
if err != nil {
return result, err
}
if match {
result.Ok = false
result.DictionaryMatch = true
}
}
return result, nil
}
/*
* we compress with Flate level 9 (max) and see if the result is
* smaller than the password, in which case it could be compressed and
* contains repeating characters; OR it is larger than the password,
* in which case it could NOT be compressed, which is what we want.
*/
func GetCompression(passphrase []byte) (int, error) {
var b bytes.Buffer
flater, _ := flate.NewWriter(&b, 9)
if _, err := flater.Write(passphrase); err != nil {
return 0, fmt.Errorf("failed to write to flate writer: %w", err)
}
if err := flater.Flush(); err != nil {
return 0, fmt.Errorf("failed to flush flate writer: %w", err)
}
if err := flater.Close(); err != nil {
return 0, fmt.Errorf("failed to close flate writer: %w", err)
}
// use floats to avoid division by zero panic
length := float32(len(passphrase))
compressed := float32(len(b.Bytes()))
if compressed >= length {
return 0, nil
}
percent := 100 - (compressed / (length / 100))
return int(percent), nil
}
/*
* Return the entropy as bits/rune, where rune is a unicode char in
* utf8 space.
*/
func GetEntropyUTF8(passphrase string) (float64, error) {
var entropy float64
length := len(passphrase)
wherechar := make([]int, MAX_UTF8)
hist := make([]int, length)
var histlen int
for i := 0; i < MAX_UTF8; i++ {
wherechar[i] = -1
}
for _, char := range passphrase {
if wherechar[char] == -1 {
wherechar[char] = histlen
histlen++
}
hist[wherechar[char]]++
}
for i := 0; i < histlen; i++ {
diff := float64(hist[i]) / float64(length)
entropy -= diff * math.Log2(diff)
}
return entropy, nil
}
/*
Return the entropy as bits/char, where char is a printable char in
US-ASCII space. Returns error if a char is non-printable.
*/
func GetEntropyAscii(passphrase string) (float64, error) {
var entropy float64
length := len(passphrase)
wherechar := make([]int, MAX_CHARS)
hist := make([]int, length)
var histlen int
for i := 0; i < MAX_CHARS; i++ {
wherechar[i] = -1
}
for _, char := range []byte(passphrase) {
if char < MIN_ASCII || char > 126 {
return 0, fmt.Errorf("non-printable ASCII character encountered: %c", char)
}
if wherechar[char-MIN_ASCII] == -1 {
wherechar[char-MIN_ASCII] = histlen
histlen++
}
hist[wherechar[char-MIN_ASCII]]++
}
for i := 0; i < histlen; i++ {
diff := float64(hist[i]) / float64(length)
entropy -= diff * math.Log2(diff)
}
return entropy, nil
}
/*
* Return character distribution in utf8 space
*/
func GetDistributionUTF8(passphrase string) float64 {
hash := make([]int, MAX_UTF8)
var chars float64
for _, char := range passphrase {
hash[char]++
}
for i := 0; i < MAX_UTF8; i++ {
if hash[i] > 0 {
chars++
}
}
return chars / (float64(MAX_UTF8) / 100)
}
/*
* Return character distribution in US-ASCII space
*/
func GetDistributionAscii(passphrase string) float64 {
hash := make([]int, MAX_CHARS)
var chars float64
for _, char := range []byte(passphrase) {
hash[char-MIN_ASCII]++
}
for i := 0; i < MAX_CHARS; i++ {
if hash[i] > 0 {
chars++
}
}
return chars / (float64(MAX_CHARS) / 100)
}
/*
* Return true if password can be found in given dictionary. This has
* to be supplied by the user, we do NOT ship with a dictionary!
*/
func GetDictMatch(passphrase string, dict *Dictionary) (bool, error) {
if len(dict.Words) < MIN_DICT_LEN {
return false, fmt.Errorf("provided dictionary is too small")
}
lcpass := strings.ToLower(passphrase)
if dict.Submatch {
for _, word := range dict.Words {
if strings.Contains(strings.ToLower(word), lcpass) {
return true, nil
}
}
} else {
for _, word := range dict.Words {
if lcpass == strings.ToLower(word) {
return true, nil
}
}
}
return false, nil
}

268
lib_test.go Normal file
View File

@@ -0,0 +1,268 @@
package valpass_test
import (
"bufio"
"os"
"testing"
"github.com/tlinden/valpass"
)
type Tests struct {
name string
want bool
negate bool
opts valpass.Options
}
var pass_random_good = []string{
`5W@'"5b5=S)b]):xwBuEEu=,x}A46<aS`,
`QAfwWn;]6ECn-(wZ-z7MxZL)zRA!TO%t`,
`_5>}+RMm=FRj1a>r/!gG*3tQ>s<&Uh{I`,
`~Dc6RHW?Yj"nDj)WaWAg#F<IsA[4j?G{`,
`B;S0|lq:Ns#!{r1UaE0QG7R}tA'K'TNW`,
`/~]-bT':EeA:dK&[+752EKvS@C1\U70d`,
`3>cNh2_1(gB(DsA]m$4f/[hHf>{}E*\Q`,
`Gr5#qF/!:ih?n7p|c?pN50IWc]5$+Q(]`,
`S#(|irk.%U}[RBFZ2L;}XdDrmOU;SP<\`,
`+L:T#&@ce[yqWZ0mTfm[D'#a=Ke[j7w'`,
`:N8vqQ{Vb]@.y?\P2d8,)yHHE?>l|Gi_`,
`^+s5,#2h<,?_s_Qsd2l;|D42TV3h{7M^`,
`.^e#(l5$3}1l/-/Uk0,;t^Z[$X0,'h)O`,
`]-xAyz-"P$98_Z[77@bmo9ZF)I#"Fa,6`,
`HLkM\]n70U2qU)%Mp{gK@CHt,twiPzH%`,
`wU2?2&4yx/7HuR@k:~]%/77,DyaNW|"Q`,
`nb\ZmKT[J)%@=\nF9E2!%N-(+S}Lq95B`,
`=+0b2[#FMcT~re:PifIWh$IL+>4uyBg1`,
`xEm]AS#<]cgayw)>O/c<i,)BO[MC0qF,`,
`EScP'NqM|7/>7e2'orRcS%x6v[sgX(!p`,
`[.L|hvRRd.@)y?dH?Z46EcEa%/#!m39j`,
`,$88R.N+C>+adUcw!D"11$H">:SKOiKp`,
`8#uY]ByJ]iCNp?6-#;&m\pO[G>*!27ge`,
`@UNu)/qMT{ekO(}qhh4!HI9\QRdrdh^'`,
`FfoO3pLr_aoGC]lpvo"?RT3E@2f8-764`,
`Us.dn65ZmF]M}e0Z!$!r0ex-/Z5nwx?J`,
`e6p{,373[@c@/:CcQ"+(u^U"}^CzxRY.`,
`kwpHHIqcsuWOio@jlIA2UbO63dkhh'|D`,
`Yeq@?/Fq.}}"i2dXT=vR2C56hY9R)!_w`,
`49ZFp54$@\kJ:D;[ZV(VcY|!&sI\O8;&`,
`SK(ILi(q#FD-*uBbX4,;;1MM2</Md57(`,
`TA"s$ix&5tlHqk^)182870PpW4X8jH_]`,
`i"0&lJa?FA>]sD#:AVI)O7|L2x$$WI>(`,
`_ao{jJ4Z0#njg}GCV{UpQsQubgb!F$-?`,
`KtAkA~]c}0gj)H7.C6is*>50eIT$OW?*`,
`Cv[o<mOux790E|[kNrh<n;S\1qU42kNN`,
`Xj3:.j%kN?k_qYkNMUcQJe@[<K6v.4R~`,
`aNRU-vO~LX~AwFbUe9t}[WK*3r;PGc/b`,
`|E|Jl]YjM<4gNh0b1%)^SP:_;%#A\b4b`,
`Q4#U1/2'5V[_CzYdm7OSZJJE-cSf9^cG`,
`!jK6zb4)pGrAL/|w|#$a}O||C(0:>:.6`,
`7t&/B36m8IeM*^e}.)-/X+M8r7'\q:cu`,
`0iw8o:,bQJ=;d&<CK6?UcaqggQ&r!~%E`,
`^/FPWoYDwij"B//t}|3aV6vaLI$\3E4%`,
`^sJ~J.>r?$u'0J,2VD6$Fou,[D~q_vzO`,
`rVV\wI.L@AAI?+;lU@gnmxKFiob>?s!8`,
`o]K;x.6$u|^M7kL:lM"13a@rQiD1IJoh`,
`xM;!)\?;=!lH]|j^jzGG}?6v*O:s~*o=`,
`f"7#AnRu*b9_=sk^^mMX?+K^ElemvJ(<`,
`L4WSx8ocC1$74A4#zF!*h8Bq_Eq/1s7s`,
}
var pass_diceware_good = []string{
`abutting Eucharist dramatized unlearns`,
`Terrence decorates dwarfed saucing`,
`swamping nauseated tapioca ascribe`,
`insatiably ensconcing royally Clarice`,
`inshore watchdog blunderers methods`,
`Plasticine brotherly prances dryness`,
`rustproof flipper commodity nudging`,
`unburdened frostings adapter vivider`,
`facile Niamey begrudge menage`,
`nightcaps miniseries Hannibal strongly`,
`foresails produces sufficing cannibal`,
`berths allowing Lewiston sounds`,
`hazier Hockney snobbier redefines`,
`Monroe castaways narwhals roadbed`,
`schuss Trieste assist kebobs`,
`anteater pianos damping attaining`,
`desisting colossus refused Madagascan`,
`misguiding urinalyses moonscapes Taiping`,
`fracases Indies dishwasher crimsons`,
`doorman Kleenexes hostessed stooped`,
`telephoto boozing monoxide Asiago`,
`completed dogfish rawboned curvacious`,
`physics virtually rocketing relevant`,
`infantile sharpest buckler gazillions`,
`forbids midlands accosts furniture`,
`concocts Alcestis nitpicker Hindustan`,
`heirlooms wending Borodin billows`,
`commotion absinthe chilis drainer`,
`prerecord brokerages colonel implied`,
`spoons abates swathed Pocono`,
`speedy poultices Smollett tracing`,
`viragoes unwind gasped earache`,
`rulings Mencken damasking matched`,
`Sarajevo footbridge stables furloughed`,
`proclaimed baffling carefully Anatolia`,
`Cecily Nicaraguan excrete lobbed`,
`enfold cranny tearjerker blazon`,
`bucketed Corneille eclectic Maurine`,
`Berwick gasohol slices bonkers`,
`swearers iodized Ohioans warden`,
`Cortez insular several phloem`,
`assented insolvent beguile aquaplane`,
`commend trails Amazon clambering`,
`excretory greatness plackets creeks`,
`transistor exclusion inboxes sidling`,
`cherries elongating Lollard piques`,
`heartening orbiting zombie revile`,
`reconcile completes roughs innocence`,
`quickness Cheever Thimbu scours`,
`hobble piteously precepts sorest`,
`braving shirted backstage Taiping`,
}
var pass_worst_bad = []string{
`123456`, `charlie`, `summer`, `sophie`, `merlin`,
`password`, `aa123456`, `George`, `Ferrari`, `cookie`,
`123456789`, `donald`, `Harley`, `Cheese`, `ashley`,
`12345678`, `password1`, `222222`, `Computer`, `bandit`,
`12345`, `qwerty123`, `Jessica`, `jesus`, `killer`,
`111111`, `letmein`, `ginger`, `Corvette`, `aaaaaa`,
`1234567`, `zxcvbnm`, `abcdef`, `Mercedes`, `1q2w3e`,
`sunshine`, `login`, `Jordan`, `flower`, `zaq1zaq1`,
`qwerty`, `starwars`, `55555`, `Blahblah`, `mustang`,
`iloveyou`, `121212`, `Tigger`, `Maverick`, `test`,
`princess`, `bailey`, `Joshua`, `Hello`, `hockey`,
`admin`, `freedom`, `Pepper`, `loveme`, `dallas`,
`welcome`, `shadow`, `Robert`, `nicole`, `whatever`,
`666666`, `passw0rd`, `Matthew`, `hunter`, `admin123`,
`abc123`, `master`, `12341234`, `amanda`, `michael`,
`football`, `baseball`, `Andrew`, `jennifer`, `liverpool`,
`123123`, `buster`, `lakers`, `banana`, `querty`,
`monkey`, `Daniel`, `andrea`, `chelsea`, `william`,
`654321`, `Hannah`, `1qaz2wsx`, `ranger`, `soccer`,
`!@#$%^&*`, `Thomas`, `starwars`, `trustno1`, `london`,
}
var pass_dict_bad = []string{
`clued`, `lads`, `stifle`,
`receptivity`, `apprehends`, `accounts`,
`putts`, `spurt`, `sideswipe`,
`dabbed`, `goatskin`, `nooks`,
`sulkiness`, `worships`, `coevals`,
`entwining`, `sportscasters`, `pew`,
`horse`, `daybeds`, `booklet`,
`Suzette`, `abbreviate`, `stubborn`,
`govern`, `ageism`, `refereeing`,
`dents`, `Wyeth`, `concentric`,
`Kamehameha`, `grosser`, `belie`,
`wherefore`, `president`, `pipit`,
`pinholes`, `mummifying`, `quartermasters`,
`fruitlessness`, `seafarer`, `Einsteins`,
`stomping`, `glided`, `retried`,
`effected`, `ministry`,
}
var opts_std = valpass.Options{
Compress: valpass.MIN_COMPRESS,
CharDistribution: valpass.MIN_DIST,
Entropy: valpass.MIN_ENTROPY,
Dictionary: nil,
UTF8: false,
}
var opts_dict = valpass.Options{
Compress: valpass.MIN_COMPRESS,
CharDistribution: valpass.MIN_DIST,
Entropy: valpass.MIN_ENTROPY,
Dictionary: &valpass.Dictionary{Words: ReadDict("t/american-english")},
UTF8: false,
}
var goodtests = []Tests{
{
name: "checkgood",
want: true,
opts: opts_std,
},
{
name: "checkgood-dict",
want: true,
opts: opts_dict,
},
}
var badtests = []Tests{
{
name: "checkbad",
want: false,
opts: opts_std,
},
{
name: "checkbad-dict",
want: false,
opts: opts_dict,
},
}
func TestValidate(t *testing.T) {
t.Parallel()
for _, tt := range goodtests {
for _, pass := range pass_random_good {
CheckPassword(t, pass, tt.name, tt.want, tt.opts)
}
for _, pass := range pass_diceware_good {
CheckPassword(t, pass, tt.name, tt.want, tt.opts)
}
}
for _, tt := range badtests {
for _, pass := range pass_worst_bad {
CheckPassword(t, pass, tt.name, tt.want, tt.opts)
}
for _, pass := range pass_dict_bad {
CheckPassword(t, pass, tt.name, tt.want, tt.opts)
}
}
}
func CheckPassword(t *testing.T, password string,
name string, want bool, opts valpass.Options) {
result, err := valpass.Validate(password, opts)
if err != nil {
t.Errorf("test %s failed with error: %s\n", name, err)
}
if want && !result.Ok {
t.Errorf("test %s failed. pass: %s, want: %t, got: %t, dict: %t\nresult: %v\n",
name, password, want, result.Ok, result.DictionaryMatch, result)
}
if !want && result.Ok {
t.Errorf("test %s failed. pass: %s, want: %t, got: %t, dict: %t\nresult: %v\n",
name, password, want, result.Ok, result.DictionaryMatch, result)
}
}
func ReadDict(path string) []string {
file, err := os.Open(path)
if err != nil {
panic(err)
}
defer file.Close()
var lines []string
scanner := bufio.NewScanner(file)
for scanner.Scan() {
lines = append(lines, scanner.Text())
}
return lines
}

104332
t/american-english Normal file

File diff suppressed because it is too large Load Diff

BIN
test Executable file

Binary file not shown.