forked from Masterminds/sprig
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
44e3642
commit 9e5d6d8
Showing
20 changed files
with
1,910 additions
and
1,820 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
|
||
.PHONY: test | ||
test: | ||
go test -v . |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,148 @@ | ||
package sprig | ||
|
||
import ( | ||
"bytes" | ||
"crypto/dsa" | ||
"crypto/ecdsa" | ||
"crypto/elliptic" | ||
"crypto/hmac" | ||
"crypto/rand" | ||
"crypto/rsa" | ||
"crypto/sha256" | ||
"crypto/x509" | ||
"encoding/asn1" | ||
"encoding/binary" | ||
"encoding/hex" | ||
"encoding/pem" | ||
"fmt" | ||
"math/big" | ||
|
||
uuid "github.com/satori/go.uuid" | ||
"golang.org/x/crypto/scrypt" | ||
) | ||
|
||
func sha256sum(input string) string { | ||
hash := sha256.Sum256([]byte(input)) | ||
return hex.EncodeToString(hash[:]) | ||
} | ||
|
||
// uuidv4 provides a safe and secure UUID v4 implementation | ||
func uuidv4() string { | ||
return fmt.Sprintf("%s", uuid.NewV4()) | ||
} | ||
|
||
var master_password_seed = "com.lyndir.masterpassword" | ||
|
||
var password_type_templates = map[string][][]byte{ | ||
"maximum": {[]byte("anoxxxxxxxxxxxxxxxxx"), []byte("axxxxxxxxxxxxxxxxxno")}, | ||
"long": {[]byte("CvcvnoCvcvCvcv"), []byte("CvcvCvcvnoCvcv"), []byte("CvcvCvcvCvcvno"), []byte("CvccnoCvcvCvcv"), []byte("CvccCvcvnoCvcv"), | ||
[]byte("CvccCvcvCvcvno"), []byte("CvcvnoCvccCvcv"), []byte("CvcvCvccnoCvcv"), []byte("CvcvCvccCvcvno"), []byte("CvcvnoCvcvCvcc"), | ||
[]byte("CvcvCvcvnoCvcc"), []byte("CvcvCvcvCvccno"), []byte("CvccnoCvccCvcv"), []byte("CvccCvccnoCvcv"), []byte("CvccCvccCvcvno"), | ||
[]byte("CvcvnoCvccCvcc"), []byte("CvcvCvccnoCvcc"), []byte("CvcvCvccCvccno"), []byte("CvccnoCvcvCvcc"), []byte("CvccCvcvnoCvcc"), | ||
[]byte("CvccCvcvCvccno")}, | ||
"medium": {[]byte("CvcnoCvc"), []byte("CvcCvcno")}, | ||
"short": {[]byte("Cvcn")}, | ||
"basic": {[]byte("aaanaaan"), []byte("aannaaan"), []byte("aaannaaa")}, | ||
"pin": {[]byte("nnnn")}, | ||
} | ||
|
||
var template_characters = map[byte]string{ | ||
'V': "AEIOU", | ||
'C': "BCDFGHJKLMNPQRSTVWXYZ", | ||
'v': "aeiou", | ||
'c': "bcdfghjklmnpqrstvwxyz", | ||
'A': "AEIOUBCDFGHJKLMNPQRSTVWXYZ", | ||
'a': "AEIOUaeiouBCDFGHJKLMNPQRSTVWXYZbcdfghjklmnpqrstvwxyz", | ||
'n': "0123456789", | ||
'o': "@&%?,=[]_:-+*$#!'^~;()/.", | ||
'x': "AEIOUaeiouBCDFGHJKLMNPQRSTVWXYZbcdfghjklmnpqrstvwxyz0123456789!@#$%^&*()", | ||
} | ||
|
||
func derivePassword(counter uint32, password_type, password, user, site string) string { | ||
var templates = password_type_templates[password_type] | ||
if templates == nil { | ||
return fmt.Sprintf("cannot find password template %s", password_type) | ||
} | ||
|
||
var buffer bytes.Buffer | ||
buffer.WriteString(master_password_seed) | ||
binary.Write(&buffer, binary.BigEndian, uint32(len(user))) | ||
buffer.WriteString(user) | ||
|
||
salt := buffer.Bytes() | ||
key, err := scrypt.Key([]byte(password), salt, 32768, 8, 2, 64) | ||
if err != nil { | ||
return fmt.Sprintf("failed to derive password: %s", err) | ||
} | ||
|
||
buffer.Truncate(len(master_password_seed)) | ||
binary.Write(&buffer, binary.BigEndian, uint32(len(site))) | ||
buffer.WriteString(site) | ||
binary.Write(&buffer, binary.BigEndian, counter) | ||
|
||
var hmacv = hmac.New(sha256.New, key) | ||
hmacv.Write(buffer.Bytes()) | ||
var seed = hmacv.Sum(nil) | ||
var temp = templates[int(seed[0])%len(templates)] | ||
|
||
buffer.Truncate(0) | ||
for i, element := range temp { | ||
pass_chars := template_characters[element] | ||
pass_char := pass_chars[int(seed[i+1])%len(pass_chars)] | ||
buffer.WriteByte(pass_char) | ||
} | ||
|
||
return buffer.String() | ||
} | ||
|
||
func generatePrivateKey(typ string) string { | ||
var priv interface{} | ||
var err error | ||
switch typ { | ||
case "", "rsa": | ||
// good enough for government work | ||
priv, err = rsa.GenerateKey(rand.Reader, 4096) | ||
case "dsa": | ||
key := new(dsa.PrivateKey) | ||
// again, good enough for government work | ||
if err = dsa.GenerateParameters(&key.Parameters, rand.Reader, dsa.L2048N256); err != nil { | ||
return fmt.Sprintf("failed to generate dsa params: %s", err) | ||
} | ||
err = dsa.GenerateKey(key, rand.Reader) | ||
priv = key | ||
case "ecdsa": | ||
// again, good enough for government work | ||
priv, err = ecdsa.GenerateKey(elliptic.P256(), rand.Reader) | ||
default: | ||
return "Unknown type " + typ | ||
} | ||
if err != nil { | ||
return fmt.Sprintf("failed to generate private key: %s", err) | ||
} | ||
|
||
return string(pem.EncodeToMemory(pemBlockForKey(priv))) | ||
} | ||
|
||
type DSAKeyFormat struct { | ||
Version int | ||
P, Q, G, Y, X *big.Int | ||
} | ||
|
||
func pemBlockForKey(priv interface{}) *pem.Block { | ||
switch k := priv.(type) { | ||
case *rsa.PrivateKey: | ||
return &pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(k)} | ||
case *dsa.PrivateKey: | ||
val := DSAKeyFormat{ | ||
P: k.P, Q: k.Q, G: k.G, | ||
Y: k.Y, X: k.X, | ||
} | ||
bytes, _ := asn1.Marshal(val) | ||
return &pem.Block{Type: "DSA PRIVATE KEY", Bytes: bytes} | ||
case *ecdsa.PrivateKey: | ||
b, _ := x509.MarshalECPrivateKey(k) | ||
return &pem.Block{Type: "EC PRIVATE KEY", Bytes: b} | ||
default: | ||
return nil | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,110 @@ | ||
package sprig | ||
|
||
import ( | ||
"strings" | ||
"testing" | ||
) | ||
|
||
func TestSha256Sum(t *testing.T) { | ||
tpl := `{{"abc" | sha256sum}}` | ||
if err := runt(tpl, "ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad"); err != nil { | ||
t.Error(err) | ||
} | ||
} | ||
|
||
func TestDerivePassword(t *testing.T) { | ||
expectations := map[string]string{ | ||
`{{derivePassword 1 "long" "password" "user" "example.com"}}`: "ZedaFaxcZaso9*", | ||
`{{derivePassword 2 "long" "password" "user" "example.com"}}`: "Fovi2@JifpTupx", | ||
`{{derivePassword 1 "maximum" "password" "user" "example.com"}}`: "pf4zS1LjCg&LjhsZ7T2~", | ||
`{{derivePassword 1 "medium" "password" "user" "example.com"}}`: "ZedJuz8$", | ||
`{{derivePassword 1 "basic" "password" "user" "example.com"}}`: "pIS54PLs", | ||
`{{derivePassword 1 "short" "password" "user" "example.com"}}`: "Zed5", | ||
`{{derivePassword 1 "pin" "password" "user" "example.com"}}`: "6685", | ||
} | ||
|
||
for tpl, result := range expectations { | ||
out, err := runRaw(tpl, nil) | ||
if err != nil { | ||
t.Error(err) | ||
} | ||
if 0 != strings.Compare(out, result) { | ||
t.Error("Generated password does not match for", tpl) | ||
} | ||
} | ||
} | ||
|
||
// NOTE(bacongobbler): this test is really _slow_ because of how long it takes to compute | ||
// and generate a new crypto key. | ||
func TestGenPrivateKey(t *testing.T) { | ||
// test that calling by default generates an RSA private key | ||
tpl := `{{genPrivateKey ""}}` | ||
out, err := runRaw(tpl, nil) | ||
if err != nil { | ||
t.Error(err) | ||
} | ||
if !strings.Contains(out, "RSA PRIVATE KEY") { | ||
t.Error("Expected RSA PRIVATE KEY") | ||
} | ||
// test all acceptable arguments | ||
tpl = `{{genPrivateKey "rsa"}}` | ||
out, err = runRaw(tpl, nil) | ||
if err != nil { | ||
t.Error(err) | ||
} | ||
if !strings.Contains(out, "RSA PRIVATE KEY") { | ||
t.Error("Expected RSA PRIVATE KEY") | ||
} | ||
tpl = `{{genPrivateKey "dsa"}}` | ||
out, err = runRaw(tpl, nil) | ||
if err != nil { | ||
t.Error(err) | ||
} | ||
if !strings.Contains(out, "DSA PRIVATE KEY") { | ||
t.Error("Expected DSA PRIVATE KEY") | ||
} | ||
tpl = `{{genPrivateKey "ecdsa"}}` | ||
out, err = runRaw(tpl, nil) | ||
if err != nil { | ||
t.Error(err) | ||
} | ||
if !strings.Contains(out, "EC PRIVATE KEY") { | ||
t.Error("Expected EC PRIVATE KEY") | ||
} | ||
// test bad | ||
tpl = `{{genPrivateKey "bad"}}` | ||
out, err = runRaw(tpl, nil) | ||
if err != nil { | ||
t.Error(err) | ||
} | ||
if out != "Unknown type bad" { | ||
t.Error("Expected type 'bad' to be an unknown crypto algorithm") | ||
} | ||
// ensure that we can base64 encode the string | ||
tpl = `{{genPrivateKey "rsa" | b64enc}}` | ||
out, err = runRaw(tpl, nil) | ||
if err != nil { | ||
t.Error(err) | ||
} | ||
} | ||
|
||
func TestUUIDGeneration(t *testing.T) { | ||
tpl := `{{uuidv4}}` | ||
out, err := runRaw(tpl, nil) | ||
if err != nil { | ||
t.Error(err) | ||
} | ||
|
||
if len(out) != 36 { | ||
t.Error("Expected UUID of length 36") | ||
} | ||
|
||
out2, err := runRaw(tpl, nil) | ||
if err != nil { | ||
t.Error(err) | ||
} | ||
|
||
if out == out2 { | ||
t.Error("Expected subsequent UUID generations to be different") | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,53 @@ | ||
package sprig | ||
|
||
import ( | ||
"time" | ||
) | ||
|
||
// Given a format and a date, format the date string. | ||
// | ||
// Date can be a `time.Time` or an `int, int32, int64`. | ||
// In the later case, it is treated as seconds since UNIX | ||
// epoch. | ||
func date(fmt string, date interface{}) string { | ||
return dateInZone(fmt, date, "Local") | ||
} | ||
|
||
func htmlDate(date interface{}) string { | ||
return dateInZone("2006-01-02", date, "Local") | ||
} | ||
|
||
func htmlDateInZone(date interface{}, zone string) string { | ||
return dateInZone("2006-01-02", date, zone) | ||
} | ||
|
||
func dateInZone(fmt string, date interface{}, zone string) string { | ||
var t time.Time | ||
switch date := date.(type) { | ||
default: | ||
t = time.Now() | ||
case time.Time: | ||
t = date | ||
case int64: | ||
t = time.Unix(date, 0) | ||
case int: | ||
t = time.Unix(int64(date), 0) | ||
case int32: | ||
t = time.Unix(int64(date), 0) | ||
} | ||
|
||
loc, err := time.LoadLocation(zone) | ||
if err != nil { | ||
loc, _ = time.LoadLocation("UTC") | ||
} | ||
|
||
return t.In(loc).Format(fmt) | ||
} | ||
|
||
func dateModify(fmt string, date time.Time) time.Time { | ||
d, err := time.ParseDuration(fmt) | ||
if err != nil { | ||
return date | ||
} | ||
return date.Add(d) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
package sprig | ||
|
||
import ( | ||
"testing" | ||
) | ||
|
||
func TestHtmlDate(t *testing.T) { | ||
t.Skip() | ||
tpl := `{{ htmlDate 0}}` | ||
if err := runt(tpl, "1970-01-01"); err != nil { | ||
t.Error(err) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,62 @@ | ||
package sprig | ||
|
||
import ( | ||
"reflect" | ||
) | ||
|
||
// dfault checks whether `given` is set, and returns default if not set. | ||
// | ||
// This returns `d` if `given` appears not to be set, and `given` otherwise. | ||
// | ||
// For numeric types 0 is unset. | ||
// For strings, maps, arrays, and slices, len() = 0 is considered unset. | ||
// For bool, false is unset. | ||
// Structs are never considered unset. | ||
// | ||
// For everything else, including pointers, a nil value is unset. | ||
func dfault(d interface{}, given ...interface{}) interface{} { | ||
|
||
if empty(given) || empty(given[0]) { | ||
return d | ||
} | ||
return given[0] | ||
} | ||
|
||
// empty returns true if the given value has the zero value for its type. | ||
func empty(given interface{}) bool { | ||
g := reflect.ValueOf(given) | ||
if !g.IsValid() { | ||
return true | ||
} | ||
|
||
// Basically adapted from text/template.isTrue | ||
switch g.Kind() { | ||
default: | ||
return g.IsNil() | ||
case reflect.Array, reflect.Slice, reflect.Map, reflect.String: | ||
return g.Len() == 0 | ||
case reflect.Bool: | ||
return g.Bool() == false | ||
case reflect.Complex64, reflect.Complex128: | ||
return g.Complex() == 0 | ||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: | ||
return g.Int() == 0 | ||
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: | ||
return g.Uint() == 0 | ||
case reflect.Float32, reflect.Float64: | ||
return g.Float() == 0 | ||
case reflect.Struct: | ||
return false | ||
} | ||
return true | ||
} | ||
|
||
// coalesce returns the first non-empty value. | ||
func coalesce(v ...interface{}) interface{} { | ||
for _, val := range v { | ||
if !empty(val) { | ||
return val | ||
} | ||
} | ||
return nil | ||
} |
Oops, something went wrong.