-
Notifications
You must be signed in to change notification settings - Fork 1
/
key.go
298 lines (250 loc) · 8.94 KB
/
key.go
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
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
// SPDX-FileCopyrightText: 2020 Google LLC
// SPDX-License-Identifier: Apache-2.0
package piv
import (
"crypto"
"crypto/ecdh"
"crypto/ecdsa"
"crypto/ed25519"
"crypto/rsa"
"encoding/asn1"
"errors"
"fmt"
"cunicu.li/go-iso7816/encoding/tlv"
)
var (
// errMismatchingAlgorithms is returned when a cryptographic operation
// is given keys using different algorithms.
errMismatchingAlgorithms = errors.New("mismatching key algorithms")
// errUnsupportedKeySize is returned when a key has an unsupported size
errUnsupportedKeySize = errors.New("unsupported key size")
errInvalidPKCS1Padding = errors.New("invalid PKCS#1 v1.5 padding")
errInvalidSerialNumber = errors.New("invalid serial number")
errMissingPIN = errors.New("pin required but wasn't provided")
errParseCert = errors.New("failed to parse certificate")
errUnexpectedLength = errors.New("unexpected length")
errUnmarshal = errors.New("failed to unmarshal")
errUnsupportedAlgorithm = errors.New("unsupported algorithm")
errUnsupportedHashAlgorithm = errors.New("unsupported hash algorithm")
errUnsupportedPinPolicy = errors.New("unsupported pin policy")
errUnsupportedTouchPolicy = errors.New("unsupported touch policy")
errUnsupportedKeyType = errors.New("unsupported key type")
errUnsupportedOrigin = errors.New("unsupported origin")
)
// UnsupportedCurveError is used when a key has an unsupported curve
type UnsupportedCurveError struct {
curve int
}
func (e UnsupportedCurveError) Error() string {
return fmt.Sprintf("unsupported curve: %d", e.curve)
}
// Slot is a private key and certificate combination managed by the security key.
type Slot struct {
// Key is a reference for a key type.
//
// See: https://nvlpubs.nist.gov/nistpubs/SpecialPublications/NIST.SP.800-73-4.pdf#page=32
Key byte
// Object is a reference for data object.
//
// See: https://nvlpubs.nist.gov/nistpubs/SpecialPublications/NIST.SP.800-73-4.pdf#page=30
Object Object
}
//nolint:gochecknoglobals
var (
extIDFirmwareVersion = asn1.ObjectIdentifier([]int{1, 3, 6, 1, 4, 1, 41482, 3, 3})
extIDSerialNumber = asn1.ObjectIdentifier([]int{1, 3, 6, 1, 4, 1, 41482, 3, 7})
extIDKeyPolicy = asn1.ObjectIdentifier([]int{1, 3, 6, 1, 4, 1, 41482, 3, 8})
extIDFormFactor = asn1.ObjectIdentifier([]int{1, 3, 6, 1, 4, 1, 41482, 3, 9})
)
// Key is used for key generation and holds different options for the key.
//
// While keys can have default PIN and touch policies, this package currently
// doesn't support this option, and all fields must be provided.
type Key struct {
// Algorithm to use when generating the key.
Algorithm Algorithm
// PINPolicy for the key.
//
// BUG(ericchiang): some older YubiKeys (third generation) will silently
// drop this value. If PINPolicyNever or PINPolicyOnce is supplied but the
// key still requires a PIN every time, you may be using a buggy key and
// should supply PINPolicyAlways. See https://cunicu.li/go-piv/issues/60
PINPolicy PINPolicy
// TouchPolicy for the key.
TouchPolicy TouchPolicy
}
// GenerateKey generates an asymmetric key on the card, returning the key's
// public key.
func (c *Card) GenerateKey(key ManagementKey, slot Slot, opts Key) (crypto.PublicKey, error) {
if err := c.authenticate(key); err != nil {
return nil, fmt.Errorf("failed to authenticate with management key: %w", err)
}
tp, ok := touchPolicyMap[opts.TouchPolicy]
if !ok {
return nil, errUnsupportedTouchPolicy
}
pp, ok := pinPolicyMap[opts.PINPolicy]
if !ok {
return nil, errUnsupportedPinPolicy
}
// https://nvlpubs.nist.gov/nistpubs/SpecialPublications/NIST.SP.800-73-4.pdf#page=95
resp, err := sendTLV(c.tx, insGenerateAsymmetric, 0, slot.Key,
tlv.New(0xac,
tlv.New(tagAlg, byte(opts.Algorithm)),
tlv.New(tagPINPolicy, pp),
tlv.New(tagTouchPolicy, tp),
),
)
if err != nil {
return nil, fmt.Errorf("failed to execute command: %w", err)
}
// https://nvlpubs.nist.gov/nistpubs/SpecialPublications/NIST.SP.800-73-4.pdf#page=95
pub, _, ok := resp.Get(0x7f49)
if !ok {
return nil, fmt.Errorf("failed to unmarshal response: %w", err)
}
return decodePublic(pub, opts.Algorithm)
}
func decodePublic(b []byte, alg Algorithm) (pub crypto.PublicKey, err error) {
tvs, err := tlv.DecodeBER(b)
if err != nil {
return nil, err
}
switch alg {
case AlgRSA1024, AlgRSA2048:
if pub, err = decodeRSAPublic(tvs); err != nil {
return nil, fmt.Errorf("failed to decode RSA public key: %w", err)
}
case AlgECCP256:
if pub, err = decodeECDSAPublic(tvs, ecdh.P256()); err != nil {
return nil, fmt.Errorf("failed to decode P256 public key: %w", err)
}
case AlgECCP384:
if pub, err = decodeECDSAPublic(tvs, ecdh.P384()); err != nil {
return nil, fmt.Errorf("failed to decode P384 public key: %w", err)
}
case AlgEd25519:
if pub, err = decodeEd25519Public(tvs); err != nil {
return nil, fmt.Errorf("failed to decode Ed25519 public key: %w", err)
}
default:
return nil, errUnsupportedAlgorithm
}
return pub, nil
}
// PrivateKey is used to access signing and decryption options for the key
// stored in the slot. The returned key implements crypto.Signer and/or
// crypto.Decrypter depending on the key type.
//
// If the public key hasn't been stored externally, it can be provided by
// fetching the slot's attestation certificate:
//
// cert, err := c.Attest(slot)
// if err != nil {
// // ...
// }
// priv, err := c.PrivateKey(slot, cert.PublicKey, auth)
func (c *Card) PrivateKey(slot Slot, public crypto.PublicKey, auth KeyAuth) (crypto.PrivateKey, error) {
pp := PINPolicyNever
if _, ok := pinPolicyMap[auth.PINPolicy]; ok {
// If the PIN policy is manually specified, trust that value instead of
// trying to use the attestation certificate.
pp = auth.PINPolicy
} else if auth.PIN != "" || auth.PINPrompt != nil {
// Attempt to determine the key's PIN policy. This helps inform the
// strategy for when to prompt for a PIN.
policy, err := pinPolicy(c, slot)
if err != nil {
return nil, err
}
pp = policy
}
switch pub := public.(type) {
case *ecdsa.PublicKey:
return &ECPPPrivateKey{c, slot, pub, auth, pp}, nil
case ed25519.PublicKey:
return &keyEd25519{c, slot, pub, auth, pp}, nil
case *rsa.PublicKey:
return &keyRSA{c, slot, pub, auth, pp}, nil
default:
return nil, fmt.Errorf("%w: %T", errUnsupportedKeyType, public)
}
}
// SetPrivateKeyInsecure is an insecure method which imports a private key into the slot.
// Users should almost always use GeneratePrivateKey() instead.
//
// Importing a private key breaks functionality provided by this package, including
// AttestationCertificate() and Attest(). There are no stability guarantees for other
// methods for imported private keys.
//
// Keys generated outside of the YubiKey should not be considered hardware-backed,
// as there's no way to prove the key wasn't copied, exfiltrated, or replaced with malicious
// material before being imported.
func (c *Card) SetPrivateKeyInsecure(key ManagementKey, slot Slot, private crypto.PrivateKey, policy Key) error {
// Reference implementation
// https://github.com/Yubico/yubico-piv-tool/blob/671a5740ef09d6c5d9d33f6e5575450750b58bde/lib/ykpiv.c#L1812
if err := c.authenticate(key); err != nil {
return fmt.Errorf("failed to authenticate with management key: %w", err)
}
tp, ok := touchPolicyMap[policy.TouchPolicy]
if !ok {
return errUnsupportedTouchPolicy
}
pp, ok := pinPolicyMap[policy.PINPolicy]
if !ok {
return errUnsupportedPinPolicy
}
tvs := tlv.TagValues{
tlv.New(tagPINPolicy, pp),
tlv.New(tagTouchPolicy, tp),
}
pad := func(l int, b []byte) (k []byte) {
k = make([]byte, l)
p := len(k) - len(b)
copy(k[p:], b)
return k
}
var elemLen int
switch priv := private.(type) {
case *rsa.PrivateKey:
switch priv.N.BitLen() {
case 1024:
policy.Algorithm = AlgRSA1024
elemLen = 64
case 2048:
policy.Algorithm = AlgRSA2048
elemLen = 128
default:
return errUnsupportedKeySize
}
priv.Precompute()
tvs = append(tvs,
tlv.New(0x01, pad(elemLen, priv.Primes[0].Bytes())), // P
tlv.New(0x02, pad(elemLen, priv.Primes[1].Bytes())), // Q
tlv.New(0x03, pad(elemLen, priv.Precomputed.Dp.Bytes())), // dP
tlv.New(0x04, pad(elemLen, priv.Precomputed.Dq.Bytes())), // dQ
tlv.New(0x05, pad(elemLen, priv.Precomputed.Qinv.Bytes())), // Qinv
)
case *ecdsa.PrivateKey:
size := priv.PublicKey.Params().BitSize
switch size {
case 256:
policy.Algorithm = AlgECCP256
elemLen = 32
case 384:
policy.Algorithm = AlgECCP384
elemLen = 48
default:
return UnsupportedCurveError{curve: size}
}
tvs = append(tvs, tlv.New(0x06, pad(elemLen, priv.D.Bytes()))) // S value
default:
return errUnsupportedKeyType
}
// This command is a Yubico PIV extension.
// https://developers.yubico.com/PIV/Introduction/Yubico_extensions.html
if _, err := sendTLV(c.tx, insImportKey, byte(policy.Algorithm), slot.Key, tvs...); err != nil {
return fmt.Errorf("failed to execute command: %w", err)
}
return nil
}