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 { http.Error(w, "Method Not Allowed", http.StatusMethodNotAllowed) return } opts, err := getOpts(r) if nil != err { http.Error(w, err.Error(), http.StatusBadRequest) return } privkey, err := getPrivKey(opts) if nil != err { http.Error(w, err.Error(), http.StatusBadRequest) return } jwk := keypairs.MarshalJWKPublicKey(keypairs.NewPublicKey(privkey.Public())) w.Write(append(jwk, '\n')) } // GeneratePrivateJWK will create a new private key in JWK format func GeneratePrivateJWK(w http.ResponseWriter, r *http.Request) { if "POST" != r.Method { http.Error(w, "Method Not Allowed", http.StatusMethodNotAllowed) return } opts, err := getOpts(r) if nil != err { http.Error(w, err.Error(), http.StatusBadRequest) return } privkey := genPrivKey(opts) jwk := xkeypairs.MarshalJWKPrivateKey(privkey) w.Write(append(jwk, '\n')) } // GeneratePublicDER will create a new private key in JWK format func GeneratePublicDER(w http.ResponseWriter, r *http.Request) { if "POST" != r.Method { http.Error(w, "Method Not Allowed", http.StatusMethodNotAllowed) return } opts, err := getOpts(r) if nil != err { http.Error(w, err.Error(), http.StatusBadRequest) return } privkey, err := getPrivKey(opts) if nil != err { http.Error(w, err.Error(), http.StatusBadRequest) return } b, _ := xkeypairs.MarshalDERPublicKey(privkey.Public()) w.Write(b) } // GeneratePrivateDER will create a new private key in a valid DER encoding func GeneratePrivateDER(w http.ResponseWriter, r *http.Request) { if "POST" != r.Method { http.Error(w, "Method Not Allowed", http.StatusMethodNotAllowed) return } opts, err := getOpts(r) if nil != err { http.Error(w, err.Error(), http.StatusBadRequest) return } privkey := genPrivKey(opts) der, _ := xkeypairs.MarshalDERPrivateKey(privkey) w.Write(der) } // GeneratePublicPEM will create a new private key in JWK format func GeneratePublicPEM(w http.ResponseWriter, r *http.Request) { if "POST" != r.Method { http.Error(w, "Method Not Allowed", http.StatusMethodNotAllowed) return } opts, err := getOpts(r) if nil != err { http.Error(w, err.Error(), http.StatusBadRequest) return } privkey, err := getPrivKey(opts) if nil != err { http.Error(w, err.Error(), http.StatusBadRequest) return } b, _ := xkeypairs.MarshalPEMPublicKey(privkey.Public()) w.Write(b) } // GeneratePrivatePEM will create a new private key in a valid PEM encoding func GeneratePrivatePEM(w http.ResponseWriter, r *http.Request) { if "POST" != r.Method { http.Error(w, "Method Not Allowed", http.StatusMethodNotAllowed) return } opts, err := getOpts(r) if nil != err { http.Error(w, err.Error(), http.StatusBadRequest) return } privkey := genPrivKey(opts) privpem, _ := xkeypairs.MarshalPEMPrivateKey(privkey) w.Write(privpem) } const maxRetry = 16 func getPrivKey(opts *options) (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 }