File: generate.go

package info (click to toggle)
golang-github-smallstep-crypto 0.63.0-2
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 3,800 kB
  • sloc: sh: 66; makefile: 50
file content (204 lines) | stat: -rw-r--r-- 5,457 bytes parent folder | download | duplicates (2)
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
}