New upstream snapshot.
Debian Janitor
1 year, 6 months ago
0 | 0 | name: test |
1 | 1 | on: |
2 | push: | |
3 | branches: | |
4 | - master | |
2 | 5 | pull_request: |
3 | 6 | branches: |
4 | 7 | - master |
5 | 8 | |
6 | 9 | jobs: |
7 | 10 | build: |
11 | strategy: | |
12 | matrix: | |
13 | go-version: [1.18.x, 1.19.x] | |
8 | 14 | name: Linux |
9 | 15 | runs-on: ubuntu-latest |
10 | 16 | steps: |
11 | - name: Set up Go 1.14 | |
17 | - name: Set up Go | |
12 | 18 | uses: actions/setup-go@v2 |
13 | 19 | with: |
14 | go-version: '^1.14.2' | |
20 | go-version: ${{ matrix.go-version }} | |
15 | 21 | id: go |
16 | 22 | - name: Check out code into the Go module directory |
17 | 23 | uses: actions/checkout@v2 |
18 | - name: Install golint | |
19 | run: go get -u golang.org/x/lint/golint | |
24 | - name: Install staticcheck | |
25 | run: go install honnef.co/go/tools/cmd/staticcheck@v0.3.3 | |
20 | 26 | - name: Install libpcsc |
21 | 27 | run: sudo apt-get install -y libpcsclite-dev pcscd pcsc-tools |
22 | 28 | - name: Test |
23 | 29 | run: "make test" |
24 | 30 | build-windows: |
31 | strategy: | |
32 | matrix: | |
33 | go-version: [1.18.x, 1.19.x] | |
25 | 34 | name: Windows |
26 | 35 | runs-on: windows-latest |
27 | 36 | steps: |
28 | - name: Set up Go 1.14 | |
37 | - name: Set up Go | |
29 | 38 | uses: actions/setup-go@v2 |
30 | 39 | with: |
31 | go-version: '^1.14.2' | |
40 | go-version: ${{ matrix.go-version }} | |
32 | 41 | id: go |
33 | 42 | - name: Check out code into the Go module directory |
34 | 43 | uses: actions/checkout@v2 |
35 | - name: Install golint | |
36 | run: go get -u golang.org/x/lint/golint | |
44 | - name: Install staticcheck | |
45 | run: go install honnef.co/go/tools/cmd/staticcheck@v0.3.3 | |
37 | 46 | - name: Test |
38 | 47 | run: "make build" |
39 | 48 | env: |
3 | 3 | |
4 | 4 | .PHONY: lint |
5 | 5 | lint: |
6 | golint -set_exit_status ./... | |
6 | staticcheck ./... | |
7 | 7 | |
8 | 8 | .PHONY: build |
9 | 9 | build: lint |
120 | 120 | fmt.Println("Credentials set. Your PIN is: %s", newPIN) |
121 | 121 | ``` |
122 | 122 | |
123 | The user can user the PIN later to fetch the management key: | |
123 | The user can use the PIN later to fetch the management key: | |
124 | 124 | |
125 | 125 | ```go |
126 | 126 | m, err := yk.Metadata(pin) |
0 | golang-github-go-piv-piv-go (1.10.0+git20220804.1.1902689-1) UNRELEASED; urgency=low | |
1 | ||
2 | * New upstream snapshot. | |
3 | ||
4 | -- Debian Janitor <janitor@jelmer.uk> Fri, 21 Oct 2022 21:53:34 -0000 | |
5 | ||
0 | 6 | golang-github-go-piv-piv-go (1.8.0-3) unstable; urgency=medium |
1 | 7 | |
2 | 8 | [ Debian Janitor ] |
75 | 75 | } |
76 | 76 | |
77 | 77 | // Formfactor enumerates the physical set of forms a key can take. USB-A vs. |
78 | // USB-C and Keychain vs. Nano. | |
78 | // USB-C and Keychain vs. Nano (and FIPS variants for these). | |
79 | 79 | type Formfactor int |
80 | 80 | |
81 | // Formfactors recognized by this package. | |
81 | // The mapping between known Formfactor values and their descriptions. | |
82 | var formFactorStrings = map[Formfactor]string{ | |
83 | FormfactorUSBAKeychain: "USB-A Keychain", | |
84 | FormfactorUSBANano: "USB-A Nano", | |
85 | FormfactorUSBCKeychain: "USB-C Keychain", | |
86 | FormfactorUSBCNano: "USB-C Nano", | |
87 | FormfactorUSBCLightningKeychain: "USB-C/Lightning Keychain", | |
88 | ||
89 | FormfactorUSBAKeychainFIPS: "USB-A Keychain FIPS", | |
90 | FormfactorUSBANanoFIPS: "USB-A Nano FIPS", | |
91 | FormfactorUSBCKeychainFIPS: "USB-C Keychain FIPS", | |
92 | FormfactorUSBCNanoFIPS: "USB-C Nano FIPS", | |
93 | FormfactorUSBCLightningKeychainFIPS: "USB-C/Lightning Keychain FIPS", | |
94 | } | |
95 | ||
96 | // String returns the human-readable description for the given form-factor | |
97 | // value, or a fallback value for any other, unknown form-factor. | |
98 | func (f Formfactor) String() string { | |
99 | if s, ok := formFactorStrings[f]; ok { | |
100 | return s | |
101 | } | |
102 | return fmt.Sprintf("unknown(0x%02x)", int(f)) | |
103 | } | |
104 | ||
105 | // Formfactors recognized by this package. See the reference for more information: | |
106 | // https://developers.yubico.com/yubikey-manager/Config_Reference.html#_form_factor | |
82 | 107 | const ( |
83 | FormfactorUSBAKeychain = iota + 1 | |
84 | FormfactorUSBANano | |
85 | FormfactorUSBCKeychain | |
86 | FormfactorUSBCNano | |
87 | FormfactorUSBCLightningKeychain | |
108 | FormfactorUSBAKeychain = 0x1 | |
109 | FormfactorUSBANano = 0x2 | |
110 | FormfactorUSBCKeychain = 0x3 | |
111 | FormfactorUSBCNano = 0x4 | |
112 | FormfactorUSBCLightningKeychain = 0x5 | |
113 | ||
114 | FormfactorUSBAKeychainFIPS = 0x81 | |
115 | FormfactorUSBANanoFIPS = 0x82 | |
116 | FormfactorUSBCKeychainFIPS = 0x83 | |
117 | FormfactorUSBCNanoFIPS = 0x84 | |
118 | FormfactorUSBCLightningKeychainFIPS = 0x85 | |
88 | 119 | ) |
89 | 120 | |
90 | 121 | // Prefix in the x509 Subject Common Name for YubiKey attestations |
162 | 193 | if len(e.Value) != 1 { |
163 | 194 | return fmt.Errorf("expected 1 byte from formfactor, got: %d", len(e.Value)) |
164 | 195 | } |
165 | switch e.Value[0] { | |
166 | case 0x01: | |
167 | a.Formfactor = FormfactorUSBAKeychain | |
168 | case 0x02: | |
169 | a.Formfactor = FormfactorUSBANano | |
170 | case 0x03: | |
171 | a.Formfactor = FormfactorUSBCKeychain | |
172 | case 0x04: | |
173 | a.Formfactor = FormfactorUSBCNano | |
174 | case 0x05: | |
175 | a.Formfactor = FormfactorUSBCLightningKeychain | |
176 | default: | |
177 | return fmt.Errorf("unrecognized formfactor: 0x%x", e.Value[0]) | |
178 | } | |
196 | a.Formfactor = Formfactor(e.Value[0]) | |
179 | 197 | } |
180 | 198 | return nil |
181 | } | |
182 | ||
183 | func verifySignature(parent, c *x509.Certificate) error { | |
184 | return parent.CheckSignature(c.SignatureAlgorithm, c.RawTBSCertificate, c.Signature) | |
185 | 199 | } |
186 | 200 | |
187 | 201 | // Verify proves that a key was generated on a YubiKey. It ensures the slot and |
193 | 207 | } |
194 | 208 | |
195 | 209 | type verifier struct { |
196 | Root *x509.Certificate | |
210 | Roots *x509.CertPool | |
197 | 211 | } |
198 | 212 | |
199 | 213 | func (v *verifier) Verify(attestationCert, slotCert *x509.Certificate) (*Attestation, error) { |
200 | root := v.Root | |
201 | if root == nil { | |
202 | ca, err := yubicoCA() | |
214 | o := x509.VerifyOptions{KeyUsages: []x509.ExtKeyUsage{x509.ExtKeyUsageAny}} | |
215 | o.Roots = v.Roots | |
216 | if o.Roots == nil { | |
217 | cas, err := yubicoCAs() | |
203 | 218 | if err != nil { |
204 | return nil, fmt.Errorf("parsing yubico ca: %v", err) | |
205 | } | |
206 | root = ca | |
207 | } | |
208 | if err := verifySignature(root, attestationCert); err != nil { | |
209 | return nil, fmt.Errorf("attestation certifcate not signed by : %v", err) | |
210 | } | |
211 | if err := verifySignature(attestationCert, slotCert); err != nil { | |
212 | return nil, fmt.Errorf("slot certificate not signed by attestation certifcate: %v", err) | |
219 | return nil, fmt.Errorf("failed to load yubico CAs: %v", err) | |
220 | } | |
221 | o.Roots = cas | |
222 | } | |
223 | ||
224 | o.Intermediates = x509.NewCertPool() | |
225 | ||
226 | // The attestation cert in some yubikey 4 does not encode X509v3 Basic Constraints. | |
227 | // This isn't valid as per https://datatracker.ietf.org/doc/html/rfc5280#section-4.2.1.9 | |
228 | // (fourth paragraph) and thus makes x509.go validation fail. | |
229 | // Work around this by setting this constraint here. | |
230 | if !attestationCert.BasicConstraintsValid { | |
231 | attestationCert.BasicConstraintsValid = true | |
232 | attestationCert.IsCA = true | |
233 | } | |
234 | ||
235 | o.Intermediates.AddCert(attestationCert) | |
236 | ||
237 | _, err := slotCert.Verify(o) | |
238 | if err != nil { | |
239 | return nil, fmt.Errorf("error verifying attestation certificate: %v", err) | |
213 | 240 | } |
214 | 241 | return parseAttestation(slotCert) |
215 | 242 | } |
255 | 282 | return RetiredKeyManagementSlot(uint32(key)) |
256 | 283 | } |
257 | 284 | |
258 | // yubicoPIVCAPEM is the PEM encoded attestation certificate used by Yubico. | |
285 | // yubicoPIVCAPEMAfter2018 is the PEM encoded attestation certificate used by Yubico. | |
259 | 286 | // |
260 | 287 | // https://developers.yubico.com/PIV/Introduction/PIV_attestation.html |
261 | const yubicoPIVCAPEM = `-----BEGIN CERTIFICATE----- | |
288 | const yubicoPIVCAPEMAfter2018 = `-----BEGIN CERTIFICATE----- | |
262 | 289 | MIIDFzCCAf+gAwIBAgIDBAZHMA0GCSqGSIb3DQEBCwUAMCsxKTAnBgNVBAMMIFl1 |
263 | 290 | YmljbyBQSVYgUm9vdCBDQSBTZXJpYWwgMjYzNzUxMCAXDTE2MDMxNDAwMDAwMFoY |
264 | 291 | DzIwNTIwNDE3MDAwMDAwWjArMSkwJwYDVQQDDCBZdWJpY28gUElWIFJvb3QgQ0Eg |
278 | 305 | SREzU8onbBsjMg9QDiSf5oJLKvd/Ren+zGY7 |
279 | 306 | -----END CERTIFICATE-----` |
280 | 307 | |
281 | func yubicoCA() (*x509.Certificate, error) { | |
282 | b, _ := pem.Decode([]byte(yubicoPIVCAPEM)) | |
283 | if b == nil { | |
308 | // Yubikeys manufactured sometime in 2018 and prior to mid-2017 | |
309 | // were certified using the U2F root CA with serial number 457200631 | |
310 | // See https://github.com/Yubico/developers.yubico.com/pull/392/commits/a58f1003f003e04fc9baf09cad9f64f0c284fd47 | |
311 | // Cert available at https://developers.yubico.com/U2F/yubico-u2f-ca-certs.txt | |
312 | const yubicoPIVCAPEMU2F = `-----BEGIN CERTIFICATE----- | |
313 | MIIDHjCCAgagAwIBAgIEG0BT9zANBgkqhkiG9w0BAQsFADAuMSwwKgYDVQQDEyNZ | |
314 | dWJpY28gVTJGIFJvb3QgQ0EgU2VyaWFsIDQ1NzIwMDYzMTAgFw0xNDA4MDEwMDAw | |
315 | MDBaGA8yMDUwMDkwNDAwMDAwMFowLjEsMCoGA1UEAxMjWXViaWNvIFUyRiBSb290 | |
316 | IENBIFNlcmlhbCA0NTcyMDA2MzEwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEK | |
317 | AoIBAQC/jwYuhBVlqaiYWEMsrWFisgJ+PtM91eSrpI4TK7U53mwCIawSDHy8vUmk | |
318 | 5N2KAj9abvT9NP5SMS1hQi3usxoYGonXQgfO6ZXyUA9a+KAkqdFnBnlyugSeCOep | |
319 | 8EdZFfsaRFtMjkwz5Gcz2Py4vIYvCdMHPtwaz0bVuzneueIEz6TnQjE63Rdt2zbw | |
320 | nebwTG5ZybeWSwbzy+BJ34ZHcUhPAY89yJQXuE0IzMZFcEBbPNRbWECRKgjq//qT | |
321 | 9nmDOFVlSRCt2wiqPSzluwn+v+suQEBsUjTGMEd25tKXXTkNW21wIWbxeSyUoTXw | |
322 | LvGS6xlwQSgNpk2qXYwf8iXg7VWZAgMBAAGjQjBAMB0GA1UdDgQWBBQgIvz0bNGJ | |
323 | hjgpToksyKpP9xv9oDAPBgNVHRMECDAGAQH/AgEAMA4GA1UdDwEB/wQEAwIBBjAN | |
324 | BgkqhkiG9w0BAQsFAAOCAQEAjvjuOMDSa+JXFCLyBKsycXtBVZsJ4Ue3LbaEsPY4 | |
325 | MYN/hIQ5ZM5p7EjfcnMG4CtYkNsfNHc0AhBLdq45rnT87q/6O3vUEtNMafbhU6kt | |
326 | hX7Y+9XFN9NpmYxr+ekVY5xOxi8h9JDIgoMP4VB1uS0aunL1IGqrNooL9mmFnL2k | |
327 | LVVee6/VR6C5+KSTCMCWppMuJIZII2v9o4dkoZ8Y7QRjQlLfYzd3qGtKbw7xaF1U | |
328 | sG/5xUb/Btwb2X2g4InpiB/yt/3CpQXpiWX/K4mBvUKiGn05ZsqeY1gx4g0xLBqc | |
329 | U9psmyPzK+Vsgw2jeRQ5JlKDyqE0hebfC1tvFu0CCrJFcw== | |
330 | -----END CERTIFICATE-----` | |
331 | ||
332 | func yubicoCAs() (*x509.CertPool, error) { | |
333 | certPool := x509.NewCertPool() | |
334 | ||
335 | if !certPool.AppendCertsFromPEM([]byte(yubicoPIVCAPEMAfter2018)) { | |
336 | return nil, fmt.Errorf("failed to parse yubico cert") | |
337 | } | |
338 | ||
339 | bU2F, _ := pem.Decode([]byte(yubicoPIVCAPEMU2F)) | |
340 | if bU2F == nil { | |
284 | 341 | return nil, fmt.Errorf("failed to decode yubico pem data") |
285 | 342 | } |
286 | return x509.ParseCertificate(b.Bytes) | |
343 | ||
344 | certU2F, err := x509.ParseCertificate(bU2F.Bytes) | |
345 | if err != nil { | |
346 | return nil, fmt.Errorf("failed to parse yubico cert: %v", err) | |
347 | } | |
348 | ||
349 | // The U2F root cert has pathlen x509 basic constraint set to 0. | |
350 | // As per RFC 5280 this means that no intermediate cert is allowed | |
351 | // in the validation path. This isn't really helpful since we do | |
352 | // want to use the device attestation cert as intermediate cert in | |
353 | // the chain. To make this work, set pathlen of the U2F root to 1. | |
354 | // | |
355 | // See https://datatracker.ietf.org/doc/html/rfc5280#section-4.2.1.9 | |
356 | certU2F.MaxPathLen = 1 | |
357 | certPool.AddCert(certU2F) | |
358 | ||
359 | return certPool, nil | |
287 | 360 | } |
288 | 361 | |
289 | 362 | // Slot combinations pre-defined by this package. |
669 | 742 | PINPolicy PINPolicy |
670 | 743 | } |
671 | 744 | |
672 | func isAuthErr(err error) bool { | |
673 | var e *apduErr | |
674 | if !errors.As(err, &e) { | |
675 | return false | |
676 | } | |
677 | return e.sw1 == 0x69 && e.sw2 == 0x82 // "security status not satisfied" | |
678 | } | |
679 | ||
680 | 745 | func (k KeyAuth) authTx(yk *YubiKey, pp PINPolicy) error { |
681 | 746 | // PINPolicyNever shouldn't require a PIN. |
682 | 747 | if pp == PINPolicyNever { |
24 | 24 | "crypto/x509" |
25 | 25 | "crypto/x509/pkix" |
26 | 26 | "encoding/asn1" |
27 | "encoding/pem" | |
27 | 28 | "errors" |
28 | 29 | "math/big" |
29 | 30 | "testing" |
765 | 766 | t.Fatalf("decrypting data: %v", err) |
766 | 767 | } |
767 | 768 | |
768 | if bytes.Compare(data, decrypted) != 0 { | |
769 | if !bytes.Equal(data, decrypted) { | |
769 | 770 | t.Fatalf("decrypted data is different to the source data") |
770 | 771 | } |
771 | 772 | }) |
895 | 896 | }) |
896 | 897 | } |
897 | 898 | } |
899 | ||
900 | func TestVerify(t *testing.T) { | |
901 | tests := []struct { | |
902 | name string | |
903 | deviceCert string | |
904 | keyCert string | |
905 | ok bool | |
906 | }{ | |
907 | { | |
908 | // Valid attestation chain from a recent YubiKey. | |
909 | name: "ValidChain", | |
910 | deviceCert: "-----BEGIN CERTIFICATE-----\nMIIC+jCCAeKgAwIBAgIJAKs/UIpBjg1uMA0GCSqGSIb3DQEBCwUAMCsxKTAnBgNV\nBAMMIFl1YmljbyBQSVYgUm9vdCBDQSBTZXJpYWwgMjYzNzUxMCAXDTE2MDMxNDAw\nMDAwMFoYDzIwNTIwNDE3MDAwMDAwWjAhMR8wHQYDVQQDDBZZdWJpY28gUElWIEF0\ndGVzdGF0aW9uMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA0zdJWGnk\naLE8Rb+TP7iSffhJV9SJEp2Me4QcfVidgHqyIdo0lruBk69RF1nrmS3i+G1yyUh/\nymAPZkcQCpms0E23Dmhue1VRpBedcsVtO/xSrfu0qAWTslp/k57ry6vkidrQU1cx\nl2KodH3KTmnZmaskQD8eGtxXwcmLOmhKem6GSqhN/3QznaDhZmVUAvUKSOaIzOxn\n2u1mDHhGwaHhR7dklsDwN7oni4WWX1GJXtzpB8j6JhoqyqXwSbq+ck54PfzUoOFd\n/2yKyFRDXnQvzbNL7+afbxBQQMxxo1e24DNE/cp+K09eT7Gh1Urao6meaSssN4aV\nFfmkhC2NapGKMQIDAQABoykwJzARBgorBgEEAYLECgMDBAMFBAMwEgYDVR0TAQH/\nBAgwBgEB/wIBADANBgkqhkiG9w0BAQsFAAOCAQEAJfOLOQYGyIMQ5y+sDkYz+e6G\nH8BqqiYL9VOC3U3KQX9mrtZnaIexqJOCQyCFOSvaTFJvOfNiCCKQuLbmS+Qn4znd\nnSitCsdJSFKskQP7hbXqUK01epb6iTuuko4w3V57YVudnniZBD2s4XoNcJ6BFizZ\n3iXQqRMaLVfFHS9Qx0iLZLcR2s29nIl6NI/qFdIgkyo07J5cPnBiD6wxQft8FdfR\nbgx9yrrjY0mvj/k5LRN6lab8lTolgI5luJtKNueq96LVkTkAzcCaJPQ9YQ4cxeU9\nOapsEeOk6xf5bRPtdf0WhEKthXywt9D0pSHhAI+fpLNe/VtlZpt3hn9aTbqSug==\n-----END CERTIFICATE-----\n", | |
911 | keyCert: "-----BEGIN CERTIFICATE-----\nMIICVTCCAT2gAwIBAgIQAU4Yg7Qnw9FZgMBEaJ7ZMzANBgkqhkiG9w0BAQsFADAh\nMR8wHQYDVQQDDBZZdWJpY28gUElWIEF0dGVzdGF0aW9uMCAXDTE2MDMxNDAwMDAw\nMFoYDzIwNTIwNDE3MDAwMDAwWjAlMSMwIQYDVQQDDBpZdWJpS2V5IFBJViBBdHRl\nc3RhdGlvbiA5YTBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABATzM3sJuwemL2Ha\nHkGIzmCVjUMreNIVrRLOvnbZjoVflk1eab/iLUlKzk/2jXTu9TISRg2dhyXcutct\nvnqr66yjTjBMMBEGCisGAQQBgsQKAwMEAwUEAzAUBgorBgEEAYLECgMHBAYCBADw\nDxQwEAYKKwYBBAGCxAoDCAQCAgEwDwYKKwYBBAGCxAoDCQQBBDANBgkqhkiG9w0B\nAQsFAAOCAQEAFX0hL5gi/g4ZM7vCH5kDAtma7eBp0LpbCzR313GGyBR7pJFtuj2l\nbWU+V3SFRihXBTDb8q+uvyCBqgz1szdZzrpfjqNkhEPfPNabxjxJxVoe6Gdcn115\naduxfqqT2u+YIsERzaIIIisehLQkc/5zLkpocA6jbKBZnZWUBJIxuz4QmYTIf0O4\nHPE2o4JbAyGx/hRaqVvDgNeAz94ZFjb4Mp3RNbbdRUZB0ehrT/IGRJoHRu2HKFGM\nylRJL2kjKPoEc4XHbCu+MfmAIrQ4Xseg85zyI7ThhYvAzktdLHhQyfYr4wrrLCN3\noeTzmiqIHe9AataJXQ+mEQEEc9TNY23RFg==\n-----END CERTIFICATE-----\n", | |
912 | ok: true, | |
913 | }, | |
914 | { | |
915 | // Valid attestation chain from a yubikey manufactured in 2018 showing a manufacture bug (device certified using U2F root, and device cert does not encode X509 basic constraints). | |
916 | name: "ValidChain2018", | |
917 | deviceCert: "-----BEGIN CERTIFICATE-----\nMIIC6TCCAdGgAwIBAgIJALvwZFDESwMlMA0GCSqGSIb3DQEBCwUAMC4xLDAqBgNV\nBAMTI1l1YmljbyBVMkYgUm9vdCBDQSBTZXJpYWwgNDU3MjAwNjMxMCAXDTE0MDgw\nMTAwMDAwMFoYDzIwNTAwOTA0MDAwMDAwWjAhMR8wHQYDVQQDDBZZdWJpY28gUElW\nIEF0dGVzdGF0aW9uMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAqXnZ\n+lxX0nNzy3jn+lrZ+1cHTVUNYVKPqGTjvRw/7XOEnInWC1VCPJqwHYtnnoH4EIXN\n7kDGXwInfs9pwyjpgQw/V23yywFtUhaR8Xgw8zqC/YfJpeK4PetJ9/k+xFbICuX7\nWDv/k5Wth3VZSaVjm/tunWajtt3OLOQQaMSoLqP41XAHHuCyzfCwJ2Vsa2FyCINF\nyG6XobokeICDRnH44POqudcLVIDvZLQqu2LF+mZd+OO5nqmTa68kkwRf/m93eOJP\no7GvYtQSp7CPJC7ks2gl8U7wuT9DQT5/0wqkoEyLZg/KLUlzgXjMa+7GtCLTC1Ku\nOh9vw02f4K44RW4nWwIDAQABoxUwEzARBgorBgEEAYLECgMDBAMEAwcwDQYJKoZI\nhvcNAQELBQADggEBAHD/uXqNgCYywj2ee7s7kix2TT4XN9OIn0fTNh5LEiUN+q7U\nzJc9q7b5WD7PfaG6UNyuaSnLaq+dLOCJ4bX4h+/MwQSndQg0epMra1ThVQZkMkGa\nktAJ5JT6j9qxNxD1RWMl91e4JwtGzFyDwFyyUGnSwhMsqMdwfBsmTpvgxmAD/NMs\nkWB/m91FV9D+UBqsZRoLoc44kEFYBZ09ypTsR699oJRsBfG0AqVYyK7rnG6663fF\nGUSWk7noVdUPXedlwXCqCymCsVheoss9qF1cffaFIl9RxGvVvCFybx0LGiYDxfgv\n80yGZIY/mAqZVDWyHZSs4f6kWK9GeLKU2Y9yby4=\n-----END CERTIFICATE-----\n", | |
918 | keyCert: "-----BEGIN CERTIFICATE-----\nMIICLzCCARegAwIBAgIRAIxiihk4fSKK6keqJYujvnkwDQYJKoZIhvcNAQELBQAw\nITEfMB0GA1UEAwwWWXViaWNvIFBJViBBdHRlc3RhdGlvbjAgFw0xNDA4MDEwMDAw\nMDBaGA8yMDUwMDkwNDAwMDAwMFowJTEjMCEGA1UEAwwaWXViaUtleSBQSVYgQXR0\nZXN0YXRpb24gOWEwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAATHEzJsrhTHuvsx\n685AiWsAuT8Poe/zQfDRZNfpUSzJ31v6MZ9nz70pNrdd/sbG7O1UA6ceWhq1jHTU\n96Dnp99voycwJTARBgorBgEEAYLECgMDBAMEAwcwEAYKKwYBBAGCxAoDCAQCAgEw\nDQYJKoZIhvcNAQELBQADggEBADoswZ1LJ5GYVNgtRE0+zMQkAzam8YqeKmIDHtir\nvolIpGtJHzgCG2SdJlR/KnjRWF/1i8TRMhQ0O/KgkIEh+IyhJtD7DojgWvIBsCnX\nJXF7EPQMy17l7/9940QSOnQRIDb+z0eq9ACAjC3FWzqeR5VgN4C1QpCw7gKgqLTs\npmmDHHg4HsKl0PsPwim0bYIqEHttrLjPQiPnoa3qixzNKbwJjXb4/f/dvCTx9dRP\n0FVABj5Yh8f728xzrzw2nLZ9X/c0GoXfKu9s7lGNLcZ5OO+zys1ATei2h/PFJLDH\nAdrenw31WOYRtdjcNBKyAk80ajryjTAX3GXfbKpkdVB9hEo=\n-----END CERTIFICATE-----\n", | |
919 | ok: true, | |
920 | }, | |
921 | { | |
922 | // Invalid attestation chain. Device cert from yubikey A, key cert from yubikey B. | |
923 | name: "InvalidChain", | |
924 | deviceCert: "-----BEGIN CERTIFICATE-----\nMIIC+jCCAeKgAwIBAgIJAKs/UIpBjg1uMA0GCSqGSIb3DQEBCwUAMCsxKTAnBgNV\nBAMMIFl1YmljbyBQSVYgUm9vdCBDQSBTZXJpYWwgMjYzNzUxMCAXDTE2MDMxNDAw\nMDAwMFoYDzIwNTIwNDE3MDAwMDAwWjAhMR8wHQYDVQQDDBZZdWJpY28gUElWIEF0\ndGVzdGF0aW9uMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA0zdJWGnk\naLE8Rb+TP7iSffhJV9SJEp2Me4QcfVidgHqyIdo0lruBk69RF1nrmS3i+G1yyUh/\nymAPZkcQCpms0E23Dmhue1VRpBedcsVtO/xSrfu0qAWTslp/k57ry6vkidrQU1cx\nl2KodH3KTmnZmaskQD8eGtxXwcmLOmhKem6GSqhN/3QznaDhZmVUAvUKSOaIzOxn\n2u1mDHhGwaHhR7dklsDwN7oni4WWX1GJXtzpB8j6JhoqyqXwSbq+ck54PfzUoOFd\n/2yKyFRDXnQvzbNL7+afbxBQQMxxo1e24DNE/cp+K09eT7Gh1Urao6meaSssN4aV\nFfmkhC2NapGKMQIDAQABoykwJzARBgorBgEEAYLECgMDBAMFBAMwEgYDVR0TAQH/\nBAgwBgEB/wIBADANBgkqhkiG9w0BAQsFAAOCAQEAJfOLOQYGyIMQ5y+sDkYz+e6G\nH8BqqiYL9VOC3U3KQX9mrtZnaIexqJOCQyCFOSvaTFJvOfNiCCKQuLbmS+Qn4znd\nnSitCsdJSFKskQP7hbXqUK01epb6iTuuko4w3V57YVudnniZBD2s4XoNcJ6BFizZ\n3iXQqRMaLVfFHS9Qx0iLZLcR2s29nIl6NI/qFdIgkyo07J5cPnBiD6wxQft8FdfR\nbgx9yrrjY0mvj/k5LRN6lab8lTolgI5luJtKNueq96LVkTkAzcCaJPQ9YQ4cxeU9\nOapsEeOk6xf5bRPtdf0WhEKthXywt9D0pSHhAI+fpLNe/VtlZpt3hn9aTbqSug==\n-----END CERTIFICATE-----\n", | |
925 | keyCert: "-----BEGIN CERTIFICATE-----\nMIICLzCCARegAwIBAgIRAIxiihk4fSKK6keqJYujvnkwDQYJKoZIhvcNAQELBQAw\nITEfMB0GA1UEAwwWWXViaWNvIFBJViBBdHRlc3RhdGlvbjAgFw0xNDA4MDEwMDAw\nMDBaGA8yMDUwMDkwNDAwMDAwMFowJTEjMCEGA1UEAwwaWXViaUtleSBQSVYgQXR0\nZXN0YXRpb24gOWEwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAATHEzJsrhTHuvsx\n685AiWsAuT8Poe/zQfDRZNfpUSzJ31v6MZ9nz70pNrdd/sbG7O1UA6ceWhq1jHTU\n96Dnp99voycwJTARBgorBgEEAYLECgMDBAMEAwcwEAYKKwYBBAGCxAoDCAQCAgEw\nDQYJKoZIhvcNAQELBQADggEBADoswZ1LJ5GYVNgtRE0+zMQkAzam8YqeKmIDHtir\nvolIpGtJHzgCG2SdJlR/KnjRWF/1i8TRMhQ0O/KgkIEh+IyhJtD7DojgWvIBsCnX\nJXF7EPQMy17l7/9940QSOnQRIDb+z0eq9ACAjC3FWzqeR5VgN4C1QpCw7gKgqLTs\npmmDHHg4HsKl0PsPwim0bYIqEHttrLjPQiPnoa3qixzNKbwJjXb4/f/dvCTx9dRP\n0FVABj5Yh8f728xzrzw2nLZ9X/c0GoXfKu9s7lGNLcZ5OO+zys1ATei2h/PFJLDH\nAdrenw31WOYRtdjcNBKyAk80ajryjTAX3GXfbKpkdVB9hEo=\n-----END CERTIFICATE-----\n", | |
926 | ok: false, | |
927 | }, | |
928 | { | |
929 | // Invalid attestation chain. Device cert from yubikey B, key cert from yubikey A. | |
930 | name: "InvalidChain2", | |
931 | deviceCert: "-----BEGIN CERTIFICATE-----\nMIIC6TCCAdGgAwIBAgIJALvwZFDESwMlMA0GCSqGSIb3DQEBCwUAMC4xLDAqBgNV\nBAMTI1l1YmljbyBVMkYgUm9vdCBDQSBTZXJpYWwgNDU3MjAwNjMxMCAXDTE0MDgw\nMTAwMDAwMFoYDzIwNTAwOTA0MDAwMDAwWjAhMR8wHQYDVQQDDBZZdWJpY28gUElW\nIEF0dGVzdGF0aW9uMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAqXnZ\n+lxX0nNzy3jn+lrZ+1cHTVUNYVKPqGTjvRw/7XOEnInWC1VCPJqwHYtnnoH4EIXN\n7kDGXwInfs9pwyjpgQw/V23yywFtUhaR8Xgw8zqC/YfJpeK4PetJ9/k+xFbICuX7\nWDv/k5Wth3VZSaVjm/tunWajtt3OLOQQaMSoLqP41XAHHuCyzfCwJ2Vsa2FyCINF\nyG6XobokeICDRnH44POqudcLVIDvZLQqu2LF+mZd+OO5nqmTa68kkwRf/m93eOJP\no7GvYtQSp7CPJC7ks2gl8U7wuT9DQT5/0wqkoEyLZg/KLUlzgXjMa+7GtCLTC1Ku\nOh9vw02f4K44RW4nWwIDAQABoxUwEzARBgorBgEEAYLECgMDBAMEAwcwDQYJKoZI\nhvcNAQELBQADggEBAHD/uXqNgCYywj2ee7s7kix2TT4XN9OIn0fTNh5LEiUN+q7U\nzJc9q7b5WD7PfaG6UNyuaSnLaq+dLOCJ4bX4h+/MwQSndQg0epMra1ThVQZkMkGa\nktAJ5JT6j9qxNxD1RWMl91e4JwtGzFyDwFyyUGnSwhMsqMdwfBsmTpvgxmAD/NMs\nkWB/m91FV9D+UBqsZRoLoc44kEFYBZ09ypTsR699oJRsBfG0AqVYyK7rnG6663fF\nGUSWk7noVdUPXedlwXCqCymCsVheoss9qF1cffaFIl9RxGvVvCFybx0LGiYDxfgv\n80yGZIY/mAqZVDWyHZSs4f6kWK9GeLKU2Y9yby4=\n-----END CERTIFICATE-----\n", | |
932 | keyCert: "-----BEGIN CERTIFICATE-----\nMIICVTCCAT2gAwIBAgIQAU4Yg7Qnw9FZgMBEaJ7ZMzANBgkqhkiG9w0BAQsFADAh\nMR8wHQYDVQQDDBZZdWJpY28gUElWIEF0dGVzdGF0aW9uMCAXDTE2MDMxNDAwMDAw\nMFoYDzIwNTIwNDE3MDAwMDAwWjAlMSMwIQYDVQQDDBpZdWJpS2V5IFBJViBBdHRl\nc3RhdGlvbiA5YTBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABATzM3sJuwemL2Ha\nHkGIzmCVjUMreNIVrRLOvnbZjoVflk1eab/iLUlKzk/2jXTu9TISRg2dhyXcutct\nvnqr66yjTjBMMBEGCisGAQQBgsQKAwMEAwUEAzAUBgorBgEEAYLECgMHBAYCBADw\nDxQwEAYKKwYBBAGCxAoDCAQCAgEwDwYKKwYBBAGCxAoDCQQBBDANBgkqhkiG9w0B\nAQsFAAOCAQEAFX0hL5gi/g4ZM7vCH5kDAtma7eBp0LpbCzR313GGyBR7pJFtuj2l\nbWU+V3SFRihXBTDb8q+uvyCBqgz1szdZzrpfjqNkhEPfPNabxjxJxVoe6Gdcn115\naduxfqqT2u+YIsERzaIIIisehLQkc/5zLkpocA6jbKBZnZWUBJIxuz4QmYTIf0O4\nHPE2o4JbAyGx/hRaqVvDgNeAz94ZFjb4Mp3RNbbdRUZB0ehrT/IGRJoHRu2HKFGM\nylRJL2kjKPoEc4XHbCu+MfmAIrQ4Xseg85zyI7ThhYvAzktdLHhQyfYr4wrrLCN3\noeTzmiqIHe9AataJXQ+mEQEEc9TNY23RFg==\n-----END CERTIFICATE-----\n", | |
933 | ok: false, | |
934 | }, | |
935 | } | |
936 | ||
937 | parseCert := func(cert string) (*x509.Certificate, error) { | |
938 | block, _ := pem.Decode([]byte(cert)) | |
939 | if block == nil { | |
940 | t.Fatalf("decoding PEM cert, empty block") | |
941 | } | |
942 | return x509.ParseCertificate(block.Bytes) | |
943 | } | |
944 | ||
945 | for _, test := range tests { | |
946 | t.Run(test.name, func(t *testing.T) { | |
947 | deviceCert, err := parseCert(test.deviceCert) | |
948 | if err != nil { | |
949 | t.Fatalf("parsing device cert: %v", err) | |
950 | } | |
951 | ||
952 | keyCert, err := parseCert(test.keyCert) | |
953 | if err != nil { | |
954 | t.Fatalf("parsing key cert: %v", err) | |
955 | } | |
956 | ||
957 | _, err = Verify(deviceCert, keyCert) | |
958 | if (err == nil) != test.ok { | |
959 | t.Errorf("Verify returned %v, expected test outcome %v", err, test.ok) | |
960 | } | |
961 | }) | |
962 | } | |
963 | } |
25 | 25 | } |
26 | 26 | |
27 | 27 | func isRCNoReaders(rc C.long) bool { |
28 | return rc == 0x8010002E | |
28 | return uint32(rc) == 0x8010002E | |
29 | 29 | } |
0 | // Copyright 2020 Google LLC | |
1 | // | |
2 | // Licensed under the Apache License, Version 2.0 (the "License"); | |
3 | // you may not use this file except in compliance with the License. | |
4 | // You may obtain a copy of the License at | |
5 | // | |
6 | // https://www.apache.org/licenses/LICENSE-2.0 | |
7 | // | |
8 | // Unless required by applicable law or agreed to in writing, software | |
9 | // distributed under the License is distributed on an "AS IS" BASIS, | |
10 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
11 | // See the License for the specific language governing permissions and | |
12 | // limitations under the License. | |
13 | ||
14 | package piv | |
15 | ||
16 | import "C" | |
17 | ||
18 | // Return codes for PCSC are different on different platforms (int vs. long). | |
19 | ||
20 | func scCheck(rc C.long) error { | |
21 | if rc == rcSuccess { | |
22 | return nil | |
23 | } | |
24 | return &scErr{int64(rc)} | |
25 | } | |
26 | ||
27 | func isRCNoReaders(rc C.long) bool { | |
28 | return rc == 0x8010002E | |
29 | } |
11 | 11 | // See the License for the specific language governing permissions and |
12 | 12 | // limitations under the License. |
13 | 13 | |
14 | // +build darwin linux freebsd | |
14 | //go:build darwin || linux || freebsd || openbsd | |
15 | // +build darwin linux freebsd openbsd | |
15 | 16 | |
16 | 17 | package piv |
17 | 18 | |
23 | 24 | // #cgo freebsd CFLAGS: -I/usr/local/include/PCSC |
24 | 25 | // #cgo freebsd LDFLAGS: -L/usr/local/lib/ |
25 | 26 | // #cgo freebsd LDFLAGS: -lpcsclite |
27 | // #cgo openbsd CFLAGS: -I/usr/local/include/ | |
28 | // #cgo openbsd CFLAGS: -I/usr/local/include/PCSC | |
29 | // #cgo openbsd LDFLAGS: -L/usr/local/lib/ | |
30 | // #cgo openbsd LDFLAGS: -lpcsclite | |
26 | 31 | // #include <PCSC/winscard.h> |
27 | 32 | // #include <PCSC/wintypes.h> |
28 | 33 | import "C" |
126 | 126 | handle syscall.Handle |
127 | 127 | activeProtocol uint16 |
128 | 128 | ) |
129 | readerPtr, err := syscall.UTF16PtrFromString(reader) | |
130 | if err != nil { | |
131 | return nil, fmt.Errorf("invalid reader string: %v", err) | |
132 | } | |
129 | 133 | r0, _, _ := procSCardConnectW.Call( |
130 | 134 | uintptr(c.ctx), |
131 | uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr(reader))), | |
135 | uintptr(unsafe.Pointer(readerPtr)), | |
132 | 136 | scardShareExclusive, |
133 | 137 | scardProtocolT1, |
134 | 138 | uintptr(unsafe.Pointer(&handle)), |
401 | 401 | response := make([]byte, 8) |
402 | 402 | block.Encrypt(response, challenge) |
403 | 403 | |
404 | data := append([]byte{ | |
404 | data := []byte{ | |
405 | 405 | 0x7c, // Dynamic Authentication Template tag |
406 | 406 | 20, // 2+8+2+8 |
407 | 407 | 0x80, // 'Witness' |
408 | 408 | 0x08, // Tag length |
409 | }) | |
409 | } | |
410 | 410 | data = append(data, cardResponse...) |
411 | 411 | data = append(data, |
412 | 412 | 0x81, // 'Challenge' |
631 | 631 | return binary.BigEndian.Uint32(resp), nil |
632 | 632 | } |
633 | 633 | |
634 | // ykChangeManagementKey sets the Management Key to the new key provided. The | |
635 | // user must have authenticated with the existing key first. | |
636 | func ykChangeManagementKey(tx *scTx, key [24]byte) error { | |
637 | cmd := apdu{ | |
638 | instruction: insSetMGMKey, | |
639 | param1: 0xff, | |
640 | param2: 0xff, // TODO: support touch policy | |
641 | data: append([]byte{ | |
642 | alg3DES, keyCardManagement, 24, | |
643 | }, key[:]...), | |
644 | } | |
645 | if _, err := tx.Transmit(cmd); err != nil { | |
646 | return fmt.Errorf("command failed: %w", err) | |
647 | } | |
648 | return nil | |
649 | } | |
650 | ||
651 | func unmarshalDERField(b []byte, tag uint64) (obj []byte, err error) { | |
652 | var prefix []byte | |
653 | for tag > 0 { | |
654 | prefix = append(prefix, byte(tag)) | |
655 | tag = tag >> 8 | |
656 | } | |
657 | for i, j := 0, len(prefix)-1; i < j; i, j = i+1, j-1 { | |
658 | prefix[i], prefix[j] = prefix[j], prefix[i] | |
659 | } | |
660 | ||
661 | hasPrefix := bytes.HasPrefix(b, prefix) | |
662 | for len(b) > 0 { | |
663 | var v asn1.RawValue | |
664 | b, err = asn1.Unmarshal(b, &v) | |
665 | if err != nil { | |
666 | return nil, err | |
667 | } | |
668 | if hasPrefix { | |
669 | return v.Bytes, nil | |
670 | } | |
671 | } | |
672 | return nil, fmt.Errorf("no der value with tag 0x%x", prefix) | |
673 | } | |
674 | ||
675 | 634 | // Metadata returns protected data stored on the card. This can be used to |
676 | 635 | // retrieve PIN protected management keys. |
677 | 636 | func (yk *YubiKey) Metadata(pin string) (*Metadata, error) { |
779 | 738 | return fmt.Errorf("invalid management key length: %d", len(v.Bytes)) |
780 | 739 | } |
781 | 740 | var key [24]byte |
782 | for i := 0; i < len(v.Bytes); i++ { | |
783 | key[i] = v.Bytes[i] | |
784 | } | |
741 | copy(key[:], v.Bytes) | |
785 | 742 | m.ManagementKey = &key |
786 | 743 | } |
787 | 744 | return nil |