├── .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 | [![Go Reference](https://pkg.go.dev/badge/github.com/go-piv/piv-go/v2/piv.svg)](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 | --------------------------------------------------------------------------------