1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204
|
package jose
import (
"crypto"
"crypto/ecdsa"
"crypto/ed25519"
"crypto/rsa"
"crypto/x509"
"encoding/base64"
"github.com/pkg/errors"
"go.step.sm/crypto/keyutil"
"go.step.sm/crypto/pemutil"
"go.step.sm/crypto/x25519"
)
const (
jwksUsageSig = "sig"
jwksUsageEnc = "enc"
// defaultKeyType is the default type of the one-time token key.
defaultKeyType = EC
// defaultKeyCurve is the default curve of the one-time token key.
defaultKeyCurve = P256
// defaultKeyAlg is the default algorithm of the one-time token key.
defaultKeyAlg = ES256
// defaultKeySize is the default size of the one-time token key.
defaultKeySize = 0
)
var (
errAmbiguousCertKeyUsage = errors.New("jose/generate: certificate's key usage is ambiguous, it should be for signature or encipherment, but not both (use --subtle to ignore usage field)")
errNoCertKeyUsage = errors.New("jose/generate: certificate doesn't contain any key usage (use --subtle to ignore usage field)")
)
// Thumbprint computes the JWK Thumbprint of a key using SHA256 as the hash
// algorithm. It returns the hash encoded in the Base64 raw url encoding.
func Thumbprint(jwk *JSONWebKey) (string, error) {
var sum []byte
var err error
switch key := jwk.Key.(type) {
case x25519.PublicKey:
sum, err = x25519Thumbprint(key, crypto.SHA256)
case x25519.PrivateKey:
var pub x25519.PublicKey
if pub, err = key.PublicKey(); err == nil {
sum, err = x25519Thumbprint(pub, crypto.SHA256)
}
case OpaqueSigner:
sum, err = key.Public().Thumbprint(crypto.SHA256)
default:
sum, err = jwk.Thumbprint(crypto.SHA256)
}
if err != nil {
return "", errors.Wrap(err, "error generating JWK thumbprint")
}
return base64.RawURLEncoding.EncodeToString(sum), nil
}
// GenerateDefaultKeyPair generates an asymmetric public/private key pair.
// Returns the public key as a JWK and the private key as an encrypted JWE.
func GenerateDefaultKeyPair(passphrase []byte) (*JSONWebKey, *JSONWebEncryption, error) {
if len(passphrase) == 0 {
return nil, nil, errors.New("step-jose: password cannot be empty when encryptying a JWK")
}
// Generate the OTT key
jwk, err := GenerateJWK(defaultKeyType, defaultKeyCurve, defaultKeyAlg, jwksUsageSig, "", defaultKeySize)
if err != nil {
return nil, nil, err
}
jwk.KeyID, err = Thumbprint(jwk)
if err != nil {
return nil, nil, err
}
jwe, err := EncryptJWK(jwk, passphrase)
if err != nil {
return nil, nil, err
}
public := jwk.Public()
return &public, jwe, nil
}
// GenerateJWK generates a JWK given the key type, curve, alg, use, kid and
// the size of the RSA or oct keys if necessary.
func GenerateJWK(kty, crv, alg, use, kid string, size int) (jwk *JSONWebKey, err error) {
if kty == "OKP" && use == "enc" && (crv == "" || crv == "Ed25519") {
return nil, errors.New("invalid algorithm: Ed25519 cannot be used for encryption")
}
switch {
case kty == "EC" && crv == "":
crv = P256
case kty == "OKP" && crv == "":
crv = Ed25519
case kty == "RSA" && size == 0:
size = DefaultRSASize
case kty == "oct" && size == 0:
size = DefaultOctSize
}
key, err := keyutil.GenerateKey(kty, crv, size)
if err != nil {
return nil, err
}
jwk = &JSONWebKey{
Key: key,
KeyID: kid,
Use: use,
Algorithm: alg,
}
guessJWKAlgorithm(&context{alg: alg}, jwk)
if jwk.KeyID == "" && kty != "oct" {
jwk.KeyID, err = Thumbprint(jwk)
}
return jwk, err
}
// GenerateJWKFromPEM returns an incomplete JSONWebKey using the key from a
// PEM file.
func GenerateJWKFromPEM(filename string, subtle bool) (*JSONWebKey, error) {
key, err := pemutil.Read(filename)
if err != nil {
return nil, err
}
switch key := key.(type) {
case *rsa.PrivateKey, *rsa.PublicKey:
return &JSONWebKey{
Key: key,
}, nil
case *ecdsa.PrivateKey, *ecdsa.PublicKey, ed25519.PrivateKey, ed25519.PublicKey:
return &JSONWebKey{
Key: key,
Algorithm: algForKey(key),
}, nil
case *x509.Certificate:
var use string
if !subtle {
use, err = keyUsageForCert(key)
if err != nil {
return nil, err
}
}
return &JSONWebKey{
Key: key.PublicKey,
Certificates: []*x509.Certificate{key},
Algorithm: algForKey(key.PublicKey),
Use: use,
}, nil
default:
return nil, errors.Errorf("error parsing %s: unsupported key type '%T'", filename, key)
}
}
func algForKey(key crypto.PublicKey) string {
switch key := key.(type) {
case *ecdsa.PrivateKey:
return getECAlgorithm(key.Curve)
case *ecdsa.PublicKey:
return getECAlgorithm(key.Curve)
case ed25519.PrivateKey, ed25519.PublicKey:
return EdDSA
default:
return ""
}
}
func keyUsageForCert(cert *x509.Certificate) (string, error) {
isDigitalSignature := containsUsage(cert.KeyUsage,
x509.KeyUsageDigitalSignature,
x509.KeyUsageContentCommitment,
x509.KeyUsageCertSign,
x509.KeyUsageCRLSign,
)
isEncipherment := containsUsage(cert.KeyUsage,
x509.KeyUsageKeyEncipherment,
x509.KeyUsageDataEncipherment,
x509.KeyUsageKeyAgreement,
x509.KeyUsageEncipherOnly,
x509.KeyUsageDecipherOnly,
)
if isDigitalSignature && isEncipherment {
return "", errAmbiguousCertKeyUsage
}
if isDigitalSignature {
return jwksUsageSig, nil
}
if isEncipherment {
return jwksUsageEnc, nil
}
return "", errNoCertKeyUsage
}
func containsUsage(usage x509.KeyUsage, queries ...x509.KeyUsage) bool {
for _, query := range queries {
if usage&query == query {
return true
}
}
return false
}
|