add CLI to generate and marshal keypair
This commit is contained in:
parent
db6a30b358
commit
f46e11257b
|
@ -0,0 +1,81 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"flag"
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"git.rootprojects.org/root/keypairs"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
if 1 == len(os.Args) || "gen" != os.Args[1] {
|
||||||
|
fmt.Fprintln(os.Stderr, "Usage: keypairs gen -o <filename> [--pub <filename>]")
|
||||||
|
os.Exit(1)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var privname string
|
||||||
|
var pubname string
|
||||||
|
flag.StringVar(&privname, "o", "", "private key file (should have .jwk.json or pkcs8.pem extension)")
|
||||||
|
flag.StringVar(&pubname, "pub", "", "public key file (should have .jwk.json or spki.pem extension)")
|
||||||
|
flag.Parse()
|
||||||
|
|
||||||
|
priv := keypairs.NewDefaultPrivateKey()
|
||||||
|
marshalPriv(priv, privname)
|
||||||
|
marshalPub(keypairs.NewPublicKey(priv.Public()), pubname)
|
||||||
|
}
|
||||||
|
|
||||||
|
func marshalPriv(priv keypairs.PrivateKey, privname string) {
|
||||||
|
if "" == privname {
|
||||||
|
b := indentJSON(keypairs.MarshalJWKPrivateKey(priv))
|
||||||
|
|
||||||
|
fmt.Fprintf(os.Stdout, string(b)+"\n")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var b []byte
|
||||||
|
if strings.HasSuffix(privname, ".json") {
|
||||||
|
b = indentJSON(keypairs.MarshalJWKPrivateKey(priv))
|
||||||
|
} else if strings.HasSuffix(privname, ".pem") {
|
||||||
|
b, _ = keypairs.MarshalPEMPrivateKey(priv)
|
||||||
|
} else if strings.HasSuffix(privname, ".der") {
|
||||||
|
b, _ = keypairs.MarshalDERPrivateKey(priv)
|
||||||
|
} else {
|
||||||
|
fmt.Fprintf(os.Stderr, "private key extension should be .jwk.json, .pem, or .der")
|
||||||
|
os.Exit(1)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ioutil.WriteFile(privname, b, 0600)
|
||||||
|
}
|
||||||
|
|
||||||
|
func marshalPub(pub keypairs.PublicKey, pubname string) {
|
||||||
|
var b []byte
|
||||||
|
if "" == pubname {
|
||||||
|
b = indentJSON(keypairs.MarshalJWKPublicKey(pub))
|
||||||
|
|
||||||
|
fmt.Fprintf(os.Stderr, string(b)+"\n")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if strings.HasSuffix(pubname, ".json") {
|
||||||
|
b = indentJSON(keypairs.MarshalJWKPublicKey(pub))
|
||||||
|
} else if strings.HasSuffix(pubname, ".pem") {
|
||||||
|
b, _ = keypairs.MarshalPEMPublicKey(pub)
|
||||||
|
} else if strings.HasSuffix(pubname, ".der") {
|
||||||
|
b, _ = keypairs.MarshalDERPublicKey(pub)
|
||||||
|
}
|
||||||
|
|
||||||
|
ioutil.WriteFile(pubname, b, 0644)
|
||||||
|
}
|
||||||
|
|
||||||
|
func indentJSON(b []byte) []byte {
|
||||||
|
m := map[string]interface{}{}
|
||||||
|
_ = json.Unmarshal(b, &m)
|
||||||
|
b, _ = json.MarshalIndent(&m, "", " ")
|
||||||
|
return b
|
||||||
|
}
|
|
@ -0,0 +1,83 @@
|
||||||
|
package keypairs
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/ecdsa"
|
||||||
|
"crypto/elliptic"
|
||||||
|
"crypto/rand"
|
||||||
|
"crypto/rsa"
|
||||||
|
"io"
|
||||||
|
mathrand "math/rand"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
var randReader io.Reader = rand.Reader
|
||||||
|
var maxRetry = 1
|
||||||
|
|
||||||
|
// 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 {
|
||||||
|
return randReader
|
||||||
|
/*
|
||||||
|
if 0 == o.Seed {
|
||||||
|
return randReader
|
||||||
|
}
|
||||||
|
return mathrand.New(mathrand.NewSource(o.Seed))
|
||||||
|
*/
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewDefaultPrivateKey generates a key with reasonable strength.
|
||||||
|
// Today that means a 256-bit equivalent - either RSA 2048 or EC P-256.
|
||||||
|
func NewDefaultPrivateKey() PrivateKey {
|
||||||
|
mathrand.Seed(time.Now().UnixNano())
|
||||||
|
coin := mathrand.Int()
|
||||||
|
if 0 == coin%2 {
|
||||||
|
return newPrivateKey(&keyOptions{
|
||||||
|
KeyType: "RSA",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return newPrivateKey(&keyOptions{
|
||||||
|
KeyType: "EC",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// newPrivateKey generates a 256-bit entropy RSA or ECDSA private key
|
||||||
|
func newPrivateKey(opts *keyOptions) PrivateKey {
|
||||||
|
var privkey 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
|
||||||
|
}
|
|
@ -0,0 +1,171 @@
|
||||||
|
package keypairs
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto"
|
||||||
|
"crypto/ecdsa"
|
||||||
|
"crypto/rsa"
|
||||||
|
"crypto/x509"
|
||||||
|
"encoding/base64"
|
||||||
|
"encoding/pem"
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"math/big"
|
||||||
|
mathrand "math/rand"
|
||||||
|
)
|
||||||
|
|
||||||
|
// MarshalPEMPublicKey outputs the given public key as JWK
|
||||||
|
func MarshalPEMPublicKey(pubkey crypto.PublicKey) ([]byte, error) {
|
||||||
|
block, err := marshalDERPublicKey(pubkey)
|
||||||
|
if nil != err {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return pem.EncodeToMemory(block), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// MarshalDERPublicKey outputs the given public key as JWK
|
||||||
|
func MarshalDERPublicKey(pubkey crypto.PublicKey) ([]byte, error) {
|
||||||
|
block, err := marshalDERPublicKey(pubkey)
|
||||||
|
if nil != err {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return block.Bytes, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// marshalDERPublicKey outputs the given public key as JWK
|
||||||
|
func marshalDERPublicKey(pubkey crypto.PublicKey) (*pem.Block, error) {
|
||||||
|
|
||||||
|
var der []byte
|
||||||
|
var typ string
|
||||||
|
var err error
|
||||||
|
switch k := pubkey.(type) {
|
||||||
|
case *rsa.PublicKey:
|
||||||
|
der = x509.MarshalPKCS1PublicKey(k)
|
||||||
|
typ = "RSA PUBLIC KEY"
|
||||||
|
case *ecdsa.PublicKey:
|
||||||
|
typ = "PUBLIC KEY"
|
||||||
|
der, err = x509.MarshalPKIXPublicKey(k)
|
||||||
|
if nil != err {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
panic("Developer Error: impossible key type")
|
||||||
|
}
|
||||||
|
|
||||||
|
return &pem.Block{
|
||||||
|
Bytes: der,
|
||||||
|
Type: typ,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// MarshalJWKPrivateKey outputs the given private key as JWK
|
||||||
|
func MarshalJWKPrivateKey(privkey PrivateKey) []byte {
|
||||||
|
// thumbprint keys are alphabetically sorted and only include the necessary public parts
|
||||||
|
switch k := privkey.(type) {
|
||||||
|
case *rsa.PrivateKey:
|
||||||
|
return MarshalRSAPrivateKey(k)
|
||||||
|
case *ecdsa.PrivateKey:
|
||||||
|
return MarshalECPrivateKey(k)
|
||||||
|
default:
|
||||||
|
// this is unreachable because we know the types that we pass in
|
||||||
|
log.Printf("keytype: %t, %+v\n", privkey, privkey)
|
||||||
|
panic(ErrInvalidPublicKey)
|
||||||
|
//return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MarshalDERPrivateKey outputs the given private key as ASN.1 DER
|
||||||
|
func MarshalDERPrivateKey(privkey PrivateKey) ([]byte, error) {
|
||||||
|
// thumbprint keys are alphabetically sorted and only include the necessary public parts
|
||||||
|
switch k := privkey.(type) {
|
||||||
|
case *rsa.PrivateKey:
|
||||||
|
return x509.MarshalPKCS1PrivateKey(k), nil
|
||||||
|
case *ecdsa.PrivateKey:
|
||||||
|
return x509.MarshalECPrivateKey(k)
|
||||||
|
default:
|
||||||
|
// this is unreachable because we know the types that we pass in
|
||||||
|
log.Printf("keytype: %t, %+v\n", privkey, privkey)
|
||||||
|
panic(ErrInvalidPublicKey)
|
||||||
|
//return nil, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func marshalDERPrivateKey(privkey PrivateKey) (*pem.Block, error) {
|
||||||
|
var typ string
|
||||||
|
var bytes []byte
|
||||||
|
var err error
|
||||||
|
|
||||||
|
switch k := privkey.(type) {
|
||||||
|
case *rsa.PrivateKey:
|
||||||
|
if 0 == mathrand.Intn(2) {
|
||||||
|
typ = "PRIVATE KEY"
|
||||||
|
bytes, err = x509.MarshalPKCS8PrivateKey(k)
|
||||||
|
if nil != err {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
typ = "RSA PRIVATE KEY"
|
||||||
|
bytes = x509.MarshalPKCS1PrivateKey(k)
|
||||||
|
}
|
||||||
|
return &pem.Block{
|
||||||
|
Type: typ,
|
||||||
|
Bytes: bytes,
|
||||||
|
}, nil
|
||||||
|
case *ecdsa.PrivateKey:
|
||||||
|
if 0 == mathrand.Intn(2) {
|
||||||
|
typ = "PRIVATE KEY"
|
||||||
|
bytes, err = x509.MarshalPKCS8PrivateKey(k)
|
||||||
|
} else {
|
||||||
|
typ = "EC PRIVATE KEY"
|
||||||
|
bytes, err = x509.MarshalECPrivateKey(k)
|
||||||
|
}
|
||||||
|
if nil != err {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &pem.Block{
|
||||||
|
Type: typ,
|
||||||
|
Bytes: bytes,
|
||||||
|
}, nil
|
||||||
|
default:
|
||||||
|
// this is unreachable because we know the types that we pass in
|
||||||
|
log.Printf("keytype: %t, %+v\n", privkey, privkey)
|
||||||
|
panic(ErrInvalidPublicKey)
|
||||||
|
//return nil, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MarshalPEMPrivateKey outputs the given private key as ASN.1 PEM
|
||||||
|
func MarshalPEMPrivateKey(privkey PrivateKey) ([]byte, error) {
|
||||||
|
block, err := marshalDERPrivateKey(privkey)
|
||||||
|
if nil != err {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return pem.EncodeToMemory(block), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// MarshalECPrivateKey will output the given private key as JWK
|
||||||
|
func MarshalECPrivateKey(k *ecdsa.PrivateKey) []byte {
|
||||||
|
crv := k.Curve.Params().Name
|
||||||
|
d := base64.RawURLEncoding.EncodeToString(k.D.Bytes())
|
||||||
|
x := base64.RawURLEncoding.EncodeToString(k.X.Bytes())
|
||||||
|
y := base64.RawURLEncoding.EncodeToString(k.Y.Bytes())
|
||||||
|
return []byte(fmt.Sprintf(
|
||||||
|
`{"crv":%q,"d":%q,"kty":"EC","x":%q,"y":%q}`,
|
||||||
|
crv, d, x, y,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
// MarshalRSAPrivateKey will output the given private key as JWK
|
||||||
|
func MarshalRSAPrivateKey(pk *rsa.PrivateKey) []byte {
|
||||||
|
e := base64.RawURLEncoding.EncodeToString(big.NewInt(int64(pk.E)).Bytes())
|
||||||
|
n := base64.RawURLEncoding.EncodeToString(pk.N.Bytes())
|
||||||
|
d := base64.RawURLEncoding.EncodeToString(pk.D.Bytes())
|
||||||
|
p := base64.RawURLEncoding.EncodeToString(pk.Primes[0].Bytes())
|
||||||
|
q := base64.RawURLEncoding.EncodeToString(pk.Primes[1].Bytes())
|
||||||
|
dp := base64.RawURLEncoding.EncodeToString(pk.Precomputed.Dp.Bytes())
|
||||||
|
dq := base64.RawURLEncoding.EncodeToString(pk.Precomputed.Dq.Bytes())
|
||||||
|
qi := base64.RawURLEncoding.EncodeToString(pk.Precomputed.Qinv.Bytes())
|
||||||
|
return []byte(fmt.Sprintf(
|
||||||
|
`{"d":%q,"dp":%q,"dq":%q,"e":%q,"kty":"RSA","n":%q,"p":%q,"q":%q,"qi":%q}`,
|
||||||
|
d, dp, dq, e, n, p, q, qi,
|
||||||
|
))
|
||||||
|
}
|
Loading…
Reference in New Issue