460 lines
11 KiB
Go
460 lines
11 KiB
Go
package keypairs
|
|
|
|
import (
|
|
"crypto"
|
|
"crypto/dsa"
|
|
"crypto/ecdsa"
|
|
"crypto/elliptic"
|
|
"crypto/rsa"
|
|
"crypto/sha256"
|
|
"crypto/x509"
|
|
"encoding/base64"
|
|
"encoding/json"
|
|
"encoding/pem"
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
"log"
|
|
"math/big"
|
|
"time"
|
|
)
|
|
|
|
var EInvalidPrivateKey = errors.New("PrivateKey must be of type *rsa.PrivateKey or *ecdsa.PrivateKey")
|
|
var EInvalidPublicKey = errors.New("PublicKey must be of type *rsa.PublicKey or *ecdsa.PublicKey")
|
|
var EParsePrivateKey = errors.New("PrivateKey bytes could not be parsed as PEM or DER (PKCS8, SEC1, or PKCS1) or JWK")
|
|
var EParseJWK = errors.New("JWK is missing required base64-encoded JSON fields")
|
|
var EInvalidKeyType = errors.New("The JWK's 'kty' must be either 'RSA' or 'EC'")
|
|
var EInvalidCurve = errors.New("The JWK's 'crv' must be either of the NIST standards 'P-256' or 'P-384'")
|
|
|
|
const EDevSwapPrivatePublic = "[Developer Error] You passed either crypto.PrivateKey or crypto.PublicKey where the other was expected."
|
|
|
|
const EDevBadKeyType = "[Developer Error] crypto.PublicKey and crypto.PrivateKey are somewhat deceptive. They're actually empty interfaces that accept any object, even non-crypto objects. You passed an object of type '%T' by mistake."
|
|
|
|
// PrivateKey is a zero-cost typesafe substitue for crypto.PrivateKey
|
|
type PrivateKey interface {
|
|
Public() crypto.PublicKey
|
|
}
|
|
|
|
// PublicKey thinly veils crypto.PublicKey for type safety
|
|
type PublicKey interface {
|
|
crypto.PublicKey
|
|
Thumbprint() string
|
|
Key() crypto.PublicKey
|
|
}
|
|
|
|
type ECPublicKey struct {
|
|
PublicKey *ecdsa.PublicKey // empty interface
|
|
KID string
|
|
Expiry time.Time
|
|
}
|
|
|
|
type RSAPublicKey struct {
|
|
PublicKey *rsa.PublicKey // empty interface
|
|
KID string
|
|
Expiry time.Time
|
|
}
|
|
|
|
func (p *ECPublicKey) Thumbprint() string {
|
|
return ThumbprintUntypedPublicKey(p.PublicKey)
|
|
}
|
|
func (p *ECPublicKey) Key() crypto.PublicKey {
|
|
return p.PublicKey
|
|
}
|
|
func (p *ECPublicKey) ExpireAt(t time.Time) {
|
|
p.Expiry = t
|
|
}
|
|
|
|
func (p *RSAPublicKey) Thumbprint() string {
|
|
return ThumbprintUntypedPublicKey(p.PublicKey)
|
|
}
|
|
func (p *RSAPublicKey) Key() crypto.PublicKey {
|
|
return p.PublicKey
|
|
}
|
|
func (p *RSAPublicKey) ExpireAt(t time.Time) {
|
|
p.Expiry = t
|
|
}
|
|
|
|
// TypesafePublicKey wraps a crypto.PublicKey to make it typesafe.
|
|
func NewPublicKey(pub crypto.PublicKey, kid ...string) PublicKey {
|
|
var k PublicKey
|
|
switch p := pub.(type) {
|
|
case *ecdsa.PublicKey:
|
|
eckey := &ECPublicKey{
|
|
PublicKey: p,
|
|
}
|
|
if 0 != len(kid) {
|
|
eckey.KID = kid[0]
|
|
} else {
|
|
eckey.KID = ThumbprintECPublicKey(p)
|
|
}
|
|
k = eckey
|
|
case *rsa.PublicKey:
|
|
rsakey := &RSAPublicKey{
|
|
PublicKey: p,
|
|
}
|
|
if 0 != len(kid) {
|
|
rsakey.KID = kid[0]
|
|
} else {
|
|
rsakey.KID = ThumbprintRSAPublicKey(p)
|
|
}
|
|
k = rsakey
|
|
case *ecdsa.PrivateKey:
|
|
panic(errors.New(EDevSwapPrivatePublic))
|
|
case *rsa.PrivateKey:
|
|
panic(errors.New(EDevSwapPrivatePublic))
|
|
case *dsa.PublicKey:
|
|
panic(EInvalidPublicKey)
|
|
case *dsa.PrivateKey:
|
|
panic(EInvalidPublicKey)
|
|
default:
|
|
panic(errors.New(fmt.Sprintf(EDevBadKeyType, pub)))
|
|
}
|
|
|
|
return k
|
|
}
|
|
|
|
func MarshalJWKPublicKey(key PublicKey, exp ...time.Time) []byte {
|
|
// thumbprint keys are alphabetically sorted and only include the necessary public parts
|
|
switch k := key.Key().(type) {
|
|
case *rsa.PublicKey:
|
|
return MarshalRSAPublicKey(k, exp...)
|
|
case *ecdsa.PublicKey:
|
|
return MarshalECPublicKey(k, exp...)
|
|
case *dsa.PublicKey:
|
|
panic(EInvalidPublicKey)
|
|
default:
|
|
// this is unreachable because we know the types that we pass in
|
|
log.Printf("keytype: %t, %+v\n", key, key)
|
|
panic(EInvalidPublicKey)
|
|
}
|
|
}
|
|
|
|
func ThumbprintPublicKey(pub *PublicKey) string {
|
|
return ThumbprintUntypedPublicKey(pub)
|
|
}
|
|
|
|
func ThumbprintUntypedPublicKey(pub crypto.PublicKey) string {
|
|
switch p := pub.(type) {
|
|
case *ecdsa.PublicKey:
|
|
return ThumbprintECPublicKey(p)
|
|
case *rsa.PublicKey:
|
|
return ThumbprintRSAPublicKey(p)
|
|
default:
|
|
panic(EInvalidPublicKey)
|
|
}
|
|
}
|
|
|
|
func MarshalECPublicKey(k *ecdsa.PublicKey, exp ...time.Time) []byte {
|
|
thumb := ThumbprintECPublicKey(k)
|
|
crv := k.Curve.Params().Name
|
|
x := base64.RawURLEncoding.EncodeToString(k.X.Bytes())
|
|
y := base64.RawURLEncoding.EncodeToString(k.Y.Bytes())
|
|
expstr := ""
|
|
if 0 != len(exp) {
|
|
expstr = fmt.Sprintf(`"exp":%q,`, exp[0].Format(time.RFC3339))
|
|
}
|
|
return []byte(fmt.Sprintf(`{"kid":%q,%s"crv":%q,"kty":"EC","x":%q,"y":%q}`, expstr, thumb, crv, x, y))
|
|
}
|
|
|
|
func MarshalECPublicKeyWithoutKeyID(k *ecdsa.PublicKey) []byte {
|
|
crv := k.Curve.Params().Name
|
|
x := base64.RawURLEncoding.EncodeToString(k.X.Bytes())
|
|
y := base64.RawURLEncoding.EncodeToString(k.Y.Bytes())
|
|
return []byte(fmt.Sprintf(`{"crv":%q,"kty":"EC","x":%q,"y":%q}`, crv, x, y))
|
|
}
|
|
|
|
func ThumbprintECPublicKey(k *ecdsa.PublicKey) string {
|
|
thumbprintable := MarshalECPublicKeyWithoutKeyID(k)
|
|
sha := sha256.Sum256(thumbprintable)
|
|
return base64.RawURLEncoding.EncodeToString(sha[:])
|
|
}
|
|
|
|
func MarshalRSAPublicKey(p *rsa.PublicKey, exp ...time.Time) []byte {
|
|
thumb := ThumbprintRSAPublicKey(p)
|
|
e := base64.RawURLEncoding.EncodeToString(big.NewInt(int64(p.E)).Bytes())
|
|
n := base64.RawURLEncoding.EncodeToString(p.N.Bytes())
|
|
expstr := ""
|
|
if 0 != len(exp) {
|
|
expstr = fmt.Sprintf(`"exp":%q,`, exp[0].Format(time.RFC3339))
|
|
}
|
|
return []byte(fmt.Sprintf(`{"kid":%q,%s"e":%q,"kty":"RSA","n":%q}`, expstr, thumb, e, n))
|
|
}
|
|
|
|
func MarshalRSAPublicKeyWithoutKeyID(p *rsa.PublicKey) []byte {
|
|
e := base64.RawURLEncoding.EncodeToString(big.NewInt(int64(p.E)).Bytes())
|
|
n := base64.RawURLEncoding.EncodeToString(p.N.Bytes())
|
|
return []byte(fmt.Sprintf(`{"e":%q,"kty":"RSA","n":%q}`, e, n))
|
|
}
|
|
|
|
func ThumbprintRSAPublicKey(p *rsa.PublicKey) string {
|
|
thumbprintable := MarshalRSAPublicKeyWithoutKeyID(p)
|
|
sha := sha256.Sum256([]byte(thumbprintable))
|
|
return base64.RawURLEncoding.EncodeToString(sha[:])
|
|
}
|
|
|
|
func ParsePrivateKey(block []byte) (PrivateKey, error) {
|
|
var pemblock *pem.Block
|
|
var blocks = make([][]byte, 0, 1)
|
|
|
|
// Parse the PEM, if it's a pem
|
|
for {
|
|
pemblock, block = pem.Decode(block)
|
|
if nil != pemblock {
|
|
// got one block, there may be more
|
|
blocks = append(blocks, pemblock.Bytes)
|
|
} else {
|
|
// the last block was not a PEM block
|
|
// therefore the next isn't either
|
|
if 0 != len(block) {
|
|
blocks = append(blocks, block)
|
|
}
|
|
break
|
|
}
|
|
}
|
|
|
|
// Parse PEM blocks (openssl generates junk metadata blocks for ECs)
|
|
// or the original DER, or the JWK
|
|
for i, _ := range blocks {
|
|
block = blocks[i]
|
|
if key, err := parsePrivateKey(block); nil == err {
|
|
return key, nil
|
|
}
|
|
}
|
|
|
|
// If we didn't parse a key arleady, we failed
|
|
return nil, EParsePrivateKey
|
|
}
|
|
|
|
func ParsePrivateKeyString(block string) (PrivateKey, error) {
|
|
return ParsePrivateKey([]byte(block))
|
|
}
|
|
|
|
func parsePrivateKey(der []byte) (PrivateKey, error) {
|
|
var key PrivateKey
|
|
|
|
//fmt.Println("1. ParsePKCS8PrivateKey")
|
|
xkey, err := x509.ParsePKCS8PrivateKey(der)
|
|
if nil == err {
|
|
switch k := xkey.(type) {
|
|
case *rsa.PrivateKey:
|
|
key = k
|
|
case *ecdsa.PrivateKey:
|
|
key = k
|
|
default:
|
|
// ignore nil and unknown key types
|
|
}
|
|
}
|
|
|
|
if nil != err {
|
|
//fmt.Println("2. ParseECPrivateKey")
|
|
key, err = x509.ParseECPrivateKey(der)
|
|
if nil != err {
|
|
//fmt.Println("3. ParsePKCS1PrivateKey")
|
|
key, err = x509.ParsePKCS1PrivateKey(der)
|
|
if nil != err {
|
|
//fmt.Println("4. ParseJWKPrivateKey")
|
|
key, err = ParseJWKPrivateKey(der)
|
|
}
|
|
}
|
|
}
|
|
|
|
// But did you know?
|
|
// You must return nil explicitly for interfaces
|
|
// https://golang.org/doc/faq#nil_error
|
|
if nil != err {
|
|
return nil, err
|
|
}
|
|
|
|
return key, nil
|
|
}
|
|
|
|
func NewJWKPublicKey(m map[string]string) (PublicKey, error) {
|
|
switch m["kty"] {
|
|
case "RSA":
|
|
return parseRSAPublicKey(m)
|
|
case "EC":
|
|
return parseECPublicKey(m)
|
|
default:
|
|
return nil, EInvalidKeyType
|
|
}
|
|
}
|
|
|
|
func ParseJWKPublicKey(b []byte) (PublicKey, error) {
|
|
return newJWKPublicKey(b)
|
|
}
|
|
|
|
func ParseJWKPublicKeyString(s string) (PublicKey, error) {
|
|
return newJWKPublicKey(s)
|
|
}
|
|
|
|
func DecodeJWKPublicKey(r io.Reader) (PublicKey, error) {
|
|
return newJWKPublicKey(r)
|
|
}
|
|
|
|
func newJWKPublicKey(data interface{}) (PublicKey, error) {
|
|
var m map[string]string
|
|
|
|
switch d := data.(type) {
|
|
case map[string]string:
|
|
m = d
|
|
case io.Reader:
|
|
m = make(map[string]string)
|
|
if err := json.NewDecoder(d).Decode(&m); nil != err {
|
|
return nil, err
|
|
}
|
|
case string:
|
|
if err := json.Unmarshal([]byte(d), &m); nil != err {
|
|
return nil, err
|
|
}
|
|
case []byte:
|
|
if err := json.Unmarshal(d, &m); nil != err {
|
|
return nil, err
|
|
}
|
|
default:
|
|
panic("Developer Error: unsupported interface type")
|
|
}
|
|
|
|
return NewJWKPublicKey(m)
|
|
}
|
|
|
|
func ParseJWKPrivateKey(b []byte) (PrivateKey, error) {
|
|
var m map[string]string
|
|
if err := json.Unmarshal(b, &m); nil != err {
|
|
return nil, err
|
|
}
|
|
|
|
switch m["kty"] {
|
|
case "RSA":
|
|
return parseRSAPrivateKey(m)
|
|
case "EC":
|
|
return parseECPrivateKey(m)
|
|
default:
|
|
return nil, EInvalidKeyType
|
|
}
|
|
}
|
|
|
|
func parseRSAPublicKey(m map[string]string) (*RSAPublicKey, error) {
|
|
// TODO grab expiry?
|
|
kid, _ := m["kid"]
|
|
n, _ := base64.RawURLEncoding.DecodeString(m["n"])
|
|
e, _ := base64.RawURLEncoding.DecodeString(m["e"])
|
|
if 0 == len(n) || 0 == len(e) {
|
|
return nil, EParseJWK
|
|
}
|
|
ni := &big.Int{}
|
|
ni.SetBytes(n)
|
|
ei := &big.Int{}
|
|
ei.SetBytes(e)
|
|
|
|
pub := &rsa.PublicKey{
|
|
N: ni,
|
|
E: int(ei.Int64()),
|
|
}
|
|
|
|
return &RSAPublicKey{
|
|
PublicKey: pub,
|
|
KID: kid,
|
|
}, nil
|
|
}
|
|
|
|
func parseRSAPrivateKey(m map[string]string) (key *rsa.PrivateKey, err error) {
|
|
pub, err := parseRSAPublicKey(m)
|
|
if nil != err {
|
|
return
|
|
}
|
|
|
|
d, _ := base64.RawURLEncoding.DecodeString(m["d"])
|
|
p, _ := base64.RawURLEncoding.DecodeString(m["p"])
|
|
q, _ := base64.RawURLEncoding.DecodeString(m["q"])
|
|
dp, _ := base64.RawURLEncoding.DecodeString(m["dp"])
|
|
dq, _ := base64.RawURLEncoding.DecodeString(m["dq"])
|
|
qinv, _ := base64.RawURLEncoding.DecodeString(m["qi"])
|
|
if 0 == len(d) || 0 == len(p) || 0 == len(dp) || 0 == len(dq) || 0 == len(qinv) {
|
|
return nil, EParseJWK
|
|
}
|
|
|
|
di := &big.Int{}
|
|
di.SetBytes(d)
|
|
pi := &big.Int{}
|
|
pi.SetBytes(p)
|
|
qi := &big.Int{}
|
|
qi.SetBytes(q)
|
|
dpi := &big.Int{}
|
|
dpi.SetBytes(dp)
|
|
dqi := &big.Int{}
|
|
dqi.SetBytes(dq)
|
|
qinvi := &big.Int{}
|
|
qinvi.SetBytes(qinv)
|
|
|
|
key = &rsa.PrivateKey{
|
|
PublicKey: *pub.PublicKey,
|
|
D: di,
|
|
Primes: []*big.Int{pi, qi},
|
|
Precomputed: rsa.PrecomputedValues{
|
|
Dp: dpi,
|
|
Dq: dqi,
|
|
Qinv: qinvi,
|
|
},
|
|
}
|
|
|
|
return
|
|
}
|
|
|
|
func parseECPublicKey(m map[string]string) (*ECPublicKey, error) {
|
|
// TODO grab expiry?
|
|
kid, _ := m["kid"]
|
|
x, _ := base64.RawURLEncoding.DecodeString(m["x"])
|
|
y, _ := base64.RawURLEncoding.DecodeString(m["y"])
|
|
if 0 == len(x) || 0 == len(y) || 0 == len(m["crv"]) {
|
|
return nil, EParseJWK
|
|
}
|
|
|
|
xi := &big.Int{}
|
|
xi.SetBytes(x)
|
|
|
|
yi := &big.Int{}
|
|
yi.SetBytes(y)
|
|
|
|
var crv elliptic.Curve
|
|
switch m["crv"] {
|
|
case "P-256":
|
|
crv = elliptic.P256()
|
|
case "P-384":
|
|
crv = elliptic.P384()
|
|
case "P-521":
|
|
crv = elliptic.P521()
|
|
default:
|
|
return nil, EInvalidCurve
|
|
}
|
|
|
|
pub := &ecdsa.PublicKey{
|
|
Curve: crv,
|
|
X: xi,
|
|
Y: yi,
|
|
}
|
|
|
|
return &ECPublicKey{
|
|
PublicKey: pub,
|
|
KID: kid,
|
|
}, nil
|
|
}
|
|
|
|
func parseECPrivateKey(m map[string]string) (*ecdsa.PrivateKey, error) {
|
|
pub, err := parseECPublicKey(m)
|
|
if nil != err {
|
|
return nil, err
|
|
}
|
|
|
|
d, _ := base64.RawURLEncoding.DecodeString(m["d"])
|
|
if 0 == len(d) {
|
|
return nil, EParseJWK
|
|
}
|
|
di := &big.Int{}
|
|
di.SetBytes(d)
|
|
|
|
return &ecdsa.PrivateKey{
|
|
PublicKey: *pub.PublicKey,
|
|
D: di,
|
|
}, nil
|
|
}
|