diff --git a/mockid/api/common.go b/mockid/api/common.go index 2d71551..c2ca5ef 100644 --- a/mockid/api/common.go +++ b/mockid/api/common.go @@ -11,35 +11,17 @@ import ( "math/rand" mathrand "math/rand" "net/http" + + "git.coolaj86.com/coolaj86/go-mockid/xkeypairs" ) -type Object = map[string]interface{} - -// options are the things that we may need to know about a request to fulfill it properly -type options struct { - Key string `json:"key"` - KeyType string `json:"kty"` - Seed int64 `json:"-"` - SeedStr string `json:"seed"` - Claims Object `json:"claims"` - Header Object `json:"header"` -} - -// this shananigans is only for testing and debug API stuff -func (o *options) nextReader() io.Reader { - if 0 == o.Seed { - return RandomReader - } - return rand.New(rand.NewSource(o.Seed)) -} - /* -func getJWS(r *http.Request) (*options, error) { +func getJWS(r *http.Request) (*xkeypairs.KeyOptions, error) { } */ -func getOpts(r *http.Request) (*options, error) { +func getOpts(r *http.Request) (*xkeypairs.KeyOptions, error) { tok := make(map[string]interface{}) decoder := json.NewDecoder(r.Body) err := decoder.Decode(&tok) @@ -60,17 +42,17 @@ func getOpts(r *http.Request) (*options, error) { } key, _ := tok["key"].(string) - opts := &options{ + opts := &xkeypairs.KeyOptions{ Seed: seed, Key: key, } - opts.Claims, _ = tok["claims"].(Object) - opts.Header, _ = tok["header"].(Object) + opts.Claims, _ = tok["claims"].(xkeypairs.Object) + opts.Header, _ = tok["header"].(xkeypairs.Object) var n int if 0 != seed { - n = opts.nextReader().(*mathrand.Rand).Intn(2) + n = opts.MyFooNextReader().(*mathrand.Rand).Intn(2) } else { n = rand.Intn(2) } diff --git a/mockid/api/generate.go b/mockid/api/generate.go index 55b7398..3f36fb5 100644 --- a/mockid/api/generate.go +++ b/mockid/api/generate.go @@ -1,21 +1,12 @@ package api import ( - "crypto/ecdsa" - "crypto/elliptic" - "crypto/rand" - "crypto/rsa" - "io" - "log" "net/http" "git.coolaj86.com/coolaj86/go-mockid/xkeypairs" "git.rootprojects.org/root/keypairs" ) -// RandomReader may be overwritten for testing -var RandomReader io.Reader = rand.Reader - // GeneratePublicJWK will create a new private key in JWK format func GeneratePublicJWK(w http.ResponseWriter, r *http.Request) { if "POST" != r.Method { @@ -52,7 +43,7 @@ func GeneratePrivateJWK(w http.ResponseWriter, r *http.Request) { return } - privkey := genPrivKey(opts) + privkey := xkeypairs.GenPrivKey(opts) jwk := xkeypairs.MarshalJWKPrivateKey(privkey) w.Write(append(jwk, '\n')) @@ -95,7 +86,7 @@ func GeneratePrivateDER(w http.ResponseWriter, r *http.Request) { return } - privkey := genPrivKey(opts) + privkey := xkeypairs.GenPrivKey(opts) der, _ := xkeypairs.MarshalDERPrivateKey(privkey) w.Write(der) @@ -138,7 +129,7 @@ func GeneratePrivatePEM(w http.ResponseWriter, r *http.Request) { return } - privkey := genPrivKey(opts) + privkey := xkeypairs.GenPrivKey(opts) privpem, _ := xkeypairs.MarshalPEMPrivateKey(privkey) w.Write(privpem) @@ -146,39 +137,9 @@ func GeneratePrivatePEM(w http.ResponseWriter, r *http.Request) { const maxRetry = 16 -func getPrivKey(opts *options) (keypairs.PrivateKey, error) { +func getPrivKey(opts *xkeypairs.KeyOptions) (keypairs.PrivateKey, error) { if "" != opts.Key { return keypairs.ParsePrivateKey([]byte(opts.Key)) } - return genPrivKey(opts), nil -} - -func genPrivKey(opts *options) keypairs.PrivateKey { - var privkey keypairs.PrivateKey - - if "RSA" == opts.KeyType { - keylen := 2048 - privkey, _ = rsa.GenerateKey(opts.nextReader(), keylen) - if 0 != opts.Seed { - for i := 0; i < maxRetry; i++ { - otherkey, _ := rsa.GenerateKey(opts.nextReader(), keylen) - otherCmp := otherkey.D.Cmp(privkey.(*rsa.PrivateKey).D) - if 0 != otherCmp { - // There are two possible keys, choose the lesser D value - // See https://github.com/square/go-jose/issues/189 - if otherCmp < 0 { - privkey = otherkey - } - break - } - if maxRetry == i-1 { - log.Printf("error: coinflip landed on heads %d times", maxRetry) - } - } - } - } else { - // TODO: EC keys may also suffer the same random problems in the future - privkey, _ = ecdsa.GenerateKey(elliptic.P256(), opts.nextReader()) - } - return privkey + return xkeypairs.GenPrivKey(opts), nil } diff --git a/xkeypairs/generate.go b/xkeypairs/generate.go new file mode 100644 index 0000000..73a59c6 --- /dev/null +++ b/xkeypairs/generate.go @@ -0,0 +1,61 @@ +package xkeypairs + +import ( + "crypto/ecdsa" + "crypto/elliptic" + "crypto/rsa" + "io" + "log" + "math/rand" + + "git.rootprojects.org/root/keypairs" +) + +// KeyOptions are the things that we may need to know about a request to fulfill it properly +type KeyOptions struct { + Key string `json:"key"` + KeyType string `json:"kty"` + Seed int64 `json:"-"` + SeedStr string `json:"seed"` + Claims Object `json:"claims"` + Header Object `json:"header"` +} + +// this shananigans is only for testing and debug API stuff +func (o *KeyOptions) MyFooNextReader() io.Reader { + if 0 == o.Seed { + return RandomReader + } + return rand.New(rand.NewSource(o.Seed)) +} + +// GenPrivKey generates a 256-bit entropy RSA or ECDSA private key +func GenPrivKey(opts *KeyOptions) keypairs.PrivateKey { + var privkey keypairs.PrivateKey + + if "RSA" == opts.KeyType { + keylen := 2048 + privkey, _ = rsa.GenerateKey(opts.MyFooNextReader(), keylen) + if 0 != opts.Seed { + for i := 0; i < maxRetry; i++ { + otherkey, _ := rsa.GenerateKey(opts.MyFooNextReader(), keylen) + otherCmp := otherkey.D.Cmp(privkey.(*rsa.PrivateKey).D) + if 0 != otherCmp { + // There are two possible keys, choose the lesser D value + // See https://github.com/square/go-jose/issues/189 + if otherCmp < 0 { + privkey = otherkey + } + break + } + if maxRetry == i-1 { + log.Printf("error: coinflip landed on heads %d times", maxRetry) + } + } + } + } else { + // TODO: EC keys may also suffer the same random problems in the future + privkey, _ = ecdsa.GenerateKey(elliptic.P256(), opts.MyFooNextReader()) + } + return privkey +} diff --git a/xkeypairs/parse.go b/xkeypairs/parse.go index 41efa3a..5f2e9f3 100644 --- a/xkeypairs/parse.go +++ b/xkeypairs/parse.go @@ -1,7 +1,7 @@ package xkeypairs import ( - "strconv" + "io/ioutil" "git.rootprojects.org/root/keypairs" ) @@ -12,37 +12,11 @@ func ParsePEMPrivateKey(block []byte) (keypairs.PrivateKey, error) { return keypairs.ParsePrivateKey(block) } -func ParseDuration(exp string) (int, error) { - if "" == exp { - exp = "15m" - } - - mult := 1 - switch exp[len(exp)-1] { - case 'w': - mult *= 7 - fallthrough - case 'd': - mult *= 24 - fallthrough - case 'h': - mult *= 60 - fallthrough - case 'm': - mult *= 60 - fallthrough - case 's': - // no fallthrough - default: - // could be 'k' or 'z', but we assume its empty - exp += "s" - } - - // 15m => num=15, mult=1*60 - num, err := strconv.Atoi(exp[:len(exp)-1]) +// ParsePrivateKeyFile returns the private key from the given file path, if available +func ParsePrivateKeyFile(pathname string) (keypairs.PrivateKey, error) { + block, err := ioutil.ReadFile(pathname) if nil != err { - return 0, err + return nil, err } - - return num * mult, nil + return keypairs.ParsePrivateKey(block) } diff --git a/xkeypairs/sign.go b/xkeypairs/sign.go index c2f4492..d8fe0d3 100644 --- a/xkeypairs/sign.go +++ b/xkeypairs/sign.go @@ -17,7 +17,10 @@ import ( "git.rootprojects.org/root/keypairs" ) -var RandomReader = rand.Reader +// RandomReader may be overwritten for testing +var RandomReader io.Reader = rand.Reader + +//var RandomReader = rand.Reader type JWS struct { Header Object `json:"header"` // JSON @@ -29,6 +32,7 @@ type JWS struct { type Object = map[string]interface{} +// SignClaims adds `typ`, `kid` (or `jwk`), and `alg` in the header and expects claims for `jti`, `exp`, `iss`, and `iat` func SignClaims(privkey keypairs.PrivateKey, header Object, claims Object) (*JWS, error) { var randsrc io.Reader = RandomReader seed, _ := header["_seed"].(int64) @@ -119,11 +123,12 @@ func claimsToPayload(claims Object) ([]byte, error) { // parse if exp is actually a duration, such as "15m" if 0 == exp && "" != dur { - s, err := ParseDuration(dur) + s, err := time.ParseDuration(dur) + // TODO s, err := time.ParseDuration(dur) if nil != err { return nil, err } - exp = time.Now().Add(time.Duration(s) * time.Second).Unix() + exp = time.Now().Add(s * time.Second).Unix() claims["exp"] = exp } if "" == jti && 0 == exp && !insecure { @@ -155,8 +160,6 @@ func Sign(rand io.Reader, privkey keypairs.PrivateKey, hash []byte) []byte { case *ecdsa.PrivateKey: r, s, _ := ecdsa.Sign(rand, k, hash[:]) rb := r.Bytes() - fmt.Println("debug:") - fmt.Println(r, s) for len(rb) < 32 { rb = append([]byte{0}, rb...) }