├── .github
└── workflows
│ └── test.yaml
├── CONTRIBUTING.md
├── LICENSE
├── README.md
└── v2
├── go.mod
├── piv
├── doc.go
├── key.go
├── key_test.go
├── pcsc.go
├── pcsc_darwin.go
├── pcsc_errors
├── pcsc_errors.go
├── pcsc_errors.py
├── pcsc_freebsd.go
├── pcsc_linux.go
├── pcsc_openbsd.go
├── pcsc_test.go
├── pcsc_unix.go
├── pcsc_windows.go
├── piv.go
└── piv_test.go
└── third_party
└── rsa
├── LICENSE
├── README
└── pss.go
/.github/workflows/test.yaml:
--------------------------------------------------------------------------------
1 | name: test
2 | on:
3 | push:
4 | branches:
5 | - master
6 | - v2
7 | pull_request:
8 | branches:
9 | - master
10 | - v2
11 |
12 | jobs:
13 | build:
14 | strategy:
15 | matrix:
16 | go-version: [1.22.x, 1.23.x]
17 | name: Linux
18 | runs-on: ubuntu-latest
19 | steps:
20 | - name: Set up Go
21 | uses: actions/setup-go@v2
22 | with:
23 | go-version: ${{ matrix.go-version }}
24 | id: go
25 | - name: Check out code into the Go module directory
26 | uses: actions/checkout@v2
27 | - name: Install libpcsc
28 | run: sudo apt-get install -y libpcsclite-dev pcscd pcsc-tools
29 | - name: Test
30 | run: "go test -C v2 ./..."
31 | build-windows:
32 | strategy:
33 | matrix:
34 | go-version: [1.21.x, 1.22.x]
35 | name: Windows
36 | runs-on: windows-latest
37 | steps:
38 | - name: Set up Go
39 | uses: actions/setup-go@v2
40 | with:
41 | go-version: ${{ matrix.go-version }}
42 | id: go
43 | - name: Check out code into the Go module directory
44 | uses: actions/checkout@v2
45 | - name: Test
46 | run: "go build -C v2 ./..."
47 | env:
48 | CGO_ENABLED: 0
49 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # How to Contribute
2 |
3 | We'd love to accept your patches and contributions to this project. There are
4 | just a few small guidelines you need to follow.
5 |
6 | ## Contributor License Agreement
7 |
8 | Contributions to this project must be accompanied by a Contributor License
9 | Agreement. You (or your employer) retain the copyright to your contribution;
10 | this simply gives us permission to use and redistribute your contributions as
11 | part of the project. Head over to to see
12 | your current agreements on file or to sign a new one.
13 |
14 | You generally only need to submit a CLA once, so if you've already submitted one
15 | (even if it was for a different project), you probably don't need to do it
16 | again.
17 |
18 | ## Code reviews
19 |
20 | All submissions, including submissions by project members, require review. We
21 | use GitHub pull requests for this purpose. Consult
22 | [GitHub Help](https://help.github.com/articles/about-pull-requests/) for more
23 | information on using pull requests.
24 |
25 | ## Community Guidelines
26 |
27 | This project follows
28 | [Google's Open Source Community Guidelines](https://opensource.google/conduct/).
29 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 |
2 | Apache License
3 | Version 2.0, January 2004
4 | http://www.apache.org/licenses/
5 |
6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
7 |
8 | 1. Definitions.
9 |
10 | "License" shall mean the terms and conditions for use, reproduction,
11 | and distribution as defined by Sections 1 through 9 of this document.
12 |
13 | "Licensor" shall mean the copyright owner or entity authorized by
14 | the copyright owner that is granting the License.
15 |
16 | "Legal Entity" shall mean the union of the acting entity and all
17 | other entities that control, are controlled by, or are under common
18 | control with that entity. For the purposes of this definition,
19 | "control" means (i) the power, direct or indirect, to cause the
20 | direction or management of such entity, whether by contract or
21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
22 | outstanding shares, or (iii) beneficial ownership of such entity.
23 |
24 | "You" (or "Your") shall mean an individual or Legal Entity
25 | exercising permissions granted by this License.
26 |
27 | "Source" form shall mean the preferred form for making modifications,
28 | including but not limited to software source code, documentation
29 | source, and configuration files.
30 |
31 | "Object" form shall mean any form resulting from mechanical
32 | transformation or translation of a Source form, including but
33 | not limited to compiled object code, generated documentation,
34 | and conversions to other media types.
35 |
36 | "Work" shall mean the work of authorship, whether in Source or
37 | Object form, made available under the License, as indicated by a
38 | copyright notice that is included in or attached to the work
39 | (an example is provided in the Appendix below).
40 |
41 | "Derivative Works" shall mean any work, whether in Source or Object
42 | form, that is based on (or derived from) the Work and for which the
43 | editorial revisions, annotations, elaborations, or other modifications
44 | represent, as a whole, an original work of authorship. For the purposes
45 | of this License, Derivative Works shall not include works that remain
46 | separable from, or merely link (or bind by name) to the interfaces of,
47 | the Work and Derivative Works thereof.
48 |
49 | "Contribution" shall mean any work of authorship, including
50 | the original version of the Work and any modifications or additions
51 | to that Work or Derivative Works thereof, that is intentionally
52 | submitted to Licensor for inclusion in the Work by the copyright owner
53 | or by an individual or Legal Entity authorized to submit on behalf of
54 | the copyright owner. For the purposes of this definition, "submitted"
55 | means any form of electronic, verbal, or written communication sent
56 | to the Licensor or its representatives, including but not limited to
57 | communication on electronic mailing lists, source code control systems,
58 | and issue tracking systems that are managed by, or on behalf of, the
59 | Licensor for the purpose of discussing and improving the Work, but
60 | excluding communication that is conspicuously marked or otherwise
61 | designated in writing by the copyright owner as "Not a Contribution."
62 |
63 | "Contributor" shall mean Licensor and any individual or Legal Entity
64 | on behalf of whom a Contribution has been received by Licensor and
65 | subsequently incorporated within the Work.
66 |
67 | 2. Grant of Copyright License. Subject to the terms and conditions of
68 | this License, each Contributor hereby grants to You a perpetual,
69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
70 | copyright license to reproduce, prepare Derivative Works of,
71 | publicly display, publicly perform, sublicense, and distribute the
72 | Work and such Derivative Works in Source or Object form.
73 |
74 | 3. Grant of Patent License. Subject to the terms and conditions of
75 | this License, each Contributor hereby grants to You a perpetual,
76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
77 | (except as stated in this section) patent license to make, have made,
78 | use, offer to sell, sell, import, and otherwise transfer the Work,
79 | where such license applies only to those patent claims licensable
80 | by such Contributor that are necessarily infringed by their
81 | Contribution(s) alone or by combination of their Contribution(s)
82 | with the Work to which such Contribution(s) was submitted. If You
83 | institute patent litigation against any entity (including a
84 | cross-claim or counterclaim in a lawsuit) alleging that the Work
85 | or a Contribution incorporated within the Work constitutes direct
86 | or contributory patent infringement, then any patent licenses
87 | granted to You under this License for that Work shall terminate
88 | as of the date such litigation is filed.
89 |
90 | 4. Redistribution. You may reproduce and distribute copies of the
91 | Work or Derivative Works thereof in any medium, with or without
92 | modifications, and in Source or Object form, provided that You
93 | meet the following conditions:
94 |
95 | (a) You must give any other recipients of the Work or
96 | Derivative Works a copy of this License; and
97 |
98 | (b) You must cause any modified files to carry prominent notices
99 | stating that You changed the files; and
100 |
101 | (c) You must retain, in the Source form of any Derivative Works
102 | that You distribute, all copyright, patent, trademark, and
103 | attribution notices from the Source form of the Work,
104 | excluding those notices that do not pertain to any part of
105 | the Derivative Works; and
106 |
107 | (d) If the Work includes a "NOTICE" text file as part of its
108 | distribution, then any Derivative Works that You distribute must
109 | include a readable copy of the attribution notices contained
110 | within such NOTICE file, excluding those notices that do not
111 | pertain to any part of the Derivative Works, in at least one
112 | of the following places: within a NOTICE text file distributed
113 | as part of the Derivative Works; within the Source form or
114 | documentation, if provided along with the Derivative Works; or,
115 | within a display generated by the Derivative Works, if and
116 | wherever such third-party notices normally appear. The contents
117 | of the NOTICE file are for informational purposes only and
118 | do not modify the License. You may add Your own attribution
119 | notices within Derivative Works that You distribute, alongside
120 | or as an addendum to the NOTICE text from the Work, provided
121 | that such additional attribution notices cannot be construed
122 | as modifying the License.
123 |
124 | You may add Your own copyright statement to Your modifications and
125 | may provide additional or different license terms and conditions
126 | for use, reproduction, or distribution of Your modifications, or
127 | for any such Derivative Works as a whole, provided Your use,
128 | reproduction, and distribution of the Work otherwise complies with
129 | the conditions stated in this License.
130 |
131 | 5. Submission of Contributions. Unless You explicitly state otherwise,
132 | any Contribution intentionally submitted for inclusion in the Work
133 | by You to the Licensor shall be under the terms and conditions of
134 | this License, without any additional terms or conditions.
135 | Notwithstanding the above, nothing herein shall supersede or modify
136 | the terms of any separate license agreement you may have executed
137 | with Licensor regarding such Contributions.
138 |
139 | 6. Trademarks. This License does not grant permission to use the trade
140 | names, trademarks, service marks, or product names of the Licensor,
141 | except as required for reasonable and customary use in describing the
142 | origin of the Work and reproducing the content of the NOTICE file.
143 |
144 | 7. Disclaimer of Warranty. Unless required by applicable law or
145 | agreed to in writing, Licensor provides the Work (and each
146 | Contributor provides its Contributions) on an "AS IS" BASIS,
147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
148 | implied, including, without limitation, any warranties or conditions
149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
150 | PARTICULAR PURPOSE. You are solely responsible for determining the
151 | appropriateness of using or redistributing the Work and assume any
152 | risks associated with Your exercise of permissions under this License.
153 |
154 | 8. Limitation of Liability. In no event and under no legal theory,
155 | whether in tort (including negligence), contract, or otherwise,
156 | unless required by applicable law (such as deliberate and grossly
157 | negligent acts) or agreed to in writing, shall any Contributor be
158 | liable to You for damages, including any direct, indirect, special,
159 | incidental, or consequential damages of any character arising as a
160 | result of this License or out of the use or inability to use the
161 | Work (including but not limited to damages for loss of goodwill,
162 | work stoppage, computer failure or malfunction, or any and all
163 | other commercial damages or losses), even if such Contributor
164 | has been advised of the possibility of such damages.
165 |
166 | 9. Accepting Warranty or Additional Liability. While redistributing
167 | the Work or Derivative Works thereof, You may choose to offer,
168 | and charge a fee for, acceptance of support, warranty, indemnity,
169 | or other liability obligations and/or rights consistent with this
170 | License. However, in accepting such obligations, You may act only
171 | on Your own behalf and on Your sole responsibility, not on behalf
172 | of any other Contributor, and only if You agree to indemnify,
173 | defend, and hold each Contributor harmless for any liability
174 | incurred by, or claims asserted against, such Contributor by reason
175 | of your accepting any such warranty or additional liability.
176 |
177 | END OF TERMS AND CONDITIONS
178 |
179 | APPENDIX: How to apply the Apache License to your work.
180 |
181 | To apply the Apache License to your work, attach the following
182 | boilerplate notice, with the fields enclosed by brackets "[]"
183 | replaced with your own identifying information. (Don't include
184 | the brackets!) The text should be enclosed in the appropriate
185 | comment syntax for the file format. We also recommend that a
186 | file or class name and description of purpose be included on the
187 | same "printed page" as the copyright notice for easier
188 | identification within third-party archives.
189 |
190 | Copyright [yyyy] [name of copyright owner]
191 |
192 | Licensed under the Apache License, Version 2.0 (the "License");
193 | you may not use this file except in compliance with the License.
194 | You may obtain a copy of the License at
195 |
196 | http://www.apache.org/licenses/LICENSE-2.0
197 |
198 | Unless required by applicable law or agreed to in writing, software
199 | distributed under the License is distributed on an "AS IS" BASIS,
200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
201 | See the License for the specific language governing permissions and
202 | limitations under the License.
203 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | This is not an officially supported Google product
2 |
3 | # A Go YubiKey PIV implementation
4 |
5 | [](https://pkg.go.dev/github.com/go-piv/piv-go/v2/piv)
6 |
7 | YubiKeys implement the PIV specification for managing smart card certificates.
8 | This applet is a simpler alternative to GPG for managing asymmetric keys on a
9 | YubiKey.
10 |
11 | This package is an alternative to Paul Tagliamonte's [go-ykpiv](https://github.com/paultag/go-ykpiv),
12 | a wrapper for YubiKey's ykpiv.h C library. This package aims to provide:
13 |
14 | * Better error messages
15 | * Idiomatic Go APIs
16 | * Modern features such as PIN protected management keys
17 |
18 | V2 of this package was released in 2024 to support newer kinds of management
19 | keys, and is now the default branch for new features. The import path is:
20 |
21 | ```
22 | import "github.com/go-piv/piv-go/v2/piv"
23 | ```
24 |
25 | ## Examples
26 |
27 | * [Signing](#signing)
28 | * [PINs](#pins)
29 | * [Certificates](#certificates)
30 | * [Attestation](#attestation)
31 |
32 | ### Signing
33 |
34 | The piv-go package can be used to generate keys and store certificates on a
35 | YubiKey. This uses a management key to generate new keys on the applet, and a
36 | PIN for signing operations. The package provides default PIN values. If the PIV
37 | credentials on the YubiKey haven't been modified, the follow code generates a
38 | new EC key on the smartcard, and provides a signing interface:
39 |
40 | ```go
41 | // List all smartcards connected to the system.
42 | cards, err := piv.Cards()
43 | if err != nil {
44 | // ...
45 | }
46 |
47 | // Find a YubiKey and open the reader.
48 | var yk *piv.YubiKey
49 | for _, card := range cards {
50 | if strings.Contains(strings.ToLower(card), "yubikey") {
51 | if yk, err = piv.Open(card); err != nil {
52 | // ...
53 | }
54 | break
55 | }
56 | }
57 | if yk == nil {
58 | // ...
59 | }
60 |
61 | // Generate a private key on the YubiKey.
62 | key := piv.Key{
63 | Algorithm: piv.AlgorithmEC256,
64 | PINPolicy: piv.PINPolicyAlways,
65 | TouchPolicy: piv.TouchPolicyAlways,
66 | }
67 | pub, err := yk.GenerateKey(piv.DefaultManagementKey, piv.SlotAuthentication, key)
68 | if err != nil {
69 | // ...
70 | }
71 |
72 | auth := piv.KeyAuth{PIN: piv.DefaultPIN}
73 | priv, err := yk.PrivateKey(piv.SlotAuthentication, pub, auth)
74 | if err != nil {
75 | // ...
76 | }
77 | // Use private key to sign or decrypt.
78 | ```
79 |
80 | ### PINs
81 |
82 | The PIV applet has three unique credentials:
83 |
84 | * Management key (3DES key) used to generate new keys on the YubiKey.
85 | * YubiKey firmware 5.4.0+ adds support for AES128/192/256 keys
86 | * PIN (up to 8 digits, usually 6) used to access signing operations.
87 | * PUK (up to 8 digits) used to unblock the PIN. Usually set once and thrown
88 | away or managed by an administrator.
89 |
90 | piv-go implements PIN protected management keys to store the management key on
91 | the YubiKey. This allows users to only provide a PIN and still access management
92 | capabilities.
93 |
94 | The following code generates new, random credentials for a YubiKey:
95 |
96 | ```go
97 | newPINInt, err := rand.Int(rand.Reader, big.NewInt(1_000_000))
98 | if err != nil {
99 | // ...
100 | }
101 | newPUKInt, err := rand.Int(rand.Reader, big.NewInt(100_000_000))
102 | if err != nil {
103 | // ...
104 | }
105 | newKey := make([]byte, 24)
106 | if _, err := io.ReadFull(rand.Reader, newKey); err != nil {
107 | // ...
108 | }
109 | // Format with leading zeros.
110 | newPIN := fmt.Sprintf("%06d", newPINInt)
111 | newPUK := fmt.Sprintf("%08d", newPUKInt)
112 |
113 | // If you want to change PIN/PUK retries, it's recommended to do it BEFORE changing
114 | // the PIN/PUK, as SetRetries will reset PIN/PUK to their default values.
115 | if err := yk.SetRetries(piv.DefaultManagementKey, piv.DefaultPIN, 5, 4); err != nil {
116 | // ...
117 | }
118 |
119 | // Set all values to a new value.
120 | if err := yk.SetManagementKey(piv.DefaultManagementKey, newKey); err != nil {
121 | // ...
122 | }
123 | if err := yk.SetPUK(piv.DefaultPUK, newPUK); err != nil {
124 | // ...
125 | }
126 | if err := yk.SetPIN(piv.DefaultPIN, newPIN); err != nil {
127 | // ...
128 | }
129 | // Store management key on the YubiKey.
130 | m := piv.Metadata{ManagementKey: &newKey}
131 | if err := yk.SetMetadata(newKey, m); err != nil {
132 | // ...
133 | }
134 |
135 | fmt.Println("Credentials set. Your PIN is: %s", newPIN)
136 | ```
137 |
138 | The user can use the PIN later to fetch the management key:
139 |
140 | ```go
141 | m, err := yk.Metadata(pin)
142 | if err != nil {
143 | // ...
144 | }
145 | if m.ManagementKey == nil {
146 | // ...
147 | }
148 | key := *m.ManagementKey
149 | ```
150 |
151 | ### Certificates
152 |
153 | The PIV applet can also store X.509 certificates on the YubiKey:
154 |
155 | ```go
156 | cert, err := x509.ParseCertificate(certDER)
157 | if err != nil {
158 | // ...
159 | }
160 | if err := yk.SetCertificate(managementKey, piv.SlotAuthentication, cert); err != nil {
161 | // ...
162 | }
163 | ```
164 |
165 | The certificate can later be used in combination with the private key. For
166 | example, to serve TLS traffic:
167 |
168 | ```go
169 | cert, err := yk.Certificate(piv.SlotAuthentication)
170 | if err != nil {
171 | // ...
172 | }
173 | priv, err := yk.PrivateKey(piv.SlotAuthentication, cert.PublicKey, auth)
174 | if err != nil {
175 | // ...
176 | }
177 | s := &http.Server{
178 | TLSConfig: &tls.Config{
179 | Certificates: []tls.Certificate{
180 | {
181 | Certificate: [][]byte{cert.Raw},
182 | PrivateKey: priv,
183 | },
184 | },
185 | },
186 | Handler: myHandler,
187 | }
188 | ```
189 |
190 | ### Attestation
191 |
192 | YubiKeys can attest that a particular key was generated on the smartcard, and
193 | that it was set with specific PIN and touch policies. The client generates a
194 | key, then asks the YubiKey to sign an attestation certificate:
195 |
196 | ```go
197 | // Get the YubiKey's attestation certificate, which is signed by Yubico.
198 | yubiKeyAttestationCert, err := yk.AttestationCertificate()
199 | if err != nil {
200 | // ...
201 | }
202 |
203 | // Generate a key on the YubiKey and generate an attestation certificate for
204 | // that key. This will be signed by the YubiKey's attestation certificate.
205 | key := piv.Key{
206 | Algorithm: piv.AlgorithmEC256,
207 | PINPolicy: piv.PINPolicyAlways,
208 | TouchPolicy: piv.TouchPolicyAlways,
209 | }
210 | if _, err := yk.GenerateKey(managementKey, piv.SlotAuthentication, key); err != nil {
211 | // ...
212 | }
213 | slotAttestationCertificate, err := yk.Attest(piv.SlotAuthentication)
214 | if err != nil {
215 | // ...
216 | }
217 |
218 | // Send certificates to server.
219 | ```
220 |
221 | A CA can then verify the attestation, proving a key was generated on the card
222 | and enforce policy:
223 |
224 | ```go
225 | // Server receives both certificates, then proves a key was generated on the
226 | // YubiKey.
227 | a, err := piv.Verify(yubiKeyAttestationCert, slotAttestationCertificate)
228 | if err != nil {
229 | // ...
230 | }
231 | if a.TouchPolicy != piv.TouchPolicyAlways {
232 | // ...
233 | }
234 |
235 | // Record YubiKey's serial number and public key.
236 | pub := slotAttestationCertificate.PublicKey
237 | serial := a.Serial
238 | ```
239 |
240 | ## Installation
241 |
242 | On MacOS, piv-go doesn't require any additional packages.
243 |
244 | To build on Linux, piv-go requires PCSC lite. To install on Debian-based
245 | distros, run:
246 |
247 | ```
248 | sudo apt-get install libpcsclite-dev
249 | ```
250 |
251 | On Fedora:
252 |
253 | ```
254 | sudo yum install pcsc-lite-devel
255 | ```
256 |
257 | On CentOS:
258 |
259 | ```
260 | sudo yum install 'dnf-command(config-manager)'
261 | sudo yum config-manager --set-enabled PowerTools
262 | sudo yum install pcsc-lite-devel
263 | ```
264 |
265 | On FreeBSD:
266 |
267 | ```
268 | sudo pkg install pcsc-lite
269 | ```
270 |
271 | On Windows:
272 |
273 | No prerequisites are needed. The default driver by Microsoft supports all functionalities
274 | which get tested by unittests. However if you run into problems try the official
275 | [YubiKey Smart Card Minidriver](https://www.yubico.com/products/services-software/download/smart-card-drivers-tools/). Yubico states on their website the driver adds [_additional
276 | smart functionality_](https://www.yubico.com/authentication-standards/smart-card/).
277 |
278 | Please notice the following:
279 |
280 | >Windows support is best effort due to lack of test hardware. This means the maintainers will take patches for Windows, but if you encounter a bug or the build is broken, you may be asked to fix it.
281 |
282 | ## Non-YubiKey smartcards
283 |
284 | Non-YubiKey smartcards that implement the PIV standard are not officially supported due to a lack of test hardware. However, PRs that fix integrations with other smartcards are welcome, and piv-go will attempt to not break that support.
285 |
286 | ## Testing
287 |
288 | Tests automatically find connected available YubiKeys, but won't modify the
289 | smart card without the `--wipe-yubikey` flag. To let the tests modify your
290 | YubiKey's PIV applet, run:
291 |
292 | ```
293 | go test -v ./piv --wipe-yubikey
294 | ```
295 |
296 | Longer tests can be skipped with the `--test.short` flag.
297 |
298 | ```
299 | go test -v --short ./piv --wipe-yubikey
300 | ```
301 |
302 | ## Why?
303 |
304 | YubiKey's C PIV library, ykpiv, is brittle. The error messages aren't terrific,
305 | and while it has debug options, plumbing them through isn't idiomatic or
306 | convenient.
307 |
308 | ykpiv wraps PC/SC APIs available on Windows, Mac, and Linux. There's no
309 | requirement for it to be written in any particular langauge. As an alternative
310 | to [pault.ag/go/ykpiv][go-ykpiv] this package re-implements ykpiv in Go instead
311 | of calling it.
312 |
313 | ## Alternatives
314 |
315 | OpenSSH has experimental support for U2F keys ([announcement][openssh-u2f]) that
316 | directly use browser U2F challenges for smart cards.
317 |
318 | [go-ykpiv]: https://github.com/paultag/go-ykpiv
319 | [openssh-u2f]: https://marc.info/?l=openssh-unix-dev&m=157259802529972&w=2
320 |
--------------------------------------------------------------------------------
/v2/go.mod:
--------------------------------------------------------------------------------
1 | module github.com/go-piv/piv-go/v2
2 |
3 | go 1.20
4 |
--------------------------------------------------------------------------------
/v2/piv/doc.go:
--------------------------------------------------------------------------------
1 | // Copyright 2020 Google LLC
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // https://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | // Package piv implements management functionality for the YubiKey PIV applet.
16 | package piv
17 |
--------------------------------------------------------------------------------
/v2/piv/key.go:
--------------------------------------------------------------------------------
1 | // Copyright 2020 Google LLC
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // https://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | package piv
16 |
17 | import (
18 | "bytes"
19 | "crypto"
20 | "crypto/ecdh"
21 | "crypto/ecdsa"
22 | "crypto/ed25519"
23 | "crypto/elliptic"
24 | "crypto/rsa"
25 | "crypto/x509"
26 | "crypto/x509/pkix"
27 | "encoding/asn1"
28 | "encoding/pem"
29 | "errors"
30 | "fmt"
31 | "io"
32 | "math/big"
33 | "strconv"
34 | "strings"
35 |
36 | rsafork "github.com/go-piv/piv-go/v2/third_party/rsa"
37 | )
38 |
39 | // errMismatchingAlgorithms is returned when a cryptographic operation
40 | // is given keys using different algorithms.
41 | var errMismatchingAlgorithms = errors.New("mismatching key algorithms")
42 |
43 | // errUnsupportedKeySize is returned when a key has an unsupported size
44 | var errUnsupportedKeySize = errors.New("unsupported key size")
45 |
46 | // unsupportedCurveError is used when a key has an unsupported curve
47 | type unsupportedCurveError struct {
48 | curve int
49 | }
50 |
51 | func (e unsupportedCurveError) Error() string {
52 | return fmt.Sprintf("unsupported curve: %d", e.curve)
53 | }
54 |
55 | // Slot is a private key and certificate combination managed by the security key.
56 | type Slot struct {
57 | // Key is a reference for a key type.
58 | //
59 | // See: https://nvlpubs.nist.gov/nistpubs/SpecialPublications/NIST.SP.800-73-4.pdf#page=32
60 | Key uint32
61 | // Object is a reference for data object.
62 | //
63 | // See: https://nvlpubs.nist.gov/nistpubs/SpecialPublications/NIST.SP.800-73-4.pdf#page=30
64 | Object uint32
65 | }
66 |
67 | var (
68 | extIDFirmwareVersion = asn1.ObjectIdentifier([]int{1, 3, 6, 1, 4, 1, 41482, 3, 3})
69 | extIDSerialNumber = asn1.ObjectIdentifier([]int{1, 3, 6, 1, 4, 1, 41482, 3, 7})
70 | extIDKeyPolicy = asn1.ObjectIdentifier([]int{1, 3, 6, 1, 4, 1, 41482, 3, 8})
71 | extIDFormFactor = asn1.ObjectIdentifier([]int{1, 3, 6, 1, 4, 1, 41482, 3, 9})
72 | )
73 |
74 | // Version encodes a major, minor, and patch version.
75 | type Version struct {
76 | Major int
77 | Minor int
78 | Patch int
79 | }
80 |
81 | // Formfactor enumerates the physical set of forms a key can take. USB-A vs.
82 | // USB-C and Keychain vs. Nano (and FIPS variants for these).
83 | type Formfactor int
84 |
85 | // The mapping between known Formfactor values and their descriptions.
86 | var formFactorStrings = map[Formfactor]string{
87 | FormfactorUSBAKeychain: "USB-A Keychain",
88 | FormfactorUSBANano: "USB-A Nano",
89 | FormfactorUSBCKeychain: "USB-C Keychain",
90 | FormfactorUSBCNano: "USB-C Nano",
91 | FormfactorUSBCLightningKeychain: "USB-C/Lightning Keychain",
92 |
93 | FormfactorUSBAKeychainFIPS: "USB-A Keychain FIPS",
94 | FormfactorUSBANanoFIPS: "USB-A Nano FIPS",
95 | FormfactorUSBCKeychainFIPS: "USB-C Keychain FIPS",
96 | FormfactorUSBCNanoFIPS: "USB-C Nano FIPS",
97 | FormfactorUSBCLightningKeychainFIPS: "USB-C/Lightning Keychain FIPS",
98 | }
99 |
100 | // String returns the human-readable description for the given form-factor
101 | // value, or a fallback value for any other, unknown form-factor.
102 | func (f Formfactor) String() string {
103 | if s, ok := formFactorStrings[f]; ok {
104 | return s
105 | }
106 | return fmt.Sprintf("unknown(0x%02x)", int(f))
107 | }
108 |
109 | // Formfactors recognized by this package. See the reference for more information:
110 | // https://developers.yubico.com/yubikey-manager/Config_Reference.html#_form_factor
111 | const (
112 | FormfactorUSBAKeychain = 0x1
113 | FormfactorUSBANano = 0x2
114 | FormfactorUSBCKeychain = 0x3
115 | FormfactorUSBCNano = 0x4
116 | FormfactorUSBCLightningKeychain = 0x5
117 |
118 | FormfactorUSBAKeychainFIPS = 0x81
119 | FormfactorUSBANanoFIPS = 0x82
120 | FormfactorUSBCKeychainFIPS = 0x83
121 | FormfactorUSBCNanoFIPS = 0x84
122 | FormfactorUSBCLightningKeychainFIPS = 0x85
123 | )
124 |
125 | // Prefix in the x509 Subject Common Name for YubiKey attestations
126 | // https://developers.yubico.com/PIV/Introduction/PIV_attestation.html
127 | const yubikeySubjectCNPrefix = "YubiKey PIV Attestation "
128 |
129 | // Attestation returns additional information about a key attested to be generated
130 | // on a card. See https://developers.yubico.com/PIV/Introduction/PIV_attestation.html
131 | // for more information.
132 | type Attestation struct {
133 | // Version of the YubiKey's firmware.
134 | Version Version
135 | // Serial is the YubiKey's serial number.
136 | Serial uint32
137 | // Formfactor indicates the physical type of the YubiKey.
138 | //
139 | // Formfactor may be empty Formfactor(0) for some YubiKeys.
140 | Formfactor Formfactor
141 |
142 | // PINPolicy set on the slot.
143 | PINPolicy PINPolicy
144 | // TouchPolicy set on the slot.
145 | TouchPolicy TouchPolicy
146 |
147 | // Slot is the inferred slot the attested key resides in based on the
148 | // common name in the attestation. If the slot cannot be determined,
149 | // this field will be an empty struct.
150 | Slot Slot
151 | }
152 |
153 | func (a *Attestation) addExt(e pkix.Extension) error {
154 | if e.Id.Equal(extIDFirmwareVersion) {
155 | if len(e.Value) != 3 {
156 | return fmt.Errorf("expected 3 bytes for firmware version, got: %d", len(e.Value))
157 | }
158 | a.Version = Version{
159 | Major: int(e.Value[0]),
160 | Minor: int(e.Value[1]),
161 | Patch: int(e.Value[2]),
162 | }
163 | } else if e.Id.Equal(extIDSerialNumber) {
164 | var serial int64
165 | if _, err := asn1.Unmarshal(e.Value, &serial); err != nil {
166 | return fmt.Errorf("parsing serial number: %v", err)
167 | }
168 | if serial < 0 {
169 | return fmt.Errorf("serial number was negative: %d", serial)
170 | }
171 | a.Serial = uint32(serial)
172 | } else if e.Id.Equal(extIDKeyPolicy) {
173 | if len(e.Value) != 2 {
174 | return fmt.Errorf("expected 2 bytes from key policy, got: %d", len(e.Value))
175 | }
176 | switch e.Value[0] {
177 | case 0x01:
178 | a.PINPolicy = PINPolicyNever
179 | case 0x02:
180 | a.PINPolicy = PINPolicyOnce
181 | case 0x03:
182 | a.PINPolicy = PINPolicyAlways
183 | default:
184 | return fmt.Errorf("unrecognized pin policy: 0x%x", e.Value[0])
185 | }
186 | switch e.Value[1] {
187 | case 0x01:
188 | a.TouchPolicy = TouchPolicyNever
189 | case 0x02:
190 | a.TouchPolicy = TouchPolicyAlways
191 | case 0x03:
192 | a.TouchPolicy = TouchPolicyCached
193 | default:
194 | return fmt.Errorf("unrecognized touch policy: 0x%x", e.Value[1])
195 | }
196 | } else if e.Id.Equal(extIDFormFactor) {
197 | if len(e.Value) != 1 {
198 | return fmt.Errorf("expected 1 byte from formfactor, got: %d", len(e.Value))
199 | }
200 | a.Formfactor = Formfactor(e.Value[0])
201 | }
202 | return nil
203 | }
204 |
205 | // Verify proves that a key was generated on a YubiKey. It ensures the slot and
206 | // YubiKey certificate chains up to the Yubico CA, parsing additional information
207 | // out of the slot certificate, such as the touch and PIN policies of a key.
208 | func Verify(attestationCert, slotCert *x509.Certificate) (*Attestation, error) {
209 | var v Verifier
210 | return v.Verify(attestationCert, slotCert)
211 | }
212 |
213 | // Verifier allows specifying options when verifying attestations produced by
214 | // YubiKeys.
215 | type Verifier struct {
216 | // Root certificates to use to validate challenges. If nil, this defaults to Yubico's
217 | // CA bundle.
218 | //
219 | // https://developers.yubico.com/PIV/Introduction/PIV_attestation.html
220 | // https://developers.yubico.com/PIV/Introduction/piv-attestation-ca.pem
221 | // https://developers.yubico.com/U2F/yubico-u2f-ca-certs.txt
222 | Roots *x509.CertPool
223 | }
224 |
225 | // Verify proves that a key was generated on a YubiKey.
226 | //
227 | // As opposed to the package level [Verify], it uses any options enabled on the [Verifier].
228 | func (v *Verifier) Verify(attestationCert, slotCert *x509.Certificate) (*Attestation, error) {
229 | o := x509.VerifyOptions{KeyUsages: []x509.ExtKeyUsage{x509.ExtKeyUsageAny}}
230 | o.Roots = v.Roots
231 | if o.Roots == nil {
232 | cas, err := yubicoCAs()
233 | if err != nil {
234 | return nil, fmt.Errorf("failed to load yubico CAs: %v", err)
235 | }
236 | o.Roots = cas
237 | }
238 |
239 | o.Intermediates = x509.NewCertPool()
240 |
241 | // The attestation cert in some yubikey 4 does not encode X509v3 Basic Constraints.
242 | // This isn't valid as per https://datatracker.ietf.org/doc/html/rfc5280#section-4.2.1.9
243 | // (fourth paragraph) and thus makes x509.go validation fail.
244 | // Work around this by setting this constraint here.
245 | if !attestationCert.BasicConstraintsValid {
246 | attestationCert.BasicConstraintsValid = true
247 | attestationCert.IsCA = true
248 | }
249 |
250 | o.Intermediates.AddCert(attestationCert)
251 |
252 | _, err := slotCert.Verify(o)
253 | if err != nil {
254 | return nil, fmt.Errorf("error verifying attestation certificate: %v", err)
255 | }
256 | return parseAttestation(slotCert)
257 | }
258 |
259 | func parseAttestation(slotCert *x509.Certificate) (*Attestation, error) {
260 | var a Attestation
261 | for _, ext := range slotCert.Extensions {
262 | if err := a.addExt(ext); err != nil {
263 | return nil, fmt.Errorf("parsing extension: %v", err)
264 | }
265 | }
266 |
267 | slot, ok := parseSlot(slotCert.Subject.CommonName)
268 | if ok {
269 | a.Slot = slot
270 | }
271 |
272 | return &a, nil
273 | }
274 |
275 | func parseSlot(commonName string) (Slot, bool) {
276 | if !strings.HasPrefix(commonName, yubikeySubjectCNPrefix) {
277 | return Slot{}, false
278 | }
279 |
280 | slotName := strings.TrimPrefix(commonName, yubikeySubjectCNPrefix)
281 | key, err := strconv.ParseUint(slotName, 16, 32)
282 | if err != nil {
283 | return Slot{}, false
284 | }
285 |
286 | switch uint32(key) {
287 | case SlotAuthentication.Key:
288 | return SlotAuthentication, true
289 | case SlotSignature.Key:
290 | return SlotSignature, true
291 | case SlotCardAuthentication.Key:
292 | return SlotCardAuthentication, true
293 | case SlotKeyManagement.Key:
294 | return SlotKeyManagement, true
295 | }
296 |
297 | return RetiredKeyManagementSlot(uint32(key))
298 | }
299 |
300 | // yubicoPIVCAPEMAfter2018 is the PEM encoded attestation certificate used by Yubico.
301 | //
302 | // https://developers.yubico.com/PIV/Introduction/PIV_attestation.html
303 | const yubicoPIVCAPEMAfter2018 = `-----BEGIN CERTIFICATE-----
304 | MIIDFzCCAf+gAwIBAgIDBAZHMA0GCSqGSIb3DQEBCwUAMCsxKTAnBgNVBAMMIFl1
305 | YmljbyBQSVYgUm9vdCBDQSBTZXJpYWwgMjYzNzUxMCAXDTE2MDMxNDAwMDAwMFoY
306 | DzIwNTIwNDE3MDAwMDAwWjArMSkwJwYDVQQDDCBZdWJpY28gUElWIFJvb3QgQ0Eg
307 | U2VyaWFsIDI2Mzc1MTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMN2
308 | cMTNR6YCdcTFRxuPy31PabRn5m6pJ+nSE0HRWpoaM8fc8wHC+Tmb98jmNvhWNE2E
309 | ilU85uYKfEFP9d6Q2GmytqBnxZsAa3KqZiCCx2LwQ4iYEOb1llgotVr/whEpdVOq
310 | joU0P5e1j1y7OfwOvky/+AXIN/9Xp0VFlYRk2tQ9GcdYKDmqU+db9iKwpAzid4oH
311 | BVLIhmD3pvkWaRA2H3DA9t7H/HNq5v3OiO1jyLZeKqZoMbPObrxqDg+9fOdShzgf
312 | wCqgT3XVmTeiwvBSTctyi9mHQfYd2DwkaqxRnLbNVyK9zl+DzjSGp9IhVPiVtGet
313 | X02dxhQnGS7K6BO0Qe8CAwEAAaNCMEAwHQYDVR0OBBYEFMpfyvLEojGc6SJf8ez0
314 | 1d8Cv4O/MA8GA1UdEwQIMAYBAf8CAQEwDgYDVR0PAQH/BAQDAgEGMA0GCSqGSIb3
315 | DQEBCwUAA4IBAQBc7Ih8Bc1fkC+FyN1fhjWioBCMr3vjneh7MLbA6kSoyWF70N3s
316 | XhbXvT4eRh0hvxqvMZNjPU/VlRn6gLVtoEikDLrYFXN6Hh6Wmyy1GTnspnOvMvz2
317 | lLKuym9KYdYLDgnj3BeAvzIhVzzYSeU77/Cupofj093OuAswW0jYvXsGTyix6B3d
318 | bW5yWvyS9zNXaqGaUmP3U9/b6DlHdDogMLu3VLpBB9bm5bjaKWWJYgWltCVgUbFq
319 | Fqyi4+JE014cSgR57Jcu3dZiehB6UtAPgad9L5cNvua/IWRmm+ANy3O2LH++Pyl8
320 | SREzU8onbBsjMg9QDiSf5oJLKvd/Ren+zGY7
321 | -----END CERTIFICATE-----`
322 |
323 | // Yubikeys manufactured sometime in 2018 and prior to mid-2017
324 | // were certified using the U2F root CA with serial number 457200631
325 | // See https://github.com/Yubico/developers.yubico.com/pull/392/commits/a58f1003f003e04fc9baf09cad9f64f0c284fd47
326 | // Cert available at https://developers.yubico.com/U2F/yubico-u2f-ca-certs.txt
327 | const yubicoPIVCAPEMU2F = `-----BEGIN CERTIFICATE-----
328 | MIIDHjCCAgagAwIBAgIEG0BT9zANBgkqhkiG9w0BAQsFADAuMSwwKgYDVQQDEyNZ
329 | dWJpY28gVTJGIFJvb3QgQ0EgU2VyaWFsIDQ1NzIwMDYzMTAgFw0xNDA4MDEwMDAw
330 | MDBaGA8yMDUwMDkwNDAwMDAwMFowLjEsMCoGA1UEAxMjWXViaWNvIFUyRiBSb290
331 | IENBIFNlcmlhbCA0NTcyMDA2MzEwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEK
332 | AoIBAQC/jwYuhBVlqaiYWEMsrWFisgJ+PtM91eSrpI4TK7U53mwCIawSDHy8vUmk
333 | 5N2KAj9abvT9NP5SMS1hQi3usxoYGonXQgfO6ZXyUA9a+KAkqdFnBnlyugSeCOep
334 | 8EdZFfsaRFtMjkwz5Gcz2Py4vIYvCdMHPtwaz0bVuzneueIEz6TnQjE63Rdt2zbw
335 | nebwTG5ZybeWSwbzy+BJ34ZHcUhPAY89yJQXuE0IzMZFcEBbPNRbWECRKgjq//qT
336 | 9nmDOFVlSRCt2wiqPSzluwn+v+suQEBsUjTGMEd25tKXXTkNW21wIWbxeSyUoTXw
337 | LvGS6xlwQSgNpk2qXYwf8iXg7VWZAgMBAAGjQjBAMB0GA1UdDgQWBBQgIvz0bNGJ
338 | hjgpToksyKpP9xv9oDAPBgNVHRMECDAGAQH/AgEAMA4GA1UdDwEB/wQEAwIBBjAN
339 | BgkqhkiG9w0BAQsFAAOCAQEAjvjuOMDSa+JXFCLyBKsycXtBVZsJ4Ue3LbaEsPY4
340 | MYN/hIQ5ZM5p7EjfcnMG4CtYkNsfNHc0AhBLdq45rnT87q/6O3vUEtNMafbhU6kt
341 | hX7Y+9XFN9NpmYxr+ekVY5xOxi8h9JDIgoMP4VB1uS0aunL1IGqrNooL9mmFnL2k
342 | LVVee6/VR6C5+KSTCMCWppMuJIZII2v9o4dkoZ8Y7QRjQlLfYzd3qGtKbw7xaF1U
343 | sG/5xUb/Btwb2X2g4InpiB/yt/3CpQXpiWX/K4mBvUKiGn05ZsqeY1gx4g0xLBqc
344 | U9psmyPzK+Vsgw2jeRQ5JlKDyqE0hebfC1tvFu0CCrJFcw==
345 | -----END CERTIFICATE-----`
346 |
347 | func yubicoCAs() (*x509.CertPool, error) {
348 | certPool := x509.NewCertPool()
349 |
350 | if !certPool.AppendCertsFromPEM([]byte(yubicoPIVCAPEMAfter2018)) {
351 | return nil, fmt.Errorf("failed to parse yubico cert")
352 | }
353 |
354 | bU2F, _ := pem.Decode([]byte(yubicoPIVCAPEMU2F))
355 | if bU2F == nil {
356 | return nil, fmt.Errorf("failed to decode yubico pem data")
357 | }
358 |
359 | certU2F, err := x509.ParseCertificate(bU2F.Bytes)
360 | if err != nil {
361 | return nil, fmt.Errorf("failed to parse yubico cert: %v", err)
362 | }
363 |
364 | // The U2F root cert has pathlen x509 basic constraint set to 0.
365 | // As per RFC 5280 this means that no intermediate cert is allowed
366 | // in the validation path. This isn't really helpful since we do
367 | // want to use the device attestation cert as intermediate cert in
368 | // the chain. To make this work, set pathlen of the U2F root to 1.
369 | //
370 | // See https://datatracker.ietf.org/doc/html/rfc5280#section-4.2.1.9
371 | certU2F.MaxPathLen = 1
372 | certPool.AddCert(certU2F)
373 |
374 | return certPool, nil
375 | }
376 |
377 | // Slot combinations pre-defined by this package.
378 | //
379 | // Object IDs are specified in NIST 800-73-4 section 4.3:
380 | // https://nvlpubs.nist.gov/nistpubs/SpecialPublications/NIST.SP.800-73-4.pdf#page=30
381 | //
382 | // Key IDs are specified in NIST 800-73-4 section 5.1:
383 | // https://nvlpubs.nist.gov/nistpubs/SpecialPublications/NIST.SP.800-73-4.pdf#page=32
384 | var (
385 | SlotAuthentication = Slot{0x9a, 0x5fc105}
386 | SlotSignature = Slot{0x9c, 0x5fc10a}
387 | SlotCardAuthentication = Slot{0x9e, 0x5fc101}
388 | SlotKeyManagement = Slot{0x9d, 0x5fc10b}
389 |
390 | slotAttestation = Slot{0xf9, 0x5fff01}
391 | )
392 |
393 | var retiredKeyManagementSlots = map[uint32]Slot{
394 | 0x82: {0x82, 0x5fc10d},
395 | 0x83: {0x83, 0x5fc10e},
396 | 0x84: {0x84, 0x5fc10f},
397 | 0x85: {0x85, 0x5fc110},
398 | 0x86: {0x86, 0x5fc111},
399 | 0x87: {0x87, 0x5fc112},
400 | 0x88: {0x88, 0x5fc113},
401 | 0x89: {0x89, 0x5fc114},
402 | 0x8a: {0x8a, 0x5fc115},
403 | 0x8b: {0x8b, 0x5fc116},
404 | 0x8c: {0x8c, 0x5fc117},
405 | 0x8d: {0x8d, 0x5fc118},
406 | 0x8e: {0x8e, 0x5fc119},
407 | 0x8f: {0x8f, 0x5fc11a},
408 | 0x90: {0x90, 0x5fc11b},
409 | 0x91: {0x91, 0x5fc11c},
410 | 0x92: {0x92, 0x5fc11d},
411 | 0x93: {0x93, 0x5fc11e},
412 | 0x94: {0x94, 0x5fc11f},
413 | 0x95: {0x95, 0x5fc120},
414 | }
415 |
416 | // RetiredKeyManagementSlot provides access to "retired" slots. Slots meant for old Key Management
417 | // keys that have been rotated. YubiKeys 4 and later support values between 0x82 and 0x95 (inclusive).
418 | //
419 | // slot, ok := RetiredKeyManagementSlot(0x82)
420 | // if !ok {
421 | // // unrecognized slot
422 | // }
423 | // pub, err := yk.GenerateKey(managementKey, slot, key)
424 | //
425 | // https://developers.yubico.com/PIV/Introduction/Certificate_slots.html#_slot_82_95_retired_key_management
426 | func RetiredKeyManagementSlot(key uint32) (Slot, bool) {
427 | slot, ok := retiredKeyManagementSlots[key]
428 | return slot, ok
429 | }
430 |
431 | // String returns the two-character hex representation of the slot
432 | func (s Slot) String() string {
433 | return strconv.FormatUint(uint64(s.Key), 16)
434 | }
435 |
436 | // Algorithm represents a specific algorithm and bit size supported by the PIV
437 | // specification.
438 | type Algorithm int
439 |
440 | // Algorithms supported by this package. Note that not all cards will support
441 | // every algorithm.
442 | //
443 | // For algorithm discovery, see: https://github.com/ericchiang/piv-go/issues/1
444 | const (
445 | AlgorithmEC256 Algorithm = iota + 1
446 | AlgorithmEC384
447 | AlgorithmEd25519
448 | AlgorithmRSA1024
449 | AlgorithmRSA2048
450 | AlgorithmRSA3072
451 | AlgorithmRSA4096
452 | AlgorithmX25519
453 | )
454 |
455 | // PINPolicy represents PIN requirements when signing or decrypting with an
456 | // asymmetric key in a given slot.
457 | type PINPolicy int
458 |
459 | // PIN policies supported by this package.
460 | //
461 | // BUG(ericchiang): Caching for PINPolicyOnce isn't supported on YubiKey
462 | // versions older than 4.3.0 due to issues with verifying if a PIN is needed.
463 | // If specified, a PIN will be required for every operation.
464 | const (
465 | PINPolicyNever PINPolicy = iota + 1
466 | PINPolicyOnce
467 | PINPolicyAlways
468 | )
469 |
470 | // TouchPolicy represents proof-of-presence requirements when signing or
471 | // decrypting with asymmetric key in a given slot.
472 | type TouchPolicy int
473 |
474 | // Touch policies supported by this package.
475 | const (
476 | TouchPolicyNever TouchPolicy = iota + 1
477 | TouchPolicyAlways
478 | TouchPolicyCached
479 | )
480 |
481 | // Origin represents whether a key was generated on the hardware, or has been
482 | // imported into it.
483 | type Origin int
484 |
485 | // Origins supported by this package.
486 | const (
487 | OriginGenerated Origin = iota + 1
488 | OriginImported
489 | )
490 |
491 | const (
492 | tagPINPolicy = 0xaa
493 | tagTouchPolicy = 0xab
494 | )
495 |
496 | var pinPolicyMap = map[PINPolicy]byte{
497 | PINPolicyNever: 0x01,
498 | PINPolicyOnce: 0x02,
499 | PINPolicyAlways: 0x03,
500 | }
501 |
502 | var pinPolicyMapInv = map[byte]PINPolicy{
503 | 0x01: PINPolicyNever,
504 | 0x02: PINPolicyOnce,
505 | 0x03: PINPolicyAlways,
506 | }
507 |
508 | var touchPolicyMap = map[TouchPolicy]byte{
509 | TouchPolicyNever: 0x01,
510 | TouchPolicyAlways: 0x02,
511 | TouchPolicyCached: 0x03,
512 | }
513 |
514 | var touchPolicyMapInv = map[byte]TouchPolicy{
515 | 0x01: TouchPolicyNever,
516 | 0x02: TouchPolicyAlways,
517 | 0x03: TouchPolicyCached,
518 | }
519 |
520 | var originMap = map[Origin]byte{
521 | OriginGenerated: 0x01,
522 | OriginImported: 0x02,
523 | }
524 |
525 | var originMapInv = map[byte]Origin{
526 | 0x01: OriginGenerated,
527 | 0x02: OriginImported,
528 | }
529 |
530 | var algorithmsMap = map[Algorithm]byte{
531 | AlgorithmEC256: algECCP256,
532 | AlgorithmEC384: algECCP384,
533 | AlgorithmEd25519: algEd25519,
534 | AlgorithmRSA1024: algRSA1024,
535 | AlgorithmRSA2048: algRSA2048,
536 | AlgorithmRSA3072: algRSA3072,
537 | AlgorithmRSA4096: algRSA4096,
538 | AlgorithmX25519: algX25519,
539 | }
540 |
541 | var algorithmsMapInv = map[byte]Algorithm{
542 | algECCP256: AlgorithmEC256,
543 | algECCP384: AlgorithmEC384,
544 | algEd25519: AlgorithmEd25519,
545 | algRSA1024: AlgorithmRSA1024,
546 | algRSA2048: AlgorithmRSA2048,
547 | algRSA3072: AlgorithmRSA3072,
548 | algRSA4096: AlgorithmRSA4096,
549 | algX25519: AlgorithmX25519,
550 | }
551 |
552 | // AttestationCertificate returns the YubiKey's attestation certificate, which
553 | // is unique to the key and signed by Yubico.
554 | func (yk *YubiKey) AttestationCertificate() (*x509.Certificate, error) {
555 | return yk.Certificate(slotAttestation)
556 | }
557 |
558 | // Attest generates a certificate for a key, signed by the YubiKey's attestation
559 | // certificate. This can be used to prove a key was generate on a specific
560 | // YubiKey.
561 | //
562 | // This method is only supported for YubiKey versions >= 4.3.0.
563 | // https://developers.yubico.com/PIV/Introduction/PIV_attestation.html
564 | //
565 | // Certificates returned by this method MUST NOT be used for anything other than
566 | // attestion or determining the slots public key. For example, the certificate
567 | // is NOT suitable for TLS.
568 | //
569 | // If the slot doesn't have a key, the returned error wraps ErrNotFound.
570 | func (yk *YubiKey) Attest(slot Slot) (*x509.Certificate, error) {
571 | cert, err := ykAttest(yk.tx, slot)
572 | if err == nil {
573 | return cert, nil
574 | }
575 | var e *apduErr
576 | if errors.As(err, &e) && e.sw1 == 0x6A && e.sw2 == 0x80 {
577 | return nil, ErrNotFound
578 | }
579 | return nil, err
580 | }
581 |
582 | func ykAttest(tx *scTx, slot Slot) (*x509.Certificate, error) {
583 | cmd := apdu{
584 | instruction: insAttest,
585 | param1: byte(slot.Key),
586 | }
587 | resp, err := tx.Transmit(cmd)
588 | if err != nil {
589 | return nil, fmt.Errorf("command failed: %w", err)
590 | }
591 | if bytes.HasPrefix(resp, []byte{0x70}) {
592 | b, _, err := unmarshalASN1(resp, 0, 0x10) // tag 0x70
593 | if err != nil {
594 | return nil, fmt.Errorf("unmarshaling certificate: %v", err)
595 | }
596 | resp = b
597 | }
598 | cert, err := x509.ParseCertificate(resp)
599 | if err != nil {
600 | return nil, fmt.Errorf("parsing certificate: %v", err)
601 | }
602 | return cert, nil
603 | }
604 |
605 | // KeyInfo holds unprotected metadata about a key slot.
606 | type KeyInfo struct {
607 | Algorithm Algorithm
608 | PINPolicy PINPolicy
609 | TouchPolicy TouchPolicy
610 | Origin Origin
611 | PublicKey crypto.PublicKey
612 | }
613 |
614 | func (ki *KeyInfo) unmarshal(b []byte) error {
615 | for len(b) > 0 {
616 | var v asn1.RawValue
617 | rest, err := asn1.Unmarshal(b, &v)
618 | if err != nil {
619 | return err
620 | }
621 | b = rest
622 | if v.Class != 0 || v.IsCompound {
623 | continue
624 | }
625 | var ok bool
626 | switch v.Tag {
627 | case 1:
628 | if len(v.Bytes) != 1 {
629 | return errors.New("invalid algorithm in response")
630 | }
631 | if ki.Algorithm, ok = algorithmsMapInv[v.Bytes[0]]; !ok {
632 | return errors.New("unknown algorithm in response")
633 | }
634 | case 2:
635 | if len(v.Bytes) != 2 {
636 | return errors.New("invalid policy in response")
637 | }
638 | if ki.PINPolicy, ok = pinPolicyMapInv[v.Bytes[0]]; !ok {
639 | return errors.New("unknown PIN policy in response")
640 | }
641 | if ki.TouchPolicy, ok = touchPolicyMapInv[v.Bytes[1]]; !ok {
642 | return errors.New("unknown touch policy in response")
643 | }
644 | case 3:
645 | if len(v.Bytes) != 1 {
646 | return errors.New("invalid origin in response")
647 | }
648 | if ki.Origin, ok = originMapInv[v.Bytes[0]]; !ok {
649 | return errors.New("unknown origin in response")
650 | }
651 | case 4:
652 | ki.PublicKey, err = decodePublic(v.Bytes, ki.Algorithm)
653 | if err != nil {
654 | return fmt.Errorf("parse public key: %w", err)
655 | }
656 | default:
657 | // TODO: According to the Yubico website, we get two more fields,
658 | // if we pass 0x80 or 0x81 as slots:
659 | // 1. Default value (for PIN/PUK and management key): Whether the
660 | // default value is used.
661 | // 2. Retries (for PIN/PUK): The number of retries remaining
662 | // However, it seems the reference implementation does not expect
663 | // these and can not parse them out:
664 | // https://github.com/Yubico/yubico-piv-tool/blob/yubico-piv-tool-2.3.1/lib/util.c#L1529
665 | // For now, we just ignore them.
666 | }
667 | }
668 | return nil
669 | }
670 |
671 | // KeyInfo returns public information about the given key slot. It is only
672 | // supported by YubiKeys with a version >= 5.3.0.
673 | func (yk *YubiKey) KeyInfo(slot Slot) (KeyInfo, error) {
674 | // https://developers.yubico.com/PIV/Introduction/Yubico_extensions.html#_get_metadata
675 | cmd := apdu{
676 | instruction: insGetMetadata,
677 | param1: 0x00,
678 | param2: byte(slot.Key),
679 | }
680 | resp, err := yk.tx.Transmit(cmd)
681 | if err != nil {
682 | return KeyInfo{}, fmt.Errorf("command failed: %w", err)
683 | }
684 | var ki KeyInfo
685 | if err := ki.unmarshal(resp); err != nil {
686 | return KeyInfo{}, err
687 | }
688 | return ki, nil
689 | }
690 |
691 | // Certificate returns the certifiate object stored in a given slot.
692 | //
693 | // If a certificate hasn't been set in the provided slot, the returned error
694 | // wraps ErrNotFound.
695 | func (yk *YubiKey) Certificate(slot Slot) (*x509.Certificate, error) {
696 | cmd := apdu{
697 | instruction: insGetData,
698 | param1: 0x3f,
699 | param2: 0xff,
700 | data: []byte{
701 | 0x5c, // Tag list
702 | 0x03, // Length of tag
703 | byte(slot.Object >> 16),
704 | byte(slot.Object >> 8),
705 | byte(slot.Object),
706 | },
707 | }
708 | resp, err := yk.tx.Transmit(cmd)
709 | if err != nil {
710 | return nil, fmt.Errorf("command failed: %w", err)
711 | }
712 | // https://nvlpubs.nist.gov/nistpubs/SpecialPublications/NIST.SP.800-73-4.pdf#page=85
713 | obj, _, err := unmarshalASN1(resp, 1, 0x13) // tag 0x53
714 | if err != nil {
715 | return nil, fmt.Errorf("unmarshaling response: %v", err)
716 | }
717 | certDER, _, err := unmarshalASN1(obj, 1, 0x10) // tag 0x70
718 | if err != nil {
719 | return nil, fmt.Errorf("unmarshaling certificate: %v", err)
720 | }
721 | cert, err := x509.ParseCertificate(certDER)
722 | if err != nil {
723 | return nil, fmt.Errorf("parsing certificate: %v", err)
724 | }
725 | return cert, nil
726 | }
727 |
728 | // marshalASN1Length encodes the length.
729 | func marshalASN1Length(n uint64) []byte {
730 | var l []byte
731 | if n < 0x80 {
732 | l = []byte{byte(n)}
733 | } else if n < 0x100 {
734 | l = []byte{0x81, byte(n)}
735 | } else {
736 | l = []byte{0x82, byte(n >> 8), byte(n)}
737 | }
738 |
739 | return l
740 | }
741 |
742 | // marshalASN1 encodes a tag, length and data.
743 | //
744 | // TODO: clean this up and maybe switch to cryptobyte?
745 | func marshalASN1(tag byte, data []byte) []byte {
746 | l := marshalASN1Length(uint64(len(data)))
747 | d := append([]byte{tag}, l...)
748 | return append(d, data...)
749 | }
750 |
751 | // SetCertificate stores a certificate object in the provided slot. Setting a
752 | // certificate isn't required to use the associated key for signing or
753 | // decryption.
754 | func (yk *YubiKey) SetCertificate(key []byte, slot Slot, cert *x509.Certificate) error {
755 | if err := ykAuthenticate(yk.tx, key, yk.rand, yk.version); err != nil {
756 | return fmt.Errorf("authenticating with management key: %w", err)
757 | }
758 | return ykStoreCertificate(yk.tx, slot, cert)
759 | }
760 |
761 | func ykStoreCertificate(tx *scTx, slot Slot, cert *x509.Certificate) error {
762 | // https://nvlpubs.nist.gov/nistpubs/SpecialPublications/NIST.SP.800-73-4.pdf#page=40
763 | data := marshalASN1(0x70, cert.Raw)
764 | // "for a certificate encoded in uncompressed form CertInfo shall be 0x00"
765 | data = append(data, marshalASN1(0x71, []byte{0x00})...)
766 | // Error Detection Code
767 | data = append(data, marshalASN1(0xfe, nil)...)
768 | // https://nvlpubs.nist.gov/nistpubs/SpecialPublications/NIST.SP.800-73-4.pdf#page=94
769 | data = append([]byte{
770 | 0x5c, // Tag list
771 | 0x03, // Length of tag
772 | byte(slot.Object >> 16),
773 | byte(slot.Object >> 8),
774 | byte(slot.Object),
775 | }, marshalASN1(0x53, data)...)
776 | cmd := apdu{
777 | instruction: insPutData,
778 | param1: 0x3f,
779 | param2: 0xff,
780 | data: data,
781 | }
782 | if _, err := tx.Transmit(cmd); err != nil {
783 | return fmt.Errorf("command failed: %v", err)
784 | }
785 | return nil
786 | }
787 |
788 | // Key is used for key generation and holds different options for the key.
789 | //
790 | // While keys can have default PIN and touch policies, this package currently
791 | // doesn't support this option, and all fields must be provided.
792 | type Key struct {
793 | // Algorithm to use when generating the key.
794 | Algorithm Algorithm
795 | // PINPolicy for the key.
796 | //
797 | // BUG(ericchiang): some older YubiKeys (third generation) will silently
798 | // drop this value. If PINPolicyNever or PINPolicyOnce is supplied but the
799 | // key still requires a PIN every time, you may be using a buggy key and
800 | // should supply PINPolicyAlways. See https://github.com/go-piv/piv-go/issues/60
801 | PINPolicy PINPolicy
802 | // TouchPolicy for the key.
803 | TouchPolicy TouchPolicy
804 | }
805 |
806 | // GenerateKey generates an asymmetric key on the card, returning the key's
807 | // public key.
808 | func (yk *YubiKey) GenerateKey(key []byte, slot Slot, opts Key) (crypto.PublicKey, error) {
809 | if err := ykAuthenticate(yk.tx, key, yk.rand, yk.version); err != nil {
810 | return nil, fmt.Errorf("authenticating with management key: %w", err)
811 | }
812 | return ykGenerateKey(yk.tx, slot, opts)
813 | }
814 |
815 | func ykGenerateKey(tx *scTx, slot Slot, o Key) (crypto.PublicKey, error) {
816 | alg, ok := algorithmsMap[o.Algorithm]
817 | if !ok {
818 | return nil, fmt.Errorf("unsupported algorithm")
819 | }
820 | tp, ok := touchPolicyMap[o.TouchPolicy]
821 | if !ok {
822 | return nil, fmt.Errorf("unsupported touch policy")
823 | }
824 | pp, ok := pinPolicyMap[o.PINPolicy]
825 | if !ok {
826 | return nil, fmt.Errorf("unsupported pin policy")
827 | }
828 | // https://nvlpubs.nist.gov/nistpubs/SpecialPublications/NIST.SP.800-73-4.pdf#page=95
829 | cmd := apdu{
830 | instruction: insGenerateAsymmetric,
831 | param2: byte(slot.Key),
832 | data: []byte{
833 | 0xac,
834 | 0x09, // length of remaining data
835 | algTag, 0x01, alg,
836 | tagPINPolicy, 0x01, pp,
837 | tagTouchPolicy, 0x01, tp,
838 | },
839 | }
840 | resp, err := tx.Transmit(cmd)
841 | if err != nil {
842 | return nil, fmt.Errorf("command failed: %w", err)
843 | }
844 |
845 | // https://nvlpubs.nist.gov/nistpubs/SpecialPublications/NIST.SP.800-73-4.pdf#page=95
846 | obj, _, err := unmarshalASN1(resp, 1, 0x49)
847 | if err != nil {
848 | return nil, fmt.Errorf("unmarshal response: %v", err)
849 | }
850 |
851 | return decodePublic(obj, o.Algorithm)
852 | }
853 |
854 | func decodePublic(b []byte, alg Algorithm) (crypto.PublicKey, error) {
855 | var curve elliptic.Curve
856 | switch alg {
857 | case AlgorithmRSA1024, AlgorithmRSA2048, AlgorithmRSA3072, AlgorithmRSA4096:
858 | pub, err := decodeRSAPublic(b)
859 | if err != nil {
860 | return nil, fmt.Errorf("decoding rsa public key: %v", err)
861 | }
862 | return pub, nil
863 | case AlgorithmEC256:
864 | curve = elliptic.P256()
865 | case AlgorithmEC384:
866 | curve = elliptic.P384()
867 | case AlgorithmEd25519:
868 | pub, err := decodeEd25519Public(b)
869 | if err != nil {
870 | return nil, fmt.Errorf("decoding ed25519 public key: %v", err)
871 | }
872 | return pub, nil
873 | case AlgorithmX25519:
874 | pub, err := decodeX25519Public(b)
875 | if err != nil {
876 | return nil, fmt.Errorf("decoding X25519 public key: %v", err)
877 | }
878 | return pub, nil
879 | default:
880 | return nil, fmt.Errorf("unsupported algorithm")
881 | }
882 | pub, err := decodeECPublic(b, curve)
883 | if err != nil {
884 | return nil, fmt.Errorf("decoding ec public key: %v", err)
885 | }
886 | return pub, nil
887 | }
888 |
889 | // KeyAuth is used to authenticate against the YubiKey on each signing and
890 | // decryption request.
891 | type KeyAuth struct {
892 | // PIN, if provided, is a static PIN used to authenticate against the key.
893 | // If provided, PINPrompt is ignored.
894 | PIN string
895 | // PINPrompt can be used to interactively request the PIN from the user. The
896 | // method is only called when needed. For example, if a key specifies
897 | // PINPolicyOnce, PINPrompt will only be called once per YubiKey struct.
898 | PINPrompt func() (pin string, err error)
899 |
900 | // PINPolicy can be used to specify the PIN caching strategy for the slot. If
901 | // not provided, this will be inferred from the attestation certificate.
902 | //
903 | // This field is required on older (<4.3.0) YubiKeys when using PINPrompt,
904 | // as well as for keys imported to the card.
905 | PINPolicy PINPolicy
906 | }
907 |
908 | func (k KeyAuth) authTx(yk *YubiKey, pp PINPolicy) error {
909 | // PINPolicyNever shouldn't require a PIN.
910 | if pp == PINPolicyNever {
911 | return nil
912 | }
913 |
914 | // PINPolicyAlways should always prompt a PIN even if the key says that
915 | // login isn't needed.
916 | // https://github.com/go-piv/piv-go/issues/49
917 | if pp != PINPolicyAlways && !ykLoginNeeded(yk.tx) {
918 | return nil
919 | }
920 |
921 | pin := k.PIN
922 | if pin == "" && k.PINPrompt != nil {
923 | p, err := k.PINPrompt()
924 | if err != nil {
925 | return fmt.Errorf("pin prompt: %v", err)
926 | }
927 | pin = p
928 | }
929 | if pin == "" {
930 | return fmt.Errorf("pin required but wasn't provided")
931 | }
932 | return ykLogin(yk.tx, pin)
933 | }
934 |
935 | func (k KeyAuth) do(yk *YubiKey, pp PINPolicy, f func(tx *scTx) ([]byte, error)) ([]byte, error) {
936 | if err := k.authTx(yk, pp); err != nil {
937 | return nil, err
938 | }
939 | return f(yk.tx)
940 | }
941 |
942 | func pinPolicy(yk *YubiKey, slot Slot) (PINPolicy, error) {
943 | if supportsVersion(yk.version, 5, 3, 0) {
944 | info, err := yk.KeyInfo(slot)
945 | if err != nil {
946 | return 0, fmt.Errorf("get key info: %v", err)
947 | }
948 | return info.PINPolicy, nil
949 | }
950 | cert, err := yk.Attest(slot)
951 | if err != nil {
952 | var e *apduErr
953 | if errors.As(err, &e) && e.sw1 == 0x6d && e.sw2 == 0x00 {
954 | // Attestation cert command not supported, probably an older YubiKey.
955 | // Guess PINPolicyAlways.
956 | //
957 | // See https://github.com/go-piv/piv-go/issues/55
958 | return PINPolicyAlways, nil
959 | }
960 | return 0, fmt.Errorf("get attestation cert: %v", err)
961 | }
962 | a, err := parseAttestation(cert)
963 | if err != nil {
964 | return 0, fmt.Errorf("parse attestation cert: %v", err)
965 | }
966 | if _, ok := pinPolicyMap[a.PINPolicy]; ok {
967 | return a.PINPolicy, nil
968 | }
969 | return PINPolicyOnce, nil
970 | }
971 |
972 | // PrivateKey is used to access signing and decryption options for the key
973 | // stored in the slot. The returned key implements crypto.Signer and/or
974 | // crypto.Decrypter depending on the key type.
975 | //
976 | // If the public key hasn't been stored externally, it can be provided by
977 | // fetching the slot's attestation certificate:
978 | //
979 | // cert, err := yk.Attest(slot)
980 | // if err != nil {
981 | // // ...
982 | // }
983 | // priv, err := yk.PrivateKey(slot, cert.PublicKey, auth)
984 | func (yk *YubiKey) PrivateKey(slot Slot, public crypto.PublicKey, auth KeyAuth) (crypto.PrivateKey, error) {
985 | pp := PINPolicyNever
986 | if _, ok := pinPolicyMap[auth.PINPolicy]; ok {
987 | // If the PIN policy is manually specified, trust that value instead of
988 | // trying to use the attestation certificate.
989 | pp = auth.PINPolicy
990 | } else if auth.PIN != "" || auth.PINPrompt != nil {
991 | // Attempt to determine the key's PIN policy. This helps inform the
992 | // strategy for when to prompt for a PIN.
993 | policy, err := pinPolicy(yk, slot)
994 | if err != nil {
995 | return nil, err
996 | }
997 | pp = policy
998 | }
999 |
1000 | switch pub := public.(type) {
1001 | case *ecdsa.PublicKey:
1002 | return &ECDSAPrivateKey{yk, slot, pub, auth, pp}, nil
1003 | case ed25519.PublicKey:
1004 | return &keyEd25519{yk, slot, pub, auth, pp}, nil
1005 | case *rsa.PublicKey:
1006 | return &keyRSA{yk, slot, pub, auth, pp}, nil
1007 | case *ecdh.PublicKey:
1008 | if crv := pub.Curve(); crv != ecdh.X25519() {
1009 | return nil, fmt.Errorf("unsupported ecdh curve: %v", crv)
1010 | }
1011 | return &X25519PrivateKey{yk, slot, pub, auth, pp}, nil
1012 | default:
1013 | return nil, fmt.Errorf("unsupported public key type: %T", public)
1014 | }
1015 | }
1016 |
1017 | // SetPrivateKeyInsecure is an insecure method which imports a private key into the slot.
1018 | // Users should almost always use GeneratePrivateKey() instead.
1019 | //
1020 | // Importing a private key breaks functionality provided by this package, including
1021 | // AttestationCertificate() and Attest(). There are no stability guarantees for other
1022 | // methods for imported private keys.
1023 | //
1024 | // Keys generated outside of the YubiKey should not be considered hardware-backed,
1025 | // as there's no way to prove the key wasn't copied, exfiltrated, or replaced with malicious
1026 | // material before being imported.
1027 | func (yk *YubiKey) SetPrivateKeyInsecure(key []byte, slot Slot, private crypto.PrivateKey, policy Key) error {
1028 | // Reference implementation
1029 | // https://github.com/Yubico/yubico-piv-tool/blob/671a5740ef09d6c5d9d33f6e5575450750b58bde/lib/ykpiv.c#L1812
1030 |
1031 | params := make([][]byte, 0)
1032 |
1033 | var paramTag byte
1034 | var elemLen int
1035 |
1036 | switch priv := private.(type) {
1037 | case *rsa.PrivateKey:
1038 | paramTag = 0x01
1039 | switch priv.N.BitLen() {
1040 | case 1024:
1041 | policy.Algorithm = AlgorithmRSA1024
1042 | elemLen = 64
1043 | case 2048:
1044 | policy.Algorithm = AlgorithmRSA2048
1045 | elemLen = 128
1046 | case 3072:
1047 | policy.Algorithm = AlgorithmRSA3072
1048 | elemLen = 192
1049 | case 4096:
1050 | policy.Algorithm = AlgorithmRSA4096
1051 | elemLen = 256
1052 | default:
1053 | return errUnsupportedKeySize
1054 | }
1055 |
1056 | priv.Precompute()
1057 |
1058 | params = append(params, priv.Primes[0].Bytes()) // P
1059 | params = append(params, priv.Primes[1].Bytes()) // Q
1060 | params = append(params, priv.Precomputed.Dp.Bytes()) // dP
1061 | params = append(params, priv.Precomputed.Dq.Bytes()) // dQ
1062 | params = append(params, priv.Precomputed.Qinv.Bytes()) // Qinv
1063 | case *ecdsa.PrivateKey:
1064 | paramTag = 0x6
1065 | size := priv.PublicKey.Params().BitSize
1066 | switch size {
1067 | case 256:
1068 | policy.Algorithm = AlgorithmEC256
1069 | elemLen = 32
1070 | case 384:
1071 | policy.Algorithm = AlgorithmEC384
1072 | elemLen = 48
1073 | default:
1074 | return unsupportedCurveError{curve: size}
1075 | }
1076 |
1077 | // S value
1078 | privateKey := make([]byte, elemLen)
1079 | valueBytes := priv.D.Bytes()
1080 | padding := len(privateKey) - len(valueBytes)
1081 | copy(privateKey[padding:], valueBytes)
1082 |
1083 | params = append(params, privateKey)
1084 | case ed25519.PrivateKey:
1085 | paramTag = 0x07
1086 | elemLen = ed25519.SeedSize
1087 |
1088 | // seed
1089 | privateKey := make([]byte, elemLen)
1090 | copy(privateKey, priv[:32])
1091 | params = append(params, privateKey)
1092 | case *ecdh.PrivateKey:
1093 | if crv := priv.Curve(); crv != ecdh.X25519() {
1094 | return fmt.Errorf("unsupported ecdh curve: %v", crv)
1095 | }
1096 | paramTag = 0x08
1097 | elemLen = 32
1098 |
1099 | // seed
1100 | params = append(params, priv.Bytes())
1101 | default:
1102 | return fmt.Errorf("unsupported private key type: %T", private)
1103 | }
1104 |
1105 | elemLenASN1 := marshalASN1Length(uint64(elemLen))
1106 |
1107 | tags := make([]byte, 0)
1108 | for i, param := range params {
1109 | tag := paramTag + byte(i)
1110 | tags = append(tags, tag)
1111 | tags = append(tags, elemLenASN1...)
1112 |
1113 | padding := elemLen - len(param)
1114 | param = append(make([]byte, padding), param...)
1115 | tags = append(tags, param...)
1116 | }
1117 |
1118 | if err := ykAuthenticate(yk.tx, key, yk.rand, yk.version); err != nil {
1119 | return fmt.Errorf("authenticating with management key: %w", err)
1120 | }
1121 |
1122 | return ykImportKey(yk.tx, tags, slot, policy)
1123 | }
1124 |
1125 | func ykImportKey(tx *scTx, tags []byte, slot Slot, o Key) error {
1126 | alg, ok := algorithmsMap[o.Algorithm]
1127 | if !ok {
1128 | return fmt.Errorf("unsupported algorithm")
1129 | }
1130 | tp, ok := touchPolicyMap[o.TouchPolicy]
1131 | if !ok {
1132 | return fmt.Errorf("unsupported touch policy")
1133 | }
1134 | pp, ok := pinPolicyMap[o.PINPolicy]
1135 | if !ok {
1136 | return fmt.Errorf("unsupported pin policy")
1137 | }
1138 |
1139 | // This command is a Yubico PIV extension.
1140 | // https://developers.yubico.com/PIV/Introduction/Yubico_extensions.html
1141 | cmd := apdu{
1142 | instruction: insImportKey,
1143 | param1: alg,
1144 | param2: byte(slot.Key),
1145 | data: append(tags, []byte{
1146 | tagPINPolicy, 0x01, pp,
1147 | tagTouchPolicy, 0x01, tp,
1148 | }...),
1149 | }
1150 |
1151 | if _, err := tx.Transmit(cmd); err != nil {
1152 | return fmt.Errorf("command failed: %w", err)
1153 | }
1154 |
1155 | return nil
1156 | }
1157 |
1158 | // ECDSAPrivateKey is a crypto.PrivateKey implementation for ECDSA
1159 | // keys. It implements crypto.Signer and the method SharedKey performs
1160 | // Diffie-Hellman key agreements.
1161 | //
1162 | // Keys returned by YubiKey.PrivateKey() may be type asserted to
1163 | // *ECDSAPrivateKey, if the slot contains an ECDSA key.
1164 | type ECDSAPrivateKey struct {
1165 | yk *YubiKey
1166 | slot Slot
1167 | pub *ecdsa.PublicKey
1168 | auth KeyAuth
1169 | pp PINPolicy
1170 | }
1171 |
1172 | // Public returns the public key associated with this private key.
1173 | func (k *ECDSAPrivateKey) Public() crypto.PublicKey {
1174 | return k.pub
1175 | }
1176 |
1177 | var _ crypto.Signer = (*ECDSAPrivateKey)(nil)
1178 |
1179 | // Sign implements crypto.Signer.
1180 | func (k *ECDSAPrivateKey) Sign(rand io.Reader, digest []byte, opts crypto.SignerOpts) ([]byte, error) {
1181 | return k.auth.do(k.yk, k.pp, func(tx *scTx) ([]byte, error) {
1182 | return ykSignECDSA(tx, k.slot, k.pub, digest)
1183 | })
1184 | }
1185 |
1186 | // SharedKey performs a Diffie-Hellman key agreement with the peer
1187 | // to produce a shared secret key.
1188 | //
1189 | // Peer's public key must use the same algorithm as the key in
1190 | // this slot, or an error will be returned.
1191 | //
1192 | // Length of the result depends on the types and sizes of the keys
1193 | // used for the operation. Callers should use a cryptographic key
1194 | // derivation function to extract the amount of bytes they need.
1195 | func (k *ECDSAPrivateKey) SharedKey(peer *ecdsa.PublicKey) ([]byte, error) {
1196 | peerECDH, err := peer.ECDH()
1197 | if err != nil {
1198 | return nil, unsupportedCurveError{curve: peer.Params().BitSize}
1199 | }
1200 | return k.ECDH(peerECDH)
1201 | }
1202 |
1203 | // ECDH performs a Diffie-Hellman key agreement with the peer
1204 | // to produce a shared secret key.
1205 | //
1206 | // Peer's public key must use the same algorithm as the key in
1207 | // this slot, or an error will be returned.
1208 | //
1209 | // Length of the result depends on the types and sizes of the keys
1210 | // used for the operation. Callers should use a cryptographic key
1211 | // derivation function to extract the amount of bytes they need.
1212 | func (k *ECDSAPrivateKey) ECDH(peer *ecdh.PublicKey) ([]byte, error) {
1213 | ourECDH, err := k.pub.ECDH()
1214 | if err != nil {
1215 | return nil, unsupportedCurveError{curve: k.pub.Params().BitSize}
1216 | }
1217 | if peer.Curve() != ourECDH.Curve() {
1218 | return nil, errMismatchingAlgorithms
1219 | }
1220 | msg := peer.Bytes()
1221 | return k.auth.do(k.yk, k.pp, func(tx *scTx) ([]byte, error) {
1222 | var alg byte
1223 | size := k.pub.Params().BitSize
1224 | switch size {
1225 | case 256:
1226 | alg = algECCP256
1227 | case 384:
1228 | alg = algECCP384
1229 | default:
1230 | return nil, unsupportedCurveError{curve: size}
1231 | }
1232 |
1233 | // https://nvlpubs.nist.gov/nistpubs/SpecialPublications/NIST.SP.800-73-4.pdf#page=118
1234 | // https://nvlpubs.nist.gov/nistpubs/SpecialPublications/NIST.SP.800-73-4.pdf#page=93
1235 | cmd := apdu{
1236 | instruction: insAuthenticate,
1237 | param1: alg,
1238 | param2: byte(k.slot.Key),
1239 | data: marshalASN1(0x7c,
1240 | append([]byte{0x82, 0x00},
1241 | marshalASN1(0x85, msg)...)),
1242 | }
1243 | resp, err := tx.Transmit(cmd)
1244 | if err != nil {
1245 | return nil, fmt.Errorf("command failed: %w", err)
1246 | }
1247 | sig, _, err := unmarshalASN1(resp, 1, 0x1c) // 0x7c
1248 | if err != nil {
1249 | return nil, fmt.Errorf("unmarshal response: %v", err)
1250 | }
1251 | rs, _, err := unmarshalASN1(sig, 2, 0x02) // 0x82
1252 | if err != nil {
1253 | return nil, fmt.Errorf("unmarshal response signature: %v", err)
1254 | }
1255 | return rs, nil
1256 | })
1257 | }
1258 |
1259 | // X25519PrivateKey is a crypto.PrivateKey implementation for X25519 keys. It
1260 | // implements the method ECDH to perform Diffie-Hellman key agreements.
1261 | //
1262 | // Keys returned by YubiKey.PrivateKey() may be type asserted to
1263 | // *X25519PrivateKey, if the slot contains an X25519 key.
1264 | type X25519PrivateKey struct {
1265 | yk *YubiKey
1266 | slot Slot
1267 | pub *ecdh.PublicKey
1268 | auth KeyAuth
1269 | pp PINPolicy
1270 | }
1271 |
1272 | func (k *X25519PrivateKey) Public() crypto.PublicKey {
1273 | return k.pub
1274 | }
1275 |
1276 | // ECDH performs an ECDH exchange and returns the shared secret.
1277 | //
1278 | // Peer's public key must use the same algorithm as the key in this slot, or an
1279 | // error will be returned.
1280 | func (k *X25519PrivateKey) ECDH(peer *ecdh.PublicKey) ([]byte, error) {
1281 | return k.auth.do(k.yk, k.pp, func(tx *scTx) ([]byte, error) {
1282 | return ykECDHX25519(tx, k.slot, k.pub, peer)
1283 | })
1284 | }
1285 |
1286 | type keyEd25519 struct {
1287 | yk *YubiKey
1288 | slot Slot
1289 | pub ed25519.PublicKey
1290 | auth KeyAuth
1291 | pp PINPolicy
1292 | }
1293 |
1294 | func (k *keyEd25519) Public() crypto.PublicKey {
1295 | return k.pub
1296 | }
1297 |
1298 | func (k *keyEd25519) Sign(rand io.Reader, message []byte, opts crypto.SignerOpts) ([]byte, error) {
1299 | return k.auth.do(k.yk, k.pp, func(tx *scTx) ([]byte, error) {
1300 | return ykSignEd25519(tx, k.slot, k.pub, message, opts)
1301 | })
1302 | }
1303 |
1304 | type keyRSA struct {
1305 | yk *YubiKey
1306 | slot Slot
1307 | pub *rsa.PublicKey
1308 | auth KeyAuth
1309 | pp PINPolicy
1310 | }
1311 |
1312 | func (k *keyRSA) Public() crypto.PublicKey {
1313 | return k.pub
1314 | }
1315 |
1316 | func (k *keyRSA) Sign(rand io.Reader, digest []byte, opts crypto.SignerOpts) ([]byte, error) {
1317 | return k.auth.do(k.yk, k.pp, func(tx *scTx) ([]byte, error) {
1318 | return ykSignRSA(tx, rand, k.slot, k.pub, digest, opts)
1319 | })
1320 | }
1321 |
1322 | func (k *keyRSA) Decrypt(rand io.Reader, msg []byte, opts crypto.DecrypterOpts) ([]byte, error) {
1323 | return k.auth.do(k.yk, k.pp, func(tx *scTx) ([]byte, error) {
1324 | return ykDecryptRSA(tx, k.slot, k.pub, msg)
1325 | })
1326 | }
1327 |
1328 | func ykSignECDSA(tx *scTx, slot Slot, pub *ecdsa.PublicKey, digest []byte) ([]byte, error) {
1329 | var alg byte
1330 | size := pub.Params().BitSize
1331 | switch size {
1332 | case 256:
1333 | alg = algECCP256
1334 | case 384:
1335 | alg = algECCP384
1336 | default:
1337 | return nil, unsupportedCurveError{curve: size}
1338 | }
1339 |
1340 | // Same as the standard library
1341 | // https://github.com/golang/go/blob/go1.13.5/src/crypto/ecdsa/ecdsa.go#L125-L128
1342 | orderBytes := (size + 7) / 8
1343 | if len(digest) > orderBytes {
1344 | digest = digest[:orderBytes]
1345 | }
1346 |
1347 | // https://nvlpubs.nist.gov/nistpubs/SpecialPublications/NIST.SP.800-73-4.pdf#page=118
1348 | cmd := apdu{
1349 | instruction: insAuthenticate,
1350 | param1: alg,
1351 | param2: byte(slot.Key),
1352 | data: marshalASN1(0x7c,
1353 | append([]byte{0x82, 0x00},
1354 | marshalASN1(0x81, digest)...)),
1355 | }
1356 | resp, err := tx.Transmit(cmd)
1357 | if err != nil {
1358 | return nil, fmt.Errorf("command failed: %w", err)
1359 | }
1360 | sig, _, err := unmarshalASN1(resp, 1, 0x1c) // 0x7c
1361 | if err != nil {
1362 | return nil, fmt.Errorf("unmarshal response: %v", err)
1363 | }
1364 | rs, _, err := unmarshalASN1(sig, 2, 0x02) // 0x82
1365 | if err != nil {
1366 | return nil, fmt.Errorf("unmarshal response signature: %v", err)
1367 | }
1368 | return rs, nil
1369 | }
1370 |
1371 | func ykECDHX25519(tx *scTx, slot Slot, pub *ecdh.PublicKey, peer *ecdh.PublicKey) ([]byte, error) {
1372 | if crv := pub.Curve(); crv != ecdh.X25519() {
1373 | return nil, fmt.Errorf("unsupported ecdh curve: %v", crv)
1374 | }
1375 | if pub.Curve() != peer.Curve() {
1376 | return nil, errMismatchingAlgorithms
1377 | }
1378 | cmd := apdu{
1379 | instruction: insAuthenticate,
1380 | param1: algX25519,
1381 | param2: byte(slot.Key),
1382 | data: marshalASN1(0x7c,
1383 | append([]byte{0x82, 0x00},
1384 | marshalASN1(0x85, peer.Bytes())...)),
1385 | }
1386 | resp, err := tx.Transmit(cmd)
1387 | if err != nil {
1388 | return nil, fmt.Errorf("command failed: %w", err)
1389 | }
1390 |
1391 | sig, _, err := unmarshalASN1(resp, 1, 0x1c) // 0x7c
1392 | if err != nil {
1393 | return nil, fmt.Errorf("unmarshal response: %v", err)
1394 | }
1395 | sharedSecret, _, err := unmarshalASN1(sig, 2, 0x02) // 0x82
1396 | if err != nil {
1397 | return nil, fmt.Errorf("unmarshal response signature: %v", err)
1398 | }
1399 |
1400 | return sharedSecret, nil
1401 | }
1402 |
1403 | func ykSignEd25519(tx *scTx, slot Slot, pub ed25519.PublicKey, message []byte, opts crypto.SignerOpts) ([]byte, error) {
1404 | if opts.HashFunc() != crypto.Hash(0) {
1405 | return nil, fmt.Errorf("ed25519ph not supported")
1406 | }
1407 | if ed25519opts, ok := opts.(*ed25519.Options); ok && ed25519opts.Context != "" {
1408 | return nil, fmt.Errorf("ed25519ctx not supported")
1409 | }
1410 |
1411 | // Adaptation of
1412 | // https://nvlpubs.nist.gov/nistpubs/SpecialPublications/NIST.SP.800-73-4.pdf#page=118
1413 | cmd := apdu{
1414 | instruction: insAuthenticate,
1415 | param1: algEd25519,
1416 | param2: byte(slot.Key),
1417 | data: marshalASN1(0x7c,
1418 | append([]byte{0x82, 0x00},
1419 | marshalASN1(0x81, message)...)),
1420 | }
1421 | resp, err := tx.Transmit(cmd)
1422 | if err != nil {
1423 | return nil, fmt.Errorf("command failed: %w", err)
1424 | }
1425 | sig, _, err := unmarshalASN1(resp, 1, 0x1c) // 0x7c
1426 | if err != nil {
1427 | return nil, fmt.Errorf("unmarshal response: %v", err)
1428 | }
1429 | rs, _, err := unmarshalASN1(sig, 2, 0x02) // 0x82
1430 | if err != nil {
1431 | return nil, fmt.Errorf("unmarshal response signature: %v", err)
1432 | }
1433 | return rs, nil
1434 | }
1435 |
1436 | func unmarshalASN1(b []byte, class, tag int) (obj, rest []byte, err error) {
1437 | var v asn1.RawValue
1438 | rest, err = asn1.Unmarshal(b, &v)
1439 | if err != nil {
1440 | return nil, nil, err
1441 | }
1442 | if v.Class != class || v.Tag != tag {
1443 | return nil, nil, fmt.Errorf("unexpected class=%d and tag=0x%x", v.Class, v.Tag)
1444 | }
1445 | return v.Bytes, rest, nil
1446 | }
1447 |
1448 | func decodeECPublic(b []byte, curve elliptic.Curve) (*ecdsa.PublicKey, error) {
1449 | // https://nvlpubs.nist.gov/nistpubs/SpecialPublications/NIST.SP.800-73-4.pdf#page=95
1450 | p, _, err := unmarshalASN1(b, 2, 0x06)
1451 | if err != nil {
1452 | return nil, fmt.Errorf("unmarshal points: %v", err)
1453 | }
1454 | // https://nvlpubs.nist.gov/nistpubs/SpecialPublications/NIST.SP.800-73-4.pdf#page=96
1455 | size := curve.Params().BitSize / 8
1456 | if len(p) != (size*2)+1 {
1457 | return nil, fmt.Errorf("unexpected points length: %d", len(p))
1458 | }
1459 | // Are points uncompressed?
1460 | if p[0] != 0x04 {
1461 | return nil, fmt.Errorf("points were not uncompressed")
1462 | }
1463 | p = p[1:]
1464 | var x, y big.Int
1465 | x.SetBytes(p[:size])
1466 | y.SetBytes(p[size:])
1467 | if !curve.IsOnCurve(&x, &y) {
1468 | return nil, fmt.Errorf("resulting points are not on curve")
1469 | }
1470 | return &ecdsa.PublicKey{Curve: curve, X: &x, Y: &y}, nil
1471 | }
1472 |
1473 | func decodeEd25519Public(b []byte) (ed25519.PublicKey, error) {
1474 | // Adaptation of
1475 | // https://nvlpubs.nist.gov/nistpubs/SpecialPublications/NIST.SP.800-73-4.pdf#page=95
1476 | p, _, err := unmarshalASN1(b, 2, 0x06)
1477 | if err != nil {
1478 | return nil, fmt.Errorf("unmarshal points: %v", err)
1479 | }
1480 | if len(p) != ed25519.PublicKeySize {
1481 | return nil, fmt.Errorf("unexpected points length: %d", len(p))
1482 | }
1483 | return ed25519.PublicKey(p), nil
1484 | }
1485 |
1486 | func decodeRSAPublic(b []byte) (*rsa.PublicKey, error) {
1487 | // https://nvlpubs.nist.gov/nistpubs/SpecialPublications/NIST.SP.800-73-4.pdf#page=95
1488 | mod, r, err := unmarshalASN1(b, 2, 0x01)
1489 | if err != nil {
1490 | return nil, fmt.Errorf("unmarshal modulus: %v", err)
1491 | }
1492 | exp, _, err := unmarshalASN1(r, 2, 0x02)
1493 | if err != nil {
1494 | return nil, fmt.Errorf("unmarshal exponent: %v", err)
1495 | }
1496 | var n, e big.Int
1497 | n.SetBytes(mod)
1498 | e.SetBytes(exp)
1499 | if !e.IsInt64() {
1500 | return nil, fmt.Errorf("returned exponent too large: %s", e.String())
1501 | }
1502 | return &rsa.PublicKey{N: &n, E: int(e.Int64())}, nil
1503 | }
1504 |
1505 | func decodeX25519Public(b []byte) (*ecdh.PublicKey, error) {
1506 | // Adaptation of
1507 | // https://nvlpubs.nist.gov/nistpubs/SpecialPublications/NIST.SP.800-73-4.pdf#page=95
1508 | p, _, err := unmarshalASN1(b, 2, 0x06)
1509 | if err != nil {
1510 | return nil, fmt.Errorf("unmarshal points: %v", err)
1511 | }
1512 | return ecdh.X25519().NewPublicKey(p)
1513 | }
1514 |
1515 | func rsaAlg(pub *rsa.PublicKey) (byte, error) {
1516 | size := pub.N.BitLen()
1517 | switch size {
1518 | case 1024:
1519 | return algRSA1024, nil
1520 | case 2048:
1521 | return algRSA2048, nil
1522 | case 3072:
1523 | return algRSA3072, nil
1524 | case 4096:
1525 | return algRSA4096, nil
1526 | default:
1527 | return 0, fmt.Errorf("unsupported rsa key size: %d", size)
1528 | }
1529 | }
1530 |
1531 | func ykDecryptRSA(tx *scTx, slot Slot, pub *rsa.PublicKey, data []byte) ([]byte, error) {
1532 | alg, err := rsaAlg(pub)
1533 | if err != nil {
1534 | return nil, err
1535 | }
1536 | cmd := apdu{
1537 | instruction: insAuthenticate,
1538 | param1: alg,
1539 | param2: byte(slot.Key),
1540 | data: marshalASN1(0x7c,
1541 | append([]byte{0x82, 0x00},
1542 | marshalASN1(0x81, data)...)),
1543 | }
1544 | resp, err := tx.Transmit(cmd)
1545 | if err != nil {
1546 | return nil, fmt.Errorf("command failed: %w", err)
1547 | }
1548 |
1549 | sig, _, err := unmarshalASN1(resp, 1, 0x1c) // 0x7c
1550 | if err != nil {
1551 | return nil, fmt.Errorf("unmarshal response: %v", err)
1552 | }
1553 | decrypted, _, err := unmarshalASN1(sig, 2, 0x02) // 0x82
1554 | if err != nil {
1555 | return nil, fmt.Errorf("unmarshal response signature: %v", err)
1556 | }
1557 | // Decrypted blob contains a bunch of random data. Look for a NULL byte which
1558 | // indicates where the plain text starts.
1559 | for i := 2; i+1 < len(decrypted); i++ {
1560 | if decrypted[i] == 0x00 {
1561 | return decrypted[i+1:], nil
1562 | }
1563 | }
1564 | return nil, fmt.Errorf("invalid pkcs#1 v1.5 padding")
1565 | }
1566 |
1567 | // PKCS#1 v15 is largely informed by the standard library
1568 | // https://github.com/golang/go/blob/go1.13.5/src/crypto/rsa/pkcs1v15.go
1569 |
1570 | func ykSignRSA(tx *scTx, rand io.Reader, slot Slot, pub *rsa.PublicKey, digest []byte, opts crypto.SignerOpts) ([]byte, error) {
1571 | hash := opts.HashFunc()
1572 | if hash.Size() != len(digest) {
1573 | return nil, fmt.Errorf("input must be a hashed message")
1574 | }
1575 |
1576 | alg, err := rsaAlg(pub)
1577 | if err != nil {
1578 | return nil, err
1579 | }
1580 |
1581 | var data []byte
1582 | if o, ok := opts.(*rsa.PSSOptions); ok {
1583 | salt, err := rsafork.NewSalt(rand, pub, hash, o)
1584 | if err != nil {
1585 | return nil, err
1586 | }
1587 | em, err := rsafork.EMSAPSSEncode(digest, pub, salt, hash.New())
1588 | if err != nil {
1589 | return nil, err
1590 | }
1591 | data = em
1592 | } else {
1593 | prefix, ok := hashPrefixes[hash]
1594 | if !ok {
1595 | return nil, fmt.Errorf("unsupported hash algorithm: crypto.Hash(%d)", hash)
1596 | }
1597 |
1598 | // https://tools.ietf.org/pdf/rfc2313.pdf#page=9
1599 | d := make([]byte, len(prefix)+len(digest))
1600 | copy(d[:len(prefix)], prefix)
1601 | copy(d[len(prefix):], digest)
1602 |
1603 | paddingLen := pub.Size() - 3 - len(d)
1604 | if paddingLen < 0 {
1605 | return nil, fmt.Errorf("message too large")
1606 | }
1607 |
1608 | padding := make([]byte, paddingLen)
1609 | for i := range padding {
1610 | padding[i] = 0xff
1611 | }
1612 |
1613 | // https://tools.ietf.org/pdf/rfc2313.pdf#page=9
1614 | data = append([]byte{0x00, 0x01}, padding...)
1615 | data = append(data, 0x00)
1616 | data = append(data, d...)
1617 | }
1618 |
1619 | // https://nvlpubs.nist.gov/nistpubs/SpecialPublications/NIST.SP.800-73-4.pdf#page=117
1620 | cmd := apdu{
1621 | instruction: insAuthenticate,
1622 | param1: alg,
1623 | param2: byte(slot.Key),
1624 | data: marshalASN1(0x7c,
1625 | append([]byte{0x82, 0x00},
1626 | marshalASN1(0x81, data)...)),
1627 | }
1628 | resp, err := tx.Transmit(cmd)
1629 | if err != nil {
1630 | return nil, fmt.Errorf("command failed: %w", err)
1631 | }
1632 |
1633 | sig, _, err := unmarshalASN1(resp, 1, 0x1c) // 0x7c
1634 | if err != nil {
1635 | return nil, fmt.Errorf("unmarshal response: %v", err)
1636 | }
1637 | pkcs1v15Sig, _, err := unmarshalASN1(sig, 2, 0x02) // 0x82
1638 | if err != nil {
1639 | return nil, fmt.Errorf("unmarshal response signature: %v", err)
1640 | }
1641 | return pkcs1v15Sig, nil
1642 | }
1643 |
1644 | var hashPrefixes = map[crypto.Hash][]byte{
1645 | crypto.MD5: {0x30, 0x20, 0x30, 0x0c, 0x06, 0x08, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x02, 0x05, 0x05, 0x00, 0x04, 0x10},
1646 | crypto.SHA1: {0x30, 0x21, 0x30, 0x09, 0x06, 0x05, 0x2b, 0x0e, 0x03, 0x02, 0x1a, 0x05, 0x00, 0x04, 0x14},
1647 | crypto.SHA224: {0x30, 0x2d, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x04, 0x05, 0x00, 0x04, 0x1c},
1648 | crypto.SHA256: {0x30, 0x31, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x01, 0x05, 0x00, 0x04, 0x20},
1649 | crypto.SHA384: {0x30, 0x41, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x02, 0x05, 0x00, 0x04, 0x30},
1650 | crypto.SHA512: {0x30, 0x51, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x03, 0x05, 0x00, 0x04, 0x40},
1651 | crypto.MD5SHA1: {}, // A special TLS case which doesn't use an ASN1 prefix.
1652 | crypto.RIPEMD160: {0x30, 0x20, 0x30, 0x08, 0x06, 0x06, 0x28, 0xcf, 0x06, 0x03, 0x00, 0x31, 0x04, 0x14},
1653 | }
1654 |
--------------------------------------------------------------------------------
/v2/piv/key_test.go:
--------------------------------------------------------------------------------
1 | // Copyright 2020 Google LLC
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // https://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | package piv
16 |
17 | import (
18 | "bytes"
19 | "crypto"
20 | "crypto/ecdh"
21 | "crypto/ecdsa"
22 | "crypto/ed25519"
23 | "crypto/elliptic"
24 | "crypto/rand"
25 | "crypto/rsa"
26 | "crypto/sha256"
27 | "crypto/sha512"
28 | "crypto/tls"
29 | "crypto/x509"
30 | "crypto/x509/pkix"
31 | "encoding/asn1"
32 | "encoding/pem"
33 | "errors"
34 | "fmt"
35 | "io"
36 | "math/big"
37 | "reflect"
38 | "testing"
39 | "time"
40 | )
41 |
42 | func TestYubiKeySignECDSA(t *testing.T) {
43 | yk, close := newTestYubiKey(t)
44 | defer close()
45 |
46 | if err := yk.Reset(); err != nil {
47 | t.Fatalf("reset yubikey: %v", err)
48 | }
49 |
50 | slot := SlotAuthentication
51 |
52 | key := Key{
53 | Algorithm: AlgorithmEC256,
54 | TouchPolicy: TouchPolicyNever,
55 | PINPolicy: PINPolicyNever,
56 | }
57 | pubKey, err := yk.GenerateKey(DefaultManagementKey, slot, key)
58 | if err != nil {
59 | t.Fatalf("generating key: %v", err)
60 | }
61 | pub, ok := pubKey.(*ecdsa.PublicKey)
62 | if !ok {
63 | t.Fatalf("public key is not an ecdsa key")
64 | }
65 | data := sha256.Sum256([]byte("hello"))
66 | priv, err := yk.PrivateKey(slot, pub, KeyAuth{})
67 | if err != nil {
68 | t.Fatalf("getting private key: %v", err)
69 | }
70 | s, ok := priv.(crypto.Signer)
71 | if !ok {
72 | t.Fatalf("expected private key to implement crypto.Signer")
73 | }
74 | out, err := s.Sign(rand.Reader, data[:], crypto.SHA256)
75 | if err != nil {
76 | t.Fatalf("signing failed: %v", err)
77 | }
78 | var sig struct {
79 | R, S *big.Int
80 | }
81 | if _, err := asn1.Unmarshal(out, &sig); err != nil {
82 | t.Fatalf("unmarshaling signature: %v", err)
83 | }
84 | if !ecdsa.Verify(pub, data[:], sig.R, sig.S) {
85 | t.Errorf("signature didn't match")
86 | }
87 | }
88 |
89 | func TestYubiKeyECDSAECDH(t *testing.T) {
90 | yk, close := newTestYubiKey(t)
91 | defer close()
92 |
93 | slot := SlotAuthentication
94 |
95 | key := Key{
96 | Algorithm: AlgorithmEC256,
97 | TouchPolicy: TouchPolicyNever,
98 | PINPolicy: PINPolicyNever,
99 | }
100 | pubKey, err := yk.GenerateKey(DefaultManagementKey, slot, key)
101 | if err != nil {
102 | t.Fatalf("generating key: %v", err)
103 | }
104 | pub, ok := pubKey.(*ecdsa.PublicKey)
105 | if !ok {
106 | t.Fatalf("public key is not an ecdsa key")
107 | }
108 | pubECDH, err := pub.ECDH()
109 | if err != nil {
110 | t.Fatalf("converting pubkey to ECDH key: %v", err)
111 | }
112 | priv, err := yk.PrivateKey(slot, pub, KeyAuth{})
113 | if err != nil {
114 | t.Fatalf("getting private key: %v", err)
115 | }
116 | privECDSA, ok := priv.(*ECDSAPrivateKey)
117 | if !ok {
118 | t.Fatalf("expected private key to be ECDSA private key")
119 | }
120 |
121 | t.Run("good", func(t *testing.T) {
122 | privECDH, err := ecdh.P256().GenerateKey(rand.Reader)
123 | if err != nil {
124 | t.Fatalf("cannot generate key: %v", err)
125 | }
126 | secret1, err := privECDH.ECDH(pubECDH)
127 | if err != nil {
128 | t.Fatalf("key agreement 1 failed: %v", err)
129 | }
130 |
131 | secret2, err := privECDSA.ECDH(privECDH.PublicKey())
132 | if err != nil {
133 | t.Fatalf("key agreement 2 failed: %v", err)
134 | }
135 | if !bytes.Equal(secret1, secret2) {
136 | t.Errorf("key agreement didn't match")
137 | }
138 | })
139 |
140 | t.Run("bad", func(t *testing.T) {
141 | t.Run("size", func(t *testing.T) {
142 | privECDH, err := ecdh.P384().GenerateKey(rand.Reader)
143 | if err != nil {
144 | t.Fatalf("cannot generate key: %v", err)
145 | }
146 | _, err = privECDSA.ECDH(privECDH.PublicKey())
147 | if !errors.Is(err, errMismatchingAlgorithms) {
148 | t.Fatalf("unexpected error value: wanted errMismatchingAlgorithms: %v", err)
149 | }
150 | })
151 | })
152 | }
153 |
154 | func TestYubiKeyECDSASharedKey(t *testing.T) {
155 | yk, close := newTestYubiKey(t)
156 | defer close()
157 |
158 | slot := SlotAuthentication
159 |
160 | key := Key{
161 | Algorithm: AlgorithmEC256,
162 | TouchPolicy: TouchPolicyNever,
163 | PINPolicy: PINPolicyNever,
164 | }
165 | pubKey, err := yk.GenerateKey(DefaultManagementKey, slot, key)
166 | if err != nil {
167 | t.Fatalf("generating key: %v", err)
168 | }
169 | pub, ok := pubKey.(*ecdsa.PublicKey)
170 | if !ok {
171 | t.Fatalf("public key is not an ecdsa key")
172 | }
173 | priv, err := yk.PrivateKey(slot, pub, KeyAuth{})
174 | if err != nil {
175 | t.Fatalf("getting private key: %v", err)
176 | }
177 | privECDSA, ok := priv.(*ECDSAPrivateKey)
178 | if !ok {
179 | t.Fatalf("expected private key to be ECDSA private key")
180 | }
181 |
182 | t.Run("good", func(t *testing.T) {
183 | eph, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
184 | if err != nil {
185 | t.Fatalf("cannot generate key: %v", err)
186 | }
187 | mult, _ := pub.ScalarMult(pub.X, pub.Y, eph.D.Bytes())
188 | secret1 := mult.Bytes()
189 |
190 | secret2, err := privECDSA.SharedKey(&eph.PublicKey)
191 | if err != nil {
192 | t.Fatalf("key agreement failed: %v", err)
193 | }
194 | if !bytes.Equal(secret1, secret2) {
195 | t.Errorf("key agreement didn't match")
196 | }
197 | })
198 |
199 | t.Run("bad", func(t *testing.T) {
200 | t.Run("size", func(t *testing.T) {
201 | eph, err := ecdsa.GenerateKey(elliptic.P384(), rand.Reader)
202 | if err != nil {
203 | t.Fatalf("cannot generate key: %v", err)
204 | }
205 | _, err = privECDSA.SharedKey(&eph.PublicKey)
206 | if !errors.Is(err, errMismatchingAlgorithms) {
207 | t.Fatalf("unexpected error value: wanted errMismatchingAlgorithms: %v", err)
208 | }
209 | })
210 | })
211 | }
212 |
213 | func TestYubiKeyX25519ECDH(t *testing.T) {
214 | yk, close := newTestYubiKey(t)
215 | defer close()
216 |
217 | slot := SlotAuthentication
218 |
219 | key := Key{
220 | Algorithm: AlgorithmX25519,
221 | TouchPolicy: TouchPolicyNever,
222 | PINPolicy: PINPolicyNever,
223 | }
224 | pubKey, err := yk.GenerateKey(DefaultManagementKey, slot, key)
225 | if err != nil {
226 | t.Fatalf("generating key: %v", err)
227 | }
228 | pub, ok := pubKey.(*ecdh.PublicKey)
229 | if !ok {
230 | t.Fatalf("public key is not an ecdh key")
231 | }
232 | priv, err := yk.PrivateKey(slot, pub, KeyAuth{})
233 | if err != nil {
234 | t.Fatalf("getting private key: %v", err)
235 | }
236 | privX25519, ok := priv.(*X25519PrivateKey)
237 | if !ok {
238 | t.Fatalf("expected private key to be X25519 private key")
239 | }
240 |
241 | t.Run("good", func(t *testing.T) {
242 | peer, err := ecdh.X25519().GenerateKey(rand.Reader)
243 | if err != nil {
244 | t.Fatalf("cannot generate key: %v", err)
245 | }
246 |
247 | secret1, err := privX25519.ECDH(peer.PublicKey())
248 | if err != nil {
249 | t.Fatalf("key agreement failed: %v", err)
250 | }
251 | secret2, err := peer.ECDH(pub)
252 | if err != nil {
253 | t.Fatalf("key agreement failed: %v", err)
254 | }
255 | if !bytes.Equal(secret1, secret2) {
256 | t.Errorf("key agreement didn't match")
257 | }
258 | })
259 |
260 | t.Run("bad", func(t *testing.T) {
261 | t.Run("curve", func(t *testing.T) {
262 | peer, err := ecdh.P256().GenerateKey(rand.Reader)
263 | if err != nil {
264 | t.Fatalf("cannot generate key: %v", err)
265 | }
266 | _, err = privX25519.ECDH(peer.PublicKey())
267 | if !errors.Is(err, errMismatchingAlgorithms) {
268 | t.Fatalf("unexpected error value: wanted errMismatchingAlgorithms: %v", err)
269 | }
270 | })
271 | })
272 | }
273 |
274 | func TestYubiKeySignEd25519(t *testing.T) {
275 | yk, close := newTestYubiKey(t)
276 | defer close()
277 | testRequiresVersion(t, yk, version57)
278 |
279 | if err := yk.Reset(); err != nil {
280 | t.Fatalf("reset yubikey: %v", err)
281 | }
282 |
283 | slot := SlotAuthentication
284 |
285 | key := Key{
286 | Algorithm: AlgorithmEd25519,
287 | TouchPolicy: TouchPolicyNever,
288 | PINPolicy: PINPolicyNever,
289 | }
290 | pubKey, err := yk.GenerateKey(DefaultManagementKey, slot, key)
291 | if err != nil {
292 | t.Fatalf("generating key: %v", err)
293 | }
294 | pub, ok := pubKey.(ed25519.PublicKey)
295 | if !ok {
296 | t.Fatalf("public key is not an ecdsa key")
297 | }
298 | data := []byte("hello")
299 | priv, err := yk.PrivateKey(slot, pub, KeyAuth{})
300 | if err != nil {
301 | t.Fatalf("getting private key: %v", err)
302 | }
303 | s, ok := priv.(crypto.Signer)
304 | if !ok {
305 | t.Fatalf("expected private key to implement crypto.Signer")
306 | }
307 |
308 | t.Run("good", func(t *testing.T) {
309 | sig, err := s.Sign(rand.Reader, data, crypto.Hash(0))
310 | if err != nil {
311 | t.Fatalf("signing failed: %v", err)
312 | }
313 | if !ed25519.Verify(pub, data, sig) {
314 | t.Errorf("signature didn't match")
315 | }
316 | })
317 | t.Run("unsupported_ed25519ph", func(t *testing.T) {
318 | digest := sha512.Sum512(data)
319 | _, err := s.Sign(rand.Reader, digest[:], crypto.SHA512)
320 | if err == nil {
321 | t.Fatalf("expected signing with Ed25519ph to fail")
322 | }
323 | })
324 | t.Run("unsupported_ed25519ctx", func(t *testing.T) {
325 | _, err := s.Sign(rand.Reader, data, &ed25519.Options{Context: "test"})
326 | if err == nil {
327 | t.Fatalf("expected signing with Ed25519ctx to fail")
328 | }
329 | })
330 | }
331 |
332 | func TestPINPrompt(t *testing.T) {
333 | tests := []struct {
334 | name string
335 | policy PINPolicy
336 | want int
337 | }{
338 | {"Never", PINPolicyNever, 0},
339 | {"Once", PINPolicyOnce, 1},
340 | {"Always", PINPolicyAlways, 2},
341 | }
342 | for _, test := range tests {
343 | t.Run(test.name, func(t *testing.T) {
344 | yk, close := newTestYubiKey(t)
345 | defer close()
346 |
347 | k := Key{
348 | Algorithm: AlgorithmEC256,
349 | PINPolicy: test.policy,
350 | TouchPolicy: TouchPolicyNever,
351 | }
352 | pub, err := yk.GenerateKey(DefaultManagementKey, SlotAuthentication, k)
353 | if err != nil {
354 | t.Fatalf("generating key on slot: %v", err)
355 | }
356 | got := 0
357 | auth := KeyAuth{
358 | PINPrompt: func() (string, error) {
359 | got++
360 | return DefaultPIN, nil
361 | },
362 | }
363 |
364 | if !supportsAttestation(yk) {
365 | auth.PINPolicy = test.policy
366 | }
367 |
368 | priv, err := yk.PrivateKey(SlotAuthentication, pub, auth)
369 | if err != nil {
370 | t.Fatalf("building private key: %v", err)
371 | }
372 | s, ok := priv.(crypto.Signer)
373 | if !ok {
374 | t.Fatalf("expected crypto.Signer got %T", priv)
375 | }
376 | data := sha256.Sum256([]byte("foo"))
377 | if _, err := s.Sign(rand.Reader, data[:], crypto.SHA256); err != nil {
378 | t.Errorf("signing error: %v", err)
379 | }
380 | if _, err := s.Sign(rand.Reader, data[:], crypto.SHA256); err != nil {
381 | t.Errorf("signing error: %v", err)
382 | }
383 | if got != test.want {
384 | t.Errorf("PINPrompt called %d times, want=%d", got, test.want)
385 | }
386 | })
387 | }
388 | }
389 |
390 | func supportsAttestation(yk *YubiKey) bool {
391 | return supportsVersion(yk.version, 4, 3, 0)
392 | }
393 |
394 | func TestSlots(t *testing.T) {
395 | yk, close := newTestYubiKey(t)
396 | if err := yk.Reset(); err != nil {
397 | t.Fatalf("resetting yubikey: %v", err)
398 | }
399 | close()
400 |
401 | tests := []struct {
402 | name string
403 | slot Slot
404 | }{
405 | {"Authentication", SlotAuthentication},
406 | {"CardAuthentication", SlotCardAuthentication},
407 | {"KeyManagement", SlotKeyManagement},
408 | {"Signature", SlotSignature},
409 | }
410 |
411 | for _, test := range tests {
412 | t.Run(test.name, func(t *testing.T) {
413 | yk, close := newTestYubiKey(t)
414 | defer close()
415 |
416 | if supportsAttestation(yk) {
417 | if _, err := yk.Attest(test.slot); err == nil || !errors.Is(err, ErrNotFound) {
418 | t.Errorf("attest: got err=%v, want=ErrNotFound", err)
419 | }
420 | }
421 |
422 | k := Key{
423 | Algorithm: AlgorithmEC256,
424 | PINPolicy: PINPolicyNever,
425 | TouchPolicy: TouchPolicyNever,
426 | }
427 | pub, err := yk.GenerateKey(DefaultManagementKey, test.slot, k)
428 | if err != nil {
429 | t.Fatalf("generating key on slot: %v", err)
430 | }
431 |
432 | if supportsAttestation(yk) {
433 | if _, err := yk.Attest(test.slot); err != nil {
434 | t.Errorf("attest: %v", err)
435 | }
436 | }
437 |
438 | priv, err := yk.PrivateKey(test.slot, pub, KeyAuth{PIN: DefaultPIN})
439 | if err != nil {
440 | t.Fatalf("private key: %v", err)
441 | }
442 |
443 | tmpl := &x509.Certificate{
444 | Subject: pkix.Name{CommonName: "my-client"},
445 | SerialNumber: big.NewInt(1),
446 | NotBefore: time.Now(),
447 | NotAfter: time.Now().Add(time.Hour),
448 | KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature,
449 | ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth},
450 | }
451 | raw, err := x509.CreateCertificate(rand.Reader, tmpl, tmpl, pub, priv)
452 | if err != nil {
453 | t.Fatalf("signing self-signed certificate: %v", err)
454 | }
455 | cert, err := x509.ParseCertificate(raw)
456 | if err != nil {
457 | t.Fatalf("parse certificate: %v", err)
458 | }
459 |
460 | if _, err := yk.Certificate(test.slot); err == nil || !errors.Is(err, ErrNotFound) {
461 | t.Errorf("get certificate, got err=%v, want=ErrNotFound", err)
462 | }
463 | if err := yk.SetCertificate(DefaultManagementKey, test.slot, cert); err != nil {
464 | t.Fatalf("set certificate: %v", err)
465 | }
466 | got, err := yk.Certificate(test.slot)
467 | if err != nil {
468 | t.Fatalf("get certifiate: %v", err)
469 | }
470 | if !bytes.Equal(got.Raw, raw) {
471 | t.Errorf("certificate from slot didn't match the certificate written")
472 | }
473 | })
474 | }
475 | }
476 |
477 | func TestYubiKeySignRSA(t *testing.T) {
478 | tests := []struct {
479 | name string
480 | alg Algorithm
481 | long bool
482 | version version
483 | }{
484 | {"rsa1024", AlgorithmRSA1024, false, version{}},
485 | {"rsa2048", AlgorithmRSA2048, true, version{}},
486 | {"rsa3072", AlgorithmRSA3072, true, version57},
487 | {"rsa4096", AlgorithmRSA4096, true, version57},
488 | }
489 | for _, test := range tests {
490 | t.Run(test.name, func(t *testing.T) {
491 | if test.long && testing.Short() {
492 | t.Skip("skipping test in short mode")
493 | }
494 | yk, close := newTestYubiKey(t)
495 | defer close()
496 | testRequiresVersion(t, yk, test.version)
497 | slot := SlotAuthentication
498 | key := Key{
499 | Algorithm: test.alg,
500 | TouchPolicy: TouchPolicyNever,
501 | PINPolicy: PINPolicyNever,
502 | }
503 | pubKey, err := yk.GenerateKey(DefaultManagementKey, slot, key)
504 | if err != nil {
505 | t.Fatalf("generating key: %v", err)
506 | }
507 | pub, ok := pubKey.(*rsa.PublicKey)
508 | if !ok {
509 | t.Fatalf("public key is not an rsa key")
510 | }
511 | data := sha256.Sum256([]byte("hello"))
512 | priv, err := yk.PrivateKey(slot, pub, KeyAuth{})
513 | if err != nil {
514 | t.Fatalf("getting private key: %v", err)
515 | }
516 | s, ok := priv.(crypto.Signer)
517 | if !ok {
518 | t.Fatalf("private key didn't implement crypto.Signer")
519 | }
520 | out, err := s.Sign(rand.Reader, data[:], crypto.SHA256)
521 | if err != nil {
522 | t.Fatalf("signing failed: %v", err)
523 | }
524 | if err := rsa.VerifyPKCS1v15(pub, crypto.SHA256, data[:], out); err != nil {
525 | t.Errorf("failed to verify signature: %v", err)
526 | }
527 | })
528 | }
529 | }
530 |
531 | func TestYubiKeySignRSAPSS(t *testing.T) {
532 | tests := []struct {
533 | name string
534 | alg Algorithm
535 | long bool
536 | version version
537 | }{
538 | {"rsa1024", AlgorithmRSA1024, false, version{}},
539 | {"rsa2048", AlgorithmRSA2048, true, version{}},
540 | {"rsa3072", AlgorithmRSA3072, true, version57},
541 | {"rsa4096", AlgorithmRSA4096, true, version57},
542 | }
543 | for _, test := range tests {
544 | t.Run(test.name, func(t *testing.T) {
545 | if test.long && testing.Short() {
546 | t.Skip("skipping test in short mode")
547 | }
548 | yk, close := newTestYubiKey(t)
549 | defer close()
550 | testRequiresVersion(t, yk, test.version)
551 | slot := SlotAuthentication
552 | key := Key{
553 | Algorithm: test.alg,
554 | TouchPolicy: TouchPolicyNever,
555 | PINPolicy: PINPolicyNever,
556 | }
557 | pubKey, err := yk.GenerateKey(DefaultManagementKey, slot, key)
558 | if err != nil {
559 | t.Fatalf("generating key: %v", err)
560 | }
561 | pub, ok := pubKey.(*rsa.PublicKey)
562 | if !ok {
563 | t.Fatalf("public key is not an rsa key")
564 | }
565 | data := sha256.Sum256([]byte("hello"))
566 | priv, err := yk.PrivateKey(slot, pub, KeyAuth{})
567 | if err != nil {
568 | t.Fatalf("getting private key: %v", err)
569 | }
570 | s, ok := priv.(crypto.Signer)
571 | if !ok {
572 | t.Fatalf("private key didn't implement crypto.Signer")
573 | }
574 |
575 | opt := &rsa.PSSOptions{Hash: crypto.SHA256}
576 | out, err := s.Sign(rand.Reader, data[:], opt)
577 | if err != nil {
578 | t.Fatalf("signing failed: %v", err)
579 | }
580 | if err := rsa.VerifyPSS(pub, crypto.SHA256, data[:], out, opt); err != nil {
581 | t.Errorf("failed to verify signature: %v", err)
582 | }
583 | })
584 | }
585 | }
586 |
587 | func TestTLS13(t *testing.T) {
588 | yk, close := newTestYubiKey(t)
589 | defer close()
590 | slot := SlotAuthentication
591 | key := Key{
592 | Algorithm: AlgorithmRSA1024,
593 | TouchPolicy: TouchPolicyNever,
594 | PINPolicy: PINPolicyNever,
595 | }
596 | pub, err := yk.GenerateKey(DefaultManagementKey, slot, key)
597 | if err != nil {
598 | t.Fatalf("generating key: %v", err)
599 | }
600 | priv, err := yk.PrivateKey(slot, pub, KeyAuth{})
601 | if err != nil {
602 | t.Fatalf("getting private key: %v", err)
603 | }
604 |
605 | tmpl := &x509.Certificate{
606 | Subject: pkix.Name{CommonName: "test"},
607 | SerialNumber: big.NewInt(100),
608 | NotBefore: time.Now(),
609 | NotAfter: time.Now().Add(time.Hour),
610 | DNSNames: []string{"example.com"},
611 | KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature,
612 | ExtKeyUsage: []x509.ExtKeyUsage{
613 | x509.ExtKeyUsageClientAuth,
614 | x509.ExtKeyUsageServerAuth,
615 | },
616 | }
617 |
618 | rawCert, err := x509.CreateCertificate(rand.Reader, tmpl, tmpl, pub, priv)
619 | if err != nil {
620 | t.Fatalf("creating certificate: %v", err)
621 | }
622 | x509Cert, err := x509.ParseCertificate(rawCert)
623 | if err != nil {
624 | t.Fatalf("parsing cert: %v", err)
625 | }
626 | cert := tls.Certificate{
627 | Certificate: [][]byte{rawCert},
628 | PrivateKey: priv,
629 | SupportedSignatureAlgorithms: []tls.SignatureScheme{
630 | tls.PSSWithSHA256,
631 | },
632 | }
633 | pool := x509.NewCertPool()
634 | pool.AddCert(x509Cert)
635 |
636 | cliConf := &tls.Config{
637 | Certificates: []tls.Certificate{cert},
638 | RootCAs: pool,
639 | ServerName: "example.com",
640 | MinVersion: tls.VersionTLS13,
641 | MaxVersion: tls.VersionTLS13,
642 | }
643 | srvConf := &tls.Config{
644 | Certificates: []tls.Certificate{cert},
645 | ClientCAs: pool,
646 | ClientAuth: tls.RequireAndVerifyClientCert,
647 | MinVersion: tls.VersionTLS13,
648 | MaxVersion: tls.VersionTLS13,
649 | }
650 |
651 | srv, err := tls.Listen("tcp", "0.0.0.0:0", srvConf)
652 | if err != nil {
653 | t.Fatalf("creating tls listener: %v", err)
654 | }
655 | defer srv.Close()
656 |
657 | errCh := make(chan error, 2)
658 |
659 | want := []byte("hello, world")
660 |
661 | go func() {
662 | conn, err := srv.Accept()
663 | if err != nil {
664 | errCh <- fmt.Errorf("accepting conn: %v", err)
665 | return
666 | }
667 | defer conn.Close()
668 |
669 | got := make([]byte, len(want))
670 | if _, err := io.ReadFull(conn, got); err != nil {
671 | errCh <- fmt.Errorf("read data: %v", err)
672 | return
673 | }
674 | if !bytes.Equal(want, got) {
675 | errCh <- fmt.Errorf("unexpected value read: %s", got)
676 | return
677 | }
678 | errCh <- nil
679 | }()
680 |
681 | go func() {
682 | conn, err := tls.Dial("tcp", srv.Addr().String(), cliConf)
683 | if err != nil {
684 | errCh <- fmt.Errorf("dial: %v", err)
685 | return
686 | }
687 | defer conn.Close()
688 |
689 | if v := conn.ConnectionState().Version; v != tls.VersionTLS13 {
690 | errCh <- fmt.Errorf("client got verison 0x%x, want=0x%x", v, tls.VersionTLS13)
691 | return
692 | }
693 |
694 | if _, err := conn.Write(want); err != nil {
695 | errCh <- fmt.Errorf("write: %v", err)
696 | return
697 | }
698 | errCh <- nil
699 | }()
700 |
701 | for i := 0; i < 2; i++ {
702 | if err := <-errCh; err != nil {
703 | t.Fatalf("%v", err)
704 | }
705 | }
706 | }
707 |
708 | func TestYubiKeyDecryptRSA(t *testing.T) {
709 | tests := []struct {
710 | name string
711 | alg Algorithm
712 | long bool
713 | version version
714 | }{
715 | {"rsa1024", AlgorithmRSA1024, false, version{}},
716 | {"rsa2048", AlgorithmRSA2048, true, version{}},
717 | {"rsa3072", AlgorithmRSA3072, true, version57},
718 | {"rsa4096", AlgorithmRSA4096, true, version57},
719 | }
720 | for _, test := range tests {
721 | t.Run(test.name, func(t *testing.T) {
722 | if test.long && testing.Short() {
723 | t.Skip("skipping test in short mode")
724 | }
725 | yk, close := newTestYubiKey(t)
726 | defer close()
727 | testRequiresVersion(t, yk, test.version)
728 | slot := SlotAuthentication
729 | key := Key{
730 | Algorithm: test.alg,
731 | TouchPolicy: TouchPolicyNever,
732 | PINPolicy: PINPolicyNever,
733 | }
734 | pubKey, err := yk.GenerateKey(DefaultManagementKey, slot, key)
735 | if err != nil {
736 | t.Fatalf("generating key: %v", err)
737 | }
738 | pub, ok := pubKey.(*rsa.PublicKey)
739 | if !ok {
740 | t.Fatalf("public key is not an rsa key")
741 | }
742 |
743 | data := []byte("hello")
744 | ct, err := rsa.EncryptPKCS1v15(rand.Reader, pub, data)
745 | if err != nil {
746 | t.Fatalf("encryption failed: %v", err)
747 | }
748 |
749 | priv, err := yk.PrivateKey(slot, pub, KeyAuth{})
750 | if err != nil {
751 | t.Fatalf("getting private key: %v", err)
752 | }
753 | d, ok := priv.(crypto.Decrypter)
754 | if !ok {
755 | t.Fatalf("private key didn't implement crypto.Decypter")
756 | }
757 | got, err := d.Decrypt(rand.Reader, ct, nil)
758 | if err != nil {
759 | t.Fatalf("decryption failed: %v", err)
760 | }
761 | if !bytes.Equal(data, got) {
762 | t.Errorf("decrypt, got=%q, want=%q", got, data)
763 | }
764 | })
765 | }
766 | }
767 |
768 | func TestYubiKeyAttestation(t *testing.T) {
769 | yk, close := newTestYubiKey(t)
770 | defer close()
771 | key := Key{
772 | Algorithm: AlgorithmEC256,
773 | PINPolicy: PINPolicyNever,
774 | TouchPolicy: TouchPolicyNever,
775 | }
776 |
777 | testRequiresVersion(t, yk, version43)
778 |
779 | cert, err := yk.AttestationCertificate()
780 | if err != nil {
781 | t.Fatalf("getting attestation certificate: %v", err)
782 | }
783 |
784 | pub, err := yk.GenerateKey(DefaultManagementKey, SlotAuthentication, key)
785 | if err != nil {
786 | t.Fatalf("generate key: %v", err)
787 | }
788 | _ = pub
789 | c, err := yk.Attest(SlotAuthentication)
790 | if err != nil {
791 | t.Fatalf("attesting key: %v", err)
792 | }
793 | a, err := Verify(cert, c)
794 | if err != nil {
795 | t.Fatalf("failed to verify attestation: %v", err)
796 | }
797 | serial, err := yk.Serial()
798 | if err != nil {
799 | t.Errorf("getting serial number: %v", err)
800 | } else if a.Serial != serial {
801 | t.Errorf("attestation serial got=%d, wanted=%d", a.Serial, serial)
802 | }
803 |
804 | if a.PINPolicy != key.PINPolicy {
805 | t.Errorf("attestation pin policy got=0x%x, wanted=0x%x", a.TouchPolicy, key.PINPolicy)
806 | }
807 | if a.TouchPolicy != key.TouchPolicy {
808 | t.Errorf("attestation touch policy got=0x%x, wanted=0x%x", a.TouchPolicy, key.TouchPolicy)
809 | }
810 | if a.Version != yk.Version() {
811 | t.Errorf("attestation version got=%#v, wanted=%#v", a.Version, yk.Version())
812 | }
813 | if a.Slot != SlotAuthentication {
814 | t.Errorf("attested slot got=%v, wanted=%v", a.Slot, SlotAuthentication)
815 | }
816 | if a.Slot.String() != "9a" {
817 | t.Errorf("attested slot name got=%s, wanted=%s", a.Slot.String(), "9a")
818 | }
819 | }
820 |
821 | func TestYubiKeyStoreCertificate(t *testing.T) {
822 | yk, close := newTestYubiKey(t)
823 | defer close()
824 | slot := SlotAuthentication
825 |
826 | caPriv, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
827 | if err != nil {
828 | t.Fatalf("generating ca private: %v", err)
829 | }
830 | // Generate a self-signed certificate
831 | caTmpl := &x509.Certificate{
832 | Subject: pkix.Name{CommonName: "my-ca"},
833 | SerialNumber: big.NewInt(100),
834 | BasicConstraintsValid: true,
835 | IsCA: true,
836 | NotBefore: time.Now(),
837 | NotAfter: time.Now().Add(time.Hour),
838 | KeyUsage: x509.KeyUsageKeyEncipherment |
839 | x509.KeyUsageDigitalSignature |
840 | x509.KeyUsageCertSign,
841 | }
842 | caCertDER, err := x509.CreateCertificate(rand.Reader, caTmpl, caTmpl, caPriv.Public(), caPriv)
843 | if err != nil {
844 | t.Fatalf("generating self-signed certificate: %v", err)
845 | }
846 | caCert, err := x509.ParseCertificate(caCertDER)
847 | if err != nil {
848 | t.Fatalf("parsing ca cert: %v", err)
849 | }
850 |
851 | key := Key{
852 | Algorithm: AlgorithmEC256,
853 | TouchPolicy: TouchPolicyNever,
854 | PINPolicy: PINPolicyNever,
855 | }
856 | pub, err := yk.GenerateKey(DefaultManagementKey, slot, key)
857 | if err != nil {
858 | t.Fatalf("generating key: %v", err)
859 | }
860 |
861 | cliTmpl := &x509.Certificate{
862 | Subject: pkix.Name{CommonName: "my-client"},
863 | SerialNumber: big.NewInt(101),
864 | NotBefore: time.Now(),
865 | NotAfter: time.Now().Add(time.Hour),
866 | KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature,
867 | ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth},
868 | }
869 | cliCertDER, err := x509.CreateCertificate(rand.Reader, cliTmpl, caCert, pub, caPriv)
870 | if err != nil {
871 | t.Fatalf("creating client cert: %v", err)
872 | }
873 | cliCert, err := x509.ParseCertificate(cliCertDER)
874 | if err != nil {
875 | t.Fatalf("parsing cli cert: %v", err)
876 | }
877 | if err := yk.SetCertificate(DefaultManagementKey, slot, cliCert); err != nil {
878 | t.Fatalf("storing client cert: %v", err)
879 | }
880 | gotCert, err := yk.Certificate(slot)
881 | if err != nil {
882 | t.Fatalf("getting client cert: %v", err)
883 | }
884 | if !bytes.Equal(gotCert.Raw, cliCert.Raw) {
885 | t.Errorf("stored cert didn't match cert retrieved")
886 | }
887 | }
888 |
889 | func TestYubiKeyGenerateKey(t *testing.T) {
890 | tests := []struct {
891 | name string
892 | alg Algorithm
893 | bits int
894 | long bool // Does the key generation take a long time?
895 | version version
896 | }{
897 | {
898 | name: "ec_256",
899 | alg: AlgorithmEC256,
900 | },
901 | {
902 | name: "ec_384",
903 | alg: AlgorithmEC384,
904 | version: version43,
905 | },
906 | {
907 | name: "rsa_1024",
908 | alg: AlgorithmRSA1024,
909 | },
910 | {
911 | name: "rsa_2048",
912 | alg: AlgorithmRSA2048,
913 | long: true,
914 | },
915 | {
916 | name: "rsa_2048",
917 | alg: AlgorithmRSA2048,
918 | long: true,
919 | },
920 | {
921 | name: "rsa_3072",
922 | alg: AlgorithmRSA3072,
923 | long: true,
924 | version: version57,
925 | },
926 | {
927 | name: "rsa_4096",
928 | alg: AlgorithmRSA4096,
929 | long: true,
930 | version: version57,
931 | },
932 | {
933 | name: "ed25519",
934 | alg: AlgorithmEd25519,
935 | long: false,
936 | version: version57,
937 | },
938 | }
939 | for _, test := range tests {
940 | t.Run(test.name, func(t *testing.T) {
941 | if test.long && testing.Short() {
942 | t.Skip("skipping test in short mode")
943 | }
944 | yk, close := newTestYubiKey(t)
945 | defer close()
946 | testRequiresVersion(t, yk, test.version)
947 | key := Key{
948 | Algorithm: test.alg,
949 | TouchPolicy: TouchPolicyNever,
950 | PINPolicy: PINPolicyNever,
951 | }
952 | if _, err := yk.GenerateKey(DefaultManagementKey, SlotAuthentication, key); err != nil {
953 | t.Errorf("generating key: %v", err)
954 | }
955 | })
956 | }
957 | }
958 |
959 | func TestYubiKeyPrivateKey(t *testing.T) {
960 | alg := AlgorithmEC256
961 | slot := SlotAuthentication
962 |
963 | yk, close := newTestYubiKey(t)
964 | defer close()
965 |
966 | key := Key{
967 | Algorithm: alg,
968 | TouchPolicy: TouchPolicyNever,
969 | PINPolicy: PINPolicyNever,
970 | }
971 | pub, err := yk.GenerateKey(DefaultManagementKey, slot, key)
972 | if err != nil {
973 | t.Fatalf("generating key: %v", err)
974 | }
975 | ecdsaPub, ok := pub.(*ecdsa.PublicKey)
976 | if !ok {
977 | t.Fatalf("public key is not an *ecdsa.PublicKey: %T", pub)
978 | }
979 |
980 | auth := KeyAuth{PIN: DefaultPIN}
981 | priv, err := yk.PrivateKey(slot, pub, auth)
982 | if err != nil {
983 | t.Fatalf("getting private key: %v", err)
984 | }
985 | signer, ok := priv.(crypto.Signer)
986 | if !ok {
987 | t.Fatalf("private key doesn't implement crypto.Signer")
988 | }
989 |
990 | b := sha256.Sum256([]byte("hello"))
991 | hash := b[:]
992 | sig, err := signer.Sign(rand.Reader, hash, crypto.SHA256)
993 | if err != nil {
994 | t.Fatalf("signing failed: %v", err)
995 | }
996 |
997 | var ecdsaSignature struct {
998 | R, S *big.Int
999 | }
1000 | if _, err := asn1.Unmarshal(sig, &ecdsaSignature); err != nil {
1001 | t.Fatalf("unmarshal: %v", err)
1002 | }
1003 | if !ecdsa.Verify(ecdsaPub, hash, ecdsaSignature.R, ecdsaSignature.S) {
1004 | t.Fatalf("signature validation failed")
1005 | }
1006 | }
1007 |
1008 | func TestYubiKeyPrivateKeyPINError(t *testing.T) {
1009 | alg := AlgorithmEC256
1010 | slot := SlotAuthentication
1011 |
1012 | yk, close := newTestYubiKey(t)
1013 | defer close()
1014 |
1015 | key := Key{
1016 | Algorithm: alg,
1017 | TouchPolicy: TouchPolicyNever,
1018 | PINPolicy: PINPolicyAlways,
1019 | }
1020 | pub, err := yk.GenerateKey(DefaultManagementKey, slot, key)
1021 | if err != nil {
1022 | t.Fatalf("generating key: %v", err)
1023 | }
1024 |
1025 | auth := KeyAuth{
1026 | PINPrompt: func() (string, error) {
1027 | return "", errors.New("test error")
1028 | },
1029 | }
1030 |
1031 | priv, err := yk.PrivateKey(slot, pub, auth)
1032 | if err != nil {
1033 | t.Fatalf("getting private key: %v", err)
1034 | }
1035 | signer, ok := priv.(crypto.Signer)
1036 | if !ok {
1037 | t.Fatalf("private key doesn't implement crypto.Signer")
1038 | }
1039 |
1040 | b := sha256.Sum256([]byte("hello"))
1041 | hash := b[:]
1042 | if _, err := signer.Sign(rand.Reader, hash, crypto.SHA256); err == nil {
1043 | t.Errorf("expected sign to fail with pin prompt that returned error")
1044 | }
1045 | }
1046 |
1047 | func TestRetiredKeyManagementSlot(t *testing.T) {
1048 | tests := []struct {
1049 | name string
1050 | key uint32
1051 | wantSlot Slot
1052 | wantOk bool
1053 | }{
1054 | {
1055 | name: "Non-existent slot, before range",
1056 | key: 0x0,
1057 | wantSlot: Slot{},
1058 | wantOk: false,
1059 | },
1060 | {
1061 | name: "Non-existent slot, after range",
1062 | key: 0x96,
1063 | wantSlot: Slot{},
1064 | wantOk: false,
1065 | },
1066 | {
1067 | name: "First retired slot key",
1068 | key: 0x82,
1069 | wantSlot: Slot{0x82, 0x5fc10d},
1070 | wantOk: true,
1071 | },
1072 | {
1073 | name: "Last retired slot key",
1074 | key: 0x95,
1075 | wantSlot: Slot{0x95, 0x5fc120},
1076 | wantOk: true,
1077 | },
1078 | }
1079 | for _, tt := range tests {
1080 | t.Run(tt.name, func(t *testing.T) {
1081 | gotSlot, gotOk := RetiredKeyManagementSlot(tt.key)
1082 | if gotSlot != tt.wantSlot {
1083 | t.Errorf("RetiredKeyManagementSlot() got = %v, want %v", gotSlot, tt.wantSlot)
1084 | }
1085 | if gotOk != tt.wantOk {
1086 | t.Errorf("RetiredKeyManagementSlot() got1 = %v, want %v", gotOk, tt.wantOk)
1087 | }
1088 | })
1089 | }
1090 | }
1091 |
1092 | func TestSetRSAPrivateKey(t *testing.T) {
1093 | tests := []struct {
1094 | name string
1095 | bits int
1096 | slot Slot
1097 | wantErr error
1098 | }{
1099 | {
1100 | name: "rsa 1024",
1101 | bits: 1024,
1102 | slot: SlotSignature,
1103 | wantErr: nil,
1104 | },
1105 | {
1106 | name: "rsa 2048",
1107 | bits: 2048,
1108 | slot: SlotCardAuthentication,
1109 | wantErr: nil,
1110 | },
1111 | {
1112 | name: "rsa 512",
1113 | bits: 512,
1114 | slot: SlotKeyManagement,
1115 | wantErr: errUnsupportedKeySize,
1116 | },
1117 | }
1118 |
1119 | for _, tt := range tests {
1120 | t.Run(tt.name, func(t *testing.T) {
1121 | yk, close := newTestYubiKey(t)
1122 | defer close()
1123 |
1124 | generated, err := rsa.GenerateKey(rand.Reader, tt.bits)
1125 | if err != nil {
1126 | t.Fatalf("generating private key: %v", err)
1127 | }
1128 |
1129 | err = yk.SetPrivateKeyInsecure(DefaultManagementKey, tt.slot, generated, Key{
1130 | PINPolicy: PINPolicyNever,
1131 | TouchPolicy: TouchPolicyNever,
1132 | })
1133 | if err != tt.wantErr {
1134 | t.Fatalf("SetPrivateKeyInsecure(): wantErr=%v, got err=%v", tt.wantErr, err)
1135 | }
1136 | if err != nil {
1137 | return
1138 | }
1139 |
1140 | priv, err := yk.PrivateKey(tt.slot, &generated.PublicKey, KeyAuth{})
1141 | if err != nil {
1142 | t.Fatalf("getting private key: %v", err)
1143 | }
1144 |
1145 | data := []byte("Test data that we will encrypt")
1146 |
1147 | // Encrypt the data using our generated key
1148 | encrypted, err := rsa.EncryptPKCS1v15(rand.Reader, &generated.PublicKey, data)
1149 | if err != nil {
1150 | t.Fatalf("encrypting data: %v", err)
1151 | }
1152 |
1153 | deviceDecrypter := priv.(crypto.Decrypter)
1154 |
1155 | // Decrypt the data on the device
1156 | decrypted, err := deviceDecrypter.Decrypt(rand.Reader, encrypted, nil)
1157 | if err != nil {
1158 | t.Fatalf("decrypting data: %v", err)
1159 | }
1160 |
1161 | if !bytes.Equal(data, decrypted) {
1162 | t.Fatalf("decrypted data is different to the source data")
1163 | }
1164 | })
1165 | }
1166 | }
1167 |
1168 | func TestSetECDSAPrivateKey(t *testing.T) {
1169 | tests := []struct {
1170 | name string
1171 | curve elliptic.Curve
1172 | slot Slot
1173 | wantErr error
1174 | }{
1175 | {
1176 | name: "ecdsa P256",
1177 | curve: elliptic.P256(),
1178 | slot: SlotSignature,
1179 | wantErr: nil,
1180 | },
1181 | {
1182 | name: "ecdsa P384",
1183 | curve: elliptic.P384(),
1184 | slot: SlotCardAuthentication,
1185 | wantErr: nil,
1186 | },
1187 | {
1188 | name: "ecdsa P224",
1189 | curve: elliptic.P224(),
1190 | slot: SlotAuthentication,
1191 | wantErr: unsupportedCurveError{curve: 224},
1192 | },
1193 | {
1194 | name: "ecdsa P521",
1195 | curve: elliptic.P521(),
1196 | slot: SlotKeyManagement,
1197 | wantErr: unsupportedCurveError{curve: 521},
1198 | },
1199 | }
1200 |
1201 | for _, tt := range tests {
1202 | t.Run(tt.name, func(t *testing.T) {
1203 | yk, close := newTestYubiKey(t)
1204 | defer close()
1205 |
1206 | generated, err := ecdsa.GenerateKey(tt.curve, rand.Reader)
1207 | if err != nil {
1208 | t.Fatalf("generating private key: %v", err)
1209 | }
1210 |
1211 | err = yk.SetPrivateKeyInsecure(DefaultManagementKey, tt.slot, generated, Key{
1212 | PINPolicy: PINPolicyNever,
1213 | TouchPolicy: TouchPolicyNever,
1214 | })
1215 | if err != tt.wantErr {
1216 | t.Fatalf("SetPrivateKeyInsecure(): wantErr=%v, got err=%v", tt.wantErr, err)
1217 | }
1218 | if err != nil {
1219 | return
1220 | }
1221 |
1222 | priv, err := yk.PrivateKey(tt.slot, &generated.PublicKey, KeyAuth{})
1223 | if err != nil {
1224 | t.Fatalf("getting private key: %v", err)
1225 | }
1226 |
1227 | deviceSigner := priv.(crypto.Signer)
1228 |
1229 | hash := []byte("Test data to sign")
1230 | // Sign the data on the device
1231 | sig, err := deviceSigner.Sign(rand.Reader, hash, nil)
1232 | if err != nil {
1233 | t.Fatalf("signing data: %v", err)
1234 | }
1235 |
1236 | // Verify the signature using the generated key
1237 | if !ecdsa.VerifyASN1(&generated.PublicKey, hash, sig) {
1238 | t.Fatal("Failed to verify signed data")
1239 | }
1240 | })
1241 | }
1242 | }
1243 |
1244 | func TestParseSlot(t *testing.T) {
1245 | tests := []struct {
1246 | name string
1247 | cn string
1248 | ok bool
1249 | slot Slot
1250 | }{
1251 | {
1252 | name: "Missing Yubico PIV Prefix",
1253 | cn: "invalid",
1254 | ok: false,
1255 | slot: Slot{},
1256 | },
1257 | {
1258 | name: "Invalid Slot Name",
1259 | cn: yubikeySubjectCNPrefix + "xy",
1260 | ok: false,
1261 | slot: Slot{},
1262 | },
1263 | {
1264 | name: "Valid -- SlotAuthentication",
1265 | cn: yubikeySubjectCNPrefix + "9a",
1266 | ok: true,
1267 | slot: SlotAuthentication,
1268 | },
1269 | {
1270 | name: "Valid -- Retired Management Key",
1271 | cn: yubikeySubjectCNPrefix + "89",
1272 | ok: true,
1273 | slot: retiredKeyManagementSlots[uint32(137)],
1274 | },
1275 | }
1276 |
1277 | for _, test := range tests {
1278 | t.Run(test.name, func(t *testing.T) {
1279 | slot, ok := parseSlot(test.cn)
1280 |
1281 | if ok != test.ok {
1282 | t.Errorf("ok status returned %v, expected %v", ok, test.ok)
1283 | }
1284 |
1285 | if slot != test.slot {
1286 | t.Errorf("returned slot %+v did not match expected %+v", slot, test.slot)
1287 | }
1288 | })
1289 | }
1290 | }
1291 |
1292 | func TestVerify(t *testing.T) {
1293 | tests := []struct {
1294 | name string
1295 | deviceCert string
1296 | keyCert string
1297 | ok bool
1298 | }{
1299 | {
1300 | // Valid attestation chain from a recent YubiKey.
1301 | name: "ValidChain",
1302 | 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",
1303 | 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",
1304 | ok: true,
1305 | },
1306 | {
1307 | // 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).
1308 | name: "ValidChain2018",
1309 | 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",
1310 | 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",
1311 | ok: true,
1312 | },
1313 | {
1314 | // Invalid attestation chain. Device cert from yubikey A, key cert from yubikey B.
1315 | name: "InvalidChain",
1316 | 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",
1317 | 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",
1318 | ok: false,
1319 | },
1320 | {
1321 | // Invalid attestation chain. Device cert from yubikey B, key cert from yubikey A.
1322 | name: "InvalidChain2",
1323 | 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",
1324 | 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",
1325 | ok: false,
1326 | },
1327 | }
1328 |
1329 | parseCert := func(cert string) (*x509.Certificate, error) {
1330 | block, _ := pem.Decode([]byte(cert))
1331 | if block == nil {
1332 | t.Fatalf("decoding PEM cert, empty block")
1333 | }
1334 | return x509.ParseCertificate(block.Bytes)
1335 | }
1336 |
1337 | for _, test := range tests {
1338 | t.Run(test.name, func(t *testing.T) {
1339 | deviceCert, err := parseCert(test.deviceCert)
1340 | if err != nil {
1341 | t.Fatalf("parsing device cert: %v", err)
1342 | }
1343 |
1344 | keyCert, err := parseCert(test.keyCert)
1345 | if err != nil {
1346 | t.Fatalf("parsing key cert: %v", err)
1347 | }
1348 |
1349 | _, err = Verify(deviceCert, keyCert)
1350 | if (err == nil) != test.ok {
1351 | t.Errorf("Verify returned %v, expected test outcome %v", err, test.ok)
1352 | }
1353 | })
1354 | }
1355 | }
1356 |
1357 | func TestKeyInfo(t *testing.T) {
1358 | func() {
1359 | yk, close := newTestYubiKey(t)
1360 | defer close()
1361 |
1362 | testRequiresVersion(t, yk, version53)
1363 |
1364 | if err := yk.Reset(); err != nil {
1365 | t.Fatalf("resetting key: %v", err)
1366 | }
1367 | }()
1368 |
1369 | tests := []struct {
1370 | name string
1371 | slot Slot
1372 | importKey privateKey
1373 | policy Key
1374 | long bool
1375 | version version
1376 | }{
1377 | {
1378 | "Generated ec_256",
1379 | SlotAuthentication,
1380 | nil,
1381 | Key{AlgorithmEC256, PINPolicyNever, TouchPolicyNever},
1382 | false, version{},
1383 | },
1384 | {
1385 | "Generated ec_384",
1386 | SlotAuthentication,
1387 | nil,
1388 | Key{AlgorithmEC384, PINPolicyNever, TouchPolicyNever},
1389 | false, version43,
1390 | },
1391 | {
1392 | "Generated rsa_1024",
1393 | SlotAuthentication,
1394 | nil,
1395 | Key{AlgorithmRSA1024, PINPolicyNever, TouchPolicyNever},
1396 | false, version{},
1397 | },
1398 | {
1399 | "Generated rsa_2048",
1400 | SlotAuthentication,
1401 | nil,
1402 | Key{AlgorithmRSA2048, PINPolicyNever, TouchPolicyNever},
1403 | true, version{},
1404 | },
1405 | {
1406 | "Generated rsa_3072",
1407 | SlotAuthentication,
1408 | nil,
1409 | Key{AlgorithmRSA3072, PINPolicyNever, TouchPolicyNever},
1410 | true, version57,
1411 | },
1412 | {
1413 | "Generated rsa_4096",
1414 | SlotAuthentication,
1415 | nil,
1416 | Key{AlgorithmRSA4096, PINPolicyNever, TouchPolicyNever},
1417 | true, version57,
1418 | },
1419 | {
1420 | "Generated ed25517",
1421 | SlotAuthentication,
1422 | nil,
1423 | Key{AlgorithmEd25519, PINPolicyNever, TouchPolicyNever},
1424 | false, version57,
1425 | },
1426 | {
1427 | "Generated x25517",
1428 | SlotAuthentication,
1429 | nil,
1430 | Key{AlgorithmEd25519, PINPolicyNever, TouchPolicyNever},
1431 | false, version57,
1432 | },
1433 | {
1434 | "Imported ec_256",
1435 | SlotAuthentication,
1436 | ephemeralKey(t, AlgorithmEC256),
1437 | Key{AlgorithmEC256, PINPolicyNever, TouchPolicyNever},
1438 | false, version{},
1439 | },
1440 | {
1441 | "Imported ec_384",
1442 | SlotAuthentication,
1443 | ephemeralKey(t, AlgorithmEC384),
1444 | Key{AlgorithmEC384, PINPolicyNever, TouchPolicyNever},
1445 | false, version43,
1446 | },
1447 | {
1448 | "Imported rsa_1024",
1449 | SlotAuthentication,
1450 | ephemeralKey(t, AlgorithmRSA1024),
1451 | Key{AlgorithmRSA1024, PINPolicyNever, TouchPolicyNever},
1452 | false, version{},
1453 | },
1454 | {
1455 | "Imported rsa_2048",
1456 | SlotAuthentication,
1457 | ephemeralKey(t, AlgorithmRSA2048),
1458 | Key{AlgorithmRSA2048, PINPolicyNever, TouchPolicyNever},
1459 | false, version{},
1460 | },
1461 | {
1462 | "Imported rsa_3072",
1463 | SlotAuthentication,
1464 | ephemeralKey(t, AlgorithmRSA3072),
1465 | Key{AlgorithmRSA3072, PINPolicyNever, TouchPolicyNever},
1466 | false, version57,
1467 | },
1468 | {
1469 | "Imported rsa_4096",
1470 | SlotAuthentication,
1471 | ephemeralKey(t, AlgorithmRSA4096),
1472 | Key{AlgorithmRSA4096, PINPolicyNever, TouchPolicyNever},
1473 | false, version57,
1474 | },
1475 | {
1476 | "Imported ed25519",
1477 | SlotAuthentication,
1478 | ephemeralKey(t, AlgorithmEd25519),
1479 | Key{AlgorithmEd25519, PINPolicyNever, TouchPolicyNever},
1480 | false, version57,
1481 | },
1482 | {
1483 | "Imported x25519",
1484 | SlotAuthentication,
1485 | ephemeralKey(t, AlgorithmX25519),
1486 | Key{AlgorithmX25519, PINPolicyNever, TouchPolicyNever},
1487 | false, version57,
1488 | },
1489 | {
1490 | "PINPolicyOnce",
1491 | SlotAuthentication,
1492 | nil,
1493 | Key{AlgorithmEC256, PINPolicyOnce, TouchPolicyNever},
1494 | false, version{},
1495 | },
1496 | {
1497 | "PINPolicyAlways",
1498 | SlotAuthentication,
1499 | nil,
1500 | Key{AlgorithmEC256, PINPolicyAlways, TouchPolicyNever},
1501 | false, version{},
1502 | },
1503 | {
1504 | "TouchPolicyAlways",
1505 | SlotAuthentication,
1506 | nil,
1507 | Key{AlgorithmEC256, PINPolicyNever, TouchPolicyAlways},
1508 | false, version{},
1509 | },
1510 | {
1511 | "TouchPolicyCached",
1512 | SlotAuthentication,
1513 | nil,
1514 | Key{AlgorithmEC256, PINPolicyNever, TouchPolicyCached},
1515 | false, version{},
1516 | },
1517 | {
1518 | "SlotSignature",
1519 | SlotSignature,
1520 | nil,
1521 | Key{AlgorithmEC256, PINPolicyNever, TouchPolicyCached},
1522 | false, version{},
1523 | },
1524 | {
1525 | "SlotCardAuthentication",
1526 | SlotCardAuthentication,
1527 | nil,
1528 | Key{AlgorithmEC256, PINPolicyNever, TouchPolicyCached},
1529 | false, version{},
1530 | },
1531 | {
1532 | "SlotKeyManagement",
1533 | SlotKeyManagement,
1534 | nil,
1535 | Key{AlgorithmEC256, PINPolicyNever, TouchPolicyCached},
1536 | false, version{},
1537 | },
1538 | }
1539 | for _, test := range tests {
1540 | t.Run(test.name, func(t *testing.T) {
1541 | if test.long && testing.Short() {
1542 | t.Skip("skipping test in short mode")
1543 | }
1544 | yk, close := newTestYubiKey(t)
1545 | defer close()
1546 | testRequiresVersion(t, yk, test.version)
1547 |
1548 | want := KeyInfo{
1549 | Algorithm: test.policy.Algorithm,
1550 | PINPolicy: test.policy.PINPolicy,
1551 | TouchPolicy: test.policy.TouchPolicy,
1552 | }
1553 |
1554 | if test.importKey == nil {
1555 | pub, err := yk.GenerateKey(DefaultManagementKey, test.slot, test.policy)
1556 | if err != nil {
1557 | t.Fatalf("generating key: %v", err)
1558 | }
1559 | want.Origin = OriginGenerated
1560 | want.PublicKey = pub
1561 | } else {
1562 | err := yk.SetPrivateKeyInsecure(DefaultManagementKey, test.slot, test.importKey, test.policy)
1563 | if err != nil {
1564 | t.Fatalf("importing key: %v", err)
1565 | }
1566 | want.Origin = OriginImported
1567 | want.PublicKey = test.importKey.Public()
1568 | }
1569 |
1570 | got, err := yk.KeyInfo(test.slot)
1571 | if err != nil {
1572 | t.Fatalf("KeyInfo() = _, %v", err)
1573 | }
1574 | if !reflect.DeepEqual(got, want) {
1575 | t.Errorf("KeyInfo() = %#v, want %#v", got, want)
1576 | }
1577 | })
1578 | }
1579 | }
1580 |
1581 | // TestDerivePINPolicy checks that Yubikeys with version >= 5.3.0 use the
1582 | // KeyInfo method to determine the pin policy, instead of the attestation
1583 | // certificate.
1584 | func TestPINPolicy(t *testing.T) {
1585 | func() {
1586 | yk, close := newTestYubiKey(t)
1587 | defer close()
1588 |
1589 | testRequiresVersion(t, yk, version53)
1590 |
1591 | if err := yk.Reset(); err != nil {
1592 | t.Fatalf("resetting key: %v", err)
1593 | }
1594 | }()
1595 |
1596 | yk, close := newTestYubiKey(t)
1597 | defer close()
1598 |
1599 | // for imported keys, using the attestation certificate to derive the PIN
1600 | // policy fails. So we check that pinPolicy succeeds with imported keys.
1601 | priv := ephemeralKey(t, AlgorithmEC256)
1602 | err := yk.SetPrivateKeyInsecure(DefaultManagementKey, SlotAuthentication, priv, Key{
1603 | Algorithm: AlgorithmEC256,
1604 | PINPolicy: PINPolicyNever,
1605 | TouchPolicy: TouchPolicyNever,
1606 | })
1607 | if err != nil {
1608 | t.Fatalf("import key: %v", err)
1609 | }
1610 | if got, err := pinPolicy(yk, SlotAuthentication); err != nil || got != PINPolicyNever {
1611 | t.Fatalf("pinPolicy() = %v, %v, want %v, ", got, err, PINPolicyNever)
1612 | }
1613 | }
1614 |
1615 | // privateKey is an interface with the optional (but always supported) methods
1616 | // of crypto.PrivateKey.
1617 | type privateKey interface {
1618 | Equal(crypto.PrivateKey) bool
1619 | Public() crypto.PublicKey
1620 | }
1621 |
1622 | // ephemeralKey generates an ephemeral key for the given algorithm.
1623 | func ephemeralKey(t *testing.T, alg Algorithm) privateKey {
1624 | t.Helper()
1625 | var (
1626 | key privateKey
1627 | err error
1628 | )
1629 | switch alg {
1630 | case AlgorithmEC256:
1631 | key, err = ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
1632 | case AlgorithmEC384:
1633 | key, err = ecdsa.GenerateKey(elliptic.P384(), rand.Reader)
1634 | case AlgorithmEd25519:
1635 | _, key, err = ed25519.GenerateKey(rand.Reader)
1636 | case AlgorithmRSA1024:
1637 | key, err = rsa.GenerateKey(rand.Reader, 1024)
1638 | case AlgorithmRSA2048:
1639 | key, err = rsa.GenerateKey(rand.Reader, 2048)
1640 | case AlgorithmRSA3072:
1641 | key, err = rsa.GenerateKey(rand.Reader, 3072)
1642 | case AlgorithmRSA4096:
1643 | key, err = rsa.GenerateKey(rand.Reader, 4096)
1644 | case AlgorithmX25519:
1645 | key, err = ecdh.X25519().GenerateKey(rand.Reader)
1646 | default:
1647 | t.Fatalf("ephemeral key: unknown algorithm %d", alg)
1648 | }
1649 | if err != nil {
1650 | t.Fatalf("ephemeral key: %v", err)
1651 | }
1652 | return key
1653 | }
1654 |
--------------------------------------------------------------------------------
/v2/piv/pcsc.go:
--------------------------------------------------------------------------------
1 | // Copyright 2020 Google LLC
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // https://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | package piv
16 |
17 | import (
18 | "errors"
19 | "fmt"
20 | )
21 |
22 | type scErr struct {
23 | // rc holds the return code for a given call.
24 | rc int64
25 | }
26 |
27 | func (e *scErr) Error() string {
28 | if msg, ok := pcscErrMsgs[e.rc]; ok {
29 | return msg
30 | }
31 | return fmt.Sprintf("unknown pcsc return code 0x%08x", e.rc)
32 | }
33 |
34 | // AuthErr is an error indicating an authentication error occurred (wrong PIN or blocked).
35 | type AuthErr struct {
36 | // Retries is the number of retries remaining if this error resulted from a retriable
37 | // authentication attempt. If the authentication method is blocked or does not support
38 | // retries, this will be 0.
39 | Retries int
40 | }
41 |
42 | func (v AuthErr) Error() string {
43 | r := "retries"
44 | if v.Retries == 1 {
45 | r = "retry"
46 | }
47 | return fmt.Sprintf("verification failed (%d %s remaining)", v.Retries, r)
48 | }
49 |
50 | // ErrNotFound is returned when the requested object on the smart card is not found.
51 | var ErrNotFound = errors.New("data object or application not found")
52 |
53 | // apduErr is an error interacting with the PIV application on the smart card.
54 | // This error may wrap more accessible errors, like ErrNotFound or an instance
55 | // of AuthErr, so callers are encouraged to use errors.Is and errors.As for
56 | // these common cases.
57 | type apduErr struct {
58 | sw1 byte
59 | sw2 byte
60 | }
61 |
62 | // Status returns the Status Word returned by the card command.
63 | func (a *apduErr) Status() uint16 {
64 | return uint16(a.sw1)<<8 | uint16(a.sw2)
65 | }
66 |
67 | func (a *apduErr) Error() string {
68 | var msg string
69 | if u := a.Unwrap(); u != nil {
70 | msg = u.Error()
71 | }
72 |
73 | switch a.Status() {
74 | // 0x6300 is "verification failed", represented as AuthErr{0}
75 | // 0x63Cn is "verification failed" with retry, represented as AuthErr{n}
76 | case 0x6882:
77 | msg = "secure messaging not supported"
78 | case 0x6982:
79 | msg = "security status not satisfied"
80 | case 0x6983:
81 | // This will also be AuthErr{0} but we override the message here
82 | // so that it's clear that the reason is a block rather than a simple
83 | // failed authentication verification.
84 | msg = "authentication method blocked"
85 | case 0x6987:
86 | msg = "expected secure messaging data objects are missing"
87 | case 0x6988:
88 | msg = "secure messaging data objects are incorrect"
89 | case 0x6a80:
90 | msg = "incorrect parameter in command data field"
91 | case 0x6a81:
92 | msg = "function not supported"
93 | // 0x6a82 is "data object or application not found" aka ErrNotFound
94 | case 0x6a84:
95 | msg = "not enough memory"
96 | case 0x6a86:
97 | msg = "incorrect parameter in P1 or P2"
98 | case 0x6a88:
99 | msg = "referenced data or reference data not found"
100 | }
101 |
102 | if msg != "" {
103 | msg = ": " + msg
104 | }
105 | return fmt.Sprintf("smart card error %04x%s", a.Status(), msg)
106 | }
107 |
108 | // Unwrap retrieves an accessible error type, if able.
109 | func (a *apduErr) Unwrap() error {
110 | st := a.Status()
111 | switch {
112 | case st == 0x6a82:
113 | return ErrNotFound
114 | case st == 0x6300:
115 | return AuthErr{0}
116 | case st == 0x6983:
117 | return AuthErr{0}
118 | case st&0xfff0 == 0x63c0:
119 | return AuthErr{int(st & 0xf)}
120 | case st&0xfff0 == 0x6300:
121 | // Older YubiKeys sometimes return sw1=0x63 and sw2=0x0N to indicate the
122 | // number of retries. This isn't spec compliant, but support it anyway.
123 | //
124 | // https://github.com/go-piv/piv-go/issues/60
125 | return AuthErr{int(st & 0xf)}
126 | }
127 | return nil
128 | }
129 |
130 | type apdu struct {
131 | instruction byte
132 | param1 byte
133 | param2 byte
134 | data []byte
135 | }
136 |
137 | func (t *scTx) Transmit(d apdu) ([]byte, error) {
138 | data := d.data
139 | var resp []byte
140 | const maxAPDUDataSize = 0xff
141 | for len(data) > maxAPDUDataSize {
142 | req := make([]byte, 5+maxAPDUDataSize)
143 | req[0] = 0x10 // ISO/IEC 7816-4 5.1.1
144 | req[1] = d.instruction
145 | req[2] = d.param1
146 | req[3] = d.param2
147 | req[4] = 0xff
148 | copy(req[5:], data[:maxAPDUDataSize])
149 | data = data[maxAPDUDataSize:]
150 | _, r, err := t.transmit(req)
151 | if err != nil {
152 | return nil, fmt.Errorf("transmitting initial chunk %w", err)
153 | }
154 | resp = append(resp, r...)
155 | }
156 |
157 | req := make([]byte, 5+len(data))
158 | req[1] = d.instruction
159 | req[2] = d.param1
160 | req[3] = d.param2
161 | req[4] = byte(len(data))
162 | copy(req[5:], data)
163 | hasMore, r, err := t.transmit(req)
164 | if err != nil {
165 | return nil, err
166 | }
167 | resp = append(resp, r...)
168 |
169 | for hasMore {
170 | req := make([]byte, 5)
171 | req[1] = insGetResponseAPDU
172 | var r []byte
173 | hasMore, r, err = t.transmit(req)
174 | if err != nil {
175 | return nil, fmt.Errorf("reading further response: %w", err)
176 | }
177 | resp = append(resp, r...)
178 | }
179 |
180 | return resp, nil
181 | }
182 |
--------------------------------------------------------------------------------
/v2/piv/pcsc_darwin.go:
--------------------------------------------------------------------------------
1 | // Copyright 2020 Google LLC
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // https://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | package piv
16 |
17 | import "C"
18 |
19 | func scCheck(rc C.int) error {
20 | if rc == rcSuccess {
21 | return nil
22 | }
23 | i := int64(rc)
24 | if i < 0 {
25 | // On MacOS, int isn't big enough to handle the return codes so the
26 | // leading bit becomes a two's complement bit. If the return code is
27 | // negative, correct this.
28 | // https://github.com/go-piv/piv-go/issues/53
29 | i += (1 << 32)
30 | }
31 | return &scErr{i}
32 | }
33 |
34 | func isRCNoReaders(rc C.int) bool {
35 | // MacOS does the right thing and doesn't return an error if no smart cards
36 | // are available.
37 | return false
38 | }
39 |
--------------------------------------------------------------------------------
/v2/piv/pcsc_errors:
--------------------------------------------------------------------------------
1 | SCARD_S_SUCCESS 0x00000000
2 | No error was encountered.
3 |
4 | SCARD_F_INTERNAL_ERROR 0x80100001
5 | An internal consistency check failed.
6 |
7 | SCARD_E_CANCELLED 0x80100002
8 | The action was cancelled by an SCardCancel request.
9 |
10 | SCARD_E_INVALID_HANDLE 0x80100003
11 | The supplied handle was invalid.
12 |
13 | SCARD_E_INVALID_PARAMETER 0x80100004
14 | One or more of the supplied parameters could not be properly interpreted.
15 |
16 | SCARD_E_INVALID_TARGET 0x80100005
17 | Registry startup information is missing or invalid.
18 |
19 | SCARD_E_NO_MEMORY 0x80100006
20 | Not enough memory available to complete this command.
21 |
22 | SCARD_F_WAITED_TOO_LONG 0x80100007
23 | An internal consistency timer has expired.
24 |
25 | SCARD_E_INSUFFICIENT_BUFFER 0x80100008
26 | The data buffer to receive returned data is too small for the returned data.
27 |
28 | SCARD_E_UNKNOWN_READER 0x80100009
29 | The specified reader name is not recognized.
30 |
31 | SCARD_E_TIMEOUT 0x8010000A
32 | The user-specified timeout value has expired.
33 |
34 | SCARD_E_SHARING_VIOLATION 0x8010000B
35 | The smart card cannot be accessed because of other connections outstanding.
36 |
37 | SCARD_E_NO_SMARTCARD 0x8010000C
38 | The operation requires a Smart Card, but no Smart Card is currently in the device.
39 |
40 | SCARD_E_UNKNOWN_CARD 0x8010000D
41 | The specified smart card name is not recognized.
42 |
43 | SCARD_E_CANT_DISPOSE 0x8010000E
44 | The system could not dispose of the media in the requested manner.
45 |
46 | SCARD_E_PROTO_MISMATCH 0x8010000F
47 | The requested protocols are incompatible with the protocol currently in use with the smart card.
48 |
49 | SCARD_E_NOT_READY 0x80100010
50 | The reader or smart card is not ready to accept commands.
51 |
52 | SCARD_E_INVALID_VALUE 0x80100011
53 | One or more of the supplied parameters values could not be properly interpreted.
54 |
55 | SCARD_E_SYSTEM_CANCELLED 0x80100012
56 | The action was cancelled by the system, presumably to log off or shut down.
57 |
58 | SCARD_F_COMM_ERROR 0x80100013
59 | An internal communications error has been detected.
60 |
61 | SCARD_F_UNKNOWN_ERROR 0x80100014
62 | An internal error has been detected, but the source is unknown.
63 |
64 | SCARD_E_INVALID_ATR 0x80100015
65 | An ATR obtained from the registry is not a valid ATR string.
66 |
67 | SCARD_E_NOT_TRANSACTED 0x80100016
68 | An attempt was made to end a non-existent transaction.
69 |
70 | SCARD_E_READER_UNAVAILABLE 0x80100017
71 | The specified reader is not currently available for use.
72 |
73 | SCARD_P_SHUTDOWN 0x80100018
74 | The operation has been aborted to allow the server application to exit.
75 |
76 | SCARD_E_PCI_TOO_SMALL 0x80100019
77 | The PCI Receive buffer was too small.
78 |
79 | SCARD_E_READER_UNSUPPORTED 0x8010001A
80 | The reader driver does not meet minimal requirements for support.
81 |
82 | SCARD_E_DUPLICATE_READER 0x8010001B
83 | The reader driver did not produce a unique reader name.
84 |
85 | SCARD_E_CARD_UNSUPPORTED 0x8010001C
86 | The smart card does not meet minimal requirements for support.
87 |
88 | SCARD_E_NO_SERVICE 0x8010001D
89 | The Smart card resource manager is not running.
90 |
91 | SCARD_E_SERVICE_STOPPED 0x8010001E
92 | The Smart card resource manager has shut down.
93 |
94 | SCARD_E_UNEXPECTED 0x8010001F
95 | An unexpected card error has occurred.
96 |
97 | SCARD_E_ICC_INSTALLATION 0x80100020
98 | No primary provider can be found for the smart card.
99 |
100 | SCARD_E_ICC_CREATEORDER 0x80100021
101 | The requested order of object creation is not supported.
102 |
103 | SCARD_E_DIR_NOT_FOUND 0x80100023
104 | The identified directory does not exist in the smart card.
105 |
106 | SCARD_E_FILE_NOT_FOUND 0x80100024
107 | The identified file does not exist in the smart card.
108 |
109 | SCARD_E_NO_DIR 0x80100025
110 | The supplied path does not represent a smart card directory.
111 |
112 | SCARD_E_NO_FILE 0x80100026
113 | The supplied path does not represent a smart card file.
114 |
115 | SCARD_E_NO_ACCESS 0x80100027
116 | Access is denied to this file.
117 |
118 | SCARD_E_WRITE_TOO_MANY 0x80100028
119 | The smart card does not have enough memory to store the information.
120 |
121 | SCARD_E_BAD_SEEK 0x80100029
122 | There was an error trying to set the smart card file object pointer.
123 |
124 | SCARD_E_INVALID_CHV 0x8010002A
125 | The supplied PIN is incorrect.
126 |
127 | SCARD_E_UNKNOWN_RES_MNG 0x8010002B
128 | An unrecognized error code was returned from a layered component.
129 |
130 | SCARD_E_NO_SUCH_CERTIFICATE 0x8010002C
131 | The requested certificate does not exist.
132 |
133 | SCARD_E_CERTIFICATE_UNAVAILABLE 0x8010002D
134 | The requested certificate could not be obtained.
135 |
136 | SCARD_E_NO_READERS_AVAILABLE 0x8010002E
137 | Cannot find a smart card reader.
138 |
139 | SCARD_E_COMM_DATA_LOST 0x8010002F
140 | A communications error with the smart card has been detected. More...
141 |
142 | SCARD_E_NO_KEY_CONTAINER 0x80100030
143 | The requested key container does not exist on the smart card.
144 |
145 | SCARD_E_SERVER_TOO_BUSY 0x80100031
146 | The Smart Card Resource Manager is too busy to complete this operation.
147 |
148 | SCARD_W_UNSUPPORTED_CARD 0x80100065
149 | The reader cannot communicate with the card, due to ATR string configuration conflicts.
150 |
151 | SCARD_W_UNRESPONSIVE_CARD 0x80100066
152 | The smart card is not responding to a reset.
153 |
154 | SCARD_W_UNPOWERED_CARD 0x80100067
155 | Power has been removed from the smart card, so that further communication is not possible.
156 |
157 | SCARD_W_RESET_CARD 0x80100068
158 | The smart card has been reset, so any shared state information is invalid.
159 |
160 | SCARD_W_REMOVED_CARD 0x80100069
161 | The smart card has been removed, so further communication is not possible.
162 |
163 | SCARD_W_SECURITY_VIOLATION 0x8010006A
164 | Access was denied because of a security violation.
165 |
166 | SCARD_W_WRONG_CHV 0x8010006B
167 | The card cannot be accessed because the wrong PIN was presented.
168 |
169 | SCARD_W_CHV_BLOCKED 0x8010006C
170 | The card cannot be accessed because the maximum number of PIN entry attempts has been reached.
171 |
172 | SCARD_W_EOF 0x8010006D
173 | The end of the smart card file has been reached.
174 |
175 | SCARD_W_CANCELLED_BY_USER 0x8010006E
176 | The user pressed "Cancel" on a Smart Card Selection Dialog.
177 |
178 | SCARD_W_CARD_NOT_AUTHENTICATED 0x8010006F
179 | No PIN was presented to the smart card.
180 |
--------------------------------------------------------------------------------
/v2/piv/pcsc_errors.go:
--------------------------------------------------------------------------------
1 | // Copyright 2020 Google LLC
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // https://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | package piv
16 |
17 | // https://golang.org/s/generatedcode
18 |
19 | // Code generated by errors.py DO NOT EDIT.
20 |
21 | var pcscErrMsgs = map[int64]string{
22 | 0x00000000: "no error was encountered",
23 | 0x80100001: "an internal consistency check failed",
24 | 0x80100002: "the action was cancelled by an SCardCancel request",
25 | 0x80100003: "the supplied handle was invalid",
26 | 0x80100004: "one or more of the supplied parameters could not be properly interpreted",
27 | 0x80100005: "registry startup information is missing or invalid",
28 | 0x80100006: "not enough memory available to complete this command",
29 | 0x80100007: "an internal consistency timer has expired",
30 | 0x80100008: "the data buffer to receive returned data is too small for the returned data",
31 | 0x80100009: "the specified reader name is not recognized",
32 | 0x8010000A: "the user-specified timeout value has expired",
33 | 0x8010000B: "the smart card cannot be accessed because of other connections outstanding",
34 | 0x8010000C: "the operation requires a Smart Card, but no Smart Card is currently in the device",
35 | 0x8010000D: "the specified smart card name is not recognized",
36 | 0x8010000E: "the system could not dispose of the media in the requested manner",
37 | 0x8010000F: "the requested protocols are incompatible with the protocol currently in use with the smart card",
38 | 0x80100010: "the reader or smart card is not ready to accept commands",
39 | 0x80100011: "one or more of the supplied parameters values could not be properly interpreted",
40 | 0x80100012: "the action was cancelled by the system, presumably to log off or shut down",
41 | 0x80100013: "an internal communications error has been detected",
42 | 0x80100014: "an internal error has been detected, but the source is unknown",
43 | 0x80100015: "an ATR obtained from the registry is not a valid ATR string",
44 | 0x80100016: "an attempt was made to end a non-existent transaction",
45 | 0x80100017: "the specified reader is not currently available for use",
46 | 0x80100018: "the operation has been aborted to allow the server application to exit",
47 | 0x80100019: "the PCI Receive buffer was too small",
48 | 0x8010001A: "the reader driver does not meet minimal requirements for support",
49 | 0x8010001B: "the reader driver did not produce a unique reader name",
50 | 0x8010001C: "the smart card does not meet minimal requirements for support",
51 | 0x8010001D: "the Smart card resource manager is not running",
52 | 0x8010001E: "the Smart card resource manager has shut down",
53 | 0x8010001F: "an unexpected card error has occurred",
54 | 0x80100020: "no primary provider can be found for the smart card",
55 | 0x80100021: "the requested order of object creation is not supported",
56 | 0x80100023: "the identified directory does not exist in the smart card",
57 | 0x80100024: "the identified file does not exist in the smart card",
58 | 0x80100025: "the supplied path does not represent a smart card directory",
59 | 0x80100026: "the supplied path does not represent a smart card file",
60 | 0x80100027: "access is denied to this file",
61 | 0x80100028: "the smart card does not have enough memory to store the information",
62 | 0x80100029: "there was an error trying to set the smart card file object pointer",
63 | 0x8010002A: "the supplied PIN is incorrect",
64 | 0x8010002B: "an unrecognized error code was returned from a layered component",
65 | 0x8010002C: "the requested certificate does not exist",
66 | 0x8010002D: "the requested certificate could not be obtained",
67 | 0x8010002E: "cannot find a smart card reader",
68 | 0x8010002F: "a communications error with the smart card has been detected. More..",
69 | 0x80100030: "the requested key container does not exist on the smart card",
70 | 0x80100031: "the Smart Card Resource Manager is too busy to complete this operation",
71 | 0x80100065: "the reader cannot communicate with the card, due to ATR string configuration conflicts",
72 | 0x80100066: "the smart card is not responding to a reset",
73 | 0x80100067: "power has been removed from the smart card, so that further communication is not possible",
74 | 0x80100068: "the smart card has been reset, so any shared state information is invalid",
75 | 0x80100069: "the smart card has been removed, so further communication is not possible",
76 | 0x8010006A: "access was denied because of a security violation",
77 | 0x8010006B: "the card cannot be accessed because the wrong PIN was presented",
78 | 0x8010006C: "the card cannot be accessed because the maximum number of PIN entry attempts has been reached",
79 | 0x8010006D: "the end of the smart card file has been reached",
80 | 0x8010006E: "the user pressed \"Cancel\" on a Smart Card Selection Dialog",
81 | 0x8010006F: "no PIN was presented to the smart card",
82 | }
83 |
--------------------------------------------------------------------------------
/v2/piv/pcsc_errors.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 |
3 | # Copyright 2020 Google LLC
4 | #
5 | # Licensed under the Apache License, Version 2.0 (the "License");
6 | # you may not use this file except in compliance with the License.
7 | # You may obtain a copy of the License at
8 | #
9 | # https://www.apache.org/licenses/LICENSE-2.0
10 | #
11 | # Unless required by applicable law or agreed to in writing, software
12 | # distributed under the License is distributed on an "AS IS" BASIS,
13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | # See the License for the specific language governing permissions and
15 | # limitations under the License.
16 |
17 | with open("pcsc_errors") as f:
18 | data = f.read()
19 |
20 | name = ""
21 | val = 0
22 | desc = ""
23 |
24 | with open("pcsc_errors.go", 'w+') as f:
25 | print("""// Copyright 2020 Google LLC
26 | //
27 | // Licensed under the Apache License, Version 2.0 (the "License");
28 | // you may not use this file except in compliance with the License.
29 | // You may obtain a copy of the License at
30 | //
31 | // https://www.apache.org/licenses/LICENSE-2.0
32 | //
33 | // Unless required by applicable law or agreed to in writing, software
34 | // distributed under the License is distributed on an "AS IS" BASIS,
35 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
36 | // See the License for the specific language governing permissions and
37 | // limitations under the License.
38 |
39 | package piv
40 |
41 | // https://golang.org/s/generatedcode
42 |
43 | // Code generated by errors.py DO NOT EDIT.
44 |
45 | var pcscErrMsgs = map[int64]string{""", file=f)
46 | for line in data.split("\n"):
47 | if not line.strip():
48 | continue
49 | if line.startswith("SC"):
50 | name = line.split()[0][len("SCARD_E_"):]
51 | name = "".join([s[0] + s[1:].lower() for s in name.split("_")])
52 | name = "rc" + name
53 | val = line.split()[1]
54 | else:
55 | desc = line[:-1]
56 | desc = desc[0].lower() + desc[1:]
57 | desc = desc.replace("\"", "\\\"")
58 | print("\t%s: \"%s\"," % (val, desc), file=f)
59 | print("}", file=f)
60 |
--------------------------------------------------------------------------------
/v2/piv/pcsc_freebsd.go:
--------------------------------------------------------------------------------
1 | // Copyright 2020 Google LLC
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // https://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | package piv
16 |
17 | import "C"
18 |
19 | // Return codes for PCSC are different on different platforms (int vs. long).
20 |
21 | func scCheck(rc C.long) error {
22 | if rc == rcSuccess {
23 | return nil
24 | }
25 | return &scErr{int64(rc)}
26 | }
27 |
28 | func isRCNoReaders(rc C.long) bool {
29 | return uint32(rc) == 0x8010002E
30 | }
31 |
--------------------------------------------------------------------------------
/v2/piv/pcsc_linux.go:
--------------------------------------------------------------------------------
1 | // Copyright 2020 Google LLC
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // https://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | package piv
16 |
17 | import "C"
18 |
19 | // Return codes for PCSC are different on different platforms (int vs. long).
20 |
21 | func scCheck(rc C.long) error {
22 | if rc == rcSuccess {
23 | return nil
24 | }
25 | return &scErr{int64(rc)}
26 | }
27 |
28 | func isRCNoReaders(rc C.long) bool {
29 | return C.ulong(rc) == 0x8010002E
30 | }
31 |
--------------------------------------------------------------------------------
/v2/piv/pcsc_openbsd.go:
--------------------------------------------------------------------------------
1 | // Copyright 2020 Google LLC
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // https://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | package piv
16 |
17 | import "C"
18 |
19 | // Return codes for PCSC are different on different platforms (int vs. long).
20 |
21 | func scCheck(rc C.long) error {
22 | if rc == rcSuccess {
23 | return nil
24 | }
25 | return &scErr{int64(rc)}
26 | }
27 |
28 | func isRCNoReaders(rc C.long) bool {
29 | return rc == 0x8010002E
30 | }
31 |
--------------------------------------------------------------------------------
/v2/piv/pcsc_test.go:
--------------------------------------------------------------------------------
1 | // Copyright 2020 Google LLC
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // https://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | package piv
16 |
17 | import (
18 | "errors"
19 | "strings"
20 | "testing"
21 | )
22 |
23 | func runContextTest(t *testing.T, f func(t *testing.T, c *scContext)) {
24 | if !canModifyYubiKey {
25 | t.Skip("not running test that accesses yubikey, provide --wipe-yubikey flag")
26 | }
27 | ctx, err := newSCContext()
28 | if err != nil {
29 | t.Fatalf("creating context: %v", err)
30 | }
31 | defer func() {
32 | if err := ctx.Close(); err != nil {
33 | t.Errorf("closing context: %v", err)
34 | }
35 | }()
36 | f(t, ctx)
37 | }
38 |
39 | func TestContextClose(t *testing.T) {
40 | runContextTest(t, func(t *testing.T, c *scContext) {})
41 | }
42 |
43 | func TestContextListReaders(t *testing.T) {
44 | runContextTest(t, testContextListReaders)
45 | }
46 |
47 | func testContextListReaders(t *testing.T, c *scContext) {
48 | if _, err := c.ListReaders(); err != nil {
49 | t.Errorf("listing readers: %v", err)
50 | }
51 | }
52 |
53 | func runHandleTest(t *testing.T, f func(t *testing.T, h *scHandle)) {
54 | runContextTest(t, func(t *testing.T, c *scContext) {
55 | readers, err := c.ListReaders()
56 | if err != nil {
57 | t.Fatalf("listing smartcard readers: %v", err)
58 | }
59 | reader := ""
60 | for _, r := range readers {
61 | if strings.Contains(strings.ToLower(r), "yubikey") {
62 | reader = r
63 | break
64 | }
65 | }
66 | if reader == "" {
67 | t.Skip("could not find yubikey, skipping testing")
68 | }
69 | h, err := c.Connect(reader)
70 | if err != nil {
71 | t.Fatalf("connecting to %s: %v", reader, err)
72 | }
73 | defer func() {
74 | if err := h.Close(); err != nil {
75 | t.Errorf("disconnecting from handle: %v", err)
76 | }
77 | }()
78 | f(t, h)
79 | })
80 | }
81 |
82 | func TestHandle(t *testing.T) {
83 | runHandleTest(t, func(t *testing.T, h *scHandle) {})
84 | }
85 |
86 | func TestTransaction(t *testing.T) {
87 | runHandleTest(t, func(t *testing.T, h *scHandle) {
88 | tx, err := h.Begin()
89 | if err != nil {
90 | t.Fatalf("beginning transaction: %v", err)
91 | }
92 | if err := tx.Close(); err != nil {
93 | t.Fatalf("closing transaction: %v", err)
94 | }
95 | })
96 | }
97 |
98 | func TestErrors(t *testing.T) {
99 | tests := []struct {
100 | sw1, sw2 byte
101 | isErrNotFound bool
102 | isAuthErr bool
103 | retries int
104 | desc string
105 | }{
106 | {0x68, 0x82, false, false, 0, "secure messaging not supported"},
107 | {0x63, 0x00, false, true, 0, "verification failed"},
108 | {0x63, 0xc0, false, true, 0, "verification failed (0 retries remaining)"},
109 | {0x63, 0xc1, false, true, 1, "verification failed (1 retry remaining)"},
110 | {0x63, 0xcf, false, true, 15, "verification failed (15 retries remaining)"},
111 | {0x63, 0x01, false, true, 1, "verification failed (1 retry remaining)"},
112 | {0x63, 0x0f, false, true, 15, "verification failed (15 retries remaining)"},
113 | {0x69, 0x83, false, true, 0, "authentication method blocked"},
114 | {0x6a, 0x82, true, false, 0, "data object or application not found"},
115 | }
116 |
117 | for _, tc := range tests {
118 | err := &apduErr{tc.sw1, tc.sw2}
119 | if errors.Is(err, ErrNotFound) != tc.isErrNotFound {
120 | var s string
121 | if !tc.isErrNotFound {
122 | s = " not"
123 | }
124 | t.Errorf("%q should%s be ErrNotFound", tc.desc, s)
125 | }
126 |
127 | var authErr AuthErr
128 | if errors.As(err, &authErr) != tc.isAuthErr {
129 | var s string
130 | if !tc.isAuthErr {
131 | s = " not"
132 | }
133 | t.Errorf("%q should%s be AuthErr", tc.desc, s)
134 | }
135 | if authErr.Retries != tc.retries {
136 | t.Errorf("%q retries should be %d, got %d", tc.desc, tc.retries, authErr.Retries)
137 | }
138 | if !strings.Contains(err.Error(), tc.desc) {
139 | t.Errorf("Error %v should contain text %v", err, tc.desc)
140 | }
141 | }
142 | }
143 |
--------------------------------------------------------------------------------
/v2/piv/pcsc_unix.go:
--------------------------------------------------------------------------------
1 | // Copyright 2020 Google LLC
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // https://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | //go:build darwin || linux || freebsd || openbsd
16 | // +build darwin linux freebsd openbsd
17 |
18 | package piv
19 |
20 | // https://ludovicrousseau.blogspot.com/2010/04/pcsc-sample-in-c.html
21 |
22 | // #cgo darwin LDFLAGS: -framework PCSC
23 | // #cgo linux pkg-config: libpcsclite
24 | // #cgo freebsd CFLAGS: -I/usr/local/include/
25 | // #cgo freebsd CFLAGS: -I/usr/local/include/PCSC
26 | // #cgo freebsd LDFLAGS: -L/usr/local/lib/
27 | // #cgo freebsd LDFLAGS: -lpcsclite
28 | // #cgo openbsd CFLAGS: -I/usr/local/include/
29 | // #cgo openbsd CFLAGS: -I/usr/local/include/PCSC
30 | // #cgo openbsd LDFLAGS: -L/usr/local/lib/
31 | // #cgo openbsd LDFLAGS: -lpcsclite
32 | // #include
33 | // #include
34 | import "C"
35 |
36 | import (
37 | "bytes"
38 | "fmt"
39 | "unsafe"
40 | )
41 |
42 | const rcSuccess = C.SCARD_S_SUCCESS
43 |
44 | type scContext struct {
45 | ctx C.SCARDCONTEXT
46 | }
47 |
48 | func newSCContext() (*scContext, error) {
49 | var ctx C.SCARDCONTEXT
50 | rc := C.SCardEstablishContext(C.SCARD_SCOPE_SYSTEM, nil, nil, &ctx)
51 | if err := scCheck(rc); err != nil {
52 | return nil, err
53 | }
54 | return &scContext{ctx: ctx}, nil
55 | }
56 |
57 | func (c *scContext) Close() error {
58 | return scCheck(C.SCardReleaseContext(c.ctx))
59 | }
60 |
61 | func (c *scContext) ListReaders() ([]string, error) {
62 | var n C.DWORD
63 | rc := C.SCardListReaders(c.ctx, nil, nil, &n)
64 | // On Linux, the PC/SC daemon will return an error when no smart cards are
65 | // available. Detect this and return nil with no smart cards instead.
66 | //
67 | // isRCNoReaders is defined in OS specific packages.
68 | if isRCNoReaders(rc) {
69 | return nil, nil
70 | }
71 |
72 | if err := scCheck(rc); err != nil {
73 | return nil, err
74 | }
75 |
76 | d := make([]byte, n)
77 | rc = C.SCardListReaders(c.ctx, nil, (*C.char)(unsafe.Pointer(&d[0])), &n)
78 | if err := scCheck(rc); err != nil {
79 | return nil, err
80 | }
81 |
82 | var readers []string
83 | for _, d := range bytes.Split(d, []byte{0}) {
84 | if len(d) > 0 {
85 | readers = append(readers, string(d))
86 | }
87 | }
88 | return readers, nil
89 | }
90 |
91 | type scHandle struct {
92 | h C.SCARDHANDLE
93 | }
94 |
95 | func (c *scContext) Connect(reader string) (*scHandle, error) {
96 | var (
97 | handle C.SCARDHANDLE
98 | activeProtocol C.DWORD
99 | )
100 | rc := C.SCardConnect(c.ctx, C.CString(reader),
101 | C.SCARD_SHARE_EXCLUSIVE, C.SCARD_PROTOCOL_T1,
102 | &handle, &activeProtocol)
103 | if err := scCheck(rc); err != nil {
104 | return nil, err
105 | }
106 | return &scHandle{handle}, nil
107 | }
108 |
109 | func (h *scHandle) Close() error {
110 | return scCheck(C.SCardDisconnect(h.h, C.SCARD_LEAVE_CARD))
111 | }
112 |
113 | type scTx struct {
114 | h C.SCARDHANDLE
115 | }
116 |
117 | func (h *scHandle) Begin() (*scTx, error) {
118 | if err := scCheck(C.SCardBeginTransaction(h.h)); err != nil {
119 | return nil, err
120 | }
121 | return &scTx{h.h}, nil
122 | }
123 |
124 | func (t *scTx) Close() error {
125 | return scCheck(C.SCardEndTransaction(t.h, C.SCARD_LEAVE_CARD))
126 | }
127 |
128 | func (t *scTx) transmit(req []byte) (more bool, b []byte, err error) {
129 | var resp [C.MAX_BUFFER_SIZE_EXTENDED]byte
130 | reqN := C.DWORD(len(req))
131 | respN := C.DWORD(len(resp))
132 | rc := C.SCardTransmit(
133 | t.h,
134 | C.SCARD_PCI_T1,
135 | (*C.BYTE)(&req[0]), reqN, nil,
136 | (*C.BYTE)(&resp[0]), &respN)
137 | if err := scCheck(rc); err != nil {
138 | return false, nil, fmt.Errorf("transmitting request: %w", err)
139 | }
140 | if respN < 2 {
141 | return false, nil, fmt.Errorf("scard response too short: %d", respN)
142 | }
143 | sw1 := resp[respN-2]
144 | sw2 := resp[respN-1]
145 | if sw1 == 0x90 && sw2 == 0x00 {
146 | return false, resp[:respN-2], nil
147 | }
148 | if sw1 == 0x61 {
149 | return true, resp[:respN-2], nil
150 | }
151 | return false, nil, &apduErr{sw1, sw2}
152 | }
153 |
--------------------------------------------------------------------------------
/v2/piv/pcsc_windows.go:
--------------------------------------------------------------------------------
1 | // Copyright 2020 Google LLC
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // https://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | package piv
16 |
17 | import (
18 | "fmt"
19 | "syscall"
20 | "unsafe"
21 | )
22 |
23 | var (
24 | winscard = syscall.NewLazyDLL("Winscard.dll")
25 | procSCardEstablishContext = winscard.NewProc("SCardEstablishContext")
26 | procSCardListReadersW = winscard.NewProc("SCardListReadersW")
27 | procSCardReleaseContext = winscard.NewProc("SCardReleaseContext")
28 | procSCardConnectW = winscard.NewProc("SCardConnectW")
29 | procSCardDisconnect = winscard.NewProc("SCardDisconnect")
30 | procSCardBeginTransaction = winscard.NewProc("SCardBeginTransaction")
31 | procSCardEndTransaction = winscard.NewProc("SCardEndTransaction")
32 | procSCardTransmit = winscard.NewProc("SCardTransmit")
33 | )
34 |
35 | const (
36 | scardScopeSystem = 2
37 | scardShareExclusive = 1
38 | scardLeaveCard = 0
39 | scardProtocolT1 = 2
40 | scardPCIT1 = 0
41 | maxBufferSizeExtended = (4 + 3 + (1 << 16) + 3 + 2)
42 | rcSuccess = 0
43 | )
44 |
45 | func scCheck(rc uintptr) error {
46 | if rc == rcSuccess {
47 | return nil
48 | }
49 | return &scErr{int64(rc)}
50 | }
51 |
52 | func isRCNoReaders(rc uintptr) bool {
53 | return rc == 0x8010002E
54 | }
55 |
56 | type scContext struct {
57 | ctx syscall.Handle
58 | }
59 |
60 | func newSCContext() (*scContext, error) {
61 | var ctx syscall.Handle
62 |
63 | r0, _, _ := procSCardEstablishContext.Call(
64 | uintptr(scardScopeSystem),
65 | uintptr(0),
66 | uintptr(0),
67 | uintptr(unsafe.Pointer(&ctx)),
68 | )
69 | if err := scCheck(r0); err != nil {
70 | return nil, err
71 | }
72 | return &scContext{ctx: ctx}, nil
73 | }
74 |
75 | func (c *scContext) Close() error {
76 | r0, _, _ := procSCardReleaseContext.Call(uintptr(c.ctx))
77 | return scCheck(r0)
78 | }
79 |
80 | func (c *scContext) ListReaders() ([]string, error) {
81 | var n uint32
82 | r0, _, _ := procSCardListReadersW.Call(
83 | uintptr(c.ctx),
84 | uintptr(unsafe.Pointer(nil)),
85 | uintptr(unsafe.Pointer(nil)),
86 | uintptr(unsafe.Pointer(&n)),
87 | )
88 |
89 | if isRCNoReaders(r0) {
90 | return nil, nil
91 | }
92 |
93 | if err := scCheck(r0); err != nil {
94 | return nil, err
95 | }
96 |
97 | d := make([]uint16, n)
98 | r0, _, _ = procSCardListReadersW.Call(
99 | uintptr(c.ctx),
100 | uintptr(unsafe.Pointer(nil)),
101 | uintptr(unsafe.Pointer(&d[0])),
102 | uintptr(unsafe.Pointer(&n)),
103 | )
104 | if err := scCheck(r0); err != nil {
105 | return nil, err
106 | }
107 |
108 | var readers []string
109 | j := 0
110 | for i := 0; i < len(d); i++ {
111 | if d[i] != 0 {
112 | continue
113 | }
114 | readers = append(readers, syscall.UTF16ToString(d[j:i]))
115 | j = i + 1
116 |
117 | if d[i+1] == 0 {
118 | break
119 | }
120 | }
121 |
122 | return readers, nil
123 | }
124 |
125 | func (c *scContext) Connect(reader string) (*scHandle, error) {
126 | var (
127 | handle syscall.Handle
128 | activeProtocol uint16
129 | )
130 | readerPtr, err := syscall.UTF16PtrFromString(reader)
131 | if err != nil {
132 | return nil, fmt.Errorf("invalid reader string: %v", err)
133 | }
134 | r0, _, _ := procSCardConnectW.Call(
135 | uintptr(c.ctx),
136 | uintptr(unsafe.Pointer(readerPtr)),
137 | scardShareExclusive,
138 | scardProtocolT1,
139 | uintptr(unsafe.Pointer(&handle)),
140 | uintptr(activeProtocol),
141 | )
142 | if err := scCheck(r0); err != nil {
143 | return nil, err
144 | }
145 | return &scHandle{handle}, nil
146 | }
147 |
148 | type scHandle struct {
149 | handle syscall.Handle
150 | }
151 |
152 | func (h *scHandle) Close() error {
153 | r0, _, _ := procSCardDisconnect.Call(uintptr(h.handle), scardLeaveCard)
154 | return scCheck(r0)
155 | }
156 |
157 | func (h *scHandle) Begin() (*scTx, error) {
158 | r0, _, _ := procSCardBeginTransaction.Call(uintptr(h.handle))
159 | if err := scCheck(r0); err != nil {
160 | return nil, err
161 | }
162 | return &scTx{h.handle}, nil
163 | }
164 |
165 | func (t *scTx) Close() error {
166 | r0, _, _ := procSCardEndTransaction.Call(uintptr(t.handle), scardLeaveCard)
167 | return scCheck(r0)
168 | }
169 |
170 | type scTx struct {
171 | handle syscall.Handle
172 | }
173 |
174 | func (t *scTx) transmit(req []byte) (more bool, b []byte, err error) {
175 | var resp [maxBufferSizeExtended]byte
176 | reqN := len(req)
177 | respN := len(resp)
178 | r0, _, _ := procSCardTransmit.Call(
179 | uintptr(t.handle),
180 | uintptr(scardPCIT1),
181 | uintptr(unsafe.Pointer(&req[0])),
182 | uintptr(reqN),
183 | uintptr(0),
184 | uintptr(unsafe.Pointer(&resp[0])),
185 | uintptr(unsafe.Pointer(&respN)),
186 | )
187 |
188 | if err := scCheck(r0); err != nil {
189 | return false, nil, fmt.Errorf("transmitting request: %w", err)
190 | }
191 | if respN < 2 {
192 | return false, nil, fmt.Errorf("scard response too short: %d", respN)
193 | }
194 | sw1 := resp[respN-2]
195 | sw2 := resp[respN-1]
196 | if sw1 == 0x90 && sw2 == 0x00 {
197 | return false, resp[:respN-2], nil
198 | }
199 | if sw1 == 0x61 {
200 | return true, resp[:respN-2], nil
201 | }
202 | return false, nil, &apduErr{sw1, sw2}
203 | }
204 |
--------------------------------------------------------------------------------
/v2/piv/piv.go:
--------------------------------------------------------------------------------
1 | // Copyright 2020 Google LLC
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // https://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | package piv
16 |
17 | import (
18 | "bytes"
19 | "crypto/aes"
20 | "crypto/cipher"
21 | "crypto/des"
22 | "crypto/rand"
23 | "encoding/asn1"
24 | "encoding/binary"
25 | "errors"
26 | "fmt"
27 | "io"
28 | "math/big"
29 | )
30 |
31 | var (
32 | // DefaultPIN for the PIV applet. The PIN is used to change the Management Key,
33 | // and slots can optionally require it to perform signing operations.
34 | DefaultPIN = "123456"
35 | // DefaultPUK for the PIV applet. The PUK is only used to reset the PIN when
36 | // the card's PIN retries have been exhausted.
37 | DefaultPUK = "12345678"
38 | // DefaultManagementKey for the PIV applet. The Management Key is a Triple-DES
39 | // key required for slot actions such as generating keys, setting certificates,
40 | // and signing.
41 | DefaultManagementKey = []byte{
42 | 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08,
43 | 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08,
44 | 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08,
45 | }
46 | )
47 |
48 | // Cards lists all smart cards available via PC/SC interface. Card names are
49 | // strings describing the key, such as "Yubico Yubikey NEO OTP+U2F+CCID 00 00".
50 | //
51 | // Card names depend on the operating system and what port a card is plugged
52 | // into. To uniquely identify a card, use its serial number.
53 | //
54 | // See: https://ludovicrousseau.blogspot.com/2010/05/what-is-in-pcsc-reader-name.html
55 | func Cards() ([]string, error) {
56 | var c client
57 | return c.Cards()
58 | }
59 |
60 | const (
61 | // https://nvlpubs.nist.gov/nistpubs/SpecialPublications/NIST.SP.800-78-4.pdf#page=17
62 | algTag = 0x80
63 | alg3DES = 0x03
64 | algAES128 = 0x08
65 | algAES192 = 0x0a
66 | algAES256 = 0x0c
67 | algRSA1024 = 0x06
68 | algRSA2048 = 0x07
69 | algRSA3072 = 0x05
70 | algRSA4096 = 0x16
71 | algECCP256 = 0x11
72 | algECCP384 = 0x14
73 | // non-standard; implemented by YubiKey 5.7.x. Previous versions supported
74 | // Ed25519 on SoloKeys with the value 0x22
75 | algEd25519 = 0xE0
76 | algX25519 = 0xE1
77 |
78 | // https://nvlpubs.nist.gov/nistpubs/SpecialPublications/NIST.SP.800-78-4.pdf#page=16
79 | keyAuthentication = 0x9a
80 | keyCardManagement = 0x9b
81 | keySignature = 0x9c
82 | keyKeyManagement = 0x9d
83 | keyCardAuthentication = 0x9e
84 | keyAttestation = 0xf9
85 |
86 | insVerify = 0x20
87 | insChangeReference = 0x24
88 | insResetRetry = 0x2c
89 | insGenerateAsymmetric = 0x47
90 | insAuthenticate = 0x87
91 | insGetData = 0xcb
92 | insPutData = 0xdb
93 | insSelectApplication = 0xa4
94 | insGetResponseAPDU = 0xc0
95 |
96 | // https://github.com/Yubico/yubico-piv-tool/blob/yubico-piv-tool-1.7.0/lib/ykpiv.h#L656
97 | insSetMGMKey = 0xff
98 | insImportKey = 0xfe
99 | insGetVersion = 0xfd
100 | insReset = 0xfb
101 | insSetPINRetries = 0xfa
102 | insAttest = 0xf9
103 | insGetSerial = 0xf8
104 | insGetMetadata = 0xf7
105 | )
106 |
107 | // YubiKey is an exclusive open connection to a YubiKey smart card. While open,
108 | // no other process can query the given card.
109 | //
110 | // To release the connection, call the Close method.
111 | type YubiKey struct {
112 | ctx *scContext
113 | h *scHandle
114 | tx *scTx
115 |
116 | rand io.Reader
117 |
118 | // Used to determine how to access certain functionality.
119 | //
120 | // TODO: It's not clear what this actually communicates. Is this the
121 | // YubiKey's version or PIV version? A NEO reports v1.0.4. Figure this out
122 | // before exposing an API.
123 | version *version
124 | }
125 |
126 | // Close releases the connection to the smart card.
127 | func (yk *YubiKey) Close() error {
128 | err1 := yk.h.Close()
129 | err2 := yk.ctx.Close()
130 | if err1 == nil {
131 | return err2
132 | }
133 | return err1
134 | }
135 |
136 | // Open connects to a YubiKey smart card.
137 | func Open(card string) (*YubiKey, error) {
138 | var c client
139 | return c.Open(card)
140 | }
141 |
142 | // client is a smart card client and may be exported in the future to allow
143 | // configuration for the top level Open() and Cards() APIs.
144 | type client struct {
145 | // Rand is a cryptographic source of randomness used for card challenges.
146 | //
147 | // If nil, defaults to crypto.Rand.
148 | Rand io.Reader
149 | }
150 |
151 | func (c *client) Cards() ([]string, error) {
152 | ctx, err := newSCContext()
153 | if err != nil {
154 | return nil, fmt.Errorf("connecting to pcsc: %w", err)
155 | }
156 | defer ctx.Close()
157 | return ctx.ListReaders()
158 | }
159 |
160 | func (c *client) Open(card string) (*YubiKey, error) {
161 | ctx, err := newSCContext()
162 | if err != nil {
163 | return nil, fmt.Errorf("connecting to smart card daemon: %w", err)
164 | }
165 |
166 | h, err := ctx.Connect(card)
167 | if err != nil {
168 | ctx.Close()
169 | return nil, fmt.Errorf("connecting to smart card: %w", err)
170 | }
171 | tx, err := h.Begin()
172 | if err != nil {
173 | return nil, fmt.Errorf("beginning smart card transaction: %w", err)
174 | }
175 | if err := ykSelectApplication(tx, aidPIV[:]); err != nil {
176 | tx.Close()
177 | return nil, fmt.Errorf("selecting piv applet: %w", err)
178 | }
179 |
180 | yk := &YubiKey{ctx: ctx, h: h, tx: tx}
181 | v, err := ykVersion(yk.tx)
182 | if err != nil {
183 | yk.Close()
184 | return nil, fmt.Errorf("getting yubikey version: %w", err)
185 | }
186 | yk.version = v
187 | if c.Rand != nil {
188 | yk.rand = c.Rand
189 | } else {
190 | yk.rand = rand.Reader
191 | }
192 | return yk, nil
193 | }
194 |
195 | // Version returns the version as reported by the PIV applet. For newer
196 | // YubiKeys (>=4.0.0) this corresponds to the version of the YubiKey itself.
197 | //
198 | // Older YubiKeys return values that aren't directly related to the YubiKey
199 | // version. For example, 3rd generation YubiKeys report 1.0.X.
200 | func (yk *YubiKey) Version() Version {
201 | return Version{
202 | Major: int(yk.version.major),
203 | Minor: int(yk.version.minor),
204 | Patch: int(yk.version.patch),
205 | }
206 | }
207 |
208 | // Serial returns the YubiKey's serial number.
209 | func (yk *YubiKey) Serial() (uint32, error) {
210 | return ykSerial(yk.tx, yk.version)
211 | }
212 |
213 | func encodePIN(pin string) ([]byte, error) {
214 | data := []byte(pin)
215 | if len(data) == 0 {
216 | return nil, fmt.Errorf("pin cannot be empty")
217 | }
218 | if len(data) > 8 {
219 | return nil, fmt.Errorf("pin longer than 8 bytes")
220 | }
221 |
222 | // apply padding
223 | // 2.4 Security Architecture
224 | // 2.4.3 Authentication of an Individual
225 | // https://nvlpubs.nist.gov/nistpubs/SpecialPublications/NIST.SP.800-73-4.pdf#page=88
226 | for i := len(data); i < 8; i++ {
227 | data = append(data, 0xff)
228 | }
229 | return data, nil
230 | }
231 |
232 | // VerifyPIN attempts to authenticate against the card with the provided PIN.
233 | //
234 | // PIN authentication for other operations are handled separately, and VerifyPIN
235 | // does not need to be called before those methods.
236 | //
237 | // After a specific number of authentication attemps with an invalid PIN,
238 | // usually 3, the PIN will become block and refuse further attempts. At that
239 | // point the PUK must be used to unblock the PIN.
240 | //
241 | // Use DefaultPIN if the PIN hasn't been set.
242 | func (yk *YubiKey) VerifyPIN(pin string) error {
243 | return ykLogin(yk.tx, pin)
244 | }
245 |
246 | func ykLogin(tx *scTx, pin string) error {
247 | data, err := encodePIN(pin)
248 | if err != nil {
249 | return err
250 | }
251 |
252 | // 3.2 PIV Card Application Card Commands for Authentication
253 | // 3.2.1 VERIFY Card Command
254 | // https://csrc.nist.gov/CSRC/media/Publications/sp/800-73/4/archive/2015-05-29/documents/sp800_73-4_pt2_draft.pdf#page=20
255 | // https://nvlpubs.nist.gov/nistpubs/SpecialPublications/NIST.SP.800-73-4.pdf#page=86
256 | cmd := apdu{instruction: insVerify, param2: 0x80, data: data}
257 | if _, err := tx.Transmit(cmd); err != nil {
258 | return fmt.Errorf("verify pin: %w", err)
259 | }
260 | return nil
261 | }
262 |
263 | func ykLoginNeeded(tx *scTx) bool {
264 | cmd := apdu{instruction: insVerify, param2: 0x80}
265 | _, err := tx.Transmit(cmd)
266 | return err != nil
267 | }
268 |
269 | // Retries returns the number of attempts remaining to enter the correct PIN.
270 | func (yk *YubiKey) Retries() (int, error) {
271 | return ykPINRetries(yk.tx)
272 | }
273 |
274 | func ykPINRetries(tx *scTx) (int, error) {
275 | cmd := apdu{instruction: insVerify, param2: 0x80}
276 | _, err := tx.Transmit(cmd)
277 | if err == nil {
278 | return 0, fmt.Errorf("expected error code from empty pin")
279 | }
280 | var e AuthErr
281 | if errors.As(err, &e) {
282 | return e.Retries, nil
283 | }
284 | return 0, fmt.Errorf("invalid response: %w", err)
285 | }
286 |
287 | // Reset resets the YubiKey PIV applet to its factory settings, wiping all slots
288 | // and resetting the PIN, PUK, and Management Key to their default values. This
289 | // does NOT affect data on other applets, such as GPG or U2F.
290 | func (yk *YubiKey) Reset() error {
291 | return ykReset(yk.tx, yk.rand)
292 | }
293 |
294 | func ykReset(tx *scTx, r io.Reader) error {
295 | // Reset only works if both the PIN and PUK are blocked. Before resetting,
296 | // try the wrong PIN and PUK multiple times to block them.
297 |
298 | maxPIN := big.NewInt(100_000_000)
299 | pinInt, err := rand.Int(r, maxPIN)
300 | if err != nil {
301 | return fmt.Errorf("generating random pin: %v", err)
302 | }
303 | pukInt, err := rand.Int(r, maxPIN)
304 | if err != nil {
305 | return fmt.Errorf("generating random puk: %v", err)
306 | }
307 |
308 | pin := pinInt.String()
309 | puk := pukInt.String()
310 |
311 | for {
312 | err := ykLogin(tx, pin)
313 | if err == nil {
314 | // TODO: do we care about a 1/100million chance?
315 | return fmt.Errorf("expected error with random pin")
316 | }
317 | var e AuthErr
318 | if !errors.As(err, &e) {
319 | return fmt.Errorf("blocking pin: %w", err)
320 | }
321 | if e.Retries == 0 {
322 | break
323 | }
324 | }
325 |
326 | for {
327 | err := ykChangePUK(tx, puk, puk)
328 | if err == nil {
329 | // TODO: do we care about a 1/100million chance?
330 | return fmt.Errorf("expected error with random puk")
331 | }
332 | var e AuthErr
333 | if !errors.As(err, &e) {
334 | return fmt.Errorf("blocking puk: %w", err)
335 | }
336 | if e.Retries == 0 {
337 | break
338 | }
339 | }
340 |
341 | cmd := apdu{instruction: insReset}
342 | if _, err := tx.Transmit(cmd); err != nil {
343 | return fmt.Errorf("reseting yubikey: %w", err)
344 | }
345 | return nil
346 | }
347 |
348 | type version struct {
349 | major byte
350 | minor byte
351 | patch byte
352 | }
353 |
354 | // authManagementKey attempts to authenticate against the card with the provided
355 | // management key. The management key is required to generate new keys or add
356 | // certificates to slots.
357 | //
358 | // Use DefaultManagementKey if the management key hasn't been set.
359 | func (yk *YubiKey) authManagementKey(key []byte) error {
360 | return ykAuthenticate(yk.tx, key, yk.rand, yk.version)
361 | }
362 |
363 | var (
364 | // Smartcard Application IDs for YubiKeys.
365 | //
366 | // https://github.com/Yubico/yubico-piv-tool/blob/yubico-piv-tool-1.7.0/lib/ykpiv.c#L1877
367 | // https://github.com/Yubico/yubico-piv-tool/blob/yubico-piv-tool-1.7.0/lib/ykpiv.c#L108-L110
368 | // https://github.com/Yubico/yubico-piv-tool/blob/yubico-piv-tool-1.7.0/lib/ykpiv.c#L1117
369 |
370 | aidManagement = [...]byte{0xa0, 0x00, 0x00, 0x05, 0x27, 0x47, 0x11, 0x17}
371 | aidPIV = [...]byte{0xa0, 0x00, 0x00, 0x03, 0x08}
372 | aidYubiKey = [...]byte{0xa0, 0x00, 0x00, 0x05, 0x27, 0x20, 0x01, 0x01}
373 | )
374 |
375 | var managementKeyLengthMap = map[byte]int{
376 | alg3DES: 24,
377 | algAES128: 16,
378 | algAES192: 24,
379 | algAES256: 32,
380 | }
381 |
382 | func ykAuthenticate(tx *scTx, key []byte, rand io.Reader, version *version) error {
383 | // https://nvlpubs.nist.gov/nistpubs/SpecialPublications/NIST.SP.800-73-4.pdf#page=92
384 | // https://tsapps.nist.gov/publication/get_pdf.cfm?pub_id=918402#page=114
385 |
386 | var managementKeyType byte
387 | if supportsVersion(version, 5, 3, 0) {
388 | // if yubikey version >= 5.3.0, determine management key type using slot metadata
389 | cmd := apdu{
390 | instruction: insGetMetadata,
391 | param1: 0x00,
392 | param2: keyCardManagement,
393 | }
394 | resp, err := tx.Transmit(cmd)
395 | if err != nil {
396 | return fmt.Errorf("determining key management type: %w", err)
397 | }
398 | managementKeyType = resp[2:][0]
399 | }
400 |
401 | // set challengeLength based on managementKeyType
402 | var challengeLength byte
403 | switch managementKeyType {
404 | case algAES128, algAES192, algAES256:
405 | challengeLength = 16
406 | default:
407 | // default fallback to 3DES
408 | managementKeyType = alg3DES
409 | challengeLength = 8
410 | }
411 | if len(key) != managementKeyLengthMap[managementKeyType] {
412 | return fmt.Errorf("invalid management key length: %d bytes (expected %d)", len(key), managementKeyLengthMap[managementKeyType])
413 | }
414 |
415 | // request a witness
416 | cmd := apdu{
417 | instruction: insAuthenticate,
418 | param1: managementKeyType,
419 | param2: keyCardManagement,
420 | data: []byte{
421 | 0x7c, // Dynamic Authentication Template tag
422 | 0x02, // Length of object
423 | 0x80, // 'Witness'
424 | 0x00, // Return encrypted random
425 | },
426 | }
427 | resp, err := tx.Transmit(cmd)
428 | if err != nil {
429 | return fmt.Errorf("get auth challenge: %w", err)
430 | }
431 | if n := len(resp); n < 12 {
432 | return fmt.Errorf("challenge didn't return enough bytes: %d", n)
433 | }
434 | if !bytes.Equal(resp[:4], []byte{
435 | 0x7c,
436 | challengeLength + 2,
437 | 0x80, // 'Witness'
438 | challengeLength, // Tag length
439 | }) {
440 | return fmt.Errorf("invalid authentication object header: %x", resp[:4])
441 | }
442 |
443 | var block cipher.Block
444 | switch managementKeyType {
445 | case algAES128, algAES192, algAES256:
446 | block, err = aes.NewCipher(key[:])
447 | if err != nil {
448 | return fmt.Errorf("creating aes block cipher: %v", err)
449 | }
450 | default:
451 | block, err = des.NewTripleDESCipher(key[:])
452 | if err != nil {
453 | return fmt.Errorf("creating des block cipher: %v", err)
454 | }
455 | }
456 |
457 | cardChallenge := resp[4 : 4+challengeLength]
458 | cardResponse := make([]byte, challengeLength)
459 |
460 | block.Decrypt(cardResponse, cardChallenge)
461 |
462 | challenge := make([]byte, challengeLength)
463 | if _, err := io.ReadFull(rand, challenge); err != nil {
464 | return fmt.Errorf("reading rand data: %v", err)
465 | }
466 | response := make([]byte, challengeLength)
467 | block.Encrypt(response, challenge)
468 |
469 | data := []byte{
470 | 0x7c, // Dynamic Authentication Template tag
471 | (challengeLength + 2) * 2,
472 | 0x80, // 'Witness'
473 | challengeLength, // Tag length
474 | }
475 | data = append(data, cardResponse...)
476 | data = append(data,
477 | 0x81, // 'Challenge'
478 | challengeLength, // Tag length
479 | )
480 | data = append(data, challenge...)
481 |
482 | cmd = apdu{
483 | instruction: insAuthenticate,
484 | param1: managementKeyType,
485 | param2: keyCardManagement,
486 | data: data,
487 | }
488 | resp, err = tx.Transmit(cmd)
489 | if err != nil {
490 | return fmt.Errorf("auth challenge: %w", err)
491 | }
492 | if n := len(resp); n < 12 {
493 | return fmt.Errorf("challenge response didn't return enough bytes: %d", n)
494 | }
495 | if !bytes.Equal(resp[:4], []byte{
496 | 0x7c,
497 | challengeLength + 2,
498 | 0x82, // 'Response'
499 | challengeLength,
500 | }) {
501 | return fmt.Errorf("response invalid authentication object header: %x", resp[:4])
502 | }
503 | if !bytes.Equal(resp[4:4+challengeLength], response) {
504 | return fmt.Errorf("challenge failed")
505 | }
506 |
507 | return nil
508 | }
509 |
510 | // SetManagementKey updates the management key to a new key. Management keys
511 | // are triple-des keys, however padding isn't verified. To generate a new key,
512 | // generate 24 random bytes.
513 | //
514 | // Note: Yubikeys also support aes128, aes192, and aes256 management keys,
515 | // which are 16, 24, and 32 bytes, respectively.
516 | //
517 | // var newKey [24]byte
518 | // if _, err := io.ReadFull(rand.Reader, newKey[:]); err != nil {
519 | // // ...
520 | // }
521 | // if err := yk.SetManagementKey(piv.DefaultManagementKey, newKey[:]); err != nil {
522 | // // ...
523 | // }
524 | func (yk *YubiKey) SetManagementKey(oldKey, newKey []byte) error {
525 | if err := ykAuthenticate(yk.tx, oldKey, yk.rand, yk.version); err != nil {
526 | return fmt.Errorf("authenticating with old key: %w", err)
527 | }
528 | if err := ykSetManagementKey(yk.tx, newKey, false, yk.version); err != nil {
529 | return err
530 | }
531 | return nil
532 | }
533 |
534 | // ykSetManagementKey updates the management key to a new key. This requires
535 | // authenticating with the existing management key.
536 | func ykSetManagementKey(tx *scTx, key []byte, touch bool, version *version) error {
537 | var managementKeyType byte
538 | if supportsVersion(version, 5, 4, 0) {
539 | // if yubikey version >= 5.4.0, set AES management key
540 | switch len(key) {
541 | case 16:
542 | managementKeyType = algAES128
543 | case 24:
544 | managementKeyType = algAES192
545 | case 32:
546 | managementKeyType = algAES256
547 | default:
548 | return fmt.Errorf("invalid new AES management key length: %d bytes (expected 16, 24, or 32)", len(key))
549 | }
550 | } else if len(key) == 24 {
551 | // if yubikey version < 5.4.0, set legacy 3DES management key
552 | managementKeyType = alg3DES
553 | } else {
554 | return fmt.Errorf("invalid new 3DES management key length: %d bytes (expected 24)", len(key))
555 | }
556 | cmd := apdu{
557 | instruction: insSetMGMKey,
558 | param1: 0xff,
559 | param2: 0xff,
560 | data: append([]byte{
561 | managementKeyType, keyCardManagement, byte(len(key)),
562 | }, key[:]...),
563 | }
564 | if touch {
565 | cmd.param2 = 0xfe
566 | }
567 | if _, err := tx.Transmit(cmd); err != nil {
568 | return fmt.Errorf("command failed: %w", err)
569 | }
570 | return nil
571 | }
572 |
573 | // SetPIN updates the PIN to a new value. For compatibility, PINs should be 1-8
574 | // numeric characters.
575 | //
576 | // To generate a new PIN, use the crypto/rand package.
577 | //
578 | // // Generate a 6 character PIN.
579 | // newPINInt, err := rand.Int(rand.Reader, bit.NewInt(1_000_000))
580 | // if err != nil {
581 | // // ...
582 | // }
583 | // // Format with leading zeros.
584 | // newPIN := fmt.Sprintf("%06d", newPINInt)
585 | // if err := yk.SetPIN(piv.DefaultPIN, newPIN); err != nil {
586 | // // ...
587 | // }
588 | func (yk *YubiKey) SetPIN(oldPIN, newPIN string) error {
589 | return ykChangePIN(yk.tx, oldPIN, newPIN)
590 | }
591 |
592 | func ykChangePIN(tx *scTx, oldPIN, newPIN string) error {
593 | oldPINData, err := encodePIN(oldPIN)
594 | if err != nil {
595 | return fmt.Errorf("encoding old pin: %v", err)
596 | }
597 | newPINData, err := encodePIN(newPIN)
598 | if err != nil {
599 | return fmt.Errorf("encoding new pin: %v", err)
600 | }
601 | cmd := apdu{
602 | instruction: insChangeReference,
603 | param2: 0x80,
604 | data: append(oldPINData, newPINData...),
605 | }
606 | _, err = tx.Transmit(cmd)
607 | return err
608 | }
609 |
610 | // Unblock unblocks the PIN, setting it to a new value.
611 | func (yk *YubiKey) Unblock(puk, newPIN string) error {
612 | return ykUnblockPIN(yk.tx, puk, newPIN)
613 | }
614 |
615 | func ykUnblockPIN(tx *scTx, puk, newPIN string) error {
616 | pukData, err := encodePIN(puk)
617 | if err != nil {
618 | return fmt.Errorf("encoding puk: %v", err)
619 | }
620 | newPINData, err := encodePIN(newPIN)
621 | if err != nil {
622 | return fmt.Errorf("encoding new pin: %v", err)
623 | }
624 | cmd := apdu{
625 | instruction: insResetRetry,
626 | param2: 0x80,
627 | data: append(pukData, newPINData...),
628 | }
629 | _, err = tx.Transmit(cmd)
630 | return err
631 | }
632 |
633 | // SetPUK updates the PUK to a new value. For compatibility, PUKs should be 1-8
634 | // numeric characters.
635 | //
636 | // To generate a new PUK, use the crypto/rand package.
637 | //
638 | // // Generate a 8 character PUK.
639 | // newPUKInt, err := rand.Int(rand.Reader, big.NewInt(100_000_000))
640 | // if err != nil {
641 | // // ...
642 | // }
643 | // // Format with leading zeros.
644 | // newPUK := fmt.Sprintf("%08d", newPUKInt)
645 | // if err := yk.SetPUK(piv.DefaultPUK, newPUK); err != nil {
646 | // // ...
647 | // }
648 | func (yk *YubiKey) SetPUK(oldPUK, newPUK string) error {
649 | return ykChangePUK(yk.tx, oldPUK, newPUK)
650 | }
651 |
652 | func ykChangePUK(tx *scTx, oldPUK, newPUK string) error {
653 | oldPUKData, err := encodePIN(oldPUK)
654 | if err != nil {
655 | return fmt.Errorf("encoding old puk: %v", err)
656 | }
657 | newPUKData, err := encodePIN(newPUK)
658 | if err != nil {
659 | return fmt.Errorf("encoding new puk: %v", err)
660 | }
661 | cmd := apdu{
662 | instruction: insChangeReference,
663 | param2: 0x81,
664 | data: append(oldPUKData, newPUKData...),
665 | }
666 | _, err = tx.Transmit(cmd)
667 | return err
668 | }
669 |
670 | // SetRetries sets the allowed retry count for the PIN and the PUK.
671 | //
672 | // Yubikeys allows one byte for storing each, allowed values are 1-255. In instances of greater
673 | // than 15 retries remaining, the remaining count will show 15 as Yubikeys only have 4 bits in
674 | // the response for remaining retries.
675 | //
676 | // IMPORTANT NOTE: Changing the retries on Yubikeys RESETS THE PIN AND PUK TO THEIR DEFAULTS!
677 | // If you use SetRetries, it is *highly* recommended that you follow it with SetPIN and SetPUK.
678 | // https://docs.yubico.com/yesdk/users-manual/application-piv/commands.html#set-pin-retries
679 | //
680 | // if err := yk.SetRetries(piv.DefaultManagementKey, piv.DefaultPIN, 5, 4); err != nil {
681 | // // ...
682 | // }
683 | func (yk *YubiKey) SetRetries(managementKey []byte, pin string, pinRetries int, pukRetries int) error {
684 | return ykSetRetries(yk.tx, managementKey, pin, pinRetries, pukRetries, yk.rand, yk.version)
685 | }
686 |
687 | func ykSetRetries(tx *scTx, managementKey []byte, pin string, pinRetries int, pukRetries int, rand io.Reader, version *version) error {
688 | if pinRetries < 1 || pukRetries < 1 || pinRetries > 255 || pukRetries > 255 {
689 | return fmt.Errorf("pinRetries and pukRetries must both be in range 1 - 255")
690 | }
691 |
692 | // NOTE: this action requires the management key AND PIN to be authenticated on
693 | // the same transaction. It doesn't work otherwise.
694 | if err := ykAuthenticate(tx, managementKey, rand, version); err != nil {
695 | return fmt.Errorf("authenticating with management key: %w", err)
696 | }
697 | if err := ykLogin(tx, pin); err != nil {
698 | return fmt.Errorf("authenticating with pin: %w", err)
699 | }
700 | cmd := apdu{
701 | instruction: insSetPINRetries,
702 | param1: byte(pinRetries),
703 | param2: byte(pukRetries),
704 | }
705 | if _, err := tx.Transmit(cmd); err != nil {
706 | return fmt.Errorf("command failed: %w", err)
707 | }
708 | return nil
709 | }
710 |
711 | func ykSelectApplication(tx *scTx, id []byte) error {
712 | cmd := apdu{
713 | instruction: insSelectApplication,
714 | param1: 0x04,
715 | data: id[:],
716 | }
717 | if _, err := tx.Transmit(cmd); err != nil {
718 | return fmt.Errorf("command failed: %w", err)
719 | }
720 | return nil
721 | }
722 |
723 | func ykVersion(tx *scTx) (*version, error) {
724 | cmd := apdu{
725 | instruction: insGetVersion,
726 | }
727 | resp, err := tx.Transmit(cmd)
728 | if err != nil {
729 | return nil, fmt.Errorf("command failed: %w", err)
730 | }
731 | if n := len(resp); n != 3 {
732 | return nil, fmt.Errorf("expected response to have 3 bytes, got: %d", n)
733 | }
734 | return &version{resp[0], resp[1], resp[2]}, nil
735 | }
736 |
737 | func ykSerial(tx *scTx, v *version) (uint32, error) {
738 | cmd := apdu{instruction: insGetSerial}
739 | if v.major < 5 {
740 | // Earlier versions of YubiKeys required using the yubikey applet to get
741 | // the serial number. Newer ones have this built into the PIV applet.
742 | if err := ykSelectApplication(tx, aidYubiKey[:]); err != nil {
743 | return 0, fmt.Errorf("selecting yubikey applet: %w", err)
744 | }
745 | defer ykSelectApplication(tx, aidPIV[:])
746 | cmd = apdu{instruction: 0x01, param1: 0x10}
747 | }
748 | resp, err := tx.Transmit(cmd)
749 | if err != nil {
750 | return 0, fmt.Errorf("smart card command: %w", err)
751 | }
752 | if n := len(resp); n != 4 {
753 | return 0, fmt.Errorf("expected 4 byte serial number, got %d", n)
754 | }
755 | return binary.BigEndian.Uint32(resp), nil
756 | }
757 |
758 | // Metadata returns protected data stored on the card. This can be used to
759 | // retrieve PIN protected management keys.
760 | func (yk *YubiKey) Metadata(pin string) (*Metadata, error) {
761 | m, err := ykGetProtectedMetadata(yk.tx, pin)
762 | if err != nil {
763 | if errors.Is(err, ErrNotFound) {
764 | return &Metadata{}, nil
765 | }
766 | return nil, err
767 | }
768 | return m, nil
769 | }
770 |
771 | // SetMetadata sets PIN protected metadata on the key. This is primarily to
772 | // store the management key on the smart card instead of managing the PIN and
773 | // management key seperately.
774 | func (yk *YubiKey) SetMetadata(key []byte, m *Metadata) error {
775 | return ykSetProtectedMetadata(yk.tx, key, m, yk.rand, yk.version)
776 | }
777 |
778 | // Metadata holds protected metadata. This is primarily used by YubiKey manager
779 | // to implement PIN protect management keys, storing management keys on the card
780 | // guarded by the PIN.
781 | type Metadata struct {
782 | // ManagementKey is the management key stored directly on the YubiKey.
783 | ManagementKey *[]byte
784 |
785 | // raw, if not nil, is the full bytes
786 | raw []byte
787 | }
788 |
789 | func (m *Metadata) marshal() ([]byte, error) {
790 | if m.raw == nil {
791 | if m.ManagementKey == nil {
792 | return []byte{0x88, 0x00}, nil
793 | }
794 | return append([]byte{
795 | 0x88,
796 | 26,
797 | 0x89,
798 | 24,
799 | }, *m.ManagementKey...), nil
800 | }
801 |
802 | if m.ManagementKey == nil {
803 | return m.raw, nil
804 | }
805 |
806 | var metadata asn1.RawValue
807 | if _, err := asn1.Unmarshal(m.raw, &metadata); err != nil {
808 | return nil, fmt.Errorf("updating metadata: %v", err)
809 | }
810 | if !bytes.HasPrefix(metadata.FullBytes, []byte{0x88}) {
811 | return nil, fmt.Errorf("expected tag: 0x88")
812 | }
813 | raw := metadata.Bytes
814 |
815 | metadata.Bytes = nil
816 | metadata.FullBytes = nil
817 |
818 | for len(raw) > 0 {
819 | var (
820 | err error
821 | v asn1.RawValue
822 | )
823 | raw, err = asn1.Unmarshal(raw, &v)
824 | if err != nil {
825 | return nil, fmt.Errorf("unmarshal metadata field: %v", err)
826 | }
827 |
828 | if bytes.HasPrefix(v.FullBytes, []byte{0x89}) {
829 | continue
830 | }
831 | metadata.Bytes = append(metadata.Bytes, v.FullBytes...)
832 | }
833 | metadata.Bytes = append(metadata.Bytes, 0x89, 24)
834 | metadata.Bytes = append(metadata.Bytes, *m.ManagementKey...)
835 | return asn1.Marshal(metadata)
836 | }
837 |
838 | func (m *Metadata) unmarshal(b []byte) error {
839 | m.raw = b
840 | var md asn1.RawValue
841 | if _, err := asn1.Unmarshal(b, &md); err != nil {
842 | return err
843 | }
844 | if !bytes.HasPrefix(md.FullBytes, []byte{0x88}) {
845 | return fmt.Errorf("expected tag: 0x88")
846 | }
847 | d := md.Bytes
848 | for len(d) > 0 {
849 | var (
850 | err error
851 | v asn1.RawValue
852 | )
853 | d, err = asn1.Unmarshal(d, &v)
854 | if err != nil {
855 | return fmt.Errorf("unmarshal metadata field: %v", err)
856 | }
857 | if !bytes.HasPrefix(v.FullBytes, []byte{0x89}) {
858 | continue
859 | }
860 | // 0x89 indicates key
861 | switch len(v.Bytes) {
862 | case 16, 24, 32:
863 | default:
864 | return fmt.Errorf("invalid management key length: %d", len(v.Bytes))
865 | }
866 | m.ManagementKey = &v.Bytes
867 | }
868 | return nil
869 | }
870 |
871 | func ykGetProtectedMetadata(tx *scTx, pin string) (*Metadata, error) {
872 | // NOTE: for some reason this action requires the PIN to be authenticated on
873 | // the same transaction. It doesn't work otherwise.
874 | if err := ykLogin(tx, pin); err != nil {
875 | return nil, fmt.Errorf("authenticating with pin: %w", err)
876 | }
877 | cmd := apdu{
878 | instruction: insGetData,
879 | param1: 0x3f,
880 | param2: 0xff,
881 | data: []byte{
882 | 0x5c, // Tag list
883 | 0x03,
884 | 0x5f,
885 | 0xc1,
886 | 0x09,
887 | },
888 | }
889 | resp, err := tx.Transmit(cmd)
890 | if err != nil {
891 | return nil, fmt.Errorf("command failed: %w", err)
892 | }
893 | obj, _, err := unmarshalASN1(resp, 1, 0x13) // tag 0x53
894 | if err != nil {
895 | return nil, fmt.Errorf("unmarshaling response: %v", err)
896 | }
897 | var m Metadata
898 | if err := m.unmarshal(obj); err != nil {
899 | return nil, fmt.Errorf("unmarshal protected metadata: %v", err)
900 | }
901 | return &m, nil
902 | }
903 |
904 | func ykSetProtectedMetadata(tx *scTx, key []byte, m *Metadata, rand io.Reader, version *version) error {
905 | data, err := m.marshal()
906 | if err != nil {
907 | return fmt.Errorf("encoding metadata: %v", err)
908 | }
909 | data = append([]byte{
910 | 0x5c, // Tag list
911 | 0x03,
912 | 0x5f,
913 | 0xc1,
914 | 0x09,
915 | }, marshalASN1(0x53, data)...)
916 | // NOTE: for some reason this action requires the management key authenticated
917 | // on the same transaction. It doesn't work otherwise.
918 | if err := ykAuthenticate(tx, key, rand, version); err != nil {
919 | return fmt.Errorf("authenticating with key: %w", err)
920 | }
921 | cmd := apdu{
922 | instruction: insPutData,
923 | param1: 0x3f,
924 | param2: 0xff,
925 | data: data,
926 | }
927 | if _, err := tx.Transmit(cmd); err != nil {
928 | return fmt.Errorf("command failed: %w", err)
929 | }
930 | return nil
931 | }
932 |
933 | func supportsVersion(v *version, major, minor, patch byte) bool {
934 | if v.major != major {
935 | return v.major > major
936 | }
937 | if v.minor != minor {
938 | return v.minor > minor
939 | }
940 | return v.patch >= patch
941 | }
942 |
--------------------------------------------------------------------------------
/v2/piv/piv_test.go:
--------------------------------------------------------------------------------
1 | // Copyright 2020 Google LLC
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // https://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | package piv
16 |
17 | import (
18 | "bytes"
19 | "crypto/rand"
20 | "encoding/hex"
21 | "errors"
22 | "flag"
23 | "io"
24 | "math/bits"
25 | "strings"
26 | "testing"
27 | )
28 |
29 | // canModifyYubiKey indicates whether the test running has constented to
30 | // destroying data on YubiKeys connected to the system.
31 | var canModifyYubiKey bool
32 |
33 | var (
34 | version43 = version{4, 3, 0} // EC384 and Attestation
35 | version53 = version{5, 3, 0} // PINPolicy and KeyInfo
36 | version57 = version{5, 7, 0} // RSA3072, RSA4096, Ed25519, and X25519
37 | )
38 |
39 | func init() {
40 | flag.BoolVar(&canModifyYubiKey, "wipe-yubikey", false,
41 | "Flag required to run tests that access the yubikey")
42 | }
43 |
44 | func testGetVersion(t *testing.T, h *scHandle) {
45 | tx, err := h.Begin()
46 | if err != nil {
47 | t.Fatalf("new transaction: %v", err)
48 | }
49 | defer tx.Close()
50 | if err := ykSelectApplication(tx, aidPIV[:]); err != nil {
51 | t.Fatalf("selecting application: %v", err)
52 | }
53 | if _, err := ykVersion(tx); err != nil {
54 | t.Fatalf("listing version: %v", err)
55 | }
56 | }
57 |
58 | func testRequiresVersion(t *testing.T, yk *YubiKey, v version) {
59 | if !supportsVersion(yk.version, v.major, v.minor, v.patch) {
60 | t.Skipf("test requires yubikey version %d.%d.%d: got %d.%d.%d", v.major, v.minor, v.patch, yk.version.major, yk.version.minor, yk.version.patch)
61 | }
62 | }
63 |
64 | func TestGetVersion(t *testing.T) { runHandleTest(t, testGetVersion) }
65 |
66 | func TestCards(t *testing.T) {
67 | if !canModifyYubiKey {
68 | t.Skip("not running test that accesses yubikey, provide --wipe-yubikey flag")
69 | }
70 |
71 | if _, err := Cards(); err != nil {
72 | t.Fatalf("listing cards: %v", err)
73 | }
74 | }
75 |
76 | func newTestYubiKey(t *testing.T) (*YubiKey, func()) {
77 | if !canModifyYubiKey {
78 | t.Skip("not running test that accesses yubikey, provide --wipe-yubikey flag")
79 | }
80 |
81 | cards, err := Cards()
82 | if err != nil {
83 | t.Fatalf("listing cards: %v", err)
84 | }
85 | for _, card := range cards {
86 | if !strings.Contains(strings.ToLower(card), "yubikey") {
87 | continue
88 | }
89 | yk, err := Open(card)
90 | if err != nil {
91 | t.Fatalf("getting new yubikey: %v", err)
92 | }
93 | return yk, func() {
94 | if err := yk.Close(); err != nil {
95 | t.Errorf("closing yubikey: %v", err)
96 | }
97 | }
98 | }
99 | t.Skip("no yubikeys detected, skipping")
100 | return nil, nil
101 | }
102 |
103 | func TestNewYubiKey(t *testing.T) {
104 | _, close := newTestYubiKey(t)
105 | defer close()
106 | }
107 |
108 | func TestMultipleConnections(t *testing.T) {
109 | if !canModifyYubiKey {
110 | t.Skip("not running test that accesses yubikey, provide --wipe-yubikey flag")
111 | }
112 |
113 | cards, err := Cards()
114 | if err != nil {
115 | t.Fatalf("listing cards: %v", err)
116 | }
117 | for _, card := range cards {
118 | if !strings.Contains(strings.ToLower(card), "yubikey") {
119 | continue
120 | }
121 | if !canModifyYubiKey {
122 | t.Skip("not running test that accesses yubikey, provide --wipe-yubikey flag")
123 | }
124 | yk, err := Open(card)
125 | if err != nil {
126 | t.Fatalf("getting new yubikey: %v", err)
127 | }
128 | defer func() {
129 | if err := yk.Close(); err != nil {
130 | t.Errorf("closing yubikey: %v", err)
131 | }
132 | }()
133 |
134 | _, oerr := Open(card)
135 | if oerr == nil {
136 | t.Fatalf("expected second open operation to fail")
137 | }
138 | var e *scErr
139 | if !errors.As(oerr, &e) {
140 | t.Fatalf("expected scErr, got %v", oerr)
141 | }
142 | if e.rc != 0x8010000B {
143 | t.Fatalf("expected return code 0x8010000B, got 0x%x", e.rc)
144 | }
145 | return
146 | }
147 | t.Skip("no yubikeys detected, skipping")
148 | }
149 |
150 | func TestYubiKeySerial(t *testing.T) {
151 | yk, close := newTestYubiKey(t)
152 | defer close()
153 |
154 | if _, err := yk.Serial(); err != nil {
155 | t.Fatalf("getting serial number: %v", err)
156 | }
157 | }
158 |
159 | func TestYubiKeyLoginNeeded(t *testing.T) {
160 | yk, close := newTestYubiKey(t)
161 | defer close()
162 |
163 | testRequiresVersion(t, yk, version43)
164 |
165 | if !ykLoginNeeded(yk.tx) {
166 | t.Errorf("expected login needed")
167 | }
168 | if err := ykLogin(yk.tx, DefaultPIN); err != nil {
169 | t.Fatalf("login: %v", err)
170 | }
171 | if ykLoginNeeded(yk.tx) {
172 | t.Errorf("expected no login needed")
173 | }
174 | }
175 |
176 | func TestYubiKeyPINRetries(t *testing.T) {
177 | // The call to Retries may fail after performing the login in
178 | // TestYubiKeyLoginNeeded. It appears that the YubiKey doesn’t close the
179 | // connection immediately, leading to test failures. To prevent this, we
180 | // will reset the YubiKey before running the test.
181 | func() {
182 | yk, close := newTestYubiKey(t)
183 | defer close()
184 | if err := yk.Reset(); err != nil {
185 | t.Fatalf("resetting key: %v", err)
186 | }
187 | }()
188 |
189 | yk, close := newTestYubiKey(t)
190 | defer close()
191 |
192 | retries, err := yk.Retries()
193 | if err != nil {
194 | t.Fatalf("getting retries: %v", err)
195 | }
196 | if retries < 0 || retries > 15 {
197 | t.Fatalf("invalid number of retries: %d", retries)
198 | }
199 | }
200 |
201 | func TestYubiKeyReset(t *testing.T) {
202 | if testing.Short() {
203 | t.Skip("skipping test in short mode.")
204 | }
205 | yk, close := newTestYubiKey(t)
206 | defer close()
207 | if err := yk.Reset(); err != nil {
208 | t.Fatalf("resetting yubikey: %v", err)
209 | }
210 | if err := yk.VerifyPIN(DefaultPIN); err != nil {
211 | t.Fatalf("login: %v", err)
212 | }
213 | }
214 |
215 | func TestYubiKeyLogin(t *testing.T) {
216 | yk, close := newTestYubiKey(t)
217 | defer close()
218 |
219 | if err := yk.VerifyPIN(DefaultPIN); err != nil {
220 | t.Fatalf("login: %v", err)
221 | }
222 | }
223 |
224 | func TestYubiKeyAuthenticate(t *testing.T) {
225 | yk, close := newTestYubiKey(t)
226 | defer close()
227 |
228 | if err := yk.authManagementKey(DefaultManagementKey); err != nil {
229 | t.Errorf("authenticating: %v", err)
230 | }
231 | }
232 |
233 | func TestYubiKeySetManagementKey(t *testing.T) {
234 | yk, close := newTestYubiKey(t)
235 | defer close()
236 |
237 | var mgmtKey [24]byte
238 | if _, err := io.ReadFull(rand.Reader, mgmtKey[:]); err != nil {
239 | t.Fatalf("generating management key: %v", err)
240 | }
241 |
242 | if err := yk.SetManagementKey(DefaultManagementKey, mgmtKey[:]); err != nil {
243 | t.Fatalf("setting management key: %v", err)
244 | }
245 | if err := yk.authManagementKey(mgmtKey[:]); err != nil {
246 | t.Errorf("authenticating with new management key: %v", err)
247 | }
248 | if err := yk.SetManagementKey(mgmtKey[:], DefaultManagementKey); err != nil {
249 | t.Fatalf("resetting management key: %v", err)
250 | }
251 | }
252 |
253 | func TestYubiKeyUnblockPIN(t *testing.T) {
254 | yk, close := newTestYubiKey(t)
255 | defer close()
256 |
257 | badPIN := "0"
258 | for {
259 | err := ykLogin(yk.tx, badPIN)
260 | if err == nil {
261 | t.Fatalf("login with bad pin succeeded")
262 | }
263 | var e AuthErr
264 | if !errors.As(err, &e) {
265 | t.Fatalf("error returned was not a wrong pin error: %v", err)
266 | }
267 | if e.Retries == 0 {
268 | break
269 | }
270 | }
271 |
272 | if err := yk.Unblock(DefaultPUK, DefaultPIN); err != nil {
273 | t.Fatalf("unblocking pin: %v", err)
274 | }
275 | if err := ykLogin(yk.tx, DefaultPIN); err != nil {
276 | t.Errorf("failed to login with pin after unblock: %v", err)
277 | }
278 | }
279 |
280 | func TestYubiKeyChangePIN(t *testing.T) {
281 | yk, close := newTestYubiKey(t)
282 | defer close()
283 |
284 | newPIN := "654321"
285 | if err := yk.SetPIN(newPIN, newPIN); err == nil {
286 | t.Errorf("successfully changed pin with invalid pin, expected error")
287 | }
288 | if err := yk.SetPIN(DefaultPIN, newPIN); err != nil {
289 | t.Fatalf("changing pin: %v", err)
290 | }
291 | if err := yk.SetPIN(newPIN, DefaultPIN); err != nil {
292 | t.Fatalf("resetting pin: %v", err)
293 | }
294 | }
295 |
296 | func TestYubiKeyChangePUK(t *testing.T) {
297 | yk, close := newTestYubiKey(t)
298 | defer close()
299 |
300 | newPUK := "87654321"
301 | if err := yk.SetPUK(newPUK, newPUK); err == nil {
302 | t.Errorf("successfully changed puk with invalid puk, expected error")
303 | }
304 | if err := yk.SetPUK(DefaultPUK, newPUK); err != nil {
305 | t.Fatalf("changing puk: %v", err)
306 | }
307 | if err := yk.SetPUK(newPUK, DefaultPUK); err != nil {
308 | t.Fatalf("resetting puk: %v", err)
309 | }
310 | }
311 |
312 | func TestYubiKeyChangeRetries(t *testing.T) {
313 | yk, close := newTestYubiKey(t)
314 | defer close()
315 |
316 | if err := yk.SetRetries(DefaultManagementKey, DefaultPIN, 3, 0); err == nil {
317 | t.Errorf("successfully set retries to zeros, expected error")
318 | }
319 | if err := yk.SetRetries(DefaultManagementKey, DefaultPIN, 256, 3); err == nil {
320 | t.Errorf("successfully set retries greater than 255, expected error")
321 | }
322 | if err := yk.SetRetries(DefaultManagementKey, DefaultPIN, 5, 3); err != nil {
323 | t.Fatalf("setting pin/puk retries: %v", err)
324 | }
325 | }
326 |
327 | func TestChangeManagementKey(t *testing.T) {
328 | yk, close := newTestYubiKey(t)
329 | defer close()
330 |
331 | var newKey [24]byte
332 | if _, err := io.ReadFull(rand.Reader, newKey[:]); err != nil {
333 | t.Fatalf("generating new management key: %v", err)
334 | }
335 | // Apply odd-parity
336 | for i, b := range newKey {
337 | if bits.OnesCount8(uint8(b))%2 == 0 {
338 | newKey[i] = b ^ 1 // flip least significant bit
339 | }
340 | }
341 | if err := yk.SetManagementKey(newKey[:], newKey[:]); err == nil {
342 | t.Errorf("successfully changed management key with invalid key, expected error")
343 | }
344 | if err := yk.SetManagementKey(DefaultManagementKey, newKey[:]); err != nil {
345 | t.Fatalf("changing management key: %v", err)
346 | }
347 | if err := yk.SetManagementKey(newKey[:], DefaultManagementKey); err != nil {
348 | t.Fatalf("resetting management key: %v", err)
349 | }
350 | }
351 |
352 | func TestMetadata(t *testing.T) {
353 | if testing.Short() {
354 | t.Skip("skipping test in short mode.")
355 | }
356 | func() {
357 | yk, close := newTestYubiKey(t)
358 | defer close()
359 | if err := yk.Reset(); err != nil {
360 | t.Fatalf("resetting yubikey: %v", err)
361 | }
362 | }()
363 |
364 | yk, close := newTestYubiKey(t)
365 | defer close()
366 |
367 | if m, err := yk.Metadata(DefaultPIN); err != nil {
368 | t.Errorf("getting metadata: %v", err)
369 | } else if m.ManagementKey != nil {
370 | t.Errorf("expected no management key set")
371 | }
372 |
373 | wantKey := []byte{
374 | 0x09, 0xd9, 0x87, 0x81, 0xfb, 0xdc, 0xc9, 0xb6,
375 | 0x91, 0xa2, 0x05, 0x80, 0x6e, 0xc0, 0xba, 0x84,
376 | 0x31, 0xac, 0x0d, 0x9f, 0x59, 0xa5, 0x00, 0xad,
377 | }
378 | m := &Metadata{
379 | ManagementKey: &wantKey,
380 | }
381 | if err := yk.SetMetadata(DefaultManagementKey, m); err != nil {
382 | t.Fatalf("setting metadata: %v", err)
383 | }
384 | got, err := yk.Metadata(DefaultPIN)
385 | if err != nil {
386 | t.Fatalf("getting metadata: %v", err)
387 | }
388 | if got.ManagementKey == nil {
389 | t.Errorf("no management key")
390 | } else if !bytes.Equal(*got.ManagementKey, wantKey) {
391 | t.Errorf("wanted management key=0x%x, got=0x%x", wantKey, got.ManagementKey)
392 | }
393 | }
394 |
395 | func TestMetadataUnmarshal(t *testing.T) {
396 | data, _ := hex.DecodeString("881a891809d98781fbdcc9b691a205806ec0ba8431ac0d9f59a500ad")
397 | wantKey := []byte{
398 | 0x09, 0xd9, 0x87, 0x81, 0xfb, 0xdc, 0xc9, 0xb6,
399 | 0x91, 0xa2, 0x05, 0x80, 0x6e, 0xc0, 0xba, 0x84,
400 | 0x31, 0xac, 0x0d, 0x9f, 0x59, 0xa5, 0x00, 0xad,
401 | }
402 | var m Metadata
403 | if err := m.unmarshal(data); err != nil {
404 | t.Fatalf("parsing metadata: %v", err)
405 | }
406 | if m.ManagementKey == nil {
407 | t.Fatalf("no management key")
408 | }
409 | gotKey := *m.ManagementKey
410 | if !bytes.Equal(gotKey, wantKey) {
411 | t.Errorf("(*Metadata).unmarshal, got key=0x%x, want key=0x%x", gotKey, wantKey)
412 | }
413 | }
414 |
415 | func TestMetadataMarshal(t *testing.T) {
416 | key := []byte{
417 | 0x09, 0xd9, 0x87, 0x81, 0xfb, 0xdc, 0xc9, 0xb6,
418 | 0x91, 0xa2, 0x05, 0x80, 0x6e, 0xc0, 0xba, 0x84,
419 | 0x31, 0xac, 0x0d, 0x9f, 0x59, 0xa5, 0x00, 0xad,
420 | }
421 | want := append([]byte{
422 | 0x88,
423 | 26,
424 | 0x89,
425 | 24,
426 | }, key[:]...)
427 | m := Metadata{
428 | ManagementKey: &key,
429 | }
430 | got, err := m.marshal()
431 | if err != nil {
432 | t.Fatalf("marshaling key: %v", err)
433 | }
434 | if !bytes.Equal(want, got) {
435 | t.Errorf("(*Metadata.marshal, got=0x%x, want=0x%x", got, want)
436 | }
437 | }
438 |
439 | func TestMetadataUpdate(t *testing.T) {
440 | key := []byte{
441 | 0x09, 0xd9, 0x87, 0x81, 0xfb, 0xdc, 0xc9, 0xb6,
442 | 0x91, 0xa2, 0x05, 0x80, 0x6e, 0xc0, 0xba, 0x84,
443 | 0x31, 0xac, 0x0d, 0x9f, 0x59, 0xa5, 0x00, 0xad,
444 | }
445 | want := append([]byte{
446 | 0x88,
447 | 26,
448 | 0x89,
449 | 24,
450 | }, key[:]...)
451 |
452 | m1 := Metadata{
453 | ManagementKey: &DefaultManagementKey,
454 | }
455 | raw, err := m1.marshal()
456 | if err != nil {
457 | t.Fatalf("marshaling key: %v", err)
458 | }
459 | m2 := Metadata{
460 | ManagementKey: &key,
461 | raw: raw,
462 | }
463 | got, err := m2.marshal()
464 | if err != nil {
465 | t.Fatalf("marshaling updated metadata: %v", err)
466 | }
467 | if !bytes.Equal(want, got) {
468 | t.Errorf("(*Metadata.marshal, got=0x%x, want=0x%x", got, want)
469 | }
470 | }
471 |
472 | func TestMetadataAdditoinalFields(t *testing.T) {
473 | key := []byte{
474 | 0x09, 0xd9, 0x87, 0x81, 0xfb, 0xdc, 0xc9, 0xb6,
475 | 0x91, 0xa2, 0x05, 0x80, 0x6e, 0xc0, 0xba, 0x84,
476 | 0x31, 0xac, 0x0d, 0x9f, 0x59, 0xa5, 0x00, 0xad,
477 | }
478 | raw := []byte{
479 | 0x88,
480 | 4,
481 | // Unrecognized sub-object. The key should be added, but this object
482 | // shouldn't be impacted.
483 | 0x87,
484 | 2,
485 | 0x00,
486 | 0x01,
487 | }
488 |
489 | want := append([]byte{
490 | 0x88,
491 | 30,
492 | // Unrecognized sub-object.
493 | 0x87,
494 | 2,
495 | 0x00,
496 | 0x01,
497 | // Added management key.
498 | 0x89,
499 | 24,
500 | }, key[:]...)
501 |
502 | m := Metadata{
503 | ManagementKey: &key,
504 | raw: raw,
505 | }
506 | got, err := m.marshal()
507 | if err != nil {
508 | t.Fatalf("marshaling updated metadata: %v", err)
509 | }
510 | if !bytes.Equal(want, got) {
511 | t.Errorf("(*Metadata.marshal, got=0x%x, want=0x%x", got, want)
512 | }
513 | }
514 |
--------------------------------------------------------------------------------
/v2/third_party/rsa/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (c) 2009 The Go Authors. All rights reserved.
2 |
3 | Redistribution and use in source and binary forms, with or without
4 | modification, are permitted provided that the following conditions are
5 | met:
6 |
7 | * Redistributions of source code must retain the above copyright
8 | notice, this list of conditions and the following disclaimer.
9 | * Redistributions in binary form must reproduce the above
10 | copyright notice, this list of conditions and the following disclaimer
11 | in the documentation and/or other materials provided with the
12 | distribution.
13 | * Neither the name of Google Inc. nor the names of its
14 | contributors may be used to endorse or promote products derived from
15 | this software without specific prior written permission.
16 |
17 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
18 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
19 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
20 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
21 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
22 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
23 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
24 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
25 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
27 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28 |
--------------------------------------------------------------------------------
/v2/third_party/rsa/README:
--------------------------------------------------------------------------------
1 | This directory contains a fork of internal crypto/rsa logic to allow computation
2 | of PSS padding.
3 |
--------------------------------------------------------------------------------
/v2/third_party/rsa/pss.go:
--------------------------------------------------------------------------------
1 | // Copyright 2013 The Go Authors. All rights reserved.
2 | // Use of this source code is governed by a BSD-style
3 | // license that can be found in the LICENSE file.
4 |
5 | package rsa
6 |
7 | import (
8 | "crypto"
9 | "crypto/rsa"
10 | "errors"
11 | "hash"
12 | "io"
13 | )
14 |
15 | var invalidSaltLenErr = errors.New("crypto/rsa: PSSOptions.SaltLength cannot be negative")
16 |
17 | // Per RFC 8017, Section 9.1
18 | //
19 | // EM = MGF1 xor DB || H( 8*0x00 || mHash || salt ) || 0xbc
20 | //
21 | // where
22 | //
23 | // DB = PS || 0x01 || salt
24 | //
25 | // and PS can be empty so
26 | //
27 | // emLen = dbLen + hLen + 1 = psLen + sLen + hLen + 2
28 | //
29 |
30 | // EMSAPSSEncode is extracted from SignPSS, and is used to generate a EM value
31 | // for a PSS signature operation.
32 | func EMSAPSSEncode(mHash []byte, pub *rsa.PublicKey, salt []byte, hash hash.Hash) ([]byte, error) {
33 | emBits := pub.N.BitLen() - 1
34 |
35 | // See RFC 8017, Section 9.1.1.
36 |
37 | hLen := hash.Size()
38 | sLen := len(salt)
39 | emLen := (emBits + 7) / 8
40 |
41 | // 1. If the length of M is greater than the input limitation for the
42 | // hash function (2^61 - 1 octets for SHA-1), output "message too
43 | // long" and stop.
44 | //
45 | // 2. Let mHash = Hash(M), an octet string of length hLen.
46 |
47 | if len(mHash) != hLen {
48 | return nil, errors.New("crypto/rsa: input must be hashed with given hash")
49 | }
50 |
51 | // 3. If emLen < hLen + sLen + 2, output "encoding error" and stop.
52 |
53 | if emLen < hLen+sLen+2 {
54 | return nil, rsa.ErrMessageTooLong
55 | }
56 |
57 | em := make([]byte, emLen)
58 | psLen := emLen - sLen - hLen - 2
59 | db := em[:psLen+1+sLen]
60 | h := em[psLen+1+sLen : emLen-1]
61 |
62 | // 4. Generate a random octet string salt of length sLen; if sLen = 0,
63 | // then salt is the empty string.
64 | //
65 | // 5. Let
66 | // M' = (0x)00 00 00 00 00 00 00 00 || mHash || salt;
67 | //
68 | // M' is an octet string of length 8 + hLen + sLen with eight
69 | // initial zero octets.
70 | //
71 | // 6. Let H = Hash(M'), an octet string of length hLen.
72 |
73 | var prefix [8]byte
74 |
75 | hash.Write(prefix[:])
76 | hash.Write(mHash)
77 | hash.Write(salt)
78 |
79 | h = hash.Sum(h[:0])
80 | hash.Reset()
81 |
82 | // 7. Generate an octet string PS consisting of emLen - sLen - hLen - 2
83 | // zero octets. The length of PS may be 0.
84 | //
85 | // 8. Let DB = PS || 0x01 || salt; DB is an octet string of length
86 | // emLen - hLen - 1.
87 |
88 | db[psLen] = 0x01
89 | copy(db[psLen+1:], salt)
90 |
91 | // 9. Let dbMask = MGF(H, emLen - hLen - 1).
92 | //
93 | // 10. Let maskedDB = DB \xor dbMask.
94 |
95 | mgf1XOR(db, hash, h)
96 |
97 | // 11. Set the leftmost 8 * emLen - emBits bits of the leftmost octet in
98 | // maskedDB to zero.
99 |
100 | db[0] &= 0xff >> (8*emLen - emBits)
101 |
102 | // 12. Let EM = maskedDB || H || 0xbc.
103 | em[emLen-1] = 0xbc
104 |
105 | // 13. Output EM.
106 | return em, nil
107 | }
108 |
109 | // mgf1XOR XORs the bytes in out with a mask generated using the MGF1 function
110 | // specified in PKCS #1 v2.1.
111 | func mgf1XOR(out []byte, hash hash.Hash, seed []byte) {
112 | var counter [4]byte
113 | var digest []byte
114 |
115 | done := 0
116 | for done < len(out) {
117 | hash.Write(seed)
118 | hash.Write(counter[0:4])
119 | digest = hash.Sum(digest[:0])
120 | hash.Reset()
121 |
122 | for i := 0; i < len(digest) && done < len(out); i++ {
123 | out[done] ^= digest[i]
124 | done++
125 | }
126 | incCounter(&counter)
127 | }
128 | }
129 |
130 | // incCounter increments a four byte, big-endian counter.
131 | func incCounter(c *[4]byte) {
132 | if c[3]++; c[3] != 0 {
133 | return
134 | }
135 | if c[2]++; c[2] != 0 {
136 | return
137 | }
138 | if c[1]++; c[1] != 0 {
139 | return
140 | }
141 | c[0]++
142 | }
143 |
144 | // NewSalt is extracted from SignPSS and is used to generate a salt value for a
145 | // PSS signature.
146 | func NewSalt(rand io.Reader, pub *rsa.PublicKey, hash crypto.Hash, opts *rsa.PSSOptions) ([]byte, error) {
147 | saltLength := opts.SaltLength
148 | switch saltLength {
149 | case rsa.PSSSaltLengthAuto:
150 | saltLength = (pub.N.BitLen()-1+7)/8 - 2 - hash.Size()
151 | if saltLength < 0 {
152 | return nil, rsa.ErrMessageTooLong
153 | }
154 | case rsa.PSSSaltLengthEqualsHash:
155 | saltLength = hash.Size()
156 | default:
157 | // If we get here saltLength is either > 0 or < -1, in the
158 | // latter case we fail out.
159 | if saltLength <= 0 {
160 | return nil, invalidSaltLenErr
161 | }
162 | }
163 | salt := make([]byte, saltLength)
164 | if _, err := io.ReadFull(rand, salt); err != nil {
165 | return nil, err
166 | }
167 | return salt, nil
168 | }
169 |
--------------------------------------------------------------------------------