pax_global_header00006660000000000000000000000064135106310010014500gustar00rootroot0000000000000052 comment=3fea4babfe1b49116b9e9434bb15e457db2f3578 gokey-0.1.0/000077500000000000000000000000001351063100100126145ustar00rootroot00000000000000gokey-0.1.0/.travis.yml000066400000000000000000000003671351063100100147330ustar00rootroot00000000000000language: go os: - linux - windows go: - "1.10.x" - "1.x" - master # golang master is not available for Windows builds for some reason matrix: exclude: - os: windows go: master go_import_path: github.com/cloudflare/gokey gokey-0.1.0/LICENSE000066400000000000000000000027121351063100100136230ustar00rootroot00000000000000Copyright (c) 2016, Cloudflare. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. gokey-0.1.0/README.md000066400000000000000000000133641351063100100141020ustar00rootroot00000000000000# gokey [![Build Status](https://travis-ci.org/cloudflare/gokey.svg?branch=master)](https://travis-ci.org/cloudflare/gokey) [![Go Report Card](https://goreportcard.com/badge/github.com/cloudflare/gokey)](https://goreportcard.com/report/github.com/cloudflare/gokey) ## A simple vaultless password manager in Go **gokey** is a password manager, which does not require a password vault. Instead of storing your passwords in a vault it derives your password on the fly from your master password and supplied _realm_ string (for example, resource URL). This way you do not have to manage, backup or sync your password vault (or trust its management to a third party) as your passwords are available immediately anywhere. ###### example ``` gokey -p super-secret-master-password -r example.com ``` ###### options - `-o ` - by default **gokey** outputs generated data to `stdout` - `-P ` - path to master password file which will be used to generate other passwords/keys or to encrypt seed file (see [Modes of operation](#modes-of-operation) below, if no master password or master password file is provided, **gokey** will ask for it interactively) - `-p ` - master password which will be used to generate other passwords/keys or to encrypt seed file (see [Modes of operation](#modes-of-operation) below, if no master password or master password file is provided, **gokey** will ask for it interactively) - `-r ` - any string which identifies requested password/key, most likely key usage or resource URL - `-s ` - needed, if you want to use seed file instead of master password as an entropy source (see [Modes of operation](#modes-of-operation) below); can be generated with `-t seed` flag as described below - `-skip ` - number of bytes to skip when reading seed file - `-u` - **UNSAFE**, allow generating keys without using a seed file (see [Modes of operation](#modes-of-operation) below) - `-t ` - requested password/key output type Supported password/key types: * `pass` - default, generates a password * `seed` - generates a seed file, which can be used with `-s` option later * `raw` - generates 32 random bytes (can be used as a symmetric key) * `ec256` - generates ECC P-256 private key * `ec521` - generates ECC P-521 private key * `rsa2048` - generates 2048-bit RSA private key * `rsa4096` - generates 4096-bit RSA private key * `x25519` - generates x25519 (also known as curve25519) ECC private key * `ed25519` - generates ed25519 ECC private key ### Installation **gokey** command-line utility can be downloaded and compiled using standard `go get` approach. Assuming you have [Go](https://golang.org/doc/install) installed and `$GOPATH` set, just do ``` go get github.com/cloudflare/gokey/cmd/gokey ``` The `gokey` binary should appear in your `$GOPATH/bin` directory. ### Modes of operation **gokey** can generate passwords and cryptographic private keys (ECC and RSA keys are currently supported). However, without any additional options specified it uses your master password as a single source of entropy for generated data. For passwords it is acceptable most of the time, but keys usually have much higher entropy requirements. For cases, where higher entropy is required for generated passwords/keys, **gokey** can use a seed file (a blob with random data) instead of the master password as a source of entropy. #### Simple mode (without a seed file) In simple mode passwords are derived directly from your master password and the realm string. That is each unique combination of a master password and a realm string will produce a unique password. For example, if your master password is `super-secret-master-password` and you want to generate a password for `example.com`, you would invoke **gokey** like ``` gokey -p super-secret-master-password -r example.com ``` If you need a password for a different resource, (`example2.com`), you would change the real string ``` gokey -p super-secret-master-password -r example2.com ``` This way you need to remember only your master password and you can always recreate passwords for your services/resources. NOTE: In this mode generated passwords are as strong as your master password, so do have your master password strong enough. You can also derive private keys from your master password, but keep in mind, that these keys will have low entropy. That is why it is considered unsafe, so **gokey** does not allow it by default. If you're **_really_** know what you are doing, you can override this default by supplying `-u` flag. #### Using a seed file If you plan to generate not only passwords, but also private keys or you want to have your passwords/keys with higher entropy, you can use a seed file instead of the master password. You still need to supply a master password, when invoking **gokey**, but it will be used only to protect the seed file itself; all generated passwords/keys will be derived from the data in the seed file. **gokey** uses seed files protected (encrypted) with your master password, so it is reasonably safe to store/backup seed files to a third party location, such as Google Drive or Dropbox. To generate an encrypted seed file, use ``` gokey -p super-secret-master-password -t seed -o seedfile ``` This will create a seed file `seedfile` with 256 bytes of random data. The data is encrypted using AES-256-GCM mode and `super-secret-master-password` as a key. Then, to generate EC-256 private key for `example.com`, use ``` gokey -p super-secret-master-password -s seedfile -r example.com -t ec256 ``` NOTE: you still need to remember your master password and keep a backup copy of your seed file. If you forget your master password or loose your seed file, you loose all derived passwords/keys as well. gokey-0.1.0/cmd/000077500000000000000000000000001351063100100133575ustar00rootroot00000000000000gokey-0.1.0/cmd/gokey/000077500000000000000000000000001351063100100144755ustar00rootroot00000000000000gokey-0.1.0/cmd/gokey/.gitignore000066400000000000000000000000061351063100100164610ustar00rootroot00000000000000gokey gokey-0.1.0/cmd/gokey/main.go000066400000000000000000000100771351063100100157550ustar00rootroot00000000000000// Command gokey is a vaultless password and key manager. package main import ( "bytes" "flag" "fmt" "io" "io/ioutil" "log" "os" "strings" "github.com/cloudflare/gokey" "golang.org/x/crypto/ssh/terminal" ) var ( pass, passFile, keyType, seedPath, realm, output string unsafe bool seedSkipCount int ) func init() { flag.StringVar(&pass, "p", "", "master password (if not specified, will be asked interactively)") flag.StringVar(&passFile, "P", "", "master password file (if not specified, will be asked interactively)") flag.StringVar(&keyType, "t", "pass", "output type (can be pass, seed, raw, ec256, ec521, rsa2048, rsa4096, x25519, ed25519)") flag.StringVar(&seedPath, "s", "", "path to master seed file (optional)") flag.IntVar(&seedSkipCount, "skip", 0, "number of bytes to skip from master seed file (default 0)") flag.StringVar(&realm, "r", "", "password/key realm (most probably purpose of the password/key)") flag.StringVar(&output, "o", "", "output path to store generated key/password (default stdout)") flag.BoolVar(&unsafe, "u", false, "UNSAFE: allow key generation without a seed") } var keyTypes = map[string]gokey.KeyType{ "ec256": gokey.EC256, "ec521": gokey.EC521, "rsa2048": gokey.RSA2048, "rsa4096": gokey.RSA4096, "x25519": gokey.X25519, "ed25519": gokey.ED25519, } func genSeed(w io.Writer) { seed, err := gokey.GenerateEncryptedKeySeed(pass) if err != nil { log.Fatalln(err) } _, err = w.Write(seed) if err != nil { log.Fatalln(err) } } func genPass(seed []byte, w io.Writer) { password, err := gokey.GetPass(pass, realm, seed, &gokey.PasswordSpec{10, 3, 3, 1, 1, ""}) if err != nil { log.Fatalln(err) } _, err = io.WriteString(w, password) if err != nil { log.Fatalln(err) } fmt.Fprintln(w, "") } func genKey(seed []byte, w io.Writer) { key, err := gokey.GetKey(pass, realm, seed, keyTypes[keyType], unsafe) if err != nil { log.Fatalln(err) } err = gokey.EncodeToPem(key, w) if err != nil { log.Fatalln(err) } } // TODO: parametrize size // generates raw 32 bytes func genRaw(seed []byte, w io.Writer) { raw, err := gokey.GetRaw(pass, realm, seed, unsafe) if err != nil { log.Fatalln(err) } _, err = io.CopyN(w, raw, 32) if err != nil { log.Fatalln(err) } } func logFatal(format string, args ...interface{}) { log.Printf(format, args...) flag.PrintDefaults() os.Exit(1) } func main() { flag.Parse() var err error if pass == "" && passFile != "" { var content []byte content, err = ioutil.ReadFile(passFile) if err != nil { log.Fatalln(err) } pass = strings.TrimSpace(string(content[:])) } if pass == "" { var passBytes []byte var passBytesAgain []byte for { for len(passBytes) == 0 { fmt.Print("Master password: ") passBytes, err = terminal.ReadPassword(int(os.Stdin.Fd())) if err != nil { log.Fatalln(err) } fmt.Println("") } if seedPath != "" { break } fmt.Print("Master password again: ") passBytesAgain, err = terminal.ReadPassword(int(os.Stdin.Fd())) if err != nil { log.Fatalln(err) } fmt.Println("") if bytes.Equal(passBytes, passBytesAgain) { break } else { fmt.Println("Passwords do not match. Try again.") passBytes = nil continue } } pass = string(passBytes) } out := os.Stdout if output != "" { out, err = os.OpenFile(output, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0600) if err != nil { log.Fatalln(err) } defer out.Close() } if keyType == "seed" { genSeed(out) } else { if realm == "" { logFatal("no realm provided") } var seed []byte if seedPath != "" { seed, err = ioutil.ReadFile(seedPath) if err != nil { log.Fatalln(err) } if (seedSkipCount < 0) || (seedSkipCount >= len(seed)) { log.Fatalln("invalid skip parameter") } seed = seed[seedSkipCount:] } switch keyType { case "pass": genPass(seed, out) case "raw": genRaw(seed, out) default: if _, ok := keyTypes[keyType]; !ok { logFatal("unknown key type: %v", keyType) } genKey(seed, out) } } } gokey-0.1.0/csprng.go000066400000000000000000000042651351063100100144460ustar00rootroot00000000000000package gokey import ( "crypto/aes" "crypto/cipher" "crypto/rand" "crypto/sha256" "io" "golang.org/x/crypto/hkdf" "golang.org/x/crypto/pbkdf2" ) const ( keySeedLength = 256 ) type devZero struct{} func (dz devZero) Read(p []byte) (n int, err error) { for i, _ := range p { p[i] = 0 } return len(p), nil } func passKey(password, realm string) []byte { return pbkdf2.Key([]byte(password), []byte(realm), 4096, 32, sha256.New) } func NewDRNG(password, realm string) io.Reader { block, _ := aes.NewCipher(passKey(password, realm)) stream := cipher.NewCTR(block, make([]byte, 16)) return cipher.StreamReader{S: stream, R: devZero{}} } func NewDRNGwithSeed(password, realm string, seed []byte) (io.Reader, error) { uSeed, err := unwrapSeed(password, seed) if err != nil { return nil, err } // will reuse some of the public seed info salt := make([]byte, 12+16) copy(salt[:12], uSeed[:12]) copy(salt[12:], uSeed[len(uSeed)-16:]) hkdf := hkdf.New(sha256.New, uSeed, salt, []byte(realm)) rngSeed := make([]byte, 32) _, err = io.ReadFull(hkdf, rngSeed) if err != nil { return nil, err } block, _ := aes.NewCipher(rngSeed) stream := cipher.NewCTR(block, make([]byte, 16)) return cipher.StreamReader{S: stream, R: devZero{}}, nil } func GenerateEncryptedKeySeed(password string) ([]byte, error) { seed := make([]byte, keySeedLength) _, err := rand.Read(seed) if err != nil { return nil, err } masterkey := passKey(password, string(seed[:12])) aes, err := aes.NewCipher(masterkey) if err != nil { return nil, err } gcm, err := cipher.NewGCM(aes) if err != nil { return nil, err } pt := seed[12 : len(seed)-16] // encrypt in place gcm.Seal(pt[:0], seed[:12], pt, nil) return seed, nil } func unwrapSeed(password string, seed []byte) ([]byte, error) { masterkey := passKey(password, string(seed[:12])) aes, err := aes.NewCipher(masterkey) if err != nil { return nil, err } gcm, err := cipher.NewGCM(aes) if err != nil { return nil, err } pt := make([]byte, len(seed)) _, err = gcm.Open(pt[12:], seed[:12], seed[12:], nil) if err != nil { return nil, err } copy(pt[:12], seed[:12]) copy(pt[len(pt)-16:], seed[len(seed)-16:]) return pt, nil } gokey-0.1.0/csprng_test.go000066400000000000000000000056401351063100100155030ustar00rootroot00000000000000package gokey import ( "bytes" "io" "testing" ) func TestDRNG(t *testing.T) { var rngs [5]io.Reader rngs[0] = NewDRNG("pass1", "realm1") rngs[1] = NewDRNG("pass1", "realm2") rngs[2] = NewDRNG("pass2", "realm1") rngs[3] = NewDRNG("pass2", "realm2") rngs[4] = NewDRNG("pass1", "realm1") stream1 := make([]byte, 512) _, err := io.ReadFull(rngs[0], stream1) if err != nil { t.Fatal(err) } stream2 := make([]byte, 512) for i := 0; i < 4; i++ { _, err = io.ReadFull(rngs[i], stream2) if err != nil { t.Fatal(err) } if bytes.Compare(stream1, stream2) == 0 { t.Fatal("generated streams match") } } _, err = io.ReadFull(rngs[4], stream2) if err != nil { t.Fatal(err) } if bytes.Compare(stream1, stream2) != 0 { t.Fatal("generated streams do not match") } } func TestDRNGwithSeed(t *testing.T) { var rngs [5]io.Reader seed1, err := GenerateEncryptedKeySeed("pass1") if err != nil { t.Fatal(err) } seed2, err := GenerateEncryptedKeySeed("pass2") if err != nil { t.Fatal(err) } rngs[0], err = NewDRNGwithSeed("pass1", "realm1", seed1) if err != nil { t.Fatal(err) } rngs[1], err = NewDRNGwithSeed("pass1", "realm2", seed1) if err != nil { t.Fatal(err) } rngs[2], err = NewDRNGwithSeed("pass2", "realm1", seed2) if err != nil { t.Fatal(err) } rngs[3], err = NewDRNGwithSeed("pass2", "realm2", seed2) if err != nil { t.Fatal(err) } rngs[4], err = NewDRNGwithSeed("pass1", "realm1", seed1) if err != nil { t.Fatal(err) } // invalid password for a seed _, err = NewDRNGwithSeed("pass1", "realm2", seed2) if err == nil { t.Fatal("incorrect password for seed unwrap succeeded") } stream1 := make([]byte, 512) _, err = io.ReadFull(rngs[0], stream1) if err != nil { t.Fatal(err) } stream2 := make([]byte, 512) for i := 0; i < 4; i++ { _, err = io.ReadFull(rngs[i], stream2) if err != nil { t.Fatal(err) } if bytes.Compare(stream1, stream2) == 0 { t.Fatal("generated streams match") } } _, err = io.ReadFull(rngs[4], stream2) if err != nil { t.Fatal(err) } if bytes.Compare(stream1, stream2) != 0 { t.Fatal("generated streams do not match") } } func TestEncryptedSeed(t *testing.T) { seed, err := GenerateEncryptedKeySeed("pass1") if err != nil { t.Fatal(err) } _, err = unwrapSeed("pass2", seed) if err == nil { t.Fatal("incorrect password for seed unwrap succeeded") } uSeed, err := unwrapSeed("pass1", seed) if err != nil { t.Fatal(err) } // do not waste precious random bytes if len(uSeed) != len(seed) { t.Fatal("unwrapped seed is shorter than encrypted seed") } if bytes.Compare(uSeed[:12], seed[:12]) != 0 { t.Fatal("no nonce in unwrapped seed") } if bytes.Compare(uSeed[len(uSeed)-16:], seed[len(seed)-16:]) != 0 { t.Fatal("no auth tag in unwrapped seed") } // rest should have been encrypted if bytes.Compare(uSeed[12:len(uSeed)-16], seed[12:len(seed)-16]) == 0 { t.Fatal("seed was not properly encrypted") } } gokey-0.1.0/go.mod000066400000000000000000000002621351063100100137220ustar00rootroot00000000000000module github.com/cloudflare/gokey go 1.13 require ( golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4 golang.org/x/sys v0.0.0-20190626221950-04f50cda93cb // indirect ) gokey-0.1.0/go.sum000066400000000000000000000020071351063100100137460ustar00rootroot00000000000000golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4 h1:HuIa8hRrWRSrqYzx1qI49NNxhdi2PrY7gxVSq1JjLDc= golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a h1:1BGLXjeY4akVXGgbC9HugT3Jv3hCI0z56oJR5vAMgBU= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190626221950-04f50cda93cb h1:fgwFCsaw9buMuxNd6+DQfAuSFqbNiQZpcgJQAgJsK6k= golang.org/x/sys v0.0.0-20190626221950-04f50cda93cb/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= gokey-0.1.0/gokey.1.md000066400000000000000000000121451351063100100144160ustar00rootroot00000000000000--- date: 2018-06-06 footer: gokey header: "Gokey's Manual" layout: page license: 'Licensed under the 3-clause BSD license' section: 1 title: GOKEY --- # NAME gokey - A simple vaultless password manager in Go # SYNOPSIS **gokey** [**OPTIONS**] # DESCRIPTION **gokey** is a password manager, which does not require a password vault. Instead of storing your passwords in a vault it derives your password on the fly from your master password and supplied _realm_ string (for example, resource URL). This way you do not have to manage, backup or sync your password vault (or trust its management to a third party) as your passwords are available immediately anywhere. # OPTIONS **-o** *output_path* : by default **gokey** outputs generated data to *stdout* **-P** */path/to/password* : path to master password file which will be used to generate other passwords/keys or to encrypt seed file (see *Modes of operation* below, if no master password or master password file is provided, **gokey** will ask for it interactively) **-p** *master_password* : master password which will be used to generate other passwords/keys or to encrypt seed file (see *Modes of operation* below, if no master password or master password file is provided, **gokey** will ask for it interactively) **-r** *password/key_realm* : any string which identifies requested password/key, most likely key usage or resource URL **-s** *path_to_seed_file* : needed, if you want to use seed file instead of master password as an entropy source (see *Modes of operation* below); can be generated with **-t** *seed* flag as described below **-skip** *number_of_bytes* : number of bytes to skip when reading seed file **-u** : **UNSAFE**, allow generating keys without using a seed file (see *Modes of operation* below) **-t** *password/key_type* : requested password/key output type. Supported password/key types: * *pass* - default, generates a password * *seed* - generates a seed file, which can be used with **-s** option later * *raw* - generates 32 random bytes (can be used as a symmetric key) * *ec256* - generates ECC P-256 private key * *ec521* - generates ECC P-521 private key * *rsa2048* - generates 2048-bit RSA private key * *rsa4096* - generates 4096-bit RSA private key * *x25519* - generates x25519 (also known as curve25519) ECC private key * *ed25519* - generates ed25519 ECC private key # MODES OF OPERATION **gokey** can generate passwords and cryptographic private keys (ECC and RSA keys are currently supported). However, without any additional options specified it uses your master password as a single source of entropy for generated data. For passwords it is acceptable most of the time, but keys usually have much higher entropy requirements. For cases, where higher entropy is required for generated passwords/keys, **gokey** can use a seed file (a blob with random data) instead of the master password as a source of entropy. ## Simple mode (without a seed file) In simple mode passwords are derived directly from your master password and the realm string. That is each unique combination of a master password and a realm string will produce a unique password. For example, if your master password is *super-secret-master-password* and you want to generate a password for *example.com*, you would invoke **gokey** like ``` gokey -p super-secret-master-password -r example.com ``` If you need a password for a different resource, *example2.com*, you would change the real string ``` gokey -p super-secret-master-password -r example2.com ``` This way you need to remember only your master password and you can always recreate passwords for your services/resources. *NOTE*: In this mode generated passwords are as strong as your master password, so do have your master password strong enough. You can also derive private keys from your master password, but keep in mind, that these keys will have low entropy. That is why it is considered unsafe, so **gokey** does not allow it by default. If you're **_really_** know what you are doing, you can override this default by supplying **-u** flag. ## Using a seed file If you plan to generate not only passwords, but also private keys or you want to have your passwords/keys with higher entropy, you can use a seed file instead of the master password. You still need to supply a master password, when invoking **gokey**, but it will be used only to protect the seed file itself; all generated passwords/keys will be derived from the data in the seed file. **gokey** uses seed files protected (encrypted) with your master password, so it is reasonably safe to store/backup seed files to a third party location, such as Google Drive or Dropbox. To generate an encrypted seed file, use ``` gokey -p super-secret-master-password -t seed -o seedfile ``` This will create a seed file *seedfile* with 256 bytes of random data. The data is encrypted using AES-256-GCM mode and *super-secret-master-password* as a key. Then, to generate EC-256 private key for *example.com*, use ``` gokey -p super-secret-master-password -s seedfile -r example.com -t ec256 ``` # AUTHOR Ignat Korchagin gokey-0.1.0/gokey.go000066400000000000000000000074341351063100100142710ustar00rootroot00000000000000package gokey import ( "crypto" "crypto/ecdsa" "crypto/rsa" "crypto/x509" "crypto/x509/pkix" "encoding/asn1" "encoding/pem" "errors" "fmt" "io" "golang.org/x/crypto/ed25519" ) func getReader(password, realm string, seed []byte, allowUnsafe bool) (io.Reader, error) { var rng io.Reader var err error if seed != nil { rng, err = NewDRNGwithSeed(password, realm, seed) if err != nil { return nil, err } } else if allowUnsafe { rng = NewDRNG(password, realm) } else { return nil, errors.New("generating keys without strong seed is not allowed") } return rng, nil } func GetPass(password, realm string, seed []byte, spec *PasswordSpec) (string, error) { rng, err := getReader(password, realm+"-pass", seed, true) if err != nil { return "", err } gen := &KeyGen{rng} return gen.GeneratePassword(spec) } func GetKey(password, realm string, seed []byte, kt KeyType, allowUnsafe bool) (crypto.PrivateKey, error) { rng, err := getReader(password, realm+fmt.Sprintf("-key(%v)", kt), seed, allowUnsafe) if err != nil { return nil, err } gen := &KeyGen{rng} return gen.GenerateKey(kt) } func GetRaw(password, realm string, seed []byte, allowUnsafe bool) (io.Reader, error) { rng, err := getReader(password, realm+"-raw", seed, allowUnsafe) if err != nil { return nil, err } return rng, nil } // below code implements asn1 encoding of x25519 and ed25519 keys according // to https://tools.ietf.org/id/draft-ietf-curdle-pkix-10.txt // the output should be compatible to OpenSSL pkey functions // this code is considered temporal and is expected to go away, when Go // implements native marshalling for these types of keys // as https://tools.ietf.org/id/draft-ietf-curdle-pkix-10.txt is still a draft // future versions of gokey may produce different output // p.3 https://tools.ietf.org/id/draft-ietf-curdle-pkix-10.txt // id-X25519 OBJECT IDENTIFIER ::= { 1 3 101 110 } // id-Ed25519 OBJECT IDENTIFIER ::= { 1 3 101 112 } const ( x25519OidSuffix = 110 ed25519OidSuffix = 112 ) // x25519/ed25519 asn1 private key structure // p.7 https://tools.ietf.org/id/draft-ietf-curdle-pkix-10.txt // this implementation does not support optional attributes or public key type asn25519 struct { Version int AlgId pkix.AlgorithmIdentifier PrivateKey []byte } // Golang does not have a declaration for x25519 keys type x25519PrivateKey []byte func marshal25519PrivateKey(key crypto.PrivateKey) ([]byte, error) { var a25519 asn25519 var keyBytes []byte switch key.(type) { case x25519PrivateKey: a25519.AlgId = pkix.AlgorithmIdentifier{asn1.ObjectIdentifier{1, 3, 101, x25519OidSuffix}, asn1.RawValue{}} keyBytes = key.(x25519PrivateKey) case *ed25519.PrivateKey: a25519.AlgId = pkix.AlgorithmIdentifier{asn1.ObjectIdentifier{1, 3, 101, ed25519OidSuffix}, asn1.RawValue{}} keyBytes = key.(*ed25519.PrivateKey).Seed() } // actual key bytes are double wrapped in octet strings // see p.7 https://tools.ietf.org/id/draft-ietf-curdle-pkix-10.txt privKeyOctetString, err := asn1.Marshal(keyBytes) if err != nil { return nil, err } a25519.PrivateKey = privKeyOctetString return asn1.Marshal(a25519) } func EncodeToPem(key crypto.PrivateKey, w io.Writer) error { switch key.(type) { case *ecdsa.PrivateKey: der, err := x509.MarshalECPrivateKey(key.(*ecdsa.PrivateKey)) if err != nil { return err } return pem.Encode(w, &pem.Block{Type: "EC PRIVATE KEY", Bytes: der}) case *rsa.PrivateKey: der := x509.MarshalPKCS1PrivateKey(key.(*rsa.PrivateKey)) return pem.Encode(w, &pem.Block{Type: "RSA PRIVATE KEY", Bytes: der}) case x25519PrivateKey, *ed25519.PrivateKey: der, err := marshal25519PrivateKey(key) if err != nil { return err } return pem.Encode(w, &pem.Block{Type: "PRIVATE KEY", Bytes: der}) } return fmt.Errorf("unable to encode key type %T", key) } gokey-0.1.0/gokey_test.go000066400000000000000000000152321351063100100153230ustar00rootroot00000000000000package gokey import ( "bytes" "crypto" "encoding/asn1" "encoding/hex" "encoding/pem" "reflect" "strings" "testing" "golang.org/x/crypto/ed25519" ) var ( passSpec = &PasswordSpec{16, 3, 3, 2, 1, ""} ) func TestGetPass(t *testing.T) { pass1Seed1, err := GenerateEncryptedKeySeed("pass1") if err != nil { t.Fatal(err) } pass1Seed2, err := GenerateEncryptedKeySeed("pass1") if err != nil { t.Fatal(err) } pass1Example1, err := GetPass("pass1", "example.com", nil, passSpec) if err != nil { t.Fatal(err) } pass1Example2, err := GetPass("pass1", "example2.com", nil, passSpec) if err != nil { t.Fatal(err) } pass2Example1, err := GetPass("pass2", "example.com", nil, passSpec) if err != nil { t.Fatal(err) } if strings.Compare(pass1Example1, pass1Example2) == 0 { t.Fatal("passwords match for different realms") } if strings.Compare(pass1Example1, pass2Example1) == 0 { t.Fatal("passwords match for different master passwords") } pass1Example1Seed1, err := GetPass("pass1", "example.com", pass1Seed1, passSpec) if err != nil { t.Fatal(err) } pass1Example1Seed2, err := GetPass("pass1", "example.com", pass1Seed2, passSpec) if err != nil { t.Fatal(err) } if strings.Compare(pass1Example1, pass1Example1Seed1) == 0 { t.Fatal("passwords match for seeded and non-seeded master password") } if strings.Compare(pass1Example1Seed1, pass1Example1Seed2) == 0 { t.Fatal("passwords match for different seeds") } pass1Example1Retry, err := GetPass("pass1", "example.com", nil, passSpec) if err != nil { t.Fatal(err) } pass1Example1Seed1Retry, err := GetPass("pass1", "example.com", pass1Seed1, passSpec) if err != nil { t.Fatal(err) } if (strings.Compare(pass1Example1, pass1Example1Retry) != 0) || (strings.Compare(pass1Example1Seed1, pass1Example1Seed1Retry) != 0) { t.Fatal("passwords with same invocation options do not match") } } func keyToBytes(key crypto.PrivateKey, t *testing.T) []byte { buf := bytes.NewBuffer(nil) err := EncodeToPem(key, buf) if err != nil { t.Fatal(err) } return buf.Bytes() } func testGetKeyType(kt KeyType, t *testing.T) { pass1Seed1, err := GenerateEncryptedKeySeed("pass1") if err != nil { t.Fatal(err) } pass1Seed2, err := GenerateEncryptedKeySeed("pass1") if err != nil { t.Fatal(err) } key1Example1, err := GetKey("pass1", "example.com", nil, kt, true) if err != nil { t.Fatal(err) } key1Example2, err := GetKey("pass1", "example2.com", nil, kt, true) if err != nil { t.Fatal(err) } key2Example1, err := GetKey("pass2", "example.com", nil, kt, true) if err != nil { t.Fatal(err) } if bytes.Compare(keyToBytes(key1Example1, t), keyToBytes(key1Example2, t)) == 0 { t.Fatal("keys match for different realms") } if bytes.Compare(keyToBytes(key1Example1, t), keyToBytes(key2Example1, t)) == 0 { t.Fatal("keys match for different master passwords") } key1Example1Seed1, err := GetKey("pass1", "example.com", pass1Seed1, kt, false) if err != nil { t.Fatal(err) } key1Example1Seed2, err := GetKey("pass1", "example.com", pass1Seed2, kt, false) if err != nil { t.Fatal(err) } if bytes.Compare(keyToBytes(key1Example1, t), keyToBytes(key1Example1Seed1, t)) == 0 { t.Fatal("keys match for seeded and non-seeded master password") } if bytes.Compare(keyToBytes(key1Example1Seed1, t), keyToBytes(key1Example1Seed2, t)) == 0 { t.Fatal("keys match for different seeds") } key1Example1Retry, err := GetKey("pass1", "example.com", nil, kt, true) if err != nil { t.Fatal(err) } key1Example1Seed1Retry, err := GetKey("pass1", "example.com", pass1Seed1, kt, false) if err != nil { t.Fatal(err) } if (bytes.Compare(keyToBytes(key1Example1, t), keyToBytes(key1Example1Retry, t)) != 0) || (bytes.Compare(keyToBytes(key1Example1Seed1, t), keyToBytes(key1Example1Seed1Retry, t)) != 0) { t.Fatal("keys with same invocation options do not match") } } func TestGetKey(t *testing.T) { testGetKeyType(EC256, t) testGetKeyType(EC521, t) testGetKeyType(RSA2048, t) testGetKeyType(RSA4096, t) testGetKeyType(X25519, t) testGetKeyType(ED25519, t) } func TestGetKeyUnsafe(t *testing.T) { _, err := GetKey("pass1", "example.com", nil, EC256, false) if err == nil { t.Fatal("allowed unsafe key generation") } } func parse25519(t *testing.T, keyType KeyType, refKey string, refKeyBytes []byte) { var suffix int switch keyType { case X25519: suffix = x25519OidSuffix case ED25519: suffix = ed25519OidSuffix } block, _ := pem.Decode([]byte(refKey)) if block == nil { t.Fatal("unable to pem-decode x25519 key") } var a25519 asn25519 _, err := asn1.Unmarshal(block.Bytes, &a25519) if err != nil { t.Fatal(err) } if !reflect.DeepEqual(a25519.AlgId.Algorithm, asn1.ObjectIdentifier{1, 3, 101, suffix}) || !reflect.DeepEqual(a25519.PrivateKey, refKeyBytes) { t.Fatalf("invalid %v key after parsing", keyType) } } func TestParseX25519(t *testing.T) { // generated by // $ openssl genpkey -algorithm x25519 x25519Openssl := `-----BEGIN PRIVATE KEY----- MC4CAQAwBQYDK2VuBCIEIEBQhR7E5x8vlFgWOhonI+/H3DY1R9mCh6wdwd8Hkgl1 -----END PRIVATE KEY-----` // if you parse the key above the actual key bytes should be the following // $ echo | openssl asn1parse -i x25519OpensslKeyBytes, err := hex.DecodeString("04204050851EC4E71F2F9458163A1A2723EFC7DC363547D98287AC1DC1DF07920975") if err != nil { t.Fatal(err) } parse25519(t, X25519, x25519Openssl, x25519OpensslKeyBytes) } func TestParseEd25519(t *testing.T) { // taken from https://github.com/openssl/openssl/blob/60bbed3ff6716e8f1358396acc772908a758a0a0/test/certs/client-ed25519-key.pem ed25519Openssl := `-----BEGIN PRIVATE KEY----- MC4CAQAwBQYDK2VwBCIEINZzpIpIiXXsKx4M7mUr2cb+DMfgHyu2msRAgNa5CxJJ -----END PRIVATE KEY-----` // if you parse the key above the actual key bytes should be the following // $ echo | openssl asn1parse -i ed25519OpensslKeyBytes, err := hex.DecodeString("0420D673A48A488975EC2B1E0CEE652BD9C6FE0CC7E01F2BB69AC44080D6B90B1249") if err != nil { t.Fatal(err) } parse25519(t, ED25519, ed25519Openssl, ed25519OpensslKeyBytes) } func gen25519(t *testing.T, keyType KeyType) { seed, err := GenerateEncryptedKeySeed("pass1") if err != nil { t.Fatal(err) } key, err := GetKey("pass1", "example.com", seed, keyType, false) if err != nil { t.Fatal(err) } var b strings.Builder err = EncodeToPem(key, &b) if err != nil { t.Fatal(err) } var keyBytes []byte switch keyType { case X25519: keyBytes = key.(x25519PrivateKey)[:] case ED25519: keyBytes = key.(*ed25519.PrivateKey).Seed() } parse25519(t, keyType, b.String(), append([]byte{0x04, 0x20}, keyBytes...)) } func TestGenX25519(t *testing.T) { gen25519(t, X25519) } func TestGenEd25519(t *testing.T) { gen25519(t, ED25519) } gokey-0.1.0/keygen.go000066400000000000000000000076371351063100100144420ustar00rootroot00000000000000package gokey import ( "crypto" "crypto/ecdsa" "crypto/elliptic" "errors" "io" "strings" "unicode" deterministicRsaKeygen "github.com/cloudflare/gokey/rsa" "golang.org/x/crypto/ed25519" ) type KeyType int const ( EC256 KeyType = iota EC521 RSA2048 RSA4096 X25519 ED25519 ) //go:generate stringer -type KeyType type KeyGen struct { rng io.Reader } type PasswordSpec struct { Length int Upper int Lower int Digits int Special int AllowedSpecial string } func (spec *PasswordSpec) Valid() bool { if spec.AllowedSpecial != "" { for _, c := range spec.AllowedSpecial { if !unicode.IsSymbol(c) && !unicode.IsPunct(c) { return false } } } return spec.Length >= spec.Upper+spec.Lower+spec.Digits+spec.Special } func allowed(num, fromSpec int) bool { if num > 0 && fromSpec == 0 { return false } if num < fromSpec { return false } return true } func (spec *PasswordSpec) Compliant(password string) bool { var upper, lower, digits, special int for _, c := range password { if unicode.IsUpper(c) { upper++ } if unicode.IsLower(c) { lower++ } if unicode.IsDigit(c) { digits++ } if unicode.IsSymbol(c) || unicode.IsPunct(c) { if spec.AllowedSpecial == "" { special++ } else { if strings.ContainsRune(spec.AllowedSpecial, c) { special++ } else { return false } } } } if !allowed(upper, spec.Upper) || !allowed(lower, spec.Lower) || !allowed(digits, spec.Digits) || !allowed(special, spec.Special) { return false } return true } const chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890`~!@#$%^&*()-_=+[{]}\\|;:'\",<.>/?" func randRange(rng io.Reader, max byte) (byte, error) { var base [1]byte for { _, err := io.ReadFull(rng, base[:]) if err != nil { return 0, err } if 255 == base[0] { continue } rem := 255 % max buck := 255 / max if base[0] < 255-rem { return base[0] / buck, nil } } } func (keygen *KeyGen) genRandStr(length int) (string, error) { bytes := make([]byte, length) for i := 0; i < length; i++ { pos, err := randRange(keygen.rng, byte(len(chars))) if err != nil { return "", err } bytes[i] = chars[pos] } return string(bytes), nil } func (keygen *KeyGen) GeneratePassword(spec *PasswordSpec) (string, error) { if !spec.Valid() { return "", errors.New("invalid password specification") } for { password, err := keygen.genRandStr(spec.Length) if err != nil { return "", err } if spec.Compliant(password) { return password, nil } } } func (keygen *KeyGen) generateRsa(kt KeyType) (crypto.PrivateKey, error) { bits := 0 switch kt { case RSA2048: bits = 2048 case RSA4096: bits = 4096 default: return nil, errors.New("invalid RSA key size requested") } return deterministicRsaKeygen.GenerateKey(keygen.rng, bits) } func (keygen *KeyGen) generateEc(kt KeyType) (crypto.PrivateKey, error) { var curve elliptic.Curve switch kt { case EC256: curve = elliptic.P256() case EC521: curve = elliptic.P521() default: return nil, errors.New("invalid EC key size requested") } return ecdsa.GenerateKey(curve, keygen.rng) } func (keygen *KeyGen) generate25519(kt KeyType) (crypto.PrivateKey, error) { switch kt { case X25519: var privKey [32]byte _, err := io.ReadFull(keygen.rng, privKey[:]) // from https://cr.yp.to/ecdh.html privKey[0] &= 248 privKey[31] &= 127 privKey[31] |= 64 return x25519PrivateKey(privKey[:]), err case ED25519: _, privKey, err := ed25519.GenerateKey(keygen.rng) return &privKey, err } return nil, errors.New("invalid key type requested") } func (keygen *KeyGen) GenerateKey(kt KeyType) (crypto.PrivateKey, error) { switch kt { case EC256, EC521: return keygen.generateEc(kt) case RSA2048, RSA4096: return keygen.generateRsa(kt) case X25519, ED25519: return keygen.generate25519(kt) } return nil, errors.New("invalid key type requested") } gokey-0.1.0/keygen_test.go000066400000000000000000000010021351063100100154550ustar00rootroot00000000000000package gokey import ( "crypto/rand" "testing" "unicode" ) func TestGenPass(t *testing.T) { spec := &PasswordSpec{16, 2, 2, 1, 1, ""} keygen := &KeyGen{rand.Reader} _, err := keygen.GeneratePassword(spec) if err != nil { t.Fatal(err) } } func TestAllowedChars(t *testing.T) { for _, c := range chars { if !unicode.IsLower(c) && !unicode.IsUpper(c) && !unicode.IsSymbol(c) && !unicode.IsPunct(c) && !unicode.IsDigit(c) { t.Fatalf("not used character %c in allowed character string", c) } } } gokey-0.1.0/keytype_string.go000066400000000000000000000006541351063100100162300ustar00rootroot00000000000000// Code generated by "stringer -type KeyType"; DO NOT EDIT. package gokey import "strconv" const _KeyType_name = "EC256EC521RSA2048RSA4096X25519ED25519" var _KeyType_index = [...]uint8{0, 5, 10, 17, 24, 30, 37} func (i KeyType) String() string { if i < 0 || i >= KeyType(len(_KeyType_index)-1) { return "KeyType(" + strconv.FormatInt(int64(i), 10) + ")" } return _KeyType_name[_KeyType_index[i]:_KeyType_index[i+1]] } gokey-0.1.0/rsa/000077500000000000000000000000001351063100100134015ustar00rootroot00000000000000gokey-0.1.0/rsa/keygen.go000066400000000000000000000054741351063100100152240ustar00rootroot00000000000000package rsa import ( "crypto/rand" "crypto/rsa" "errors" "io" "math" "math/big" ) // below is taken from https://github.com/golang/go/blob/161874da2ab6d5372043a1f3938a81a19d1165ad/src/crypto/rsa/rsa.go#L220 // because later versions in standard crypto/rsa package are not deterministic var bigOne = big.NewInt(1) func GenerateKey(random io.Reader, bits int) (*rsa.PrivateKey, error) { return generateMultiPrimeKey(random, 2, bits) } func generateMultiPrimeKey(random io.Reader, nprimes int, bits int) (*rsa.PrivateKey, error) { priv := new(rsa.PrivateKey) priv.E = 65537 if nprimes < 2 { return nil, errors.New("gokey/rsa: GenerateMultiPrimeKey: nprimes must be >= 2") } if bits < 64 { primeLimit := float64(uint64(1) << uint(bits/nprimes)) // pi approximates the number of primes less than primeLimit pi := primeLimit / (math.Log(primeLimit) - 1) // Generated primes start with 11 (in binary) so we can only // use a quarter of them. pi /= 4 // Use a factor of two to ensure that key generation terminates // in a reasonable amount of time. pi /= 2 if pi <= float64(nprimes) { return nil, errors.New("gokey/rsa: too few primes of given length to generate an RSA key") } } primes := make([]*big.Int, nprimes) NextSetOfPrimes: for { todo := bits // crypto/rand should set the top two bits in each prime. // Thus each prime has the form // p_i = 2^bitlen(p_i) × 0.11... (in base 2). // And the product is: // P = 2^todo × α // where α is the product of nprimes numbers of the form 0.11... // // If α < 1/2 (which can happen for nprimes > 2), we need to // shift todo to compensate for lost bits: the mean value of 0.11... // is 7/8, so todo + shift - nprimes * log2(7/8) ~= bits - 1/2 // will give good results. if nprimes >= 7 { todo += (nprimes - 2) / 5 } for i := 0; i < nprimes; i++ { var err error primes[i], err = rand.Prime(random, todo/(nprimes-i)) if err != nil { return nil, err } todo -= primes[i].BitLen() } // Make sure that primes is pairwise unequal. for i, prime := range primes { for j := 0; j < i; j++ { if prime.Cmp(primes[j]) == 0 { continue NextSetOfPrimes } } } n := new(big.Int).Set(bigOne) totient := new(big.Int).Set(bigOne) pminus1 := new(big.Int) for _, prime := range primes { n.Mul(n, prime) pminus1.Sub(prime, bigOne) totient.Mul(totient, pminus1) } if n.BitLen() != bits { // This should never happen for nprimes == 2 because // crypto/rand should set the top two bits in each prime. // For nprimes > 2 we hope it does not happen often. continue NextSetOfPrimes } priv.D = new(big.Int) e := big.NewInt(int64(priv.E)) ok := priv.D.ModInverse(e, totient) if ok != nil { priv.Primes = primes priv.N = n break } } priv.Precompute() return priv, nil } gokey-0.1.0/rsa/keygen_test.go000066400000000000000000000201071351063100100162510ustar00rootroot00000000000000package rsa_test import ( "bytes" "crypto/rsa" "encoding/pem" "io" "runtime/debug" "strings" "testing" "github.com/cloudflare/gokey" ) func pemEqual(pem1, pem2 string, t *testing.T) bool { b1, err := pem.Decode([]byte(pem1)) if b1 == nil { t.Fatalf("failed to decode %v: %v", pem1, err) } b2, err := pem.Decode([]byte(pem2)) if b2 == nil { t.Fatalf("failed to decode %v: %v", pem2, err) } return b1.Type == b2.Type && bytes.Equal(b1.Bytes, b2.Bytes) } func getKey(kt gokey.KeyType, t *testing.T) string { priv, err := gokey.GetKey("pass", "example.com", nil, kt, true) if err != nil { t.Fatal(err) } var b strings.Builder err = gokey.EncodeToPem(priv, &b) if err != nil { t.Fatal(err) } return b.String() } // generated by "$ gokey -p pass -r example.com -u -t rsa2048" with Go < 1.11 var knownRsa2048 = `-----BEGIN RSA PRIVATE KEY----- MIIEowIBAAKCAQEAqIT/jlQnJAIxS2h/AEz6RSiN9/GWY8lh9V81vfpzjZ069NOH fVL0rQiRj11GqReHTUOjuOiCwYn6d6tPSCVC8fDyBjQ6cf9tm0G+lMf8LsMCPFTL PDivqH8KwXLgC2+Zmm906nGkGUZ79AdOa6A2k07PbQ791/7s6ghWU6Dx6lPqiHC+ jFfQctQWNUdIchYt1X9eARNTcJQAYBAikH0X+dZwcm9sfTRtJCo7WvjOBwxIk/XA k9GcoR2bbGrN8wnVGO2Luw9exg/GavfpkWI1jRx82Hfqfd+Ux3478yOS2Z1kbve1 g3DI8qscaN8oZugw4R/EBg0jOOinDwE9oISZ3wIDAQABAoIBAGJooE/+RokZmq29 jQSg7zl5sEYNV1RYYpMGkXyqh9Y37hjQefuueOGe8lm1D7Fo4wM0r6Qoa0sYByLg 8EBiOhDNMph64XJ2xgv3PZLmohawnFqc7b3yIGoWHjLPoZQsDJgJ5E2QJVL5PSNJ LPteqOAnEqxOJ+B9pt4YFklp4DuE6H21pgM8tn0azH5Hf4w6LDrkjk4+WVAFrQqj UJFSVVQ6Vq54BxnBUJzGCmlKCqDbUrOLQ2TrDodDVPHzmzxX69WVtwqtkiJ6X7HH pnodlRV9LwLMkZwgQBEcoZnQkWLimGBRBaO/uuZewmXJ9u6HgXIL9+VVHFnkcdZs S6QwLxECgYEA3ghowXxlR7GsC8+07YJUJV+KvKNK4qEZVp2MMscxOPWjcO1tWO88 8K/E54Gp8ktsqmejQjnUD7749h2pEUZHfy2sCpWOHjXzLouCkZlSZQpsWVcFkFLL qZvwv+pCV9osxpf8o2n/ZuKGP3VgKeSlql6eHLdRV6edEUEJogwFgykCgYEAwkzR d5uqToUlbz3fKTs9c4bTt+xnhlOyXPdnbRph/N96Yp0eIi2v7F0FBB5qzKZKy72M 4+DHvWrpAruh14nryXolQ/OesT27R00auxjOAWM2v2BFeNjBadwnlYfbzCtM+1Wd 6U+Ahd6Nkdk5tAkMvP03RwPxOTPXuXOtJNFgHccCgYEApbq1JqdJmegeuXpCXH5J fNQB5KgmP19sYGCcw1I3hYKkiqhOVHHOlQE+AmegiMCPzeopzEcJ6O7tOhgNmF1T BLT8k7HqMNaoO/fab/93pv/OvCjeeEm0x1ckrruW5ahuf5X296so/ozbFAbyzpJi rfaOInUa/EiaTsSzAgfjHXECgYA13opsuPHc1zlrwCGxEsWU1Bq68YY6TdYzxDwe maP1MhiCYsFKBJSz2Y5cd/pwRuKR3jnDrDx0ncGAinjyg0CmGYFfd5nV1iGoQQ5a NSRYaiNxp8VbHe1x5iHraUFdN9weCry/RNWDSBLEDw/ahG/Nrf63Z7Znf6Idvp6Q iKQ3XQKBgGpKBezvB82Jk0suZZdqZIs3OVDy7i6SIwS3MgABRE5pYgDWhlnEzGFS Tk4zAHR2tIeN4RjOcanspxKbN+Qfm1TkeFwRq5553f0ypMz7xkCdUCKIAeCfTkbs qkciBuCm/Oa4RlB/LwH+paBV3RvQOYLXw50/uzAr5S2+2wPx4wMT -----END RSA PRIVATE KEY-----` // generated by "$ gokey -p pass -r example.com -u -t rsa4096" with Go < 1.11 var knownRsa4096 = `-----BEGIN RSA PRIVATE KEY----- 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 -----END RSA PRIVATE KEY-----` func TestKnownKey(t *testing.T) { rsa2048 := getKey(gokey.RSA2048, t) if !pemEqual(rsa2048, knownRsa2048, t) { t.Fatal("generated RSA 2048 does not match the expected result") } rsa4096 := getKey(gokey.RSA4096, t) if !pemEqual(rsa4096, knownRsa4096, t) { t.Fatal("generated RSA 4096 does not match the expected result") } } // reader which tries to negate the effect of crypto/internal/randutil/randutil.MaybeReadByte // https://github.com/golang/go/blob/6269dcdc24d74379d8a609ce886149811020b2cc/src/crypto/internal/randutil/randutil.go#L25 // useful to regain deterministic output of some crypto routines with respect to a fixed pseudo-random stream // such as https://github.com/golang/go/blob/6269dcdc24d74379d8a609ce886149811020b2cc/src/crypto/rsa/rsa.go#L222 type maybenotReader struct { io.Reader *testing.T } func (r maybenotReader) Read(p []byte) (n int, err error) { if strings.Contains(string(debug.Stack()), "crypto/internal/randutil.MaybeReadByte") { r.T.Log("mitigating crypto/internal/randutil.MaybeReadByte...") // feed MaybeReadByte with dummy zeroes for i := range p { p[i] = 0 } return len(p), nil } return r.Reader.Read(p) } // generate an RSA key using stdlib crypto/rsa package // but with crypto/internal/randutil.MaybeReadByte removed func getStdRsaKey(bits int, t *testing.T) string { realm := "example.com" // mimic gokey.GetKey behaviour switch bits { case 2048: realm += "-key(RSA2048)" case 4096: realm += "-key(RSA4096)" } rng := &maybenotReader{gokey.NewDRNG("pass", realm), t} priv, err := rsa.GenerateKey(rng, bits) if err != nil { t.Fatal(err) } var b strings.Builder err = gokey.EncodeToPem(priv, &b) if err != nil { t.Fatal(err) } return b.String() } // this test is here to see if the RSA key generation algorithm in the stdlib is the same as in our package // for now we want to track any stdlib changes to the algorithm // however, this test might be removed in the future, if the changes in the stdlib will not be backwards compatible func TestStdKey(t *testing.T) { rsa2048 := getStdRsaKey(2048, t) if !pemEqual(rsa2048, knownRsa2048, t) { t.Fatal("RSA key generation algorithm from stdlib deviates from the one in gokey") } rsa4096 := getStdRsaKey(4096, t) if !pemEqual(rsa4096, knownRsa4096, t) { t.Fatal("RSA key generation algorithm from stdlib deviates from the one in gokey") } }