Compare commits

...

190 Commits

Author SHA1 Message Date
Tony Arcieri (iqlusion) fc62fc286d yubikey-piv v0.1.0 (#180) 2020-10-19 08:26:05 -07:00
Tony Arcieri 199496ab01 Merge pull request #179 from iqlusioninc/x509-parser/v0.8
Bump x509-parser to v0.8
2020-10-19 08:06:36 -07:00
Tony Arcieri ab11e037cc Bump x509-parser to v0.8 2020-10-19 07:58:35 -07:00
Tony Arcieri 41d4d1b332 Merge pull request #178 from iqlusioninc/dependabot/cargo/env_logger-0.8.1
Bump env_logger from 0.7.1 to 0.8.1
2020-10-19 06:16:29 -07:00
dependabot[bot] b21c4bd307 Bump env_logger from 0.7.1 to 0.8.1
Bumps [env_logger](https://github.com/env-logger-rs/env_logger) from 0.7.1 to 0.8.1.
- [Release notes](https://github.com/env-logger-rs/env_logger/releases)
- [Changelog](https://github.com/env-logger-rs/env_logger/blob/master/CHANGELOG.md)
- [Commits](https://github.com/env-logger-rs/env_logger/compare/v0.7.1...v0.8.1)

Signed-off-by: dependabot[bot] <support@github.com>
2020-10-19 13:01:11 +00:00
Tony Arcieri 64aea57a63 Merge pull request #177 from iqlusioninc/bump-rustcrypto-deps
Bump RustCrypto dependencies
2020-10-18 10:26:17 -07:00
Tony Arcieri 17ae87f741 Bump RustCrypto dependencies
Updates all RustCrypto crates (`crypto-mac`, `des`, `hmac`, `pbkdf2`)
to the latest versions.
2020-10-18 10:12:09 -07:00
Tony Arcieri 811a7d5373 Merge pull request #176 from iqlusioninc/ci/redo-config
CI: simplify configuration
2020-10-18 07:44:25 -07:00
Tony Arcieri 5b2e025aa4 CI: simplify configuration
Uses a trick to simplify and unify the CI configuration
2020-10-18 07:32:34 -07:00
Tony Arcieri 6313e2ec3f Merge pull request #175 from iqlusioninc/bump-p256-and-p384
Bump `p256` to v0.5; `p384` to v0.4
2020-10-17 14:26:03 -07:00
Tony Arcieri cbe60413cb Bump p256 to v0.5; p384 to v0.4; MSRV 1.44+ 2020-10-17 13:54:40 -07:00
Tony Arcieri 08220032db Merge pull request #174 from iqlusioninc/bump-deps
Cargo.lock: bump dependencies
2020-10-17 13:17:01 -07:00
Tony Arcieri 152a096a6b Cargo.lock: bump dependencies 2020-10-17 13:07:08 -07:00
dependabot[bot] ddb712cc48 Bump zeroize from 1.1.0 to 1.1.1 (#167)
Bumps [zeroize](https://github.com/iqlusioninc/crates) from 1.1.0 to 1.1.1.
- [Release notes](https://github.com/iqlusioninc/crates/releases)
- [Commits](https://github.com/iqlusioninc/crates/compare/zeroize/v1.1.0...zeroize/v1.1.1)

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2020-09-21 07:46:42 -07:00
Tony Arcieri (iqlusion) 32dcdfb67f Merge pull request #162 from str4d/update-deps
Update dependencies
2020-08-30 15:52:44 -07:00
Jack Grigg c396971496 cargo update 2020-08-30 18:19:39 +01:00
Jack Grigg f906e6a2d7 des 0.5 2020-08-30 18:18:29 +01:00
Jack Grigg 35fa940a37 secrecy 0.7 2020-08-30 18:16:45 +01:00
Jack Grigg 05a3b85934 hmac 0.9 and pbkdf2 0.5 2020-08-30 18:14:58 +01:00
Jack Grigg 23d0f96adc elliptic-curve 0.5
Requires p256 0.4 and p384 0.3
2020-08-30 18:13:17 +01:00
Shella Stephens 4435a54435 Update der-parser & x509-parser (#145)
* Update der-parser & x509-parser

* use rust v1.41 toolchain
2020-06-23 17:03:04 -07:00
dependabot[bot] 55b960501a Bump ring from 0.16.14 to 0.16.15 (#144)
Bumps [ring](https://github.com/briansmith/ring) from 0.16.14 to 0.16.15.
- [Release notes](https://github.com/briansmith/ring/releases)
- [Commits](https://github.com/briansmith/ring/commits)

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2020-06-23 14:47:23 -07:00
Shella Stephens 860c163eb9 Update rsa to v0.3 & other dependencies (#142)
* Update rsa to v0.3 & other dependencies
2020-06-15 16:40:33 -07:00
Tony Arcieri 15f9e265e6 Merge pull request #128 from BlackHoleFox/develop
Refactor key import function
2020-06-10 09:12:21 -07:00
BlackHoleFox 556b9cb671 Remove dependency on regular num-bigint 2020-06-09 18:42:56 -05:00
Shella Stephens 842198b566 Merge pull request #131 from iqlusioninc/dependabot/add-v2-config-file
Create Dependabot config file
2020-06-09 14:05:54 -07:00
dependabot-preview[bot] 812b4d588f Create Dependabot config file 2020-06-09 20:45:55 +00:00
BlackHoleFox 6e3560c10f Switch to buffer alias 2020-06-08 22:09:57 -05:00
BlackHoleFox 0f907ebd5c Implement RSA key precomputation 2020-06-08 21:48:25 -05:00
BlackHoleFox acc96e988f Refactor key import function 2020-06-01 23:07:18 -05:00
Tony Arcieri 4037dfe19d Merge pull request #125 from iqlusioninc/dependabot/cargo/sha2-0.8.2
Bump sha2 from 0.8.1 to 0.8.2
2020-05-25 07:04:19 -07:00
dependabot-preview[bot] a9e27eaadb Bump sha2 from 0.8.1 to 0.8.2
Bumps [sha2](https://github.com/RustCrypto/hashes) from 0.8.1 to 0.8.2.
- [Release notes](https://github.com/RustCrypto/hashes/releases)
- [Commits](https://github.com/RustCrypto/hashes/compare/sha2-v0.8.1...sha2-v0.8.2)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-05-25 13:36:25 +00:00
Tony Arcieri 9208cc3862 Merge pull request #118 from iqlusioninc/dependabot/cargo/x509-parser-0.7.0
Bump x509-parser from 0.6.5 to 0.7.0
2020-05-04 10:36:31 -07:00
dependabot-preview[bot] 2d4f2fa750 Bump x509-parser from 0.6.5 to 0.7.0
Bumps [x509-parser](https://github.com/rusticata/x509-parser) from 0.6.5 to 0.7.0.
- [Release notes](https://github.com/rusticata/x509-parser/releases)
- [Commits](https://github.com/rusticata/x509-parser/commits)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-05-04 17:06:10 +00:00
Tony Arcieri 3a5411d989 Merge pull request #123 from iqlusioninc/dependabot/cargo/ring-0.16.13
Bump ring from 0.16.12 to 0.16.13
2020-05-04 10:04:25 -07:00
dependabot-preview[bot] ed66d399ca Bump ring from 0.16.12 to 0.16.13
Bumps [ring](https://github.com/briansmith/ring) from 0.16.12 to 0.16.13.
- [Release notes](https://github.com/briansmith/ring/releases)
- [Commits](https://github.com/briansmith/ring/commits)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-05-04 16:14:43 +00:00
Tony Arcieri a018728f7a Merge pull request #122 from iqlusioninc/dependabot/cargo/p256-0.2.0
Bump p256 from 0.1.0 to 0.2.0
2020-05-04 09:13:09 -07:00
dependabot-preview[bot] 39d2b0982a Bump p256 from 0.1.0 to 0.2.0
Bumps [p256](https://github.com/RustCrypto/elliptic-curves) from 0.1.0 to 0.2.0.
- [Release notes](https://github.com/RustCrypto/elliptic-curves/releases)
- [Commits](https://github.com/RustCrypto/elliptic-curves/compare/p256/v0.1.0...p256/v0.2.0)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-05-04 16:03:26 +00:00
Tony Arcieri e501cce644 Merge pull request #124 from iqlusioninc/remove-unnecessary-parens
Remove unnecessary parens
2020-05-04 08:56:28 -07:00
Tony Arcieri 5e52f93f4a Remove unnecessary parens 2020-05-04 08:45:40 -07:00
Tony Arcieri c8cdff2bbf Merge pull request #121 from iqlusioninc/dependabot/cargo/gumdrop-0.8.0
Bump gumdrop from 0.7.0 to 0.8.0
2020-04-22 16:03:24 -07:00
Tony Arcieri 9d18a2cb08 Merge pull request #120 from iqlusioninc/dependabot/cargo/pcsc-2.4.0
Bump pcsc from 2.3.1 to 2.4.0
2020-04-22 16:01:53 -07:00
dependabot-preview[bot] 3aeaa7e1da Bump gumdrop from 0.7.0 to 0.8.0
Bumps [gumdrop](https://github.com/murarth/gumdrop) from 0.7.0 to 0.8.0.
- [Release notes](https://github.com/murarth/gumdrop/releases)
- [Commits](https://github.com/murarth/gumdrop/commits)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-04-20 13:42:05 +00:00
dependabot-preview[bot] 38cce09a78 Bump pcsc from 2.3.1 to 2.4.0
Bumps [pcsc](https://github.com/bluetech/pcsc-rust) from 2.3.1 to 2.4.0.
- [Release notes](https://github.com/bluetech/pcsc-rust/releases)
- [Changelog](https://github.com/bluetech/pcsc-rust/blob/master/CHANGELOG.md)
- [Commits](https://github.com/bluetech/pcsc-rust/compare/v2.3.1...v2.4.0)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-04-20 13:41:41 +00:00
Tony Arcieri 6f3d65cfc0 Merge pull request #119 from iqlusioninc/update-deps
Cargo.lock: update dependencies
2020-04-07 07:46:48 -07:00
Tony Arcieri 14cba00dcd Cargo.lock: update dependencies 2020-04-06 11:09:19 -07:00
Shella Stephens ce9b32a27e Merge pull request #116 from iqlusioninc/dependabot/cargo/x509-parser-0.6.5
Bump x509-parser from 0.6.4 to 0.6.5
2020-03-16 06:49:48 -07:00
dependabot-preview[bot] 9042454404 Bump x509-parser from 0.6.4 to 0.6.5
Bumps [x509-parser](https://github.com/rusticata/x509-parser) from 0.6.4 to 0.6.5.
- [Release notes](https://github.com/rusticata/x509-parser/releases)
- [Commits](https://github.com/rusticata/x509-parser/commits)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-03-16 13:40:52 +00:00
Tony Arcieri bf376516c9 Merge pull request #115 from iqlusioninc/dependabot/cargo/chrono-0.4.11
Bump chrono from 0.4.10 to 0.4.11
2020-03-09 07:11:13 -07:00
dependabot-preview[bot] 46685da68a Bump chrono from 0.4.10 to 0.4.11
Bumps [chrono](https://github.com/chronotope/chrono) from 0.4.10 to 0.4.11.
- [Release notes](https://github.com/chronotope/chrono/releases)
- [Changelog](https://github.com/chronotope/chrono/blob/master/CHANGELOG.md)
- [Commits](https://github.com/chronotope/chrono/compare/v0.4.10...v0.4.11)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-03-09 13:33:44 +00:00
Tony Arcieri 3c70c77e95 Merge pull request #114 from iqlusioninc/docs/fix-logo-links
Fix logo links
2020-03-07 08:32:37 -08:00
Tony Arcieri 154d8bb97a Fix logo links 2020-03-07 08:20:16 -08:00
Tony Arcieri 31617bf9a8 Merge pull request #113 from iqlusioninc/update-deps
Cargo.lock: update dependencies
2020-03-07 08:09:30 -08:00
Tony Arcieri 2144750f6f Cargo.lock: update dependencies 2020-03-07 07:59:42 -08:00
Tony Arcieri cbbbdd6d8c Merge pull request #112 from iqlusioninc/dependabot/cargo/x509-parser-0.6.4
Bump x509-parser from 0.6.2 to 0.6.4
2020-03-02 09:40:52 -08:00
dependabot-preview[bot] 9478cd392f Bump x509-parser from 0.6.2 to 0.6.4
Bumps [x509-parser](https://github.com/rusticata/x509-parser) from 0.6.2 to 0.6.4.
- [Release notes](https://github.com/rusticata/x509-parser/releases)
- [Commits](https://github.com/rusticata/x509-parser/compare/x509-parser-0.6.2...x509-parser-0.6.4)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-03-02 13:35:12 +00:00
Shella Stephens a70013d048 Merge pull request #110 from iqlusioninc/dependabot/cargo/x509-parser-0.6.2
Bump x509-parser from 0.6.0 to 0.6.2
2020-02-26 12:18:16 -08:00
Tony Arcieri e4418edb32 Merge pull request #111 from iqlusioninc/dependabot/cargo/nom-5.1.1
Bump nom from 5.1.0 to 5.1.1
2020-02-25 04:21:44 -10:00
dependabot-preview[bot] 89086bb1ac Bump nom from 5.1.0 to 5.1.1
Bumps [nom](https://github.com/Geal/nom) from 5.1.0 to 5.1.1.
- [Release notes](https://github.com/Geal/nom/releases)
- [Changelog](https://github.com/Geal/nom/blob/master/CHANGELOG.md)
- [Commits](https://github.com/Geal/nom/compare/5.1.0...5.1.1)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-02-25 13:36:15 +00:00
dependabot-preview[bot] 3a0262ce90 Bump x509-parser from 0.6.0 to 0.6.2
Bumps [x509-parser](https://github.com/rusticata/x509-parser) from 0.6.0 to 0.6.2.
- [Release notes](https://github.com/rusticata/x509-parser/releases)
- [Commits](https://github.com/rusticata/x509-parser/compare/x509-parser-0.6.0...x509-parser-0.6.2)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-02-24 13:22:36 +00:00
Tony Arcieri f4b1e8b6e2 Merge pull request #108 from iqlusioninc/update-deps
Cargo.lock: update dependencies
2020-02-15 07:13:59 -08:00
Tony Arcieri 926450b573 Cargo.lock: update dependencies 2020-02-15 07:03:21 -08:00
Tony Arcieri fec43589af Merge pull request #106 from iqlusioninc/dependabot/cargo/subtle-encoding-0.5.1
Bump subtle-encoding from 0.5.0 to 0.5.1
2020-02-10 06:16:36 -08:00
Tony Arcieri 4617ce053d Merge pull request #105 from iqlusioninc/dependabot/cargo/ring-0.16.11
Bump ring from 0.16.10 to 0.16.11
2020-02-10 06:16:18 -08:00
Tony Arcieri 8bc7b471f6 Merge pull request #107 from iqlusioninc/dependabot/cargo/cookie-factory-0.3.1
Bump cookie-factory from 0.3.0 to 0.3.1
2020-02-10 06:15:58 -08:00
dependabot-preview[bot] a1ef807dea Bump cookie-factory from 0.3.0 to 0.3.1
Bumps [cookie-factory](https://github.com/rust-bakery/cookie-factory) from 0.3.0 to 0.3.1.
- [Release notes](https://github.com/rust-bakery/cookie-factory/releases)
- [Commits](https://github.com/rust-bakery/cookie-factory/commits)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-02-10 13:25:09 +00:00
dependabot-preview[bot] d67ae3b7e3 Bump subtle-encoding from 0.5.0 to 0.5.1
Bumps [subtle-encoding](https://github.com/iqlusioninc/crates) from 0.5.0 to 0.5.1.
- [Release notes](https://github.com/iqlusioninc/crates/releases)
- [Commits](https://github.com/iqlusioninc/crates/compare/subtle-encoding/v0.5.0...subtle-encoding/v0.5.1)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-02-10 13:24:52 +00:00
dependabot-preview[bot] 268b3709fd Bump ring from 0.16.10 to 0.16.11
Bumps [ring](https://github.com/briansmith/ring) from 0.16.10 to 0.16.11.
- [Release notes](https://github.com/briansmith/ring/releases)
- [Commits](https://github.com/briansmith/ring/commits)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-02-05 13:37:06 +00:00
Tony Arcieri b8eee17284 Merge pull request #104 from iqlusioninc/dependabot/cargo/ring-0.16.10
Bump ring from 0.16.9 to 0.16.10
2020-02-03 07:13:56 -08:00
dependabot-preview[bot] 9c566c9130 Bump ring from 0.16.9 to 0.16.10
Bumps [ring](https://github.com/briansmith/ring) from 0.16.9 to 0.16.10.
- [Release notes](https://github.com/briansmith/ring/releases)
- [Commits](https://github.com/briansmith/ring/commits)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-02-03 13:25:33 +00:00
Tony Arcieri 3c07ee55f7 Merge pull request #103 from iqlusioninc/dependabot/cargo/der-parser-3.0.4
Bump der-parser from 3.0.3 to 3.0.4
2020-01-30 06:34:40 -08:00
dependabot-preview[bot] 264e6e0684 Bump der-parser from 3.0.3 to 3.0.4
Bumps [der-parser](https://github.com/rusticata/der-parser) from 3.0.3 to 3.0.4.
- [Release notes](https://github.com/rusticata/der-parser/releases)
- [Commits](https://github.com/rusticata/der-parser/compare/der-parser-3.0.3...der-parser-3.0.4)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-01-30 13:36:22 +00:00
Tony Arcieri fa5a87269b Merge pull request #101 from iqlusioninc/dependabot/cargo/num-bigint-0.2.6
Bump num-bigint from 0.2.5 to 0.2.6
2020-01-28 06:13:16 -08:00
dependabot-preview[bot] 33d6330186 Bump num-bigint from 0.2.5 to 0.2.6
Bumps [num-bigint](https://github.com/rust-num/num-bigint) from 0.2.5 to 0.2.6.
- [Release notes](https://github.com/rust-num/num-bigint/releases)
- [Changelog](https://github.com/rust-num/num-bigint/blob/master/RELEASES.md)
- [Commits](https://github.com/rust-num/num-bigint/compare/num-bigint-0.2.5...num-bigint-0.2.6)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-01-28 13:37:27 +00:00
Tony Arcieri e0fbd57937 Merge pull request #100 from iqlusioninc/cargo-lock-v2
Cargo.lock: reformat as a ResolveVersion::V2 lockfile
2020-01-26 08:28:30 -08:00
Tony Arcieri fada2a5b78 Cargo.lock: reformat as a ResolveVersion::V2 lockfile 2020-01-26 08:17:48 -08:00
Tony Arcieri 2f4c1cfb11 Merge pull request #99 from iqlusioninc/update-deps
Cargo.lock: Update deps
2020-01-26 08:16:22 -08:00
Tony Arcieri 85deed78ec Cargo.lock: Update deps
We're using a yanked version of the `log` crate
2020-01-25 16:30:42 -08:00
Tony Arcieri ed5134e9a2 Merge pull request #98 from iqlusioninc/dependabot/cargo/termcolor-1.1.0
Bump termcolor from 1.0.5 to 1.1.0
2020-01-13 09:34:06 -05:00
dependabot-preview[bot] 4770505de0 Bump termcolor from 1.0.5 to 1.1.0
Bumps [termcolor](https://github.com/BurntSushi/termcolor) from 1.0.5 to 1.1.0.
- [Release notes](https://github.com/BurntSushi/termcolor/releases)
- [Commits](https://github.com/BurntSushi/termcolor/compare/1.0.5...1.1.0)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-01-13 13:26:45 +00:00
Tony Arcieri fdb7b891ad Merge pull request #97 from iqlusioninc/dependabot/cargo/num-bigint-0.2.5
Bump num-bigint from 0.2.4 to 0.2.5
2020-01-10 09:05:44 -05:00
dependabot-preview[bot] 548d39d138 Bump num-bigint from 0.2.4 to 0.2.5
Bumps [num-bigint](https://github.com/rust-num/num-bigint) from 0.2.4 to 0.2.5.
- [Release notes](https://github.com/rust-num/num-bigint/releases)
- [Changelog](https://github.com/rust-num/num-bigint/blob/master/RELEASES.md)
- [Commits](https://github.com/rust-num/num-bigint/compare/num-bigint-0.2.4...num-bigint-0.2.5)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-01-10 13:45:24 +00:00
Tony Arcieri 15aee107ad Merge pull request #96 from iqlusioninc/dependabot/cargo/nom-5.1.0
Bump nom from 5.0.1 to 5.1.0
2020-01-08 10:09:04 -05:00
dependabot-preview[bot] 8c65f37d97 Bump nom from 5.0.1 to 5.1.0
Bumps [nom](https://github.com/Geal/nom) from 5.0.1 to 5.1.0.
- [Release notes](https://github.com/Geal/nom/releases)
- [Changelog](https://github.com/Geal/nom/blob/master/CHANGELOG.md)
- [Commits](https://github.com/Geal/nom/compare/5.0.1...5.1.0)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-01-08 13:41:21 +00:00
Tony Arcieri 8802ecb689 Merge pull request #95 from iqlusioninc/elliptic-curve/v0.3
Bump elliptic-curve from 0.2.0 to 0.3.0
2020-01-07 15:22:25 -05:00
Tony Arcieri 27504890d7 Bump elliptic-curve from 0.2.0 to 0.3.0 2020-01-07 15:11:27 -05:00
Tony Arcieri 4999139b9d Merge pull request #91 from iqlusioninc/dependabot/cargo/sha-1-0.8.2
Bump sha-1 from 0.8.1 to 0.8.2
2020-01-07 09:46:33 -05:00
Tony Arcieri b3490b4f39 Merge pull request #94 from iqlusioninc/dependabot/cargo/getrandom-0.1.14
Bump getrandom from 0.1.13 to 0.1.14
2020-01-07 09:46:03 -05:00
dependabot-preview[bot] b4a43f21d0 Bump getrandom from 0.1.13 to 0.1.14
Bumps [getrandom](https://github.com/rust-random/getrandom) from 0.1.13 to 0.1.14.
- [Release notes](https://github.com/rust-random/getrandom/releases)
- [Changelog](https://github.com/rust-random/getrandom/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rust-random/getrandom/compare/v0.1.13...v0.1.14)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-01-07 13:42:44 +00:00
dependabot-preview[bot] ee84134f26 Bump sha-1 from 0.8.1 to 0.8.2
Bumps [sha-1](https://github.com/RustCrypto/hashes) from 0.8.1 to 0.8.2.
- [Release notes](https://github.com/RustCrypto/hashes/releases)
- [Commits](https://github.com/RustCrypto/hashes/compare/sha1-v0.8.1...sha1-v0.8.2)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-01-07 02:53:06 +00:00
Tony Arcieri 3da9b3001b Merge pull request #93 from iqlusioninc/dependabot/cargo/sha2-0.8.1
Bump sha2 from 0.8.0 to 0.8.1
2020-01-06 21:51:50 -05:00
dependabot-preview[bot] 61054ae74a Bump sha2 from 0.8.0 to 0.8.1
Bumps [sha2](https://github.com/RustCrypto/hashes) from 0.8.0 to 0.8.1.
- [Release notes](https://github.com/RustCrypto/hashes/releases)
- [Commits](https://github.com/RustCrypto/hashes/compare/sha2-v0.8.0...sha2-v0.8.1)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-01-06 13:24:30 +00:00
Tony Arcieri 7cebce6156 Merge pull request #90 from iqlusioninc/dependabot/cargo/num-bigint-0.2.4
Bump num-bigint from 0.2.3 to 0.2.4
2020-01-02 11:29:55 -05:00
dependabot-preview[bot] 81e9c54a2c Bump num-bigint from 0.2.3 to 0.2.4
Bumps [num-bigint](https://github.com/rust-num/num-bigint) from 0.2.3 to 0.2.4.
- [Release notes](https://github.com/rust-num/num-bigint/releases)
- [Changelog](https://github.com/rust-num/num-bigint/blob/master/RELEASES.md)
- [Commits](https://github.com/rust-num/num-bigint/compare/num-bigint-0.2.3...num-bigint-0.2.4)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-01-02 13:50:54 +00:00
Tony Arcieri ae20e6d950 Merge pull request #89 from iqlusioninc/dependabot/cargo/pcsc-2.3.1
Bump pcsc from 2.3.0 to 2.3.1
2019-12-20 06:30:14 -08:00
dependabot-preview[bot] 741ba528a4 Bump pcsc from 2.3.0 to 2.3.1
Bumps [pcsc](https://github.com/bluetech/pcsc-rust) from 2.3.0 to 2.3.1.
- [Release notes](https://github.com/bluetech/pcsc-rust/releases)
- [Changelog](https://github.com/bluetech/pcsc-rust/blob/master/CHANGELOG.md)
- [Commits](https://github.com/bluetech/pcsc-rust/compare/v2.3.0...v2.3.1)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-12-20 13:40:49 +00:00
Tony Arcieri aaaf3b142e Merge pull request #88 from str4d/open-usability
pcsc::Error::NoReadersAvailable -> Error::NotFound in YubiKey::open*
2019-12-18 09:12:50 -08:00
Jack Grigg b5e774cf2b pcsc::Error::NoReadersAvailable -> Error::NotFound in YubiKey::open*
This provides a consistent user experience between no readers being
connected, and readers being connected but not the one we are trying to
open.
2019-12-18 11:03:30 -06:00
Tony Arcieri 15a590dd93 Merge pull request #87 from iqlusioninc/ci/split-security-audit-and-ignore-spin
.github: split security_audit.yml; ignore spin advisory
2019-12-17 07:38:08 -08:00
Tony Arcieri e6d9003d09 .github: split security_audit.yml; ignore spin advisory
Splits the security audit into a separate file which only runs on
Cargo.toml changes or on a regular schedule.

Ignores the RUSTSEC-2019-0031 warning advisory.
2019-12-17 07:26:44 -08:00
Tony Arcieri 4dd0f7b31e Merge pull request #86 from iqlusioninc/dependabot/cargo/x509-0.1.2
Bump x509 from 0.1.1 to 0.1.2
2019-12-17 07:04:53 -08:00
dependabot-preview[bot] 2d57b8e2e1 Bump x509 from 0.1.1 to 0.1.2
Bumps [x509](https://github.com/str4d/x509.rs) from 0.1.1 to 0.1.2.
- [Release notes](https://github.com/str4d/x509.rs/releases)
- [Commits](https://github.com/str4d/x509.rs/commits)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-12-17 13:42:12 +00:00
Tony Arcieri fe18a1d2e4 Merge pull request #85 from iqlusioninc/eliminate-rand-crate
tests: eliminate usage of `rand` crate
2019-12-16 07:41:54 -08:00
Tony Arcieri d4838f2652 tests: eliminate usage of rand crate
Otherwise dependabot will nag us until `num-bigint` updates.
2019-12-16 07:28:21 -08:00
Tony Arcieri 1dff6eca32 Merge pull request #84 from iqlusioninc/dependabot/cargo/log-0.4.10
Bump log from 0.4.8 to 0.4.10
2019-12-16 07:22:17 -08:00
dependabot-preview[bot] 90a44b38cb Bump log from 0.4.8 to 0.4.10
Bumps [log](https://github.com/rust-lang/log) from 0.4.8 to 0.4.10.
- [Release notes](https://github.com/rust-lang/log/releases)
- [Changelog](https://github.com/rust-lang/log/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rust-lang/log/compare/0.4.8...0.4.10)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-12-16 15:12:50 +00:00
Tony Arcieri 4cc785fee9 Merge pull request #82 from str4d/chref-enum
Extract ChangeRefAction enum
2019-12-16 07:11:29 -08:00
Jack Grigg 422f89b3e9 Extract ChangeRefAction enum 2019-12-16 06:26:41 -06:00
Jack Grigg 976af45e22 Add missing entries to Cargo.lock 2019-12-16 06:25:48 -06:00
Tony Arcieri ae4e098a41 Merge pull request #80 from str4d/cert-gen
Certificate::generate_self_signed
2019-12-15 10:00:17 -08:00
Jack Grigg 985b1d272c Add a serial number wrapper struct with Into conversions 2019-12-15 17:50:25 +00:00
Jack Grigg 58acfe6330 Simplify issuer and subject stringification 2019-12-15 17:42:47 +00:00
Jack Grigg 02ade49288 tests/integration: Verify signature on generated EC certificate 2019-12-15 17:22:52 +00:00
Jack Grigg 1a95a5f921 Fix PKCS#1 v1.5 signature generation 2019-12-15 17:09:09 +00:00
Jack Grigg 620f2bcc74 tests/integration: Verify signature on generated RSA certificate 2019-12-15 17:09:09 +00:00
Tony Arcieri 0e14110e17 Merge pull request #79 from carl-wallace/develop
move print cert info into new CLI project
2019-12-15 08:25:37 -08:00
Jack Grigg 8ac78cafb8 Certificate::generate_self_signed 2019-12-15 10:59:50 +00:00
Jack Grigg 5e8a014be2 Expose certificate serial and issuer 2019-12-15 10:35:22 +00:00
Jack Grigg d44a32453c Write certificate TLVs into correct offsets 2019-12-15 10:33:01 +00:00
Carl Wallace 220c045dcb move print cert info into new CLI project 2019-12-14 13:27:54 -05:00
Tony Arcieri 6174b62a77 Merge pull request #78 from iqlusioninc/dependabot/cargo/secrecy-0.6.0
Bump secrecy from 0.5.1 to 0.6.0
2019-12-13 06:02:56 -08:00
dependabot-preview[bot] 36408ac658 Bump secrecy from 0.5.1 to 0.6.0
Bumps [secrecy](https://github.com/iqlusioninc/crates) from 0.5.1 to 0.6.0.
- [Release notes](https://github.com/iqlusioninc/crates/releases)
- [Commits](https://github.com/iqlusioninc/crates/compare/secrecy/v0.5.1...secrecy/v0.6.0)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-12-13 13:45:51 +00:00
Tony Arcieri 16a9a1a2c6 Merge pull request #77 from iqlusioninc/dependabot/cargo/elliptic-curve-0.2.0
Bump elliptic-curve from 0.1.0 to 0.2.0
2019-12-12 06:25:10 -08:00
dependabot-preview[bot] cee7f1cef8 Bump elliptic-curve from 0.1.0 to 0.2.0
Bumps [elliptic-curve](https://github.com/RustCrypto/signatures) from 0.1.0 to 0.2.0.
- [Release notes](https://github.com/RustCrypto/signatures/releases)
- [Commits](https://github.com/RustCrypto/signatures/compare/elliptic-curve/v0.1.0...elliptic-curve/v0.2.0)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-12-12 13:49:12 +00:00
Tony Arcieri cb104f3df6 Merge pull request #76 from iqlusioninc/dependabot/cargo/rsa-0.2.0
Bump rsa from 0.1.4 to 0.2.0
2019-12-11 05:43:08 -08:00
dependabot-preview[bot] ac338cf17a Bump rsa from 0.1.4 to 0.2.0
Bumps [rsa](https://github.com/RustCrypto/RSA) from 0.1.4 to 0.2.0.
- [Release notes](https://github.com/RustCrypto/RSA/releases)
- [Changelog](https://github.com/RustCrypto/RSA/blob/master/release.toml)
- [Commits](https://github.com/RustCrypto/RSA/compare/0.1.4...0.2.0)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-12-11 13:34:30 +00:00
Tony Arcieri a8ea3ec8b7 Merge pull request #75 from str4d/cert-gen-prep
Preparatory work for certificate generation
2019-12-10 18:57:25 -08:00
Jack Grigg d113c1f4b9 impl<'a> TryFrom<&'a [u8]> for Certificate 2019-12-11 02:44:40 +00:00
Jack Grigg 2eff313064 Fix bug in key::generate and document weirdness
Bug was introduced in #73 when starting offsets were overlooked. Digging
into why they were there led to uncovering the weird not-quite-ASN.1
format that the YubiKey returns generated pubkeys in.
2019-12-11 02:26:23 +00:00
Jack Grigg 41b10d1f23 Convert certificate info into an enum 2019-12-11 02:21:49 +00:00
Jack Grigg 4c2ecea721 Replace GeneratedKey with PublicKeyInfo 2019-12-11 00:31:31 +00:00
Jack Grigg e73607e662 Rename Certificate::new to Certificate::from_bytes 2019-12-11 00:30:39 +00:00
Tony Arcieri 17839da94f Merge pull request #74 from iqlusioninc/cli/reader-name
cli: print reader name as part of `status` command
2019-12-10 09:20:50 -08:00
Tony Arcieri 08897ec7c9 cli: print reader name as part of status command 2019-12-10 08:43:33 -08:00
Tony Arcieri 26c777b6ec Merge pull request #73 from str4d/tlv-extraction
TLV extraction
2019-12-10 08:21:42 -08:00
Jack Grigg 1bf3b13e52 Add missing untested feature gates 2019-12-10 13:31:48 +00:00
Jack Grigg 8385dda201 Check buffer length in set_length 2019-12-10 13:22:21 +00:00
Jack Grigg 363bdc4351 Extract TLV writing into serialization::Tlv 2019-12-10 13:17:01 +00:00
Jack Grigg da828abe3c Extract TLV parsing into serialization::Tlv 2019-12-10 13:14:39 +00:00
Tony Arcieri 339fb69e30 Merge pull request #72 from iqlusioninc/status-command
cli: add `status` command
2019-12-09 19:29:07 -08:00
Tony Arcieri 78d5f33695 cli: add status command
Provides equivalent functionality to `yubico-piv-tool`
2019-12-09 18:00:34 -08:00
Tony Arcieri 283e6fe363 Merge pull request #71 from iqlusioninc/cli/rename-list-to-readers-improve-usage
cli: rename 'list' command to 'readers'; improve usage
2019-12-09 09:51:34 -08:00
Tony Arcieri 55d077dd80 cli: rename 'list' command to 'readers'; improve usage
There are going to be several `list` commands (e.g. `yubikey keys list`)
so this is a confusing name.

If we need more than one `readers` subcommand we can change this to be
`readers list` eventually.

Separately (in what probably should've been its own commit, mea culpa)
this adds slightly better usage.
2019-12-09 09:39:24 -08:00
Tony Arcieri fd77ba6e74 Merge pull request #70 from carl-wallace/develop
add try_from String for SlotIds in support of CLI
2019-12-09 07:55:02 -08:00
Carl Wallace 855f2ecb24 add try_from String for SlotIds in support of CLI 2019-12-08 19:25:27 -05:00
Tony Arcieri 6436d9afcb Merge pull request #69 from iqlusioninc/open-by-serial
yubikey: add `open_by_serial` method
2019-12-08 12:58:40 -08:00
Tony Arcieri 4663cffb96 yubikey: add open_by_serial method
Support for opening a `YubiKey` with a specific serial number.
2019-12-08 12:12:03 -08:00
Tony Arcieri fb7e95e6d1 Merge pull request #68 from iqlusioninc/rename-container-module-to-mscmap
Rename `container` module to `mscmap`
2019-12-08 10:40:01 -08:00
Tony Arcieri 0a100acdd2 Rename container module to mscmap
Better reflects what it actually is.
2019-12-08 10:01:00 -08:00
Tony Arcieri 39a81fc300 Merge pull request #67 from iqlusioninc/eliminate-consts-module
Finish eliminating `consts` module
2019-12-08 09:43:11 -08:00
Tony Arcieri 31efd4e78c Finish eliminating consts module
Either moves constants into their relevant modules, or puts the
remaining ones into `lib.rs`
2019-12-08 09:32:57 -08:00
Tony Arcieri 86b8c6a6db Merge pull request #66 from iqlusioninc/tame-consts
consts: Whittle down to the essentials
2019-12-08 08:51:51 -08:00
Tony Arcieri 104020d518 consts: Whittle down to the essentials
This factors the junk drawer of constants into the relevant files.

There are still a few "global" ones left but they can be addressed in a
followup commit.
2019-12-08 08:39:21 -08:00
Tony Arcieri 4dfac56753 Merge pull request #65 from iqlusioninc/cccid-chuid-tests-and-cleanups
CCCID/CHUID tests and cleanups
2019-12-07 13:33:28 -08:00
Tony Arcieri 9482ae62ab CCCID/CHUID: add basic tests and do some cleanups
- Adds tests for CCCID/CHUID, allowing not found (is that ok?)
- Move constants under their respective modules and remove `YKPIV_`
2019-12-07 13:09:38 -08:00
Tony Arcieri 2587a4ac1e CCCID/CHUID refactoring
- Move generate methods to the appropriate static types
- Remove redundant name prefixes (Rust [RFC#356])

[RFC#356]: https://github.com/rust-lang/rfcs/pull/356
2019-12-07 12:39:52 -08:00
Tony Arcieri 3cf3c0867f Merge pull request #49 from carl-wallace/develop
change ccid handling to target entire CCC object
2019-12-07 12:10:44 -08:00
Tony Arcieri b2f11f5058 Merge pull request #64 from iqlusioninc/config-tests
Test `Config::get`
2019-12-07 12:10:24 -08:00
Tony Arcieri cdecfd92dd Test Config::get
Tests reading configuration from a live device:

    Config { protected_data_available: false, puk_blocked: false, puk_noblock_on_upgrade: false, pin_last_changed: 0, mgm_type: Manual }
2019-12-07 11:47:07 -08:00
Tony Arcieri 509c438330 Merge pull request #63 from iqlusioninc/drop-neo-support
Drop YubiKey NEO support (closes #18)
2019-12-07 11:32:10 -08:00
Tony Arcieri f6915ce5df Drop YubiKey NEO support (closes #18)
YubiKey NEOs are legacy YubiKey devices, most of which contain
unpatchable security vulnerabilities.

They have smaller buffer sizes than YK4 and YK5, which necessitates a
whole bunch of conditional gating and buffer size calculations.

Getting rid of them simplifies this logic and allows us to assume
consistent buffer sizes everywhere.

We never tested on NEOs anyway, and looking at the deleted code it seems
it may have been miscalculating the NEO's buffer size!

If someone *really* wants to support NEOs, it shouldn't be that hard to
restore, but the codebase is definitely cleaner without it.
2019-12-07 11:22:51 -08:00
Tony Arcieri 962089dbf8 Merge pull request #62 from iqlusioninc/keys/move-import-and-attest
Move `import` and `attest` to the `key` module
2019-12-07 10:47:44 -08:00
Tony Arcieri d6cd0130d3 Move sign/decrypt/import/attest to the key module
These are crypto key-related functions and are better factored under
this module.
2019-12-07 10:39:02 -08:00
Tony Arcieri 7d01dba11d Merge pull request #61 from iqlusioninc/test-listing-keys
Test `Key::list`
2019-12-07 10:19:43 -08:00
Tony Arcieri d1d384d304 Test Key::list
Adds a live-against-the-device test which ensures keys can be
successfully listed.
2019-12-07 10:09:56 -08:00
Tony Arcieri cb9d5221b2 Merge pull request #60 from iqlusioninc/test-verify-pin
Test YubiKey::verify_pin (--ignored)
2019-12-07 08:52:09 -08:00
Tony Arcieri c30cf5b83a Test YubiKey::verify_pin (--ignored)
Adds an off-by-default test that the `YubiKey::verify_pin` function
works, and removes it from `untested` gating.
2019-12-07 08:44:12 -08:00
Tony Arcieri 3c88f1be13 Merge pull request #59 from str4d/elliptic-curve
Switch to elliptic-curve crate
2019-12-07 08:03:07 -08:00
Jack Grigg 0551263286 Switch to elliptic-curve crate 2019-12-07 15:47:24 +00:00
Tony Arcieri 63fbc1dcf2 Merge pull request #50 from str4d/key-generation
Key generation prep
2019-12-04 08:03:39 -08:00
Carl Wallace 82c2d08aec Merge remote-tracking branch 'upstream/develop' into develop 2019-12-03 15:12:22 -05:00
Tony Arcieri f25e14c52c Merge pull request #58 from iqlusioninc/readme/fix-license-image
README.md: Fix license image
2019-12-03 11:24:42 -08:00
Tony Arcieri b1e8702059 README.md: Fix license image 2019-12-03 11:13:00 -08:00
Tony Arcieri f4f7041626 Merge pull request #57 from iqlusioninc/dependabot/cargo/zeroize-1.1.0
Bump zeroize from 1.0.0 to 1.1.0
2019-12-03 06:45:13 -08:00
dependabot-preview[bot] d6ad70f7d1 Bump zeroize from 1.0.0 to 1.1.0
Bumps [zeroize](https://github.com/iqlusioninc/crates) from 1.0.0 to 1.1.0.
- [Release notes](https://github.com/iqlusioninc/crates/releases)
- [Commits](https://github.com/iqlusioninc/crates/compare/zeroize/v1.0.0...zeroize/v1.1.0)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-12-03 13:51:52 +00:00
Jack Grigg 76c093e68e Minor cleanups 2019-12-03 03:24:10 +00:00
Jack Grigg ada3454d26 Fix bug in MgmKey::decrypt 2019-12-03 03:24:09 +00:00
Jack Grigg 370a90f800 Correctly return StatusWords from transfer_data 2019-12-03 03:24:07 +00:00
Jack Grigg 7bcd8664a4 AlgorithmId::write helper to match policy helpers 2019-12-03 03:24:06 +00:00
Jack Grigg 3a4515d902 Convert PIN and touch policies into enums 2019-12-03 03:23:59 +00:00
Tony Arcieri 7b70ea0f91 Merge pull request #56 from iqlusioninc/cli/fix-readme-badge
cli: fix build badge
2019-12-02 12:31:30 -08:00
Tony Arcieri 9bc28f4f75 cli: fix build badge 2019-12-02 12:18:44 -08:00
Tony Arcieri 140016bbd7 Merge pull request #54 from iqlusioninc/yubikey-cli/v0.0.1
yubikey-cli v0.0.1
2019-12-02 12:08:13 -08:00
Carl Wallace a9e0363d09 remove spurious blank lines flagged by fmt 2019-12-01 18:23:32 -05:00
Carl Wallace bfd728d1ac remove sha2, which was rendered OBE as print cert info was moved to CLI 2019-12-01 18:22:18 -05:00
Carl Wallace a110289910 move print cert info to CLI 2019-12-01 18:20:18 -05:00
Carl Wallace b9d6057d4e address fmt issues 2019-12-01 15:12:05 -05:00
Carl Wallace 2087e53109 add print cert info method in support of status action a la yubico-piv-tool 2019-12-01 14:59:21 -05:00
Carl Wallace 5f5844ccb4 Merge remote-tracking branch 'upstream/develop' into develop 2019-12-01 14:49:41 -05:00
Carl Wallace c8e5c96398 change cccid handling to target entire ccc object (a la yubico-piv-tool status action) 2019-11-30 15:11:10 -05:00
35 changed files with 2970 additions and 1830 deletions
+8
View File
@@ -0,0 +1,8 @@
version: 2
updates:
- package-ecosystem: cargo
directory: "/"
schedule:
interval: weekly
time: '13:00'
open-pull-requests-limit: 10
+88
View File
@@ -0,0 +1,88 @@
name: CI
on:
pull_request: {}
push:
branches: develop
env:
CARGO_INCREMENTAL: 0
RUSTFLAGS: "-Dwarnings"
jobs:
check:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v1
- uses: actions-rs/toolchain@v1
with:
profile: minimal
toolchain: stable
override: true
- run: sudo apt-get install libpcsclite-dev
- run: cargo check
test:
strategy:
matrix:
include:
- platform: ubuntu-latest
toolchain: stable
deps: sudo apt-get install libpcsclite-dev
- platform: windows-latest
toolchain: stable
deps: true
- platform: macos-latest
toolchain: stable
deps: true
- platform: ubuntu-latest
toolchain: 1.44.0 # MSRV
deps: sudo apt-get install libpcsclite-dev
- platform: windows-latest
toolchain: 1.44.0 # MSRV
deps: true
- platform: macos-latest
toolchain: 1.44.0 # MSRV
deps: true
runs-on: ${{ matrix.platform }}
steps:
- uses: actions/checkout@v1
- uses: actions-rs/toolchain@v1
with:
profile: minimal
toolchain: ${{ matrix.toolchain }}
override: true
- run: ${{ matrix.deps }}
- run: cargo build --all --all-features --release
- run: cargo test --all --all-features --release
rustfmt:
runs-on: ubuntu-latest
steps:
- name: Checkout sources
uses: actions/checkout@v1
- name: Install stable toolchain
uses: actions-rs/toolchain@v1
with:
profile: minimal
toolchain: stable
components: rustfmt
- name: Run cargo fmt
uses: actions-rs/cargo@v1
with:
command: fmt
args: --all -- --check
clippy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v1
- uses: actions-rs/toolchain@v1
with:
profile: minimal
toolchain: 1.44.0 # MSRV
components: clippy
- run: sudo apt-get install libpcsclite-dev
- run: cargo clippy --all --exclude crypto_box --all-features -- -D warnings
-175
View File
@@ -1,175 +0,0 @@
# Based on https://github.com/actions-rs/meta/blob/master/recipes/quickstart.md
on:
pull_request: {}
push:
branches: develop
name: Rust
jobs:
check:
name: Check
runs-on: ubuntu-latest
steps:
- name: Checkout sources
uses: actions/checkout@v1
- name: Install stable toolchain
uses: actions-rs/toolchain@v1
with:
toolchain: stable
override: true
- name: Install libpcsclite-dev
run: sudo apt-get install libpcsclite-dev
- name: Run cargo check
uses: actions-rs/cargo@v1
with:
command: check
# Need to install `libpscslite-dev` on Linux
linux:
name: Test Suite
strategy:
matrix:
toolchain:
- 1.39.0
- stable
runs-on: ubuntu-latest
steps:
- name: Checkout sources
uses: actions/checkout@v1
- name: Install stable toolchain
uses: actions-rs/toolchain@v1
with:
toolchain: ${{ matrix.toolchain }}
override: true
- name: Install libpcsclite-dev
run: sudo apt-get install libpcsclite-dev
- name: Run cargo test
uses: actions-rs/cargo@v1
env:
RUSTFLAGS: -D warnings
with:
command: test
args: --all --release
- name: Run cargo build --all-features
uses: actions-rs/cargo@v1
env:
RUSTFLAGS: -D warnings
with:
command: build
args: --all --all-features
test:
name: Test Suite
strategy:
matrix:
platform:
- macos-latest
- windows-latest
toolchain:
- 1.39.0
- stable
runs-on: ${{ matrix.platform }}
steps:
- name: Checkout sources
uses: actions/checkout@v1
- name: Install stable toolchain
uses: actions-rs/toolchain@v1
with:
toolchain: ${{ matrix.toolchain }}
override: true
- name: Run cargo test
uses: actions-rs/cargo@v1
env:
RUSTFLAGS: -D warnings
with:
command: test
args: --all --release
- name: Run cargo build --all-features
uses: actions-rs/cargo@v1
env:
RUSTFLAGS: -D warnings
with:
command: build
args: --all --all-features
fmt:
name: Rustfmt
runs-on: ubuntu-latest
steps:
- name: Checkout sources
uses: actions/checkout@v1
- name: Install stable toolchain
uses: actions-rs/toolchain@v1
with:
toolchain: stable
override: true
- name: Install rustfmt
run: rustup component add rustfmt
- name: Run cargo fmt
uses: actions-rs/cargo@v1
with:
command: fmt
args: --all -- --check
clippy:
name: Clippy
runs-on: ubuntu-latest
steps:
- name: Checkout sources
uses: actions/checkout@v1
- name: Install stable toolchain
uses: actions-rs/toolchain@v1
with:
toolchain: stable
override: true
- name: Install libpcsclite-dev
run: sudo apt-get install libpcsclite-dev
- name: Install clippy
run: rustup component add clippy
- name: Run cargo clippy
uses: actions-rs/cargo@v1
with:
command: clippy
args: --all --all-features -- -D warnings
# TODO: use actions-rs/audit-check
security_audit:
name: Security Audit
runs-on: ubuntu-latest
steps:
- name: Checkout sources
uses: actions/checkout@v1
- name: Install stable toolchain
uses: actions-rs/toolchain@v1
with:
toolchain: stable
override: true
- name: Install cargo audit
run: cargo install cargo-audit
- name: Run cargo audit
uses: actions-rs/cargo@v1
with:
command: audit
args: --deny-warnings
+45
View File
@@ -0,0 +1,45 @@
name: Security Audit
on:
pull_request:
paths: Cargo.lock
push:
branches: develop
paths: Cargo.lock
schedule:
- cron: '0 0 * * *'
jobs:
# TODO: use actions-rs/audit-check
security_audit:
name: Security Audit
runs-on: ubuntu-latest
steps:
- name: Checkout sources
uses: actions/checkout@v1
- name: Cache cargo registry
uses: actions/cache@v1
with:
path: ~/.cargo/registry
key: ${{ runner.os }}-cargo-registry-${{ hashFiles('Cargo.lock') }}
- name: Cache cargo index
uses: actions/cache@v1
with:
path: ~/.cargo/git
key: ${{ runner.os }}-cargo-index-${{ hashFiles('Cargo.lock') }}
- name: Install stable toolchain
uses: actions-rs/toolchain@v1
with:
toolchain: stable
override: true
- name: Install cargo audit
run: cargo install cargo-audit
- name: Run cargo audit
uses: actions-rs/cargo@v1
with:
command: audit
args: --deny-warnings --ignore RUSTSEC-2019-0031 # spin
+45 -5
View File
@@ -4,7 +4,49 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [0.0.3] (2019-12-02)
## 0.1.0 (2020-10-18)
### Added
- `Certificate::generate_self_signed` ([#80])
- `YubiKey::open_by_serial` ([#69])
- CCCID/CHUID tests and cleanups ([#65])
- Test `Config::get` ([#64])
- Test `Key::list` ([#61])
- Test `YubiKey::verify_pin` ([#60])
### Changed
- Bump `crypto-mac`, `des`, `hmac`, `pbkdf2` ([#177])
- Bump `p256` to v0.5; `p384` to v0.4; MSRV 1.44+ ([#175])
- Refactor key import function ([#128])
- Extract `ChangeRefAction` enum ([#82])
- TLV extraction ([#73])
- Rename `container` to `mscmap` ([#68])
- Finish eliminating `consts` module ([#67])
- Move `sign`/`decrypt`/`import`/`attest` to the `key` module ([#62])
### Fixed
- `pcsc::Error::NoReadersAvailable` -> `Error::NotFound` in `YubiKey::open*` ([#88])
### Removed
- YubiKey NEO support ([#63])
[#177]: https://github.com/iqlusioninc/yubikey-piv.rs/pull/177
[#175]: https://github.com/iqlusioninc/yubikey-piv.rs/pull/175
[#128]: https://github.com/iqlusioninc/yubikey-piv.rs/pull/128
[#82]: https://github.com/iqlusioninc/yubikey-piv.rs/pull/82
[#73]: https://github.com/iqlusioninc/yubikey-piv.rs/pull/73
[#88]: https://github.com/iqlusioninc/yubikey-piv.rs/pull/88
[#80]: https://github.com/iqlusioninc/yubikey-piv.rs/pull/80
[#69]: https://github.com/iqlusioninc/yubikey-piv.rs/pull/69
[#68]: https://github.com/iqlusioninc/yubikey-piv.rs/pull/68
[#67]: https://github.com/iqlusioninc/yubikey-piv.rs/pull/67
[#65]: https://github.com/iqlusioninc/yubikey-piv.rs/pull/65
[#64]: https://github.com/iqlusioninc/yubikey-piv.rs/pull/64
[#63]: https://github.com/iqlusioninc/yubikey-piv.rs/pull/63
[#62]: https://github.com/iqlusioninc/yubikey-piv.rs/pull/62
[#61]: https://github.com/iqlusioninc/yubikey-piv.rs/pull/61
[#60]: https://github.com/iqlusioninc/yubikey-piv.rs/pull/60
## 0.0.3 (2019-12-02)
### Added
- Initial `Readers` enumerator for detecting YubiKeys ([#51])
- Certificate parsing ([#45])
@@ -20,7 +62,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- `Ins` (APDU instruction codes) enum ([#33])
- Factor `Response` into `apdu` module; improved debugging ([#32])
[0.0.3]: https://github.com/iqlusioninc/yubikey-piv.rs/pull/53
[#51]: https://github.com/iqlusioninc/yubikey-piv.rs/pull/51
[#45]: https://github.com/iqlusioninc/yubikey-piv.rs/pull/45
[#44]: https://github.com/iqlusioninc/yubikey-piv.rs/pull/44
@@ -33,7 +74,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
[#33]: https://github.com/iqlusioninc/yubikey-piv.rs/pull/33
[#32]: https://github.com/iqlusioninc/yubikey-piv.rs/pull/32
## [0.0.2] (2019-11-25)
## 0.0.2 (2019-11-25)
### Added
- `untested` Cargo feature to mark untested functionality ([#30])
- Initial connect test and docs ([#19])
@@ -48,7 +89,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Use `log` crate for logging ([#7])
- Replace `ErrorKind::Ok` with `Result` ([#6])
[0.0.2]: https://github.com/iqlusioninc/yubikey-piv.rs/pull/31
[#30]: https://github.com/iqlusioninc/yubikey-piv.rs/pull/30
[#19]: https://github.com/iqlusioninc/yubikey-piv.rs/pull/19
[#17]: https://github.com/iqlusioninc/yubikey-piv.rs/pull/17
@@ -61,4 +101,4 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
[#6]: https://github.com/iqlusioninc/yubikey-piv.rs/pull/6
## 0.0.1 (2019-11-18)
- It typechecks, ship it!
- Initial release
Generated
+725 -528
View File
File diff suppressed because it is too large Load Diff
+24 -12
View File
@@ -1,13 +1,13 @@
[package]
name = "yubikey-piv"
version = "0.0.3" # Also update html_root_url in lib.rs when bumping this
version = "0.1.0" # Also update html_root_url in lib.rs when bumping this
description = """
Pure Rust host-side driver for the YubiKey Personal Identity Verification (PIV)
application providing general-purpose public-key signing and encryption
with hardware-backed private keys for RSA (2048/1024) and ECC (P-256/P-384)
algorithms (e.g, PKCS#1v1.5, ECDSA)
"""
authors = ["Tony Arcieri <bascule@gmail.com>", "Yubico AB"]
authors = ["Tony Arcieri <tony@iqlusion.io>", "Yubico AB"]
edition = "2018"
license = "BSD-2-Clause"
repository = "https://github.com/iqlusioninc/yubikey-piv.rs"
@@ -22,24 +22,36 @@ members = [".", "cli"]
maintenance = { status = "experimental" }
[dependencies]
der-parser = "3"
des = "0.3"
ecdsa = "0.1"
chrono = "0.4"
cookie-factory = "0.3"
der-parser = "4"
des = "0.6"
elliptic-curve = "0.6"
getrandom = "0.1"
hmac = "0.7"
hmac = "0.10"
log = "0.4"
nom = "5"
pbkdf2 = "0.3"
num-bigint = { version = "0.6", features = ["rand"], package = "num-bigint-dig" }
num-traits = "0.2"
num-integer = "0.1"
pbkdf2 = "0.6"
p256 = "0.5"
p384 = "0.4"
pcsc = "2"
rsa = "0.1.4"
secrecy = "0.5"
sha-1 = "0.8"
rsa = "0.3"
secrecy = "0.7"
sha-1 = "0.9"
sha2 = "0.9"
subtle = "2"
x509-parser = "0.6"
subtle-encoding = "0.5"
x509 = "0.1.2"
x509-parser = "0.8"
zeroize = "1"
[dev-dependencies]
env_logger = "0.7"
env_logger = "0.8"
ring = "0.16.15"
lazy_static = "1"
[features]
untested = []
+17 -15
View File
@@ -1,10 +1,10 @@
<img src="https://raw.githubusercontent.com/tendermint/yubihsm-rs/develop/img/logo.png" width="150" height="110">
<img src="https://raw.githubusercontent.com/iqlusioninc/yubikey-piv.rs/develop/img/logo.png" width="150" height="110">
# yubikey-piv.rs
[![crate][crate-image]][crate-link]
[![Docs][docs-image]][docs-link]
![Apache2/MIT licensed][license-image]
[![2-Clause BSD Licensed][license-image]][license-link]
![Rust Version][rustc-image]
![Maintenance Status: Experimental][maintenance-image]
[![Safety Dance][safety-image]][safety-link]
@@ -36,15 +36,15 @@ endorsed by Yubico.
## Minimum Supported Rust Version
- Rust **1.39+**
- Rust **1.44** or newer
## Supported YubiKeys
- [YubiKey NEO] series (may be dropped in the future, see [#18])
- [YubiKey 4] series
- [YubiKey 5] series
NOTE: Nano and USB-C variants of the above are also supported
NOTE: Nano and USB-C variants of the above are also supported.
Pre-YK4 [YubiKey NEO] series is **NOT** supported (see [#18]).
## Security Warning
@@ -65,19 +65,20 @@ functions of the YubiKey:
| | Module | Issue | Description |
|----|---------------|-------|-------------|
| 🚧 | `yubikey` | [#20] | Core functionality: auth, keys, PIN/PUK, encrypt, sign, attest |
| ⚠️ | `cccid` | [#21] | Cardholder Capability Container (CCC) IDs |
| | `certificate` | [#22] | Certificates for stored keys |
| ⚠️ | `chuid` | [#23] | Cardholder Unique Identifier (CHUID) |
| | `config` | [#24] | Support for reading on-key configuration |
| ⚠️ | `container` | [#25] | MS Container Map Records |
| ⚠️ | `key` | [#26] | Crypto key management: list, generate, import |
| ⚠️ | `mgm` | [#26] | Management Key (MGM) support: set, get, derive
| 🚧 | `cccid` | [#21] | Cardholder Capability Container (CCC) IDs |
| 🚧 | `certificate` | [#22] | Certificates for stored keys |
| 🚧 | `chuid` | [#23] | Cardholder Unique Identifier (CHUID) |
| | `config` | [#24] | Support for reading on-key configuration |
| 🚧 | `key` | [#26] | Crypto key management: list, generate, import |
| 🚧 | `mgm` | [#26] | Management Key (MGM) support: set, get, derive |
| ⚠️ | `mscmap` | [#25] | MS Container Map Records |
| ⚠️ | `msroots` | [#28] | `msroots` file: PKCS#7 formatted certificate store for enterprise trusted roots |
Legend:
| | Description |
|----|------------------------------------|
| ✅ | Working |
| 🚧 | Testing and validation in progress |
| ⚠️ | Untested support |
@@ -151,7 +152,7 @@ Yubico's [yubico-piv-tool], a C library/CLI program. The original library
was licensed under a [2-Clause BSD License][BSDL], which this library inherits
as a derived work.
Copyright (c) 2014-2019 Yubico AB, Tony Arcieri
Copyright (c) 2014-2020 Yubico AB, Tony Arcieri
All rights reserved.
Redistribution and use in source and binary forms, with or without
@@ -192,11 +193,12 @@ or conditions.
[docs-image]: https://docs.rs/yubikey-piv/badge.svg
[docs-link]: https://docs.rs/yubikey-piv/
[license-image]: https://img.shields.io/badge/license-BSD-blue.svg
[rustc-image]: https://img.shields.io/badge/rustc-1.39+-blue.svg
[license-link]: https://github.com/iqlusioninc/yubikey-piv.rs/blob/develop/COPYING
[rustc-image]: https://img.shields.io/badge/rustc-1.44+-blue.svg
[maintenance-image]: https://img.shields.io/badge/maintenance-experimental-blue.svg
[safety-image]: https://img.shields.io/badge/unsafe-forbidden-success.svg
[safety-link]: https://github.com/rust-secure-code/safety-dance/
[build-image]: https://github.com/iqlusioninc/yubikey-piv.rs/workflows/Rust/badge.svg?branch=develop&event=push
[build-image]: https://github.com/iqlusioninc/yubikey-piv.rs/workflows/CI/badge.svg?branch=develop&event=push
[build-link]: https://github.com/iqlusioninc/yubikey-piv.rs/actions
[gitter-image]: https://badges.gitter.im/badge.svg
[gitter-link]: https://gitter.im/iqlusioninc/community
+10 -6
View File
@@ -2,10 +2,10 @@
name = "yubikey-cli"
version = "0.0.1"
description = """
Command-line interface for performing encryption and signing using RSA and/or
ECC keys stored on YubiKey devices.
Command-line interface for performing encryption and signing using RSA/ECC keys
stored on YubiKey devices.
"""
authors = ["Tony Arcieri <bascule@gmail.com>"]
authors = ["Tony Arcieri <tony@iqlusion.io>"]
edition = "2018"
license = "BSD-2-Clause"
repository = "https://github.com/iqlusioninc/yubikey-piv.rs"
@@ -14,8 +14,12 @@ categories = ["command-line-utilities", "cryptography", "hardware-support"]
keywords = ["ecdsa", "rsa", "piv", "pcsc", "yubikey"]
[dependencies]
gumdrop = "0.7"
env_logger = "0.7"
gumdrop = "0.8"
env_logger = "0.8"
lazy_static = "1"
log = "0.4"
sha2 = "0.8"
subtle-encoding = "0.5"
termcolor = "1"
yubikey-piv = { version = "0.0.3", path = ".." }
x509-parser = "0.7"
yubikey-piv = { version = "0.1", path = ".." }
+5 -5
View File
@@ -1,4 +1,4 @@
<img src="https://raw.githubusercontent.com/tendermint/yubihsm-rs/develop/img/logo.png" width="150" height="110">
<img src="https://raw.githubusercontent.com/iqlusioninc/yubikey-piv.rs/develop/img/logo.png" width="150" height="110">
# yubikey-cli.rs
@@ -22,11 +22,11 @@ utility with general-purpose public-key encryption and signing support.
## Supported YubiKeys
- [YubiKey NEO] series (may be dropped in the future, see [#18])
- [YubiKey 4] series
- [YubiKey 5] series
NOTE: Nano and USB-C variants of the above are also supported
NOTE: Nano and USB-C variants of the above are also supported.
Pre-YK4 [YubiKey NEO] series is **NOT** supported (see [#18]).
## Security Warning
@@ -92,8 +92,8 @@ or conditions.
[maintenance-image]: https://img.shields.io/badge/maintenance-experimental-blue.svg
[safety-image]: https://img.shields.io/badge/unsafe-forbidden-success.svg
[safety-link]: https://github.com/rust-secure-code/safety-dance/
[build-image]: https://github.com/iqlusioninc/yubikey-cli.rs/workflows/Rust/badge.svg?branch=develop&event=push
[build-link]: https://github.com/iqlusioninc/yubikey-cli.rs/actions
[build-image]: https://github.com/iqlusioninc/yubikey-piv.rs/workflows/Rust/badge.svg?branch=develop&event=push
[build-link]: https://github.com/iqlusioninc/yubikey-piv.rs/actions
[gitter-image]: https://badges.gitter.im/badge.svg
[gitter-link]: https://gitter.im/iqlusioninc/community
+2 -2
View File
@@ -9,8 +9,8 @@
)]
use gumdrop::Options;
use yubikey_cli::commands::YubikeyCli;
use yubikey_cli::commands::YubiKeyCli;
fn main() {
YubikeyCli::parse_args_default_or_exit().run();
YubiKeyCli::parse_args_default_or_exit().run();
}
+72 -15
View File
@@ -1,31 +1,69 @@
//! Commands of the CLI application
pub mod list;
pub mod readers;
pub mod status;
use self::list::ListCmd;
use crate::status;
use self::{readers::ReadersCmd, status::StatusCmd};
use crate::terminal::{self, STDOUT};
use gumdrop::Options;
use std::env;
use std::process::exit;
use termcolor::ColorChoice;
use std::{
env,
io::{self, Write},
process::exit,
};
use termcolor::{ColorChoice, ColorSpec, WriteColor};
use yubikey_piv::{Serial, YubiKey};
/// The `yubikey` CLI utility
#[derive(Debug, Options)]
pub struct YubikeyCli {
pub struct YubiKeyCli {
/// Obtain help about the current command
#[options(short = "h", help = "print help message")]
pub help: bool,
/// Specify the serial number of the YubiKey to connect to
#[options(
short = "s",
long = "serial",
help = "serial number of the YubiKey to connect to"
)]
pub serial: Option<Serial>,
/// Subcommand to execute.
#[options(command)]
pub command: Option<Commands>,
}
impl YubikeyCli {
impl YubiKeyCli {
/// Print usage information
pub fn print_usage() -> Result<(), io::Error> {
let mut stdout = STDOUT.lock();
stdout.reset()?;
let mut bold = ColorSpec::new();
bold.set_bold(true);
stdout.set_color(&bold)?;
writeln!(
stdout,
"{} {}",
env!("CARGO_PKG_NAME"),
env!("CARGO_PKG_VERSION")
)?;
stdout.reset()?;
writeln!(stdout, "{}", env!("CARGO_PKG_AUTHORS"))?;
writeln!(stdout, "{}", env!("CARGO_PKG_DESCRIPTION").trim())?;
writeln!(stdout)?;
writeln!(stdout, "{}", Commands::usage())?;
Ok(())
}
/// Run the underlying command type or print usage info and exit
pub fn run(&self) {
// TODO(tarcieri): make this more configurable
status::set_color_choice(ColorChoice::Auto);
terminal::set_color_choice(ColorChoice::Auto);
// Only show logs if `RUST_LOG` is set
if env::var("RUST_LOG").is_ok() {
@@ -33,8 +71,22 @@ impl YubikeyCli {
}
match &self.command {
Some(cmd) => cmd.run(),
None => println!("{}", Commands::usage()),
Some(cmd) => cmd.run(self.yubikey_init()),
None => Self::print_usage().unwrap(),
}
}
/// Initialize the YubiKey client driver
fn yubikey_init(&self) -> YubiKey {
match self.serial {
Some(serial) => YubiKey::open_by_serial(serial).unwrap_or_else(|e| {
status_err!("couldn't open YubiKey (serial #{}): {}", serial, e);
exit(1);
}),
None => YubiKey::open().unwrap_or_else(|e| {
status_err!("couldn't open default YubiKey: {}", e);
exit(1);
}),
}
}
}
@@ -50,18 +102,23 @@ pub enum Commands {
#[options(help = "display version information")]
Version(VersionOpts),
/// `list` subcommand
/// `readers` subcommand
#[options(help = "list detected readers")]
List(ListCmd),
Readers(ReadersCmd),
/// `status` subcommand
#[options(help = "show yubikey status")]
Status(StatusCmd),
}
impl Commands {
/// Run the given command
pub fn run(&self) {
pub fn run(&self, yubikey: YubiKey) {
match self {
Commands::Help(help) => help.run(),
Commands::Version(version) => version.run(),
Commands::List(list) => list.run(),
Commands::Readers(list) => list.run(),
Commands::Status(status) => status.run(yubikey),
}
}
}
-40
View File
@@ -1,40 +0,0 @@
//! List detected readers
use gumdrop::Options;
use std::process::exit;
use yubikey_piv::readers::Readers;
/// The `list` subcommand
#[derive(Debug, Options)]
pub struct ListCmd {}
impl ListCmd {
/// Run the `list` subcommand
pub fn run(&self) {
let mut readers = Readers::open().unwrap_or_else(|e| {
status_err!("couldn't open PC/SC context: {}", e);
exit(1);
});
let readers_iter = readers.iter().unwrap_or_else(|e| {
status_err!("couldn't enumerate PC/SC readers: {}", e);
exit(1);
});
if readers_iter.len() == 0 {
status_err!("no YubiKeys detected!");
exit(1);
}
for (i, reader) in readers_iter.enumerate() {
let name = reader.name();
let mut yubikey = match reader.open() {
Ok(yk) => yk,
Err(_) => continue,
};
let serial = yubikey.serial();
println!("{}: {} (serial: {})", i + 1, name, serial);
}
}
}
+64
View File
@@ -0,0 +1,64 @@
//! List detected readers
use crate::terminal::STDOUT;
use gumdrop::Options;
use std::{
io::{self, Write},
process::exit,
};
use termcolor::{ColorSpec, StandardStreamLock, WriteColor};
use yubikey_piv::{Readers, Serial};
/// The `readers` subcommand
#[derive(Debug, Options)]
pub struct ReadersCmd {}
impl ReadersCmd {
/// Run the `readers` subcommand
pub fn run(&self) {
let mut readers = Readers::open().unwrap_or_else(|e| {
status_err!("couldn't open PC/SC context: {}", e);
exit(1);
});
let readers_iter = readers.iter().unwrap_or_else(|e| {
status_err!("couldn't enumerate PC/SC readers: {}", e);
exit(1);
});
if readers_iter.len() == 0 {
status_err!("no YubiKeys detected!");
exit(1);
}
let mut s = STDOUT.lock();
s.reset().unwrap();
for (i, reader) in readers_iter.enumerate() {
let name = reader.name();
let yubikey = match reader.open() {
Ok(yk) => yk,
Err(_) => continue,
};
let serial = yubikey.serial();
self.print_reader(&mut s, i + 1, &name, serial).unwrap();
}
}
/// Print a reader
fn print_reader(
&self,
stream: &mut StandardStreamLock<'_>,
index: usize,
name: &str,
serial: Serial,
) -> Result<(), io::Error> {
stream.set_color(ColorSpec::new().set_bold(true))?;
write!(stream, "{:>3}:", index)?;
stream.reset()?;
writeln!(stream, " {} (serial: {})", name, serial)?;
stream.flush()?;
Ok(())
}
}
+62
View File
@@ -0,0 +1,62 @@
//! Print device status
use crate::terminal::STDOUT;
use gumdrop::Options;
use std::io::{self, Write};
use termcolor::{ColorSpec, StandardStreamLock, WriteColor};
use yubikey_piv::{key::*, YubiKey};
use crate::print_cert_info;
// String to use for `None`
const NONE_STR: &str = "<none>";
/// The `status` subcommand
#[derive(Debug, Options)]
pub struct StatusCmd {}
impl StatusCmd {
/// Run the `status` subcommand
pub fn run(&self, mut yk: YubiKey) {
let mut s = STDOUT.lock();
s.reset().unwrap();
self.attr(&mut s, "name", yk.name()).unwrap();
self.attr(&mut s, "version", yk.version()).unwrap();
self.attr(&mut s, "serial", yk.serial()).unwrap();
if let Ok(chuid) = yk.chuid() {
self.attr(&mut s, "CHUID", chuid).unwrap();
} else {
self.attr(&mut s, "CHUID", NONE_STR).unwrap();
}
if let Ok(chuid) = yk.cccid() {
self.attr(&mut s, "CCC", chuid).unwrap();
} else {
self.attr(&mut s, "CCC", NONE_STR).unwrap();
}
self.attr(&mut s, "PIN retries", yk.get_pin_retries().unwrap())
.unwrap();
for slot in SLOTS.iter().cloned() {
print_cert_info(&mut yk, slot, &mut s).unwrap();
}
}
/// Print a status attribute
fn attr(
&self,
stream: &mut StandardStreamLock<'_>,
name: &str,
value: impl ToString,
) -> Result<(), io::Error> {
stream.set_color(ColorSpec::new().set_bold(true))?;
write!(stream, "{:>12}:", name)?;
stream.reset()?;
writeln!(stream, " {}", value.to_string())?;
stream.flush()?;
Ok(())
}
}
+79 -1
View File
@@ -9,6 +9,84 @@
)]
#[macro_use]
pub mod status;
pub mod terminal;
pub mod commands;
use log::debug;
use sha2::{Digest, Sha256};
use std::io::{self, Write};
use std::str;
use subtle_encoding::hex;
use termcolor::{ColorSpec, StandardStreamLock, WriteColor};
use x509_parser::parse_x509_der;
use yubikey_piv::{certificate::Certificate, key::*, YubiKey};
///Write information about certificate found in slot a la yubico-piv-tool output.
pub fn print_cert_info(
yubikey: &mut YubiKey,
slot: SlotId,
stream: &mut StandardStreamLock<'_>,
) -> Result<(), io::Error> {
let cert = match Certificate::read(yubikey, slot) {
Ok(c) => c,
Err(e) => {
debug!("error reading certificate in slot {:?}: {}", slot, e);
return Ok(());
}
};
let buf = cert.into_buffer();
if !buf.is_empty() {
let fingerprint = Sha256::digest(&buf);
let slot_id: u8 = slot.into();
print_cert_attr(stream, "Slot", format!("{:x}", slot_id))?;
match parse_x509_der(&buf) {
Ok((_rem, cert)) => {
print_cert_attr(
stream,
"Algorithm",
cert.tbs_certificate.subject_pki.algorithm.algorithm,
)?;
print_cert_attr(stream, "Subject", cert.tbs_certificate.subject)?;
print_cert_attr(stream, "Issuer", cert.tbs_certificate.issuer)?;
print_cert_attr(
stream,
"Fingerprint",
str::from_utf8(hex::encode(fingerprint).as_slice()).unwrap(),
)?;
print_cert_attr(
stream,
"Not Before",
cert.tbs_certificate.validity.not_before.asctime(),
)?;
print_cert_attr(
stream,
"Not After",
cert.tbs_certificate.validity.not_after.asctime(),
)?;
}
_ => {
println!("Failed to parse certificate");
return Ok(());
}
};
}
Ok(())
}
/// Print a status attribute
fn print_cert_attr(
stream: &mut StandardStreamLock<'_>,
name: &str,
value: impl ToString,
) -> Result<(), io::Error> {
stream.set_color(ColorSpec::new().set_bold(true))?;
write!(stream, "{:>12}:", name)?;
stream.reset()?;
writeln!(stream, " {}", value.to_string())?;
stream.flush()?;
Ok(())
}
+3 -3
View File
@@ -9,7 +9,7 @@ use termcolor::{Color, ColorChoice, ColorSpec, StandardStream, WriteColor};
#[macro_export]
macro_rules! status_ok {
($status:expr, $msg:expr) => {
$crate::status::Status::new()
$crate::terminal::Status::new()
.justified()
.bold()
.color(termcolor::Color::Green)
@@ -25,7 +25,7 @@ macro_rules! status_ok {
#[macro_export]
macro_rules! status_warn {
($msg:expr) => {
$crate::status::Status::new()
$crate::terminal::Status::new()
.bold()
.color(termcolor::Color::Yellow)
.status("warning:")
@@ -40,7 +40,7 @@ macro_rules! status_warn {
#[macro_export]
macro_rules! status_err {
($msg:expr) => {
$crate::status::Status::new()
$crate::terminal::Status::new()
.bold()
.color(termcolor::Color::Red)
.status("error:")
-4
View File
@@ -71,7 +71,6 @@ impl APDU {
}
/// Set this APDU's class
#[cfg(feature = "untested")]
pub fn cla(&mut self, value: u8) -> &mut Self {
self.cla = value;
self
@@ -84,7 +83,6 @@ impl APDU {
}
/// Set both parameters for this APDU
#[cfg(feature = "untested")]
pub fn params(&mut self, p1: u8, p2: u8) -> &mut Self {
self.p1 = p1;
self.p2 = p2;
@@ -268,7 +266,6 @@ pub(crate) struct Response {
impl Response {
/// Create a new response from the given status words and buffer
#[cfg(feature = "untested")]
pub fn new(status_words: StatusWords, data: Vec<u8>) -> Response {
Response { status_words, data }
}
@@ -279,7 +276,6 @@ impl Response {
}
/// Get the raw [`StatusWords`] code for this response.
#[cfg(feature = "untested")]
pub fn code(&self) -> u16 {
self.status_words.code()
}
+57 -13
View File
@@ -30,8 +30,22 @@
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
use crate::{consts::*, error::Error, yubikey::YubiKey};
use crate::{error::Error, yubikey::YubiKey};
use getrandom::getrandom;
use std::fmt::{self, Debug, Display};
use subtle_encoding::hex;
/// CCCID size
pub const CCCID_SIZE: usize = 14;
/// CCC size
pub const CCC_SIZE: usize = 51;
/// CCCID offset
const CCC_ID_OFFS: usize = 9;
/// CCC Object ID
const OBJ_CAPABILITY: u32 = 0x005f_c107;
/// Cardholder Capability Container (CCC) Template
///
@@ -48,38 +62,68 @@ const CCC_TMPL: &[u8] = &[
0x00, 0xfe, 0x00,
];
/// Cardholder Capability Container (CCC) Identifier
/// Cardholder Capability Container (CCC) Identifier Card ID
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub struct CCCID(pub [u8; YKPIV_CCCID_SIZE]);
pub struct CardId(pub [u8; CCCID_SIZE]);
impl CCCID {
/// Generate a random CCCID
impl CardId {
/// Generate a random CCC Card ID
pub fn generate() -> Result<Self, Error> {
let mut id = [0u8; YKPIV_CCCID_SIZE];
let mut id = [0u8; CCCID_SIZE];
getrandom(&mut id).map_err(|_| Error::RandomnessError)?;
Ok(CCCID(id))
Ok(Self(id))
}
}
/// Cardholder Capability Container (CCC) Identifier
#[derive(Copy, Clone)]
pub struct CCC(pub [u8; CCC_SIZE]);
impl CCC {
/// Return CardId component of CCC
pub fn cccid(&self) -> Result<CardId, Error> {
let mut cccid = [0u8; CCCID_SIZE];
cccid.copy_from_slice(&self.0[CCC_ID_OFFS..(CCC_ID_OFFS + CCCID_SIZE)]);
Ok(CardId(cccid))
}
/// Get Cardholder Capability Container (CCC) ID
pub fn get(yubikey: &mut YubiKey) -> Result<Self, Error> {
let txn = yubikey.begin_transaction()?;
let response = txn.fetch_object(YKPIV_OBJ_CAPABILITY)?;
let response = txn.fetch_object(OBJ_CAPABILITY)?;
if response.len() != CCC_TMPL.len() {
return Err(Error::GenericError);
}
let mut cccid = [0u8; YKPIV_CCCID_SIZE];
cccid.copy_from_slice(&response[CCC_ID_OFFS..(CCC_ID_OFFS + YKPIV_CCCID_SIZE)]);
Ok(CCCID(cccid))
let mut ccc = [0u8; CCC_SIZE];
ccc.copy_from_slice(&response[0..CCC_SIZE]);
Ok(Self(ccc))
}
/// Get Cardholder Capability Container (CCC) ID
#[cfg(feature = "untested")]
pub fn set(&self, yubikey: &mut YubiKey) -> Result<(), Error> {
let mut buf = CCC_TMPL.to_vec();
buf[CCC_ID_OFFS..(CCC_ID_OFFS + self.0.len())].copy_from_slice(&self.0);
buf[0..self.0.len()].copy_from_slice(&self.0);
let txn = yubikey.begin_transaction()?;
txn.save_object(YKPIV_OBJ_CAPABILITY, &buf)
txn.save_object(OBJ_CAPABILITY, &buf)
}
}
impl Debug for CCC {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "CCC({:?})", &self.0[..])
}
}
impl Display for CCC {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"{}",
String::from_utf8(hex::encode(&self.0[..])).unwrap()
)
}
}
+376 -103
View File
@@ -31,48 +31,136 @@
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
use crate::{
consts::*,
error::Error,
key::{AlgorithmId, SlotId},
key::{sign_data, AlgorithmId, SlotId},
serialization::*,
transaction::Transaction,
yubikey::YubiKey,
Buffer,
};
use ecdsa::{
curve::{CompressedCurvePoint, NistP256, NistP384, UncompressedCurvePoint},
generic_array::GenericArray,
};
use chrono::{DateTime, Utc};
use elliptic_curve::sec1::EncodedPoint as EcPublicKey;
use log::error;
use rsa::{PublicKey, RSAPublicKey};
use num_bigint::BigUint;
use p256::NistP256;
use p384::NistP384;
use rsa::{PublicKeyParts, RSAPublicKey};
use sha2::{Digest, Sha256};
use std::convert::TryFrom;
use std::fmt;
use std::ops::DerefMut;
use x509_parser::{parse_x509_der, x509::SubjectPublicKeyInfo};
use zeroize::Zeroizing;
use crate::CB_OBJ_MAX;
// TODO: Make these der_parser::oid::Oid constants when it has const fn support.
const OID_RSA_ENCRYPTION: &str = "1.2.840.113549.1.1.1";
const OID_EC_PUBLIC_KEY: &str = "1.2.840.10045.2.1";
const OID_NIST_P256: &str = "1.2.840.10045.3.1.7";
const OID_NIST_P384: &str = "1.3.132.0.34";
/// An encoded point on the Nist P-256 curve.
#[derive(Clone, Eq, PartialEq)]
pub enum EcP256Point {
/// Compressed encoding of a point on the curve.
Compressed(CompressedCurvePoint<NistP256>),
const TAG_CERT: u8 = 0x70;
const TAG_CERT_COMPRESS: u8 = 0x71;
const TAG_CERT_LRC: u8 = 0xFE;
/// Uncompressed encoding of a point on the curve.
Uncompressed(UncompressedCurvePoint<NistP256>),
/// A serial number for a [`Certificate`].
#[derive(Clone, Debug)]
pub struct Serial(BigUint);
impl From<BigUint> for Serial {
fn from(num: BigUint) -> Serial {
Serial(num)
}
}
/// An encoded point on the Nist P-384 curve.
#[derive(Clone, Eq, PartialEq)]
pub enum EcP384Point {
/// Compressed encoding of a point on the curve.
Compressed(CompressedCurvePoint<NistP384>),
impl From<[u8; 20]> for Serial {
fn from(bytes: [u8; 20]) -> Serial {
Serial(BigUint::from_bytes_be(&bytes))
}
}
/// Uncompressed encoding of a point on the curve.
Uncompressed(UncompressedCurvePoint<NistP384>),
impl TryFrom<&[u8]> for Serial {
type Error = ();
fn try_from(bytes: &[u8]) -> Result<Serial, ()> {
if bytes.len() <= 20 {
Ok(Serial(BigUint::from_bytes_be(&bytes)))
} else {
Err(())
}
}
}
impl Serial {
fn to_bytes(&self) -> Vec<u8> {
self.0.to_bytes_be()
}
}
/// Information about how a [`Certificate`] is stored within a YubiKey.
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum CertInfo {
/// The certificate is uncompressed.
Uncompressed,
/// The certificate is gzip-compressed.
Gzip,
}
impl TryFrom<u8> for CertInfo {
type Error = Error;
fn try_from(value: u8) -> Result<Self, Self::Error> {
match value {
0x00 => Ok(CertInfo::Uncompressed),
0x01 => Ok(CertInfo::Gzip),
_ => Err(Error::InvalidObject),
}
}
}
impl From<CertInfo> for u8 {
fn from(certinfo: CertInfo) -> u8 {
match certinfo {
CertInfo::Uncompressed => 0x00,
CertInfo::Gzip => 0x01,
}
}
}
impl x509::AlgorithmIdentifier for AlgorithmId {
type AlgorithmOid = &'static [u64];
fn algorithm(&self) -> Self::AlgorithmOid {
match self {
// RSA encryption
AlgorithmId::Rsa1024 | AlgorithmId::Rsa2048 => &[1, 2, 840, 113_549, 1, 1, 1],
// EC Public Key
AlgorithmId::EccP256 | AlgorithmId::EccP384 => &[1, 2, 840, 10045, 2, 1],
}
}
fn parameters<W: std::io::Write>(
&self,
w: cookie_factory::WriteContext<W>,
) -> cookie_factory::GenResult<W> {
use x509::der::write::der_oid;
// From [RFC 5480](https://tools.ietf.org/html/rfc5480#section-2.1.1):
// ```text
// ECParameters ::= CHOICE {
// namedCurve OBJECT IDENTIFIER
// -- implicitCurve NULL
// -- specifiedCurve SpecifiedECDomain
// }
// ```
match self {
AlgorithmId::EccP256 => der_oid(&[1, 2, 840, 10045, 3, 1, 7][..])(w),
AlgorithmId::EccP384 => der_oid(&[1, 3, 132, 0, 34][..])(w),
_ => Ok(w),
}
}
}
/// Information about a public key within a [`Certificate`].
@@ -88,10 +176,10 @@ pub enum PublicKeyInfo {
},
/// EC P-256 keys
EcP256(EcP256Point),
EcP256(EcPublicKey<NistP256>),
/// EC P-384 keys
EcP384(EcP384Point),
EcP384(EcPublicKey<NistP384>),
}
impl fmt::Debug for PublicKeyInfo {
@@ -118,32 +206,12 @@ impl PublicKeyInfo {
OID_EC_PUBLIC_KEY => {
let key_bytes = &subject_pki.subject_public_key.data;
match read_pki::ec_parameters(&subject_pki.algorithm.parameters)? {
AlgorithmId::EccP256 => match key_bytes.len() {
33 => CompressedCurvePoint::<NistP256>::from_bytes(
GenericArray::clone_from_slice(key_bytes),
)
.map(EcP256Point::Compressed),
65 => UncompressedCurvePoint::<NistP256>::from_bytes(
GenericArray::clone_from_slice(key_bytes),
)
.map(EcP256Point::Uncompressed),
_ => None,
}
AlgorithmId::EccP256 => EcPublicKey::from_bytes(key_bytes)
.map(PublicKeyInfo::EcP256)
.ok_or(Error::InvalidObject),
AlgorithmId::EccP384 => match key_bytes.len() {
49 => CompressedCurvePoint::<NistP384>::from_bytes(
GenericArray::clone_from_slice(key_bytes),
)
.map(EcP384Point::Compressed),
97 => UncompressedCurvePoint::<NistP384>::from_bytes(
GenericArray::clone_from_slice(key_bytes),
)
.map(EcP384Point::Uncompressed),
_ => None,
}
.map_err(|_| Error::InvalidObject),
AlgorithmId::EccP384 => EcPublicKey::from_bytes(key_bytes)
.map(PublicKeyInfo::EcP384)
.ok_or(Error::InvalidObject),
.map_err(|_| Error::InvalidObject),
_ => Err(Error::AlgorithmError),
}
}
@@ -161,15 +229,209 @@ impl PublicKeyInfo {
}
}
impl x509::SubjectPublicKeyInfo for PublicKeyInfo {
type AlgorithmId = AlgorithmId;
type SubjectPublicKey = Vec<u8>;
fn algorithm_id(&self) -> AlgorithmId {
self.algorithm()
}
fn public_key(&self) -> Vec<u8> {
match self {
PublicKeyInfo::Rsa { pubkey, .. } => {
cookie_factory::gen_simple(write_pki::rsa_pubkey(pubkey), vec![])
.expect("can write to Vec")
}
PublicKeyInfo::EcP256(pubkey) => pubkey.as_bytes().to_vec(),
PublicKeyInfo::EcP384(pubkey) => pubkey.as_bytes().to_vec(),
}
}
}
/// Digest algorithms.
///
/// See RFC 4055 and RFC 8017.
enum DigestId {
/// Secure Hash Algorithm 256 (SHA256)
Sha256,
}
impl x509::AlgorithmIdentifier for DigestId {
type AlgorithmOid = &'static [u64];
fn algorithm(&self) -> Self::AlgorithmOid {
match self {
// See https://tools.ietf.org/html/rfc4055#section-2.1
DigestId::Sha256 => &[2, 16, 840, 1, 101, 3, 4, 2, 1],
}
}
fn parameters<W: std::io::Write>(
&self,
w: cookie_factory::WriteContext<W>,
) -> cookie_factory::GenResult<W> {
// Parameters are an explicit NULL
// See https://tools.ietf.org/html/rfc8017#appendix-A.2.4
x509::der::write::der_null()(w)
}
}
enum SignatureId {
/// Public-Key Cryptography Standards (PKCS) #1 version 1.5 signature algorithm with
/// Secure Hash Algorithm 256 (SHA256) and Rivest, Shamir and Adleman (RSA) encryption
///
/// See RFC 4055 and RFC 8017.
Sha256WithRsaEncryption,
/// Elliptic Curve Digital Signature Algorithm (DSA) coupled with the Secure Hash
/// Algorithm 256 (SHA256) algorithm
///
/// See RFC 5758.
EcdsaWithSha256,
}
impl x509::AlgorithmIdentifier for SignatureId {
type AlgorithmOid = &'static [u64];
fn algorithm(&self) -> Self::AlgorithmOid {
match self {
SignatureId::Sha256WithRsaEncryption => &[1, 2, 840, 113_549, 1, 1, 11],
SignatureId::EcdsaWithSha256 => &[1, 2, 840, 10045, 4, 3, 2],
}
}
fn parameters<W: std::io::Write>(
&self,
w: cookie_factory::WriteContext<W>,
) -> cookie_factory::GenResult<W> {
// No parameters for any SignatureId
Ok(w)
}
}
/// Certificates
#[derive(Clone, Debug)]
pub struct Certificate {
serial: Serial,
issuer: String,
subject: String,
subject_pki: PublicKeyInfo,
data: Buffer,
}
impl<'a> TryFrom<&'a [u8]> for Certificate {
type Error = Error;
fn try_from(bytes: &'a [u8]) -> Result<Self, Error> {
Self::from_bytes(bytes.to_vec())
}
}
impl Certificate {
/// Creates a new self-signed certificate for the given key. Writes the resulting
/// certificate to the slot before returning it.
pub fn generate_self_signed(
yubikey: &mut YubiKey,
key: SlotId,
serial: impl Into<Serial>,
not_after: Option<DateTime<Utc>>,
subject: String,
subject_pki: PublicKeyInfo,
) -> Result<Self, Error> {
let serial = serial.into();
// Issuer and subject are the same in self-signed certificates
let issuer = subject.clone();
let mut tbs_cert = Buffer::new(Vec::with_capacity(CB_OBJ_MAX));
let signature_algorithm = match subject_pki.algorithm() {
AlgorithmId::Rsa1024 | AlgorithmId::Rsa2048 => SignatureId::Sha256WithRsaEncryption,
AlgorithmId::EccP256 | AlgorithmId::EccP384 => SignatureId::EcdsaWithSha256,
};
cookie_factory::gen(
x509::write::tbs_certificate(
&serial.to_bytes(),
&signature_algorithm,
&issuer,
Utc::now(),
not_after,
&subject,
&subject_pki,
),
tbs_cert.deref_mut(),
)
.expect("can serialize to Vec");
let signature = match signature_algorithm {
SignatureId::Sha256WithRsaEncryption => {
use cookie_factory::{combinator::slice, sequence::tuple};
use x509::{
der::write::{der_octet_string, der_sequence},
write::algorithm_identifier,
};
let em_len = if let AlgorithmId::Rsa1024 = subject_pki.algorithm() {
128
} else {
256
};
let h = Sha256::digest(&tbs_cert);
let t = cookie_factory::gen_simple(
der_sequence((
algorithm_identifier(&DigestId::Sha256),
der_octet_string(&h),
)),
vec![],
)
.expect("can serialize into Vec");
let em = cookie_factory::gen_simple(
tuple((
slice(&[0x00, 0x01]),
slice(&vec![0xff; em_len - t.len() - 3]),
slice(&[0x00]),
slice(t),
)),
vec![],
)
.expect("can serialize to Vec");
sign_data(yubikey, &em, subject_pki.algorithm(), key)
}
SignatureId::EcdsaWithSha256 => sign_data(
yubikey,
&Sha256::digest(&tbs_cert),
subject_pki.algorithm(),
key,
),
}?;
let mut data = Buffer::new(Vec::with_capacity(CB_OBJ_MAX));
cookie_factory::gen(
x509::write::certificate(&tbs_cert, &signature_algorithm, &signature),
data.deref_mut(),
)
.expect("can serialize to Vec");
let cert = Certificate {
serial,
issuer,
subject,
subject_pki,
data,
};
cert.write(yubikey, key, CertInfo::Uncompressed)?;
Ok(cert)
}
/// Read a certificate from the given slot in the YubiKey
pub fn read(yubikey: &mut YubiKey, slot: SlotId) -> Result<Self, Error> {
let txn = yubikey.begin_transaction()?;
@@ -179,25 +441,29 @@ impl Certificate {
return Err(Error::InvalidObject);
}
Certificate::new(buf)
Certificate::from_bytes(buf)
}
/// Write this certificate into the YubiKey in the given slot
pub fn write(&self, yubikey: &mut YubiKey, slot: SlotId, certinfo: u8) -> Result<(), Error> {
let max_size = yubikey.obj_size_max();
pub fn write(
&self,
yubikey: &mut YubiKey,
slot: SlotId,
certinfo: CertInfo,
) -> Result<(), Error> {
let txn = yubikey.begin_transaction()?;
write_certificate(&txn, slot, Some(&self.data), certinfo, max_size)
write_certificate(&txn, slot, Some(&self.data), certinfo)
}
/// Delete a certificate located at the given slot of the given YubiKey
#[cfg(feature = "untested")]
pub fn delete(yubikey: &mut YubiKey, slot: SlotId) -> Result<(), Error> {
let max_size = yubikey.obj_size_max();
let txn = yubikey.begin_transaction()?;
write_certificate(&txn, slot, None, 0, max_size)
write_certificate(&txn, slot, None, CertInfo::Uncompressed)
}
/// Initialize a local certificate struct from the given bytebuffer
pub fn new(cert: impl Into<Buffer>) -> Result<Self, Error> {
pub fn from_bytes(cert: impl Into<Buffer>) -> Result<Self, Error> {
let cert = cert.into();
if cert.is_empty() {
@@ -210,16 +476,31 @@ impl Certificate {
_ => return Err(Error::InvalidObject),
};
let subject = format!("{}", parsed_cert.tbs_certificate.subject);
let serial = Serial::try_from(parsed_cert.tbs_certificate.serial.to_bytes_be().as_slice())
.map_err(|_| Error::InvalidObject)?;
let issuer = parsed_cert.tbs_certificate.issuer.to_string();
let subject = parsed_cert.tbs_certificate.subject.to_string();
let subject_pki = PublicKeyInfo::parse(&parsed_cert.tbs_certificate.subject_pki)?;
Ok(Certificate {
serial,
issuer,
subject,
subject_pki,
data: cert,
})
}
/// Returns the serial number of the certificate.
pub fn serial(&self) -> &Serial {
&self.serial
}
/// Returns the Issuer field of the certificate.
pub fn issuer(&self) -> &str {
&self.subject
}
/// Returns the SubjectName field of the certificate.
pub fn subject(&self) -> &str {
&self.subject
@@ -244,10 +525,9 @@ impl AsRef<[u8]> for Certificate {
/// Read certificate
pub(crate) fn read_certificate(txn: &Transaction<'_>, slot: SlotId) -> Result<Buffer, Error> {
let mut len: usize = 0;
let object_id = slot.object_id();
let mut buf = match txn.fetch_object(object_id) {
let buf = match txn.fetch_object(object_id) {
Ok(b) => b,
Err(_) => {
// TODO(tarcieri): is this really ok?
@@ -255,24 +535,15 @@ pub(crate) fn read_certificate(txn: &Transaction<'_>, slot: SlotId) -> Result<Bu
}
};
if buf.len() < CB_OBJ_TAG_MIN {
// TODO(tarcieri): is this really ok?
return Ok(Zeroizing::new(vec![]));
}
// TODO(str4d): Check the rest of the buffer (TAG_CERT_COMPRESS and TAG_CERT_LRC)
if buf[0] == TAG_CERT {
let offset = 1 + get_length(&buf[1..], &mut len);
if len > buf.len() - offset {
Tlv::parse_single(buf, TAG_CERT).or_else(|_| {
// TODO(tarcieri): is this really ok?
return Ok(Zeroizing::new(vec![]));
}
buf.copy_within(offset..offset + len, 0);
buf.truncate(len);
}
Ok(Zeroizing::new(vec![]))
})
} else {
Ok(buf)
}
}
/// Write certificate
@@ -280,12 +551,8 @@ pub(crate) fn write_certificate(
txn: &Transaction<'_>,
slot: SlotId,
data: Option<&[u8]>,
certinfo: u8,
max_size: usize,
certinfo: CertInfo,
) -> Result<(), Error> {
let mut buf = [0u8; CB_OBJ_MAX];
let mut offset = 0;
let object_id = slot.object_id();
if data.is_none() {
@@ -294,34 +561,12 @@ pub(crate) fn write_certificate(
let data = data.unwrap();
let mut req_len = 1 /* cert tag */ + 3 /* compression tag + data*/ + 2 /* lrc */;
req_len += set_length(&mut buf, data.len());
req_len += data.len();
if req_len < data.len() || req_len > max_size {
return Err(Error::SizeError);
}
buf[offset] = TAG_CERT;
offset += 1;
offset += set_length(&mut buf[offset..], data.len());
buf[offset..(offset + data.len())].copy_from_slice(&data);
offset += data.len();
let mut buf = [0u8; CB_OBJ_MAX];
let mut offset = Tlv::write(&mut buf, TAG_CERT, data)?;
// write compression info and LRC trailer
buf[offset] = TAG_CERT_COMPRESS;
buf[offset + 1] = 0x01;
buf[offset + 2] = if certinfo == YKPIV_CERTINFO_GZIP {
0x01
} else {
0x00
};
buf[offset + 3] = TAG_CERT_LRC;
buf[offset + 4] = 00;
offset += 5;
offset += Tlv::write(&mut buf[offset..], TAG_CERT_COMPRESS, &[certinfo.into()])?;
offset += Tlv::write(&mut buf[offset..], TAG_CERT_LRC, &[])?;
txn.save_object(object_id, &buf[..offset])
}
@@ -400,3 +645,31 @@ mod read_pki {
}
}
}
mod write_pki {
use cookie_factory::{SerializeFn, WriteContext};
use rsa::{BigUint, PublicKeyParts, RSAPublicKey};
use std::io::Write;
use x509::der::write::{der_integer, der_sequence};
/// Encodes a usize as an ASN.1 integer using DER.
fn der_integer_biguint<'a, W: Write + 'a>(num: &'a BigUint) -> impl SerializeFn<W> + 'a {
move |w: WriteContext<W>| der_integer(&num.to_bytes_be())(w)
}
/// From [RFC 8017](https://tools.ietf.org/html/rfc8017#appendix-A.1.1):
/// ```text
/// RSAPublicKey ::= SEQUENCE {
/// modulus INTEGER, -- n
/// publicExponent INTEGER -- e
/// }
/// ```
pub(super) fn rsa_pubkey<'a, W: Write + 'a>(
pubkey: &'a RSAPublicKey,
) -> impl SerializeFn<W> + 'a {
der_sequence((
der_integer_biguint(pubkey.n()),
der_integer_biguint(pubkey.e()),
))
}
}
+70 -23
View File
@@ -30,8 +30,34 @@
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
use crate::{consts::*, error::Error, yubikey::YubiKey};
use crate::{error::Error, yubikey::YubiKey};
use getrandom::getrandom;
use std::fmt::{self, Debug, Display};
use subtle_encoding::hex;
/// CHUID size
pub const CHUID_SIZE: usize = 59;
/// CARDID size
pub const CARDID_SIZE: usize = 16;
/// FASC-N component size
pub const FASCN_SIZE: usize = 25;
/// Expiration size
pub const EXPIRATION_SIZE: usize = 8;
/// FASC-N offset
const CHUID_FASCN_OFFS: usize = 2;
/// GUID offset
const CHUID_GUID_OFFS: usize = 29;
/// Expiration offset
const CHUID_EXPIRATION_OFFS: usize = 47;
/// CHUID Object ID
const OBJ_CHUID: u32 = 0x005f_c102;
/// Cardholder Unique Identifier (CHUID) Template
///
@@ -57,64 +83,85 @@ const CHUID_TMPL: &[u8] = &[
/// Cardholder Unique Identifier (CHUID) Card UUID/GUID value
#[derive(Copy, Clone, Debug)]
pub struct ChuidUuid(pub [u8; YKPIV_CARDID_SIZE]);
pub struct Uuid(pub [u8; CARDID_SIZE]);
impl Uuid {
/// Generate a random Cardholder Unique Identifier (CHUID) UUID
pub fn generate() -> Result<Self, Error> {
let mut id = [0u8; CARDID_SIZE];
getrandom(&mut id).map_err(|_| Error::RandomnessError)?;
Ok(Self(id))
}
}
/// Cardholder Unique Identifier (CHUID)
#[derive(Copy, Clone)]
pub struct CHUID(pub [u8; YKPIV_CHUID_SIZE]);
pub struct CHUID(pub [u8; CHUID_SIZE]);
impl CHUID {
/// Return FASC-N component of CHUID
pub fn fascn(&self) -> Result<[u8; YKPIV_FASCN_SIZE], Error> {
let mut fascn = [0u8; YKPIV_FASCN_SIZE];
fascn.copy_from_slice(&self.0[CHUID_FASCN_OFFS..(CHUID_FASCN_OFFS + YKPIV_FASCN_SIZE)]);
pub fn fascn(&self) -> Result<[u8; FASCN_SIZE], Error> {
let mut fascn = [0u8; FASCN_SIZE];
fascn.copy_from_slice(&self.0[CHUID_FASCN_OFFS..(CHUID_FASCN_OFFS + FASCN_SIZE)]);
Ok(fascn)
}
/// Return Card UUID/GUID component of CHUID
pub fn uuid(&self) -> Result<[u8; YKPIV_CARDID_SIZE], Error> {
let mut uuid = [0u8; YKPIV_CARDID_SIZE];
uuid.copy_from_slice(&self.0[CHUID_GUID_OFFS..(CHUID_GUID_OFFS + YKPIV_CARDID_SIZE)]);
pub fn uuid(&self) -> Result<[u8; CARDID_SIZE], Error> {
let mut uuid = [0u8; CARDID_SIZE];
uuid.copy_from_slice(&self.0[CHUID_GUID_OFFS..(CHUID_GUID_OFFS + CARDID_SIZE)]);
Ok(uuid)
}
/// Return expiration date component of CHUID
pub fn expiration(&self) -> Result<[u8; YKPIV_EXPIRATION_SIZE], Error> {
let mut expiration = [0u8; YKPIV_EXPIRATION_SIZE];
// TODO(tarcieri): parse expiration?
pub fn expiration(&self) -> Result<[u8; EXPIRATION_SIZE], Error> {
let mut expiration = [0u8; EXPIRATION_SIZE];
expiration.copy_from_slice(
&self.0[CHUID_EXPIRATION_OFFS..(CHUID_EXPIRATION_OFFS + YKPIV_EXPIRATION_SIZE)],
&self.0[CHUID_EXPIRATION_OFFS..(CHUID_EXPIRATION_OFFS + EXPIRATION_SIZE)],
);
Ok(expiration)
}
/// Generate a random Cardholder Unique Identifier (CHUID)
pub fn generate() -> Result<ChuidUuid, Error> {
let mut id = [0u8; YKPIV_CARDID_SIZE];
getrandom(&mut id).map_err(|_| Error::RandomnessError)?;
Ok(ChuidUuid(id))
}
/// Get Cardholder Unique Identifier (CHUID)
pub fn get(yubikey: &mut YubiKey) -> Result<CHUID, Error> {
let txn = yubikey.begin_transaction()?;
let response = txn.fetch_object(YKPIV_OBJ_CHUID)?;
let response = txn.fetch_object(OBJ_CHUID)?;
if response.len() != CHUID_TMPL.len() {
return Err(Error::GenericError);
}
let mut chuid = [0u8; YKPIV_CHUID_SIZE];
chuid.copy_from_slice(&response[0..YKPIV_CHUID_SIZE]);
let mut chuid = [0u8; CHUID_SIZE];
chuid.copy_from_slice(&response[0..CHUID_SIZE]);
let retval = CHUID { 0: chuid };
Ok(retval)
}
/// Set Cardholder Unique Identifier (CHUID)
#[cfg(feature = "untested")]
pub fn set(&self, yubikey: &mut YubiKey) -> Result<(), Error> {
let mut buf = CHUID_TMPL.to_vec();
buf[0..self.0.len()].copy_from_slice(&self.0);
let txn = yubikey.begin_transaction()?;
txn.save_object(YKPIV_OBJ_CHUID, &buf)
txn.save_object(OBJ_CHUID, &buf)
}
}
impl Display for CHUID {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"{}",
String::from_utf8(hex::encode(&self.0[..])).unwrap()
)
}
}
impl Debug for CHUID {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "CHUID({:?})", &self.0[..])
}
}
+25 -7
View File
@@ -30,12 +30,25 @@
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
use crate::{consts::*, error::Error, metadata, mgm::MgmType, yubikey::YubiKey};
use crate::{
error::Error,
metadata,
mgm::{MgmType, ADMIN_FLAGS_1_PROTECTED_MGM},
yubikey::{YubiKey, ADMIN_FLAGS_1_PUK_BLOCKED},
TAG_ADMIN, TAG_ADMIN_FLAGS_1, TAG_ADMIN_SALT, TAG_ADMIN_TIMESTAMP, TAG_PROTECTED,
TAG_PROTECTED_FLAGS_1, TAG_PROTECTED_MGM,
};
use log::error;
use std::convert::TryInto;
use std::{
convert::TryInto,
time::{Duration, SystemTime, UNIX_EPOCH},
};
const CB_ADMIN_TIMESTAMP: usize = 0x04;
const PROTECTED_FLAGS_1_PUK_NOBLOCK: u8 = 0x01;
/// Config
#[derive(Copy, Clone)]
#[derive(Copy, Clone, Debug)]
pub struct Config {
/// Protected data available
protected_data_available: bool,
@@ -47,7 +60,7 @@ pub struct Config {
puk_noblock_on_upgrade: bool,
/// PIN last changed
pin_last_changed: u32,
pin_last_changed: Option<SystemTime>,
/// MGM type
mgm_type: MgmType,
@@ -60,7 +73,7 @@ impl Config {
protected_data_available: false,
puk_blocked: false,
puk_noblock_on_upgrade: false,
pin_last_changed: 0,
pin_last_changed: None,
mgm_type: MgmType::Manual,
};
@@ -93,8 +106,13 @@ impl Config {
if item.len() != CB_ADMIN_TIMESTAMP {
error!("pin timestamp in admin metadata is an invalid size");
} else {
// TODO(tarcieri): double check this is little endian
config.pin_last_changed = u32::from_le_bytes(item.try_into().unwrap());
// TODO(tarcieri): double-check endianness is correct
let pin_last_changed = u32::from_le_bytes(item.try_into().unwrap());
if pin_last_changed != 0 {
config.pin_last_changed =
Some(UNIX_EPOCH + Duration::from_secs(pin_last_changed as u64));
}
}
}
}
-172
View File
@@ -1,172 +0,0 @@
//! Constant values
// TODO(tarcieri): refactor these into enums!
// Adapted from yubico-piv-tool:
// <https://github.com/Yubico/yubico-piv-tool/>
//
// Copyright (c) 2014-2016 Yubico AB
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
//
// * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following
// disclaimer in the documentation and/or other materials provided
// with the distribution.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
// TODO(tarcieri): document these!
#![allow(missing_docs, non_upper_case_globals)]
pub const szLOG_SOURCE: &str = "yubikey-piv.rs";
pub const ADMIN_FLAGS_1_PUK_BLOCKED: u8 = 0x01;
pub const ADMIN_FLAGS_1_PROTECTED_MGM: u8 = 0x02;
pub const CB_ADMIN_TIMESTAMP: usize = 0x04;
pub const CB_ADMIN_SALT: usize = 16;
pub const CB_ATR_MAX: usize = 33;
pub const CB_BUF_MAX_NEO: usize = 2048;
pub const CB_BUF_MAX_YK4: usize = 3072;
pub const CB_BUF_MAX: usize = CB_BUF_MAX_YK4;
pub const CB_ECC_POINTP256: usize = 65;
pub const CB_ECC_POINTP384: usize = 97;
pub const CB_OBJ_MAX_YK4: usize = CB_BUF_MAX_YK4 - 9;
pub const CB_OBJ_MAX: usize = CB_OBJ_MAX_YK4;
pub const CB_OBJ_MAX_NEO: usize = CB_BUF_MAX_NEO - 9;
pub const CB_OBJ_TAG_MIN: usize = 2; // 1 byte tag + 1 byte len
pub const CB_OBJ_TAG_MAX: usize = (CB_OBJ_TAG_MIN + 2); // 1 byte tag + 3 bytes len
pub const CB_PAGE: usize = 4096;
pub const CB_PIN_MAX: usize = 8;
pub const CCC_ID_OFFS: usize = 9;
pub const CHUID_FASCN_OFFS: usize = 2;
pub const CHUID_GUID_OFFS: usize = 29;
pub const CHUID_EXPIRATION_OFFS: usize = 47;
pub const CHREF_ACT_CHANGE_PIN: i32 = 0;
pub const CHREF_ACT_UNBLOCK_PIN: i32 = 1;
pub const CHREF_ACT_CHANGE_PUK: i32 = 2;
pub const CONTAINER_NAME_LEN: usize = 40;
pub const CONTAINER_REC_LEN: usize = (2 * CONTAINER_NAME_LEN) + 27; // 80 + 1 + 1 + 2 + 1 + 1 + 1 + 20
pub const DES_TYPE_3DES: u8 = 1;
pub const DES_LEN_DES: usize = 8;
pub const DES_LEN_3DES: usize = DES_LEN_DES * 3;
// device types
pub const DEVTYPE_UNKNOWN: u32 = 0x0000_0000;
pub const DEVTYPE_NEO: u32 = 0x4E45_0000; //"NE"
pub const DEVTYPE_YK: u32 = 0x594B_0000; //"YK"
pub const DEVTYPE_NEOr3: u32 = (DEVTYPE_NEO | 0x0000_7233); //"r3"
pub const DEVTYPE_YK4: u32 = (DEVTYPE_YK | 0x0000_0034); // "4"
pub const DEVYTPE_YK5: u32 = (DEVTYPE_YK | 0x0000_0035); // "5"
pub const ITER_MGM_PBKDF2: usize = 10000;
pub const PROTECTED_FLAGS_1_PUK_NOBLOCK: u8 = 0x01;
// sw is status words, see NIST special publication 800-73-4, section 5.6
pub const SW_SUCCESS: i32 = 0x9000;
pub const SW_ERR_SECURITY_STATUS: i32 = 0x6982;
pub const SW_ERR_AUTH_BLOCKED: i32 = 0x6983;
pub const SW_ERR_INCORRECT_PARAM: i32 = 0x6a80;
// this is a custom sw for yubikey
pub const SW_ERR_INCORRECT_SLOT: i32 = 0x6b00;
pub const SW_ERR_NOT_SUPPORTED: i32 = 0x6d00;
pub const TAG_CERT: u8 = 0x70;
pub const TAG_CERT_COMPRESS: u8 = 0x71;
pub const TAG_CERT_LRC: u8 = 0xFE;
pub const TAG_ADMIN: u8 = 0x80;
pub const TAG_ADMIN_FLAGS_1: u8 = 0x81;
pub const TAG_ADMIN_SALT: u8 = 0x82;
pub const TAG_ADMIN_TIMESTAMP: u8 = 0x83;
pub const TAG_PROTECTED: u8 = 0x88;
pub const TAG_PROTECTED_FLAGS_1: u8 = 0x81;
pub const TAG_PROTECTED_MGM: u8 = 0x89;
pub const TAG_MSCMAP: u8 = 0x81;
pub const TAG_MSROOTS_END: u8 = 0x82;
pub const TAG_MSROOTS_MID: u8 = 0x83;
pub const TAG_RSA_MODULUS: u8 = 0x81;
pub const TAG_RSA_EXP: u8 = 0x82;
pub const TAG_ECC_POINT: u8 = 0x86;
pub const YKPIV_ALGO_TAG: u8 = 0x80;
pub const YKPIV_ALGO_3DES: u8 = 0x03;
pub const YKPIV_ATR_NEO_R3: &[u8] = b";\xFC\x13\0\0\x811\xFE\x15YubikeyNEOr3\xE1\0";
pub const YKPIV_CHUID_SIZE: usize = 59;
pub const YKPIV_CARDID_SIZE: usize = 16;
pub const YKPIV_FASCN_SIZE: usize = 25;
pub const YKPIV_EXPIRATION_SIZE: usize = 8;
pub const YKPIV_CCCID_SIZE: usize = 14;
pub const YKPIV_CERTINFO_UNCOMPRESSED: u8 = 0;
pub const YKPIV_CERTINFO_GZIP: u8 = 1;
pub const YKPIV_KEY_CARDMGM: u8 = 0x9b;
pub const YKPIV_OBJ_CAPABILITY: u32 = 0x005f_c107;
pub const YKPIV_OBJ_CHUID: u32 = 0x005f_c102;
pub const YKPIV_OBJ_FINGERPRINTS: u32 = 0x005f_c103;
pub const YKPIV_OBJ_SECURITY: u32 = 0x005f_c106;
pub const YKPIV_OBJ_FACIAL: u32 = 0x005f_c108;
pub const YKPIV_OBJ_PRINTED: u32 = 0x005f_c109;
pub const YKPIV_OBJ_DISCOVERY: u32 = 0x7e;
pub const YKPIV_OBJ_KEY_HISTORY: u32 = 0x005f_c10c;
pub const YKPIV_OBJ_IRIS: u32 = 0x005f_c121;
// Internal object IDs
pub const YKPIV_OBJ_ADMIN_DATA: u32 = 0x005f_ff00;
pub const YKPIV_OBJ_MSCMAP: u32 = 0x005f_ff10;
pub const YKPIV_OBJ_MSROOTS1: u32 = 0x005f_ff11;
pub const YKPIV_OBJ_MSROOTS2: u32 = 0x005f_ff12;
pub const YKPIV_OBJ_MSROOTS3: u32 = 0x005f_ff13;
pub const YKPIV_OBJ_MSROOTS4: u32 = 0x005f_ff14;
pub const YKPIV_OBJ_MSROOTS5: u32 = 0x005f_ff15;
pub const YKPIV_PINPOLICY_TAG: u8 = 0xaa;
pub const YKPIV_PINPOLICY_DEFAULT: u8 = 0;
pub const YKPIV_PINPOLICY_NEVER: u8 = 1;
pub const YKPIV_PINPOLICY_ONCE: u8 = 2;
pub const YKPIV_PINPOLICY_ALWAYS: u8 = 3;
pub const YKPIV_TOUCHPOLICY_TAG: u8 = 0xab;
pub const YKPIV_TOUCHPOLICY_DEFAULT: u8 = 0;
pub const YKPIV_TOUCHPOLICY_NEVER: u8 = 1;
pub const YKPIV_TOUCHPOLICY_ALWAYS: u8 = 2;
pub const YKPIV_TOUCHPOLICY_CACHED: u8 = 3;
+5
View File
@@ -88,6 +88,9 @@ pub enum Error {
/// Not supported
NotSupported,
/// Not found
NotFound,
}
impl Error {
@@ -113,6 +116,7 @@ impl Error {
Error::ArgumentError => "YKPIV_ARGUMENT_ERROR",
Error::RangeError => "YKPIV_RANGE_ERROR",
Error::NotSupported => "YKPIV_NOT_SUPPORTED",
Error::NotFound => "<not found>",
}
}
@@ -135,6 +139,7 @@ impl Error {
Error::ArgumentError => "argument error",
Error::RangeError => "range error",
Error::NotSupported => "not supported",
Error::NotFound => "not found",
}
}
}
+414 -113
View File
@@ -40,16 +40,47 @@
use crate::{
apdu::{Ins, StatusWords},
certificate::{self, Certificate},
consts::*,
error::Error,
serialization::*,
settings,
yubikey::YubiKey,
Buffer, ObjectId,
ObjectId,
};
use log::{debug, error, warn};
use log::debug;
use std::convert::TryFrom;
#[cfg(feature = "untested")]
use crate::CB_OBJ_MAX;
use crate::{
certificate::PublicKeyInfo,
policy::{PinPolicy, TouchPolicy},
Buffer,
};
use elliptic_curve::sec1::EncodedPoint as EcPublicKey;
use log::{error, warn};
#[cfg(feature = "untested")]
use num_bigint::traits::ModInverse;
#[cfg(feature = "untested")]
use num_integer::Integer;
#[cfg(feature = "untested")]
use num_traits::{FromPrimitive, One};
use rsa::{BigUint, RSAPublicKey};
#[cfg(feature = "untested")]
use zeroize::Zeroizing;
const CB_ECC_POINTP256: usize = 65;
const CB_ECC_POINTP384: usize = 97;
const TAG_RSA_MODULUS: u8 = 0x81;
const TAG_RSA_EXP: u8 = 0x82;
const TAG_ECC_POINT: u8 = 0x86;
#[cfg(feature = "untested")]
const KEYDATA_LEN: usize = 1024;
#[cfg(feature = "untested")]
const KEYDATA_RSA_EXP: u64 = 65537;
/// Slot identifiers.
/// <https://developers.yubico.com/PIV/Introduction/Certificate_slots.html>
#[derive(Clone, Copy, Debug, PartialEq)]
@@ -120,6 +151,21 @@ impl From<SlotId> for u8 {
}
}
impl TryFrom<String> for SlotId {
type Error = Error;
fn try_from(s: String) -> Result<SlotId, Error> {
match s.as_ref() {
"9a" => Ok(SlotId::Authentication),
"9c" => Ok(SlotId::Signature),
"9d" => Ok(SlotId::KeyManagement),
"9e" => Ok(SlotId::CardAuthentication),
"f9" => Ok(SlotId::Attestation),
_ => RetiredSlotId::try_from(s).map(SlotId::Retired),
}
}
}
impl SlotId {
/// Returns the [`ObjectId`] that corresponds to a given [`SlotId`].
pub(crate) fn object_id(self) -> ObjectId {
@@ -190,6 +236,36 @@ impl TryFrom<u8> for RetiredSlotId {
}
}
impl TryFrom<String> for RetiredSlotId {
type Error = Error;
fn try_from(value: String) -> Result<Self, Self::Error> {
match value.as_ref() {
"82" => Ok(RetiredSlotId::R1),
"83" => Ok(RetiredSlotId::R2),
"84" => Ok(RetiredSlotId::R3),
"85" => Ok(RetiredSlotId::R4),
"86" => Ok(RetiredSlotId::R5),
"87" => Ok(RetiredSlotId::R6),
"88" => Ok(RetiredSlotId::R7),
"89" => Ok(RetiredSlotId::R8),
"8a" => Ok(RetiredSlotId::R9),
"8b" => Ok(RetiredSlotId::R10),
"8c" => Ok(RetiredSlotId::R11),
"8d" => Ok(RetiredSlotId::R12),
"8e" => Ok(RetiredSlotId::R13),
"8f" => Ok(RetiredSlotId::R14),
"90" => Ok(RetiredSlotId::R15),
"91" => Ok(RetiredSlotId::R16),
"92" => Ok(RetiredSlotId::R17),
"93" => Ok(RetiredSlotId::R18),
"94" => Ok(RetiredSlotId::R19),
"95" => Ok(RetiredSlotId::R20),
_ => Err(Error::InvalidObject),
}
}
}
impl From<RetiredSlotId> for u8 {
fn from(slot: RetiredSlotId) -> u8 {
match slot {
@@ -311,6 +387,31 @@ impl From<AlgorithmId> for u8 {
}
}
impl AlgorithmId {
/// Writes the `AlgorithmId` in the format the YubiKey expects during key generation.
pub(crate) fn write(self, buf: &mut [u8]) -> Result<usize, Error> {
Tlv::write(buf, 0x80, &[self.into()])
}
#[cfg(feature = "untested")]
fn get_elem_len(self) -> usize {
match self {
AlgorithmId::Rsa1024 => 64,
AlgorithmId::Rsa2048 => 128,
AlgorithmId::EccP256 => 32,
AlgorithmId::EccP384 => 48,
}
}
#[cfg(feature = "untested")]
fn get_param_tag(self) -> u8 {
match self {
AlgorithmId::Rsa1024 | AlgorithmId::Rsa2048 => 0x01,
AlgorithmId::EccP256 | AlgorithmId::EccP384 => 0x6,
}
}
}
/// PIV cryptographic keys stored in a YubiKey
#[derive(Clone, Debug)]
pub struct Key {
@@ -337,7 +438,7 @@ impl Key {
};
if !buf.is_empty() {
let cert = Certificate::new(buf)?;
let cert = Certificate::from_bytes(buf)?;
keys.push(Key { slot, cert });
}
}
@@ -356,69 +457,31 @@ impl Key {
}
}
// Keygen messages
// TODO(tarcieri): extract these into an I18N-handling type?
const SZ_SETTING_ROCA: &str = "Enable_Unsafe_Keygen_ROCA";
const SZ_ROCA_ALLOW_USER: &str =
"was permitted by an end-user configuration setting, but is not recommended.";
const SZ_ROCA_ALLOW_ADMIN: &str =
"was permitted by an administrator configuration setting, but is not recommended.";
const SZ_ROCA_BLOCK_USER: &str = "was blocked due to an end-user configuration setting.";
const SZ_ROCA_BLOCK_ADMIN: &str = "was blocked due to an administrator configuration setting.";
const SZ_ROCA_DEFAULT: &str = "was permitted by default, but is not recommended. The default behavior will change in a future Yubico release.";
/// Information about a generated key
// TODO(tarcieri): this could use some more work
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum GeneratedKey {
/// RSA keys
Rsa {
/// RSA algorithm
algorithm: AlgorithmId,
/// Modulus
modulus: Vec<u8>,
/// Exponent
exp: Vec<u8>,
},
/// ECC keys
Ecc {
/// ECC algorithm
algorithm: AlgorithmId,
/// Public curve point (i.e. public key)
point: Vec<u8>,
},
}
impl GeneratedKey {
/// Get the algorithm
pub fn algorithm(&self) -> AlgorithmId {
*match self {
GeneratedKey::Rsa { algorithm, .. } => algorithm,
GeneratedKey::Ecc { algorithm, .. } => algorithm,
}
}
}
/// Generate key
#[allow(clippy::cognitive_complexity)]
pub fn generate(
yubikey: &mut YubiKey,
slot: SlotId,
algorithm: AlgorithmId,
pin_policy: u8,
touch_policy: u8,
) -> Result<GeneratedKey, Error> {
let mut in_data = [0u8; 11];
let mut templ = [0, Ins::GenerateAsymmetric.code(), 0, 0];
pin_policy: PinPolicy,
touch_policy: TouchPolicy,
) -> Result<PublicKeyInfo, Error> {
// Keygen messages
// TODO(tarcieri): extract these into an I18N-handling type?
const SZ_SETTING_ROCA: &str = "Enable_Unsafe_Keygen_ROCA";
const SZ_ROCA_ALLOW_USER: &str =
"was permitted by an end-user configuration setting, but is not recommended.";
const SZ_ROCA_ALLOW_ADMIN: &str =
"was permitted by an administrator configuration setting, but is not recommended.";
const SZ_ROCA_BLOCK_USER: &str = "was blocked due to an end-user configuration setting.";
const SZ_ROCA_BLOCK_ADMIN: &str = "was blocked due to an administrator configuration setting.";
const SZ_ROCA_DEFAULT: &str = "was permitted by default, but is not recommended. The default behavior will change in a future Yubico release.";
let setting_roca: settings::BoolValue;
match algorithm {
AlgorithmId::Rsa1024 | AlgorithmId::Rsa2048 => {
if yubikey.device_model() == DEVTYPE_YK4
&& yubikey.version.major == 4
if yubikey.version.major == 4
&& (yubikey.version.minor < 3
|| yubikey.version.minor == 3 && (yubikey.version.patch < 5))
{
@@ -460,32 +523,20 @@ pub fn generate(
let txn = yubikey.begin_transaction()?;
templ[3] = slot.into();
let templ = [0, Ins::GenerateAsymmetric.code(), 0, slot.into()];
let mut offset = 5;
in_data[..offset].copy_from_slice(&[
0xac,
3, // length sans this 2-byte header
YKPIV_ALGO_TAG,
1,
algorithm.into(),
]);
let mut in_data = [0u8; 11];
let mut offset = Tlv::write_as(&mut in_data, 0xac, 3, |buf| {
assert_eq!(algorithm.write(buf).expect("large enough"), 3);
})?;
if in_data[4] == 0 {
error!("unexpected algorithm");
return Err(Error::AlgorithmError);
}
let pin_len = pin_policy.write(&mut in_data[offset..])?;
in_data[1] += pin_len as u8;
offset += pin_len;
if pin_policy != YKPIV_PINPOLICY_DEFAULT {
in_data[1] += 3;
in_data[offset..(offset + 3)].copy_from_slice(&[YKPIV_PINPOLICY_TAG, 1, pin_policy]);
offset += 3;
}
if touch_policy != YKPIV_TOUCHPOLICY_DEFAULT {
in_data[1] += 3;
in_data[offset..(offset + 3)].copy_from_slice(&[YKPIV_TOUCHPOLICY_TAG, 1, touch_policy]);
}
let touch_len = touch_policy.write(&mut in_data[offset..])?;
in_data[1] += touch_len as u8;
offset += touch_len;
let response = txn.transfer_data(&templ, &in_data[..offset], 1024)?;
@@ -498,12 +549,12 @@ pub fn generate(
return Err(Error::KeyError);
}
StatusWords::IncorrectParamError => {
if pin_policy != YKPIV_PINPOLICY_DEFAULT {
error!("{} (pin policy not supported?)", err_msg);
} else if touch_policy != YKPIV_TOUCHPOLICY_DEFAULT {
error!("{} (touch policy not supported?)", err_msg);
} else {
error!("{} (algorithm not supported?)", err_msg);
match pin_policy {
PinPolicy::Default => match touch_policy {
TouchPolicy::Default => error!("{} (algorithm not supported?)", err_msg),
_ => error!("{} (touch policy not supported?)", err_msg),
},
_ => error!("{} (pin policy not supported?)", err_msg),
}
return Err(Error::AlgorithmError);
@@ -513,45 +564,73 @@ pub fn generate(
return Err(Error::AuthenticationError);
}
other => {
error!("{} (error {:x})", err_msg, other.code());
error!("{} (error {:?})", err_msg, other);
return Err(Error::GenericError);
}
}
}
let data = Buffer::new(response.data().into());
// TODO(str4d): Response is wrapped in an ASN.1 TLV:
//
// 0x7f 0x49 -> Application | Constructed | 0x49
match algorithm {
AlgorithmId::Rsa1024 | AlgorithmId::Rsa2048 => {
let mut offset = 5;
let mut len = 0;
// It appears that the inner application-specific value returned by the
// YubiKey is constructed such that RSA pubkeys can be parsed in two ways:
//
// - Use a full ASN.1 parser on the entire datastructure:
//
// RSA 1024:
// [127, 73, 129, 136, 129, 129, 128, [ 128 octets ], 130, 3, 1, 0, 1]
// | tag | len:136 |0x81| len:128 | modulus |0x82|len3| exp |
//
// RSA 2048:
// [127, 73, 130, 1, 9, 129, 130, 1, 0, [ 256 octets ], 130, 3, 1, 0, 1]
// | tag | len:265 |0x81| len:256 | modulus |0x82|len3| exp |
//
// - Skip the first 5 bytes and use crate::serialize::get_length during TLV
// parsing (which treats 128 as a single-byte definite length instead of an
// indefinite length):
//
// RSA 1024:
// [127, 73, 129, 136, 129, 129, 128, [ 128 octets ], 130, 3, 1, 0, 1]
// | |0x81|len128| modulus |0x82|len3| exp |
//
// RSA 2048:
// [127, 73, 130, 1, 9, 129, 130, 1, 0, [ 256 octets ], 130, 3, 1, 0, 1]
// | |0x81| len:256 | modulus |0x82|len3| exp |
//
// Because of the above, treat this for now as a 2-byte ASN.1 tag with a
// 3-byte length.
let data = &response.data()[5..];
if data[offset] != TAG_RSA_MODULUS {
let (data, modulus_tlv) = Tlv::parse(data)?;
if modulus_tlv.tag != TAG_RSA_MODULUS {
error!("Failed to parse public key structure (modulus)");
return Err(Error::ParseError);
}
let modulus = modulus_tlv.value.to_vec();
offset += 1;
offset += get_length(&data[offset..], &mut len);
let modulus = data[offset..(offset + len)].to_vec();
offset += len;
if data[offset] != TAG_RSA_EXP {
let (_, exp_tlv) = Tlv::parse(data)?;
if exp_tlv.tag != TAG_RSA_EXP {
error!("failed to parse public key structure (public exponent)");
return Err(Error::ParseError);
}
let exp = exp_tlv.value.to_vec();
offset += 1;
offset += get_length(&data[offset..], &mut len);
let exp = data[offset..(offset + len)].to_vec();
Ok(GeneratedKey::Rsa {
Ok(PublicKeyInfo::Rsa {
algorithm,
modulus,
exp,
pubkey: RSAPublicKey::new(
BigUint::from_bytes_be(&modulus),
BigUint::from_bytes_be(&exp),
)
.map_err(|_| Error::InvalidObject)?,
})
}
AlgorithmId::EccP256 | AlgorithmId::EccP384 => {
let mut offset = 3;
// 2-byte ASN.1 tag, 1-byte length (because all supported EC pubkey lengths
// are shorter than 128 bytes, fitting into a definite short ASN.1 length).
let data = &response.data()[3..];
let len = if let AlgorithmId::EccP256 = algorithm {
CB_ECC_POINTP256
@@ -559,23 +638,245 @@ pub fn generate(
CB_ECC_POINTP384
};
if data[offset] != TAG_ECC_POINT {
let (_, tlv) = Tlv::parse(data)?;
if tlv.tag != TAG_ECC_POINT {
error!("failed to parse public key structure");
return Err(Error::ParseError);
}
offset += 1;
// the curve point should always be determined by the curve
let len_byte = data[offset];
offset += 1;
if len_byte as usize != len {
if tlv.value.len() != len {
error!("unexpected length");
return Err(Error::AlgorithmError);
}
let point = data[offset..(offset + len)].to_vec();
Ok(GeneratedKey::Ecc { algorithm, point })
let point = tlv.value.to_vec();
if let AlgorithmId::EccP256 = algorithm {
EcPublicKey::from_bytes(point).map(PublicKeyInfo::EcP256)
} else {
EcPublicKey::from_bytes(point).map(PublicKeyInfo::EcP384)
}
.map_err(|_| Error::InvalidObject)
}
}
}
#[cfg(feature = "untested")]
fn write_key(
yubikey: &mut YubiKey,
slot: SlotId,
params: Vec<&[u8]>,
pin_policy: PinPolicy,
touch_policy: TouchPolicy,
algorithm: AlgorithmId,
) -> Result<(), Error> {
let mut key_data = Buffer::new(vec![0u8; KEYDATA_LEN]);
let templ = [0, Ins::ImportKey.code(), algorithm.into(), slot.into()];
let mut offset = 0;
let elem_len = algorithm.get_elem_len();
let param_tag = algorithm.get_param_tag();
for (i, param) in params.into_iter().enumerate() {
offset += Tlv::write_as(
&mut key_data[offset..],
param_tag + (i as u8),
elem_len,
|buf| {
let padding = elem_len - param.len();
for b in &mut buf[..padding] {
*b = 0;
}
buf[padding..].copy_from_slice(param);
},
)?;
}
offset += pin_policy.write(&mut key_data[offset..])?;
offset += touch_policy.write(&mut key_data[offset..])?;
let txn = yubikey.begin_transaction()?;
let status_words = txn
.transfer_data(&templ, &key_data[..offset], 256)?
.status_words();
match status_words {
StatusWords::Success => Ok(()),
StatusWords::SecurityStatusError => Err(Error::AuthenticationError),
_ => Err(Error::GenericError),
}
}
/// The key data that makes up an RSA key.
#[cfg(feature = "untested")]
pub struct RsaKeyData {
/// The secret prime `p`.
p: Buffer,
/// The secret prime, `q`.
q: Buffer,
/// D mod (P-1)
dp: Buffer,
/// D mod (Q-1)
dq: Buffer,
/// Q^-1 mod P
qinv: Buffer,
}
#[cfg(feature = "untested")]
impl RsaKeyData {
/// Generates a new RSA key data set from two randomly generated, secret, primes.
///
/// Panics if `secret_p` or `secret_q` are invalid primes.
pub fn new(secret_p: &[u8], secret_q: &[u8]) -> Self {
let p = BigUint::from_bytes_be(secret_p);
let q = BigUint::from_bytes_be(secret_q);
let totient = {
let p_t = &p - BigUint::one();
let q_t = &p - BigUint::one();
p_t.lcm(&q_t)
};
let exp = BigUint::from_u64(KEYDATA_RSA_EXP).unwrap();
let d = exp.mod_inverse(&totient).unwrap();
let d = d.to_biguint().unwrap();
// We calculate the optimization values ahead of time, instead of making the user
// do so.
let dp = &d % (&p - BigUint::one());
let dq = &d % (&q - BigUint::one());
let qinv = q.clone().mod_inverse(&p).unwrap();
let (_, qinv) = qinv.to_bytes_be();
RsaKeyData {
p: Zeroizing::new(p.to_bytes_be()),
q: Zeroizing::new(q.to_bytes_be()),
dp: Zeroizing::new(dp.to_bytes_be()),
dq: Zeroizing::new(dq.to_bytes_be()),
qinv: Zeroizing::new(qinv),
}
}
fn total_len(&self) -> usize {
self.p.len() + self.q.len() + self.dp.len() + self.qinv.len()
}
}
/// Imports a private RSA encryption or signing key into the YubiKey.
///
/// Errors if `algorithm` isn't `AlgorithmId::Rsa1024` or `AlgorithmId::Rsa2048`.
#[cfg(feature = "untested")]
pub fn import_rsa_key(
yubikey: &mut YubiKey,
slot: SlotId,
algorithm: AlgorithmId,
key_data: RsaKeyData,
touch_policy: TouchPolicy,
pin_policy: PinPolicy,
) -> Result<(), Error> {
match algorithm {
AlgorithmId::Rsa1024 | AlgorithmId::Rsa2048 => (),
_ => return Err(Error::AlgorithmError),
}
if key_data.total_len() > KEYDATA_LEN {
return Err(Error::SizeError);
}
let params = vec![
key_data.p.as_slice(),
key_data.q.as_slice(),
key_data.dp.as_slice(),
key_data.dq.as_slice(),
key_data.qinv.as_slice(),
];
write_key(yubikey, slot, params, pin_policy, touch_policy, algorithm)?;
Ok(())
}
/// Imports a private ECC encryption or signing key into the YubiKey.
///
/// Errors if `algorithm` isn't `AlgorithmId::EccP256` or ` AlgorithmId::EccP384`.
#[cfg(feature = "untested")]
pub fn import_ecc_key(
yubikey: &mut YubiKey,
slot: SlotId,
algorithm: AlgorithmId,
key_data: &[u8],
touch_policy: TouchPolicy,
pin_policy: PinPolicy,
) -> Result<(), Error> {
match algorithm {
AlgorithmId::EccP256 | AlgorithmId::EccP384 => (),
_ => return Err(Error::AlgorithmError),
}
if key_data.len() > KEYDATA_LEN {
return Err(Error::SizeError);
}
let params = vec![key_data];
write_key(yubikey, slot, params, pin_policy, touch_policy, algorithm)?;
Ok(())
}
/// Generate an attestation certificate for a stored key.
/// <https://developers.yubico.com/PIV/Introduction/PIV_attestation.html>
#[cfg(feature = "untested")]
pub fn attest(yubikey: &mut YubiKey, key: SlotId) -> Result<Buffer, Error> {
let templ = [0, Ins::Attest.code(), key.into(), 0];
let txn = yubikey.begin_transaction()?;
let response = txn.transfer_data(&templ, &[], CB_OBJ_MAX)?;
if !response.is_success() {
if response.status_words() == StatusWords::NotSupportedError {
return Err(Error::NotSupported);
} else {
return Err(Error::GenericError);
}
}
if response.data()[0] != 0x30 {
return Err(Error::GenericError);
}
Ok(Buffer::new(response.data().into()))
}
/// Sign data using a PIV key
pub fn sign_data(
yubikey: &mut YubiKey,
raw_in: &[u8],
algorithm: AlgorithmId,
key: SlotId,
) -> Result<Buffer, Error> {
let txn = yubikey.begin_transaction()?;
// don't attempt to reselect in crypt operations to avoid problems with PIN_ALWAYS
txn.authenticated_command(raw_in, algorithm, key, false)
}
/// Decrypt data using a PIV key
#[cfg(feature = "untested")]
pub fn decrypt_data(
yubikey: &mut YubiKey,
input: &[u8],
algorithm: AlgorithmId,
key: SlotId,
) -> Result<Buffer, Error> {
let txn = yubikey.begin_transaction()?;
// don't attempt to reselect in crypt operations to avoid problems with PIN_ALWAYS
txn.authenticated_command(input, algorithm, key, true)
}
+43 -21
View File
@@ -14,15 +14,15 @@
//!
//! ## Minimum Supported Rust Version
//!
//! Rust 1.39+
//! Rust 1.44+
//!
//! ## Supported YubiKeys
//!
//! - [YubiKey NEO] series
//! - [YubiKey 4] series
//! - [YubiKey 5] series
//!
//! NOTE: Nano and USB-C variants of the above are also supported
//! NOTE: Nano and USB-C variants of the above are also supported.
//! Pre-YK4 [YubiKey NEO] series is **NOT** supported.
//!
//! ## Supported Algorithms
//!
@@ -122,8 +122,8 @@
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#![doc(
html_logo_url = "https://raw.githubusercontent.com/tarcieri/yubikey-piv.rs/develop/img/logo.png",
html_root_url = "https://docs.rs/yubikey-piv/0.0.3"
html_logo_url = "https://raw.githubusercontent.com/iqlusioninc/yubikey-piv.rs/develop/img/logo.png",
html_root_url = "https://docs.rs/yubikey-piv/0.1.0"
)]
#![forbid(unsafe_code)]
#![warn(
@@ -136,41 +136,63 @@
)]
mod apdu;
#[cfg(feature = "untested")]
pub mod cccid;
#[cfg(feature = "untested")]
pub mod certificate;
#[cfg(feature = "untested")]
pub mod chuid;
#[cfg(feature = "untested")]
pub mod config;
pub mod consts;
#[cfg(feature = "untested")]
pub mod container;
pub mod error;
#[cfg(feature = "untested")]
pub mod key;
#[cfg(feature = "untested")]
mod metadata;
#[cfg(feature = "untested")]
pub mod mgm;
#[cfg(feature = "untested")]
pub mod mscmap;
#[cfg(feature = "untested")]
pub mod msroots;
pub mod policy;
pub mod readers;
#[cfg(feature = "untested")]
mod serialization;
#[cfg(feature = "untested")]
pub mod settings;
mod transaction;
pub mod yubikey;
pub use self::{readers::Readers, yubikey::YubiKey};
#[cfg(feature = "untested")]
pub use self::{key::Key, mgm::MgmKey};
pub use self::{
error::Error,
key::Key,
mgm::MgmKey,
readers::Readers,
yubikey::{Serial, YubiKey},
};
/// Object identifiers
pub type ObjectId = u32;
/// Buffer type (self-zeroizing byte vector)
pub(crate) type Buffer = zeroize::Zeroizing<Vec<u8>>;
/// YubiKey max buffer size
pub(crate) const CB_BUF_MAX: usize = 3072;
/// YubiKey max object size
pub(crate) const CB_OBJ_MAX: usize = CB_BUF_MAX - 9;
pub(crate) const CB_OBJ_TAG_MIN: usize = 2; // 1 byte tag + 1 byte len
#[cfg(feature = "untested")]
pub(crate) const CB_OBJ_TAG_MAX: usize = CB_OBJ_TAG_MIN + 2; // 1 byte tag + 3 bytes len
pub(crate) const TAG_ADMIN: u8 = 0x80;
pub(crate) const TAG_ADMIN_FLAGS_1: u8 = 0x81;
pub(crate) const TAG_ADMIN_SALT: u8 = 0x82;
pub(crate) const TAG_ADMIN_TIMESTAMP: u8 = 0x83;
pub(crate) const TAG_PROTECTED: u8 = 0x88;
pub(crate) const TAG_PROTECTED_FLAGS_1: u8 = 0x81;
pub(crate) const TAG_PROTECTED_MGM: u8 = 0x89;
/// PIV Applet ID
pub(crate) const PIV_AID: [u8; 5] = [0xa0, 0x00, 0x00, 0x03, 0x08];
/// MGMT Applet ID.
/// <https://developers.yubico.com/PIV/Introduction/Admin_access.html>
#[cfg(feature = "untested")]
pub(crate) const MGMT_AID: [u8; 8] = [0xa0, 0x00, 0x00, 0x05, 0x27, 0x47, 0x11, 0x17];
/// YubiKey OTP Applet ID. Needed to query serial on YK4.
pub(crate) const YK_AID: [u8; 8] = [0xa0, 0x00, 0x00, 0x05, 0x27, 0x20, 0x01, 0x01];
+34 -76
View File
@@ -30,40 +30,36 @@
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
use crate::{consts::*, error::Error, serialization::*, transaction::Transaction, Buffer};
use crate::{
error::Error, serialization::*, transaction::Transaction, Buffer, TAG_ADMIN, TAG_PROTECTED,
};
#[cfg(feature = "untested")]
use crate::{CB_OBJ_MAX, CB_OBJ_TAG_MAX};
#[cfg(feature = "untested")]
use zeroize::Zeroizing;
pub const OBJ_ADMIN_DATA: u32 = 0x005f_ff00;
pub const OBJ_PRINTED: u32 = 0x005f_c109;
/// Get metadata item
pub(crate) fn get_item(data: &[u8], tag: u8) -> Result<&[u8], Error> {
let mut cb_temp: usize = 0;
let mut offset = 0;
pub(crate) fn get_item(mut data: &[u8], tag: u8) -> Result<&[u8], Error> {
while !data.is_empty() {
let (remaining, tlv) = Tlv::parse(data)?;
data = remaining;
while offset < data.len() {
let tag_temp = data[offset];
offset += 1;
if !has_valid_length(&data[offset..], data.len() - 1) {
return Err(Error::SizeError);
}
offset += get_length(&data[offset..], &mut cb_temp);
if tag_temp == tag {
if tlv.tag == tag {
// found tag
break;
return Ok(tlv.value);
}
}
offset += cb_temp;
}
if offset < data.len() {
Ok(&data[offset..offset + cb_temp])
} else {
Err(Error::GenericError)
}
}
/// Set metadata item
#[cfg(feature = "untested")]
pub(crate) fn set_item(
data: &mut [u8],
pcb_data: &mut usize,
@@ -99,20 +95,7 @@ pub(crate) fn set_item(
}
// We did not find an existing tag, append
offset = *pcb_data;
cb_len = get_length_size(cb_item);
// If length would cause buffer overflow, return error
if (*pcb_data + cb_len + cb_item) > cb_data_max {
return Err(Error::GenericError);
}
data[offset] = tag;
offset += 1;
offset += set_length(&mut data[offset..], cb_item);
data[offset..offset + cb_item].copy_from_slice(p_item);
*pcb_data += 1 + cb_len + cb_item;
*pcb_data += Tlv::write(&mut data[*pcb_data..], tag, p_item)?;
return Ok(());
}
@@ -153,7 +136,7 @@ pub(crate) fn set_item(
// Re-encode item and insert
if cb_item != 0 {
offset -= cb_len;
offset += set_length(&mut data[offset..], cb_item);
offset += set_length(&mut data[offset..], cb_item)?;
data[offset..offset + cb_item].copy_from_slice(p_item);
}
@@ -163,49 +146,25 @@ pub(crate) fn set_item(
/// Read metadata
pub(crate) fn read(txn: &Transaction<'_>, tag: u8) -> Result<Buffer, Error> {
let obj_id = match tag {
TAG_ADMIN => YKPIV_OBJ_ADMIN_DATA,
TAG_PROTECTED => YKPIV_OBJ_PRINTED,
TAG_ADMIN => OBJ_ADMIN_DATA,
TAG_PROTECTED => OBJ_PRINTED,
_ => return Err(Error::InvalidObject),
};
let mut data = txn.fetch_object(obj_id)?;
if data.len() < CB_OBJ_TAG_MIN {
return Err(Error::GenericError);
}
if tag != data[0] {
return Err(Error::GenericError);
}
let mut pcb_data = 0;
let offset = 1 + get_length(&data[1..], &mut pcb_data);
if pcb_data > data.len() - offset {
return Err(Error::GenericError);
}
data.copy_within(offset..offset + pcb_data, 0);
data.truncate(pcb_data);
Ok(data)
let data = txn.fetch_object(obj_id)?;
Tlv::parse_single(data, tag)
}
/// Write metadata
pub(crate) fn write(
txn: &Transaction<'_>,
tag: u8,
data: &[u8],
max_size: usize,
) -> Result<(), Error> {
let mut buf = Zeroizing::new(vec![0u8; CB_OBJ_MAX]);
if data.len() > max_size - CB_OBJ_TAG_MAX {
#[cfg(feature = "untested")]
pub(crate) fn write(txn: &Transaction<'_>, tag: u8, data: &[u8]) -> Result<(), Error> {
if data.len() > CB_OBJ_MAX - CB_OBJ_TAG_MAX {
return Err(Error::GenericError);
}
let obj_id = match tag {
TAG_ADMIN => YKPIV_OBJ_ADMIN_DATA,
TAG_PROTECTED => YKPIV_OBJ_PRINTED,
TAG_ADMIN => OBJ_ADMIN_DATA,
TAG_PROTECTED => OBJ_PRINTED,
_ => return Err(Error::InvalidObject),
};
@@ -214,15 +173,14 @@ pub(crate) fn write(
return txn.save_object(obj_id, &[]);
}
buf[0] = tag;
let mut offset = set_length(&mut buf[1..], data.len());
buf[offset..(offset + data.len())].copy_from_slice(data);
offset += data.len();
let mut buf = Zeroizing::new(vec![0u8; CB_OBJ_MAX]);
let len = Tlv::write(&mut buf, tag, data)?;
txn.save_object(obj_id, &buf[..offset])
txn.save_object(obj_id, &buf[..len])
}
/// Get the size of a length tag for the given length
#[cfg(feature = "untested")]
fn get_length_size(length: usize) -> usize {
if length < 0x80 {
1
+39 -13
View File
@@ -30,27 +30,50 @@
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
use crate::{consts::*, error::Error, metadata, yubikey::YubiKey};
use des::{
block_cipher_trait::{generic_array::GenericArray, BlockCipher},
TdesEde3,
};
use crate::error::Error;
use getrandom::getrandom;
use hmac::Hmac;
use log::error;
use pbkdf2::pbkdf2;
use sha1::Sha1;
use std::convert::{TryFrom, TryInto};
use zeroize::{Zeroize, Zeroizing};
#[cfg(feature = "untested")]
use crate::{
metadata, yubikey::YubiKey, CB_BUF_MAX, CB_OBJ_MAX, TAG_ADMIN, TAG_ADMIN_FLAGS_1,
TAG_ADMIN_SALT, TAG_PROTECTED, TAG_PROTECTED_MGM,
};
use des::{
cipher::{generic_array::GenericArray, BlockCipher, NewBlockCipher},
TdesEde3,
};
#[cfg(feature = "untested")]
use hmac::Hmac;
#[cfg(feature = "untested")]
use pbkdf2::pbkdf2;
#[cfg(feature = "untested")]
use sha1::Sha1;
pub(crate) const ADMIN_FLAGS_1_PROTECTED_MGM: u8 = 0x02;
#[cfg(feature = "untested")]
const CB_ADMIN_SALT: usize = 16;
/// Default MGM key configured on all YubiKeys
const DEFAULT_MGM_KEY: [u8; DES_LEN_3DES] = [
1, 2, 3, 4, 5, 6, 7, 8, 1, 2, 3, 4, 5, 6, 7, 8, 1, 2, 3, 4, 5, 6, 7, 8,
];
/// Size of a DES key
const DES_LEN_DES: usize = 8;
/// Size of a 3DES key
pub(crate) const DES_LEN_3DES: usize = DES_LEN_DES * 3;
/// Number of PBKDF2 iterations to use when deriving from a password
#[cfg(feature = "untested")]
const ITER_MGM_PBKDF2: u32 = 10000;
/// Management Key (MGM) key types (manual/derived/protected)
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
#[allow(non_camel_case_types)]
pub enum MgmType {
/// Manual
Manual = 0,
@@ -107,6 +130,7 @@ impl MgmKey {
}
/// Get derived management key (MGM)
#[cfg(feature = "untested")]
pub fn get_derived(yubikey: &mut YubiKey, pin: &[u8]) -> Result<Self, Error> {
let txn = yubikey.begin_transaction()?;
@@ -131,6 +155,7 @@ impl MgmKey {
}
/// Get protected management key (MGM)
#[cfg(feature = "untested")]
pub fn get_protected(yubikey: &mut YubiKey) -> Result<Self, Error> {
let txn = yubikey.begin_transaction()?;
@@ -158,16 +183,17 @@ impl MgmKey {
}
/// Set the management key (MGM)
#[cfg(feature = "untested")]
pub fn set(&self, yubikey: &mut YubiKey, touch: Option<u8>) -> Result<(), Error> {
let txn = yubikey.begin_transaction()?;
txn.set_mgm_key(&self, touch)
}
/// Set protected management key (MGM)
#[cfg(feature = "untested")]
pub fn set_protected(&self, yubikey: &mut YubiKey) -> Result<(), Error> {
let mut data = Zeroizing::new(vec![0u8; CB_BUF_MAX]);
let max_size = yubikey.obj_size_max();
let txn = yubikey.begin_transaction()?;
txn.set_mgm_key(self, None).map_err(|e| {
@@ -200,7 +226,7 @@ impl MgmKey {
) {
error!("could not set protected mgm item, err = {:?}", e);
} else {
metadata::write(&txn, TAG_PROTECTED, &data, max_size).map_err(|e| {
metadata::write(&txn, TAG_PROTECTED, &data).map_err(|e| {
error!("could not write protected data, err = {:?}", e);
e
})?;
@@ -247,7 +273,7 @@ impl MgmKey {
&flags_1,
) {
error!("could not set admin flags item, err = {}", e);
} else if let Err(e) = metadata::write(&txn, TAG_ADMIN, &data[..cb_data], max_size) {
} else if let Err(e) = metadata::write(&txn, TAG_ADMIN, &data[..cb_data]) {
error!("could not write admin data, err = {}", e);
}
@@ -268,7 +294,7 @@ impl MgmKey {
pub(crate) fn decrypt(&self, input: &[u8; DES_LEN_DES]) -> [u8; DES_LEN_DES] {
let mut output = input.to_owned();
TdesEde3::new(GenericArray::from_slice(&self.0))
.encrypt_block(GenericArray::from_mut_slice(&mut output));
.decrypt_block(GenericArray::from_mut_slice(&mut output));
output
}
}
+24 -33
View File
@@ -33,13 +33,23 @@
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
use crate::{consts::*, error::Error, key::SlotId, serialization::*, yubikey::YubiKey};
use crate::{error::Error, key::SlotId, serialization::*, yubikey::YubiKey, CB_OBJ_MAX};
use log::error;
use std::{
convert::{TryFrom, TryInto},
fmt::{self, Debug},
};
/// Container name length
const CONTAINER_NAME_LEN: usize = 40;
/// Container record length: 27 = 80 + 1 + 1 + 2 + 1 + 1 + 1 + 20
const CONTAINER_REC_LEN: usize = (2 * CONTAINER_NAME_LEN) + 27;
const OBJ_MSCMAP: u32 = 0x005f_ff10;
const TAG_MSCMAP: u8 = 0x81;
/// MS Container Map(?) Records
#[derive(Copy, Clone)]
pub struct Container {
@@ -72,28 +82,23 @@ impl Container {
/// Read MS Container Map records
pub fn read_mscmap(yubikey: &mut YubiKey) -> Result<Vec<Self>, Error> {
let txn = yubikey.begin_transaction()?;
let response = txn.fetch_object(YKPIV_OBJ_MSCMAP)?;
let response = txn.fetch_object(OBJ_MSCMAP)?;
let mut containers = vec![];
if response.len() < CB_OBJ_TAG_MIN {
let (_, tlv) = match Tlv::parse(&response) {
Ok(res) => res,
Err(_) => {
// TODO(tarcieri): is this really OK?
return Ok(containers);
}
};
if response[0] != TAG_MSCMAP {
if tlv.tag != TAG_MSCMAP {
// TODO(tarcieri): yubico-piv-tool returned success here? should we?
return Err(Error::InvalidObject);
}
let mut len = 0;
let offset = 1 + get_length(&response[1..], &mut len);
if len > response.len() - offset {
// TODO(tarcieri): is this really OK?
return Ok(containers);
}
for chunk in response[offset..(offset + len)].chunks_exact(CONTAINER_REC_LEN) {
for chunk in tlv.value.chunks_exact(CONTAINER_REC_LEN) {
containers.push(Container::new(chunk)?);
}
@@ -102,37 +107,23 @@ impl Container {
/// Write MS Container Map records.
pub fn write_mscmap(yubikey: &mut YubiKey, containers: &[Self]) -> Result<(), Error> {
let mut buf = [0u8; CB_OBJ_MAX];
let mut offset = 0;
let n_containers = containers.len();
let data_len = n_containers * CONTAINER_REC_LEN;
let max_size = yubikey.obj_size_max();
let txn = yubikey.begin_transaction()?;
if n_containers == 0 {
return txn.save_object(YKPIV_OBJ_MSCMAP, &[]);
return txn.save_object(OBJ_MSCMAP, &[]);
}
let req_len = 1 + set_length(&mut buf, data_len) + data_len;
if req_len > max_size {
return Err(Error::SizeError);
}
buf[offset] = TAG_MSCMAP;
offset += 1;
offset += set_length(&mut buf[offset..], data_len);
for (i, chunk) in buf[..data_len]
.chunks_exact_mut(CONTAINER_REC_LEN)
.enumerate()
{
let mut buf = [0u8; CB_OBJ_MAX];
let offset = Tlv::write_as(&mut buf, TAG_MSCMAP, data_len, |buf| {
for (i, chunk) in buf.chunks_exact_mut(CONTAINER_REC_LEN).enumerate() {
chunk.copy_from_slice(&containers[i].to_bytes());
}
})?;
offset += data_len;
txn.save_object(YKPIV_OBJ_MSCMAP, &buf[..offset])
txn.save_object(OBJ_MSCMAP, &buf[..offset])
}
/// Parse a container record from a byte slice
+35 -35
View File
@@ -37,9 +37,22 @@
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
use crate::{consts::*, error::Error, serialization::*, yubikey::YubiKey};
use crate::{error::Error, serialization::*, yubikey::YubiKey};
use crate::{CB_OBJ_MAX, CB_OBJ_TAG_MAX};
use log::error;
const OBJ_MSROOTS1: u32 = 0x005f_ff11;
#[allow(dead_code)]
const OBJ_MSROOTS2: u32 = 0x005f_ff12;
#[allow(dead_code)]
const OBJ_MSROOTS3: u32 = 0x005f_ff13;
#[allow(dead_code)]
const OBJ_MSROOTS4: u32 = 0x005f_ff14;
const OBJ_MSROOTS5: u32 = 0x005f_ff15;
const TAG_MSROOTS_END: u8 = 0x82;
const TAG_MSROOTS_MID: u8 = 0x83;
/// `msroots` file: PKCS#7-formatted certificate store for enterprise trust roots
pub struct MsRoots(Vec<u8>);
@@ -51,24 +64,21 @@ impl MsRoots {
/// Read `msroots` file from YubiKey
pub fn read(yubikey: &mut YubiKey) -> Result<Option<Self>, Error> {
let cb_data = yubikey.obj_size_max();
let txn = yubikey.begin_transaction()?;
// allocate first page
let mut data = Vec::with_capacity(cb_data);
let mut data = Vec::with_capacity(CB_OBJ_MAX);
for object_id in YKPIV_OBJ_MSROOTS1..YKPIV_OBJ_MSROOTS5 {
for object_id in OBJ_MSROOTS1..OBJ_MSROOTS5 {
let buf = txn.fetch_object(object_id)?;
let cb_buf = buf.len();
if cb_buf < CB_OBJ_TAG_MIN {
return Ok(None);
}
let (_, tlv) = match Tlv::parse(&buf) {
Ok(res) => res,
Err(_) => return Ok(None),
};
let tag = buf[0];
if (TAG_MSROOTS_MID != tag || YKPIV_OBJ_MSROOTS5 == object_id)
&& (TAG_MSROOTS_END != tag)
if (TAG_MSROOTS_MID != tlv.tag || OBJ_MSROOTS5 == object_id)
&& (TAG_MSROOTS_END != tlv.tag)
{
// the current object doesn't contain a valid part of a msroots file
@@ -76,17 +86,9 @@ impl MsRoots {
return Ok(None);
}
let mut len: usize = 0;
let offset = 1 + get_length(&buf[1..], &mut len);
data.extend_from_slice(tlv.value);
// check that decoded length represents object contents
if len > cb_buf - offset {
return Ok(None);
}
data.extend_from_slice(&buf[offset..offset + len]);
if tag == TAG_MSROOTS_END {
if tlv.tag == TAG_MSROOTS_END {
break;
}
}
@@ -106,15 +108,14 @@ impl MsRoots {
let data = &self.0;
let data_len = data.len();
let n_objs: usize;
let cb_obj_max = yubikey.obj_size_max();
let txn = yubikey.begin_transaction()?;
if data_len == 0 {
return txn.save_object(YKPIV_OBJ_MSROOTS1, &[]);
return txn.save_object(OBJ_MSROOTS1, &[]);
}
// Calculate number of objects required to store blob
n_objs = (data_len / (cb_obj_max - CB_OBJ_TAG_MAX)) + 1;
n_objs = (data_len / (CB_OBJ_MAX - CB_OBJ_TAG_MAX)) + 1;
if n_objs > 5 {
return Err(Error::SizeError);
@@ -123,24 +124,23 @@ impl MsRoots {
for i in 0..n_objs {
offset = 0;
data_chunk = if cb_obj_max - CB_OBJ_TAG_MAX < data_len - data_offset {
cb_obj_max - CB_OBJ_TAG_MAX
data_chunk = if CB_OBJ_MAX - CB_OBJ_TAG_MAX < data_len - data_offset {
CB_OBJ_MAX - CB_OBJ_TAG_MAX
} else {
data_len - data_offset
};
buf[offset] = if i == n_objs - 1 {
offset += Tlv::write(
&mut buf,
if i == n_objs - 1 {
TAG_MSROOTS_END
} else {
TAG_MSROOTS_MID
};
},
&data[data_offset..(data_offset + data_chunk)],
)?;
offset += 1;
offset += set_length(&mut buf[offset..], data_chunk);
buf[offset..].copy_from_slice(&data[data_offset..(data_offset + data_chunk)]);
offset += data_chunk;
txn.save_object(YKPIV_OBJ_MSROOTS1 + i as u32, &buf[..offset])?;
txn.save_object(OBJ_MSROOTS1 + i as u32, &buf[..offset])?;
data_offset += data_chunk;
}
+90
View File
@@ -0,0 +1,90 @@
//! Enums representing key policies.
use crate::{error::Error, serialization::Tlv};
/// Specifies how often the PIN needs to be entered for access to the credential in a
/// given slot. This policy must be set upon key generation or importation, and cannot be
/// changed later.
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum PinPolicy {
/// Use the default PIN policy for the slot. See the slot's documentation for details.
Default,
/// The end user PIN is **NOT** required to perform private key operations.
Never,
/// The end user PIN is required to perform any private key operations. Once the
/// correct PIN has been provided, multiple private key operations may be performed
/// without additional cardholder consent.
Once,
/// The end user PIN is required to perform any private key operations. The PIN must
/// be submitted immediately before each operation to ensure cardholder participation.
Always,
}
impl From<PinPolicy> for u8 {
fn from(policy: PinPolicy) -> u8 {
match policy {
PinPolicy::Default => 0,
PinPolicy::Never => 1,
PinPolicy::Once => 2,
PinPolicy::Always => 3,
}
}
}
impl PinPolicy {
/// Writes the `PinPolicy` in the format the YubiKey expects during key generation or
/// importation.
pub(crate) fn write(self, buf: &mut [u8]) -> Result<usize, Error> {
match self {
PinPolicy::Default => Ok(0),
_ => Tlv::write(buf, 0xaa, &[self.into()]),
}
}
}
/// Specifies under what conditions a physical touch on the metal contact is required, in
/// addition to the [`PinPolicy`]. This policy must be set upon key generation or
/// importation, and cannot be changed later.
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum TouchPolicy {
/// Use the default touch policy for the slot.
Default,
/// A physical touch is **NOT** required to perform private key operations.
Never,
/// A physical touch is required to perform any private key operations. The metal
/// contact must be touched during each operation to ensure cardholder participation.
Always,
/// A physical touch is required to perform any private key operations. Each touch
/// is cached for 15 seconds, during which time multiple private key operations may be
/// performed without additional cardholder interaction. After 15 seconds the cached
/// touch is cleared, and further operations require another physical touch.
Cached,
}
impl From<TouchPolicy> for u8 {
fn from(policy: TouchPolicy) -> u8 {
match policy {
TouchPolicy::Default => 0,
TouchPolicy::Never => 1,
TouchPolicy::Always => 2,
TouchPolicy::Cached => 3,
}
}
}
impl TouchPolicy {
/// Writes the `TouchPolicy` in the format the YubiKey expects during key generation
/// or importation.
pub(crate) fn write(self, buf: &mut [u8]) -> Result<usize, Error> {
match self {
TouchPolicy::Default => Ok(0),
_ => Tlv::write(buf, 0xab, &[self.into()]),
}
}
}
+108 -7
View File
@@ -30,30 +30,131 @@
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
use crate::{consts::*, ObjectId};
use crate::{error::Error, Buffer, ObjectId, CB_OBJ_TAG_MIN};
pub const OBJ_DISCOVERY: u32 = 0x7e;
// TODO(tarcieri): refactor these into better serializers/message builders
/// A Type-Length-Value object that has been parsed from a buffer.
pub(crate) struct Tlv<'a> {
pub(crate) tag: u8,
pub(crate) value: &'a [u8],
}
impl<'a> Tlv<'a> {
/// Parses a `Tlv` from a buffer, returning the remainder of the buffer.
pub(crate) fn parse(buffer: &'a [u8]) -> Result<(&'a [u8], Self), Error> {
if buffer.len() < CB_OBJ_TAG_MIN || !has_valid_length(&buffer[1..], buffer.len() - 1) {
return Err(Error::SizeError);
}
let tag = buffer[0];
let mut len = 0;
let offset = 1 + get_length(&buffer[1..], &mut len);
let (value, buffer) = buffer[offset..].split_at(len);
Ok((buffer, Tlv { tag, value }))
}
/// Takes a [`Buffer`] containing a single `Tlv` with the given tag, and returns a
/// `Buffer` containing only the value part of the `Tlv`.
pub(crate) fn parse_single(mut buffer: Buffer, tag: u8) -> Result<Buffer, Error> {
if buffer.len() < CB_OBJ_TAG_MIN || !has_valid_length(&buffer[1..], buffer.len() - 1) {
return Err(Error::SizeError);
}
if tag != buffer[0] {
return Err(Error::GenericError);
};
let mut len = 0;
let offset = 1 + get_length(&buffer[1..], &mut len);
buffer.copy_within(offset..offset + len, 0);
buffer.truncate(len);
Ok(buffer)
}
/// Writes a TLV to the given buffer.
pub(crate) fn write(buffer: &mut [u8], tag: u8, value: &[u8]) -> Result<usize, Error> {
if buffer.len() < CB_OBJ_TAG_MIN {
return Err(Error::SizeError);
}
buffer[0] = tag;
let offset = 1 + set_length(&mut buffer[1..], value.len())?;
if buffer.len() < offset + value.len() {
return Err(Error::SizeError);
}
buffer[offset..offset + value.len()].copy_from_slice(value);
Ok(offset + value.len())
}
/// Writes a TLV to the given buffer.
///
/// `value` is guaranteed to be called with a mutable slice of length `length`.
pub(crate) fn write_as<Gen>(
buffer: &mut [u8],
tag: u8,
length: usize,
value: Gen,
) -> Result<usize, Error>
where
Gen: FnOnce(&mut [u8]),
{
if buffer.len() < CB_OBJ_TAG_MIN {
return Err(Error::SizeError);
}
buffer[0] = tag;
let offset = 1 + set_length(&mut buffer[1..], length)?;
if buffer.len() < offset + length {
return Err(Error::SizeError);
}
value(&mut buffer[offset..offset + length]);
Ok(offset + length)
}
}
/// Set length
pub(crate) fn set_length(buffer: &mut [u8], length: usize) -> usize {
pub(crate) fn set_length(buffer: &mut [u8], length: usize) -> Result<usize, Error> {
if length < 0x80 {
if buffer.is_empty() {
Err(Error::SizeError)
} else {
buffer[0] = length as u8;
1
Ok(1)
}
} else if length < 0x100 {
if buffer.len() < 2 {
Err(Error::SizeError)
} else {
buffer[0] = 0x81;
buffer[1] = length as u8;
2
Ok(2)
}
} else if buffer.len() < 3 {
Err(Error::SizeError)
} else {
buffer[0] = 0x82;
buffer[1] = ((length >> 8) & 0xff) as u8;
buffer[2] = (length & 0xff) as u8;
3
Ok(3)
}
}
/// Parse length tag, returning the size of the length tag itself as the
/// returned value, and setting the len parameter to the parsed length.
pub(crate) fn get_length(buffer: &[u8], len: &mut usize) -> usize {
// This is not valid ASN.1 (0x80 is the indefinite length marker).
// See comment in key::generate for more context.
if buffer[0] < 0x81 {
*len = buffer[0] as usize;
1
@@ -83,9 +184,9 @@ pub(crate) fn has_valid_length(buffer: &[u8], len: usize) -> bool {
pub(crate) fn set_object(object_id: ObjectId, mut buffer: &mut [u8]) -> &mut [u8] {
buffer[0] = 0x5c;
if object_id == YKPIV_OBJ_DISCOVERY {
if object_id == OBJ_DISCOVERY {
buffer[1] = 1;
buffer[2] = YKPIV_OBJ_DISCOVERY as u8;
buffer[2] = OBJ_DISCOVERY as u8;
buffer = &mut buffer[3..];
} else if object_id > 0xffff && object_id <= 0x00ff_ffff {
buffer[1] = 3;
+74 -94
View File
@@ -1,46 +1,43 @@
//! YubiKey PC/SC transactions
use crate::{
apdu::{Ins, APDU},
apdu::Response,
apdu::{Ins, StatusWords, APDU},
error::Error,
yubikey::*,
};
#[cfg(feature = "untested")]
use crate::{
apdu::{Response, StatusWords},
consts::*,
key::{AlgorithmId, SlotId},
mgm::MgmKey,
serialization::*,
Buffer, ObjectId,
yubikey::*,
Buffer, ObjectId, CB_BUF_MAX, CB_OBJ_MAX, PIV_AID, YK_AID,
};
use log::{error, trace};
use std::convert::TryInto;
#[cfg(feature = "untested")]
use zeroize::Zeroizing;
#[cfg(feature = "untested")]
use crate::mgm::{MgmKey, DES_LEN_3DES};
const CB_PIN_MAX: usize = 8;
#[cfg(feature = "untested")]
pub(crate) enum ChangeRefAction {
ChangePin,
ChangePuk,
UnblockPin,
}
/// Exclusive transaction with the YubiKey's PC/SC card.
pub(crate) struct Transaction<'tx> {
inner: pcsc::Transaction<'tx>,
}
impl<'tx> Transaction<'tx> {
/// Create a new transaction with the given card
/// Create a new transaction with the given card.
pub fn new(card: &'tx mut pcsc::Card) -> Result<Self, Error> {
Ok(Transaction {
inner: card.transaction()?,
})
}
/// Get an attribute of the card or card reader.
pub fn get_attribute<'buf>(
&self,
attribute: pcsc::Attribute,
buffer: &'buf mut [u8],
) -> Result<&'buf [u8], Error> {
Ok(self.inner.get_attribute(attribute, buffer)?)
}
/// Transmit a single serialized APDU to the card this transaction is open
/// with and receive a response.
///
@@ -66,7 +63,7 @@ impl<'tx> Transaction<'tx> {
pub fn select_application(&self) -> Result<(), Error> {
let response = APDU::new(Ins::SelectApplication)
.p1(0x04)
.data(&AID)
.data(&PIV_AID)
.transmit(self, 0xFF)
.map_err(|e| {
error!("failed communicating with card: '{}'", e);
@@ -84,7 +81,7 @@ impl<'tx> Transaction<'tx> {
Ok(())
}
/// Get the version of the PIV application installed on the YubiKey
/// Get the version of the PIV application installed on the YubiKey.
pub fn get_version(&self) -> Result<Version, Error> {
// get version from device
let response = APDU::new(Ins::GetVersion).transmit(self, 261)?;
@@ -100,15 +97,13 @@ impl<'tx> Transaction<'tx> {
Ok(Version::new(response.data()[..3].try_into().unwrap()))
}
/// Get YubiKey device serial number
/// Get YubiKey device serial number.
pub fn get_serial(&self, version: Version) -> Result<Serial, Error> {
let yk_applet = [0xa0, 0x00, 0x00, 0x05, 0x27, 0x20, 0x01, 0x01];
let response = if version.major < 5 {
// get serial from neo/yk4 devices using the otp applet
// YK4 requires switching to the yk applet to retrieve the serial
let sw = APDU::new(Ins::SelectApplication)
.p1(0x04)
.data(&yk_applet)
.data(&YK_AID)
.transmit(self, 0xFF)?
.status_words();
@@ -130,7 +125,7 @@ impl<'tx> Transaction<'tx> {
// reselect the PIV applet
let sw = APDU::new(Ins::SelectApplication)
.p1(0x04)
.data(&AID)
.data(&PIV_AID)
.transmit(self, 0xFF)?
.status_words();
@@ -141,7 +136,7 @@ impl<'tx> Transaction<'tx> {
resp
} else {
// get serial from yk5 and later devices using the f8 command
// YK5 implements getting the serial as a PIV applet command (0xf8)
let resp = APDU::new(Ins::GetSerial).transmit(self, 0xFF)?;
if !resp.is_success() {
@@ -162,7 +157,6 @@ impl<'tx> Transaction<'tx> {
}
/// Verify device PIN.
#[cfg(feature = "untested")]
pub fn verify_pin(&self, pin: &[u8]) -> Result<(), Error> {
if pin.len() > CB_PIN_MAX {
return Err(Error::SizeError);
@@ -190,20 +184,26 @@ impl<'tx> Transaction<'tx> {
}
}
/// Change the PIN
/// Change the PIN.
#[cfg(feature = "untested")]
pub fn change_pin(&self, action: i32, current_pin: &[u8], new_pin: &[u8]) -> Result<(), Error> {
let mut templ = [0, Ins::ChangeReference.code(), 0, 0x80];
pub fn change_ref(
&self,
action: ChangeRefAction,
current_pin: &[u8],
new_pin: &[u8],
) -> Result<(), Error> {
if current_pin.len() > CB_PIN_MAX || new_pin.len() > CB_PIN_MAX {
return Err(Error::SizeError);
}
if action == CHREF_ACT_UNBLOCK_PIN {
templ[1] = Ins::ResetRetry.code();
} else if action == CHREF_ACT_CHANGE_PUK {
templ[3] = 0x81;
}
const PIN: u8 = 0x80;
const PUK: u8 = 0x81;
let templ = match action {
ChangeRefAction::ChangePin => [0, Ins::ChangeReference.code(), 0, PIN],
ChangeRefAction::ChangePuk => [0, Ins::ChangeReference.code(), 0, PUK],
ChangeRefAction::UnblockPin => [0, Ins::ResetRetry.code(), 0, PIN],
};
let mut indata = Zeroizing::new([0xff; CB_PIN_MAX * 2]);
indata[0..current_pin.len()].copy_from_slice(current_pin);
@@ -239,8 +239,8 @@ impl<'tx> Transaction<'tx> {
};
let mut data = [0u8; DES_LEN_3DES + 3];
data[0] = YKPIV_ALGO_3DES;
data[1] = YKPIV_KEY_CARDMGM;
data[0] = ALGO_3DES;
data[1] = KEY_CARDMGM;
data[2] = DES_LEN_3DES as u8;
data[3..3 + DES_LEN_3DES].copy_from_slice(new_key.as_ref());
@@ -262,7 +262,6 @@ impl<'tx> Transaction<'tx> {
/// This is the common backend for all public key encryption and signing
/// operations.
// TODO(tarcieri): refactor this to be less gross/coupled.
#[cfg(feature = "untested")]
#[allow(clippy::too_many_arguments)]
pub(crate) fn authenticated_command(
&self,
@@ -274,7 +273,6 @@ impl<'tx> Transaction<'tx> {
let in_len = sign_in.len();
let mut indata = [0u8; 1024];
let templ = [0, Ins::Authenticate.code(), algorithm.into(), key.into()];
let mut len: usize = 0;
match algorithm {
AlgorithmId::Rsa1024 | AlgorithmId::Rsa2048 => {
@@ -310,19 +308,21 @@ impl<'tx> Transaction<'tx> {
3
};
indata[0] = 0x7c;
let mut offset = 1 + set_length(&mut indata[1..], in_len + bytes + 3);
indata[offset] = 0x82;
indata[offset + 1] = 0x00;
indata[offset + 2] = match (algorithm, decipher) {
let offset = Tlv::write_as(&mut indata, 0x7c, in_len + bytes + 3, |buf| {
assert_eq!(Tlv::write(buf, 0x82, &[]).expect("large enough"), 2);
assert_eq!(
Tlv::write(
&mut buf[2..],
match (algorithm, decipher) {
(AlgorithmId::EccP256, true) | (AlgorithmId::EccP384, true) => 0x85,
_ => 0x81,
};
offset += 3;
offset += set_length(&mut indata[offset..], in_len);
indata[offset..(offset + in_len)].copy_from_slice(sign_in);
offset += in_len;
},
sign_in
)
.expect("large enough"),
1 + bytes + in_len
);
})?;
let response = self
.transfer_data(&templ, &indata[..offset], 1024)
@@ -341,32 +341,29 @@ impl<'tx> Transaction<'tx> {
}
}
let data = response.data();
let (_, outer_tlv) = Tlv::parse(response.data())?;
// skip the first 7c tag
if data[0] != 0x7c {
if outer_tlv.tag != 0x7c {
error!("failed parsing signature reply (0x7c byte)");
return Err(Error::ParseError);
}
let mut offset = 1 + get_length(&data[1..], &mut len);
let (_, inner_tlv) = Tlv::parse(outer_tlv.value)?;
// skip the 82 tag
if data[offset] != 0x82 {
if inner_tlv.tag != 0x82 {
error!("failed parsing signature reply (0x82 byte)");
return Err(Error::ParseError);
}
offset += 1;
offset += get_length(&data[offset..], &mut len);
Ok(Buffer::new(data[offset..(offset + len)].into()))
Ok(Buffer::new(inner_tlv.value.into()))
}
/// Send/receive large amounts of data to/from the YubiKey, splitting long
/// messages into smaller APDU-sized messages (using the provided APDU
/// template to construct them), and then sending those via
/// [`Transaction::transmit`].
#[cfg(feature = "untested")]
pub fn transfer_data(
&self,
templ: &[u8],
@@ -375,7 +372,7 @@ impl<'tx> Transaction<'tx> {
) -> Result<Response, Error> {
let mut in_offset = 0;
let mut out_data = vec![];
let mut sw = 0;
let mut sw;
loop {
let mut this_size = 0xff;
@@ -395,13 +392,13 @@ impl<'tx> Transaction<'tx> {
.data(&in_data[in_offset..(in_offset + this_size)])
.transmit(self, 261)?;
if !response.is_success() && (response.status_words().code() >> 8 != 0x61) {
sw = response.status_words().code();
if !response.is_success() && (sw >> 8 != 0x61) {
// TODO(tarcieri): is this really OK?
return Ok(Response::new(sw.into(), out_data));
}
sw = response.status_words().code();
if !out_data.is_empty() && (out_data.len() - response.data().len() > max_out) {
error!(
"output buffer too small: wanted to write {}, max was {}",
@@ -449,8 +446,7 @@ impl<'tx> Transaction<'tx> {
Ok(Response::new(sw.into(), out_data))
}
/// Fetch an object
#[cfg(feature = "untested")]
/// Fetch an object.
pub fn fetch_object(&self, object_id: ObjectId) -> Result<Buffer, Error> {
let mut indata = [0u8; 5];
let templ = [0, Ins::GetData.code(), 0x3f, 0xff];
@@ -462,39 +458,29 @@ impl<'tx> Transaction<'tx> {
let response = self.transfer_data(&templ, &indata[..inlen], CB_BUF_MAX)?;
if !response.is_success() {
if response.status_words() == StatusWords::NotFoundError {
return Err(Error::NotFound);
} else {
return Err(Error::GenericError);
}
let data = Buffer::new(response.data().into());
let mut outlen = 0;
if data.len() < 2 || !has_valid_length(&data[1..], data.len() - 1) {
return Err(Error::SizeError);
}
let offs = get_length(&data[1..], &mut outlen);
let (remaining, tlv) = Tlv::parse(response.data())?;
if offs == 0 {
return Err(Error::SizeError);
}
if outlen + offs + 1 != data.len() {
if !remaining.is_empty() {
error!(
"invalid length indicated in object: total len is {} but indicated length is {}",
data.len(),
outlen
tlv.value.len() + remaining.len(),
tlv.value.len()
);
return Err(Error::SizeError);
}
Ok(Zeroizing::new(
data[(1 + offs)..(1 + offs + outlen)].to_vec(),
))
Ok(Zeroizing::new(tlv.value.to_vec()))
}
/// Save an object
#[cfg(feature = "untested")]
/// Save an object.
pub fn save_object(&self, object_id: ObjectId, indata: &[u8]) -> Result<(), Error> {
let templ = [0, Ins::PutData.code(), 0x3f, 0xff];
@@ -508,14 +494,8 @@ impl<'tx> Transaction<'tx> {
let mut len = data.len();
let mut data_remaining = set_object(object_id, &mut data);
data_remaining[0] = 0x53;
data_remaining = &mut data_remaining[1..];
let offset = set_length(data_remaining, indata.len());
let offset = Tlv::write(data_remaining, 0x53, indata)?;
data_remaining = &mut data_remaining[offset..];
data_remaining[..indata.len()].copy_from_slice(indata);
data_remaining = &mut data_remaining[indata.len()..];
len -= data_remaining.len();
let status_words = self
+113 -281
View File
@@ -30,48 +30,45 @@
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#![allow(non_snake_case, non_upper_case_globals)]
#![allow(clippy::too_many_arguments, clippy::missing_safety_doc)]
#[cfg(feature = "untested")]
use crate::{
apdu::{Ins, StatusWords, APDU},
key::{AlgorithmId, SlotId},
metadata,
mgm::MgmKey,
serialization::*,
Buffer, ObjectId,
};
use crate::{
consts::*,
apdu::{Ins, APDU},
cccid::CCC,
chuid::CHUID,
config::Config,
error::Error,
mgm::MgmKey,
readers::{Reader, Readers},
transaction::Transaction,
};
#[cfg(feature = "untested")]
use getrandom::getrandom;
use log::{error, info, warn};
use log::{error, info};
use pcsc::Card;
use std::{
convert::{TryFrom, TryInto},
fmt::{self, Display},
str::FromStr,
};
#[cfg(feature = "untested")]
use crate::{
apdu::StatusWords, metadata, transaction::ChangeRefAction, Buffer, ObjectId, CB_BUF_MAX,
CB_OBJ_MAX, MGMT_AID, TAG_ADMIN, TAG_ADMIN_FLAGS_1, TAG_ADMIN_TIMESTAMP,
};
use getrandom::getrandom;
#[cfg(feature = "untested")]
use secrecy::ExposeSecret;
use std::{
convert::TryFrom,
fmt::{self, Display},
};
#[cfg(feature = "untested")]
use std::{
convert::TryInto,
time::{SystemTime, UNIX_EPOCH},
};
#[cfg(feature = "untested")]
use zeroize::Zeroizing;
use std::time::{SystemTime, UNIX_EPOCH};
/// PIV Application ID
pub const AID: [u8; 5] = [0xa0, 0x00, 0x00, 0x03, 0x08];
/// Flag for PUK blocked
pub(crate) const ADMIN_FLAGS_1_PUK_BLOCKED: u8 = 0x01;
/// MGMT Application ID.
/// <https://developers.yubico.com/PIV/Introduction/Admin_access.html>
pub const MGMT_AID: [u8; 8] = [0xa0, 0x00, 0x00, 0x05, 0x27, 0x47, 0x11, 0x17];
/// 3DES authentication
pub(crate) const ALGO_3DES: u8 = 0x03;
/// Card management key
pub(crate) const KEY_CARDMGM: u8 = 0x9b;
const TAG_DYN_AUTH: u8 = 0x7c;
/// Cached YubiKey PIN
pub type CachedPin = secrecy::SecretVec<u8>;
@@ -92,6 +89,14 @@ impl From<Serial> for u32 {
}
}
impl FromStr for Serial {
type Err = Error;
fn from_str(s: &str) -> Result<Self, Error> {
u32::from_str(s).map(Serial).map_err(|_| Error::ParseError)
}
}
impl Display for Serial {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.0)
@@ -122,6 +127,12 @@ impl Version {
}
}
impl Display for Version {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}.{}.{}", self.major, self.minor, self.patch)
}
}
/// YubiKey Device: this is the primary API for opening a session and
/// performing various operations.
///
@@ -131,8 +142,8 @@ impl Version {
#[cfg_attr(not(feature = "untested"), allow(dead_code))]
pub struct YubiKey {
pub(crate) card: Card,
pub(crate) name: String,
pub(crate) pin: Option<CachedPin>,
pub(crate) is_neo: bool,
pub(crate) version: Version,
pub(crate) serial: Serial,
}
@@ -143,10 +154,15 @@ impl YubiKey {
/// Returns an error if there is more than one YubiKey detected.
///
/// If you need to operate in environments with more than one YubiKey
/// attached to the same system, use [`yubikey_piv::Readers`] to select
/// from the available PC/SC readers connected.
/// attached to the same system, use [`YubiKey::open_by_serial`] or
///[`yubikey_piv::Readers`] to select from the available PC/SC readers.
pub fn open() -> Result<Self, Error> {
let mut readers = Readers::open()?;
let mut readers = Readers::open().map_err(|e| match e {
Error::PcscError {
inner: Some(pcsc::Error::NoReadersAvailable),
} => Error::NotFound,
other => other,
})?;
let mut reader_iter = readers.iter()?;
if let Some(reader) = reader_iter.next() {
@@ -159,7 +175,31 @@ impl YubiKey {
}
error!("no YubiKey detected!");
Err(Error::GenericError)
Err(Error::NotFound)
}
/// Open a YubiKey with a specific serial number.
pub fn open_by_serial(serial: Serial) -> Result<Self, Error> {
let mut readers = Readers::open().map_err(|e| match e {
Error::PcscError {
inner: Some(pcsc::Error::NoReadersAvailable),
} => Error::NotFound,
other => other,
})?;
for reader in readers.iter()? {
let yubikey = match reader.open() {
Ok(yk) => yk,
Err(_) => continue,
};
if serial == yubikey.serial() {
return Ok(yubikey);
}
}
error!("no YubiKey detected with serial: {}", serial);
Err(Error::NotFound)
}
/// Reconnect to a YubiKey
@@ -189,47 +229,53 @@ impl YubiKey {
}
/// Begin a transaction.
#[cfg(feature = "untested")]
pub(crate) fn begin_transaction(&mut self) -> Result<Transaction<'_>, Error> {
// TODO(tarcieri): reconnect support
Ok(Transaction::new(&mut self.card)?)
}
/// Get the name of the associated PC/SC card reader
pub fn name(&self) -> &str {
&self.name
}
/// Get the YubiKey's PIV application version.
///
/// This always uses the cached version queried when the key is initialized.
pub fn version(&mut self) -> Version {
pub fn version(&self) -> Version {
self.version
}
/// Get YubiKey device serial number.
///
/// This always uses the cached version queried when the key is initialized.
pub fn serial(&mut self) -> Serial {
pub fn serial(&self) -> Serial {
self.serial
}
/// Get YubiKey device model
// TODO(tarcieri): use an emum for this
#[cfg(feature = "untested")]
pub fn device_model(&self) -> u32 {
if self.is_neo {
DEVTYPE_NEOr3
} else {
// TODO(tarcieri): YK5?
DEVTYPE_YK4
/// Get device configuration.
pub fn config(&mut self) -> Result<Config, Error> {
Config::get(self)
}
/// Get CHUID
pub fn chuid(&mut self) -> Result<CHUID, Error> {
CHUID::get(self)
}
/// Get CCCID
pub fn cccid(&mut self) -> Result<CCC, Error> {
CCC::get(self)
}
/// Authenticate to the card using the provided management key (MGM).
#[cfg(feature = "untested")]
pub fn authenticate(&mut self, mgm_key: MgmKey) -> Result<(), Error> {
let txn = self.begin_transaction()?;
// get a challenge from the card
let challenge = APDU::new(Ins::Authenticate)
.params(YKPIV_ALGO_3DES, YKPIV_KEY_CARDMGM)
.data(&[0x7c, 0x02, 0x80, 0x00])
.params(ALGO_3DES, KEY_CARDMGM)
.data(&[TAG_DYN_AUTH, 0x02, 0x80, 0x00])
.transmit(&txn, 261)?;
if !challenge.is_success() || challenge.data().len() < 12 {
@@ -240,7 +286,7 @@ impl YubiKey {
let response = mgm_key.decrypt(challenge.data()[4..12].try_into().unwrap());
let mut data = [0u8; 22];
data[0] = 0x7c;
data[0] = TAG_DYN_AUTH;
data[1] = 20; // 2 + 8 + 2 +8
data[2] = 0x80;
data[3] = 8;
@@ -257,7 +303,7 @@ impl YubiKey {
challenge.copy_from_slice(&data[14..22]);
let authentication = APDU::new(Ins::Authenticate)
.params(YKPIV_ALGO_3DES, YKPIV_KEY_CARDMGM)
.params(ALGO_3DES, KEY_CARDMGM)
.data(&data)
.transmit(&txn, 261)?;
@@ -298,36 +344,7 @@ impl YubiKey {
Ok(())
}
/// Sign data using a PIV key
#[cfg(feature = "untested")]
pub fn sign_data(
&mut self,
raw_in: &[u8],
algorithm: AlgorithmId,
key: SlotId,
) -> Result<Buffer, Error> {
let txn = self.begin_transaction()?;
// don't attempt to reselect in crypt operations to avoid problems with PIN_ALWAYS
txn.authenticated_command(raw_in, algorithm, key, false)
}
/// Decrypt data using a PIV key
#[cfg(feature = "untested")]
pub fn decrypt_data(
&mut self,
input: &[u8],
algorithm: AlgorithmId,
key: SlotId,
) -> Result<Buffer, Error> {
let txn = self.begin_transaction()?;
// don't attempt to reselect in crypt operations to avoid problems with PIN_ALWAYS
txn.authenticated_command(input, algorithm, key, true)
}
/// Verify device PIN.
#[cfg(feature = "untested")]
pub fn verify_pin(&mut self, pin: &[u8]) -> Result<(), Error> {
{
let txn = self.begin_transaction()?;
@@ -342,7 +359,6 @@ impl YubiKey {
}
/// Get the number of PIN retries
#[cfg(feature = "untested")]
pub fn get_pin_retries(&mut self) -> Result<u8, Error> {
let txn = self.begin_transaction()?;
@@ -388,7 +404,7 @@ impl YubiKey {
pub fn change_pin(&mut self, current_pin: &[u8], new_pin: &[u8]) -> Result<(), Error> {
{
let txn = self.begin_transaction()?;
txn.change_pin(CHREF_ACT_CHANGE_PIN, current_pin, new_pin)?;
txn.change_ref(ChangeRefAction::ChangePin, current_pin, new_pin)?;
}
if !new_pin.is_empty() {
@@ -402,7 +418,6 @@ impl YubiKey {
#[cfg(feature = "untested")]
pub fn set_pin_last_changed(yubikey: &mut YubiKey) -> Result<(), Error> {
let mut data = [0u8; CB_BUF_MAX];
let max_size = yubikey.obj_size_max();
let txn = yubikey.begin_transaction()?;
let buffer = metadata::read(&txn, TAG_ADMIN)?;
@@ -428,7 +443,7 @@ impl YubiKey {
e
})?;
metadata::write(&txn, TAG_ADMIN, &data, max_size).map_err(|e| {
metadata::write(&txn, TAG_ADMIN, &data).map_err(|e| {
error!("could not write admin data, err = {}", e);
e
})?;
@@ -446,7 +461,7 @@ impl YubiKey {
#[cfg(feature = "untested")]
pub fn change_puk(&mut self, current_puk: &[u8], new_puk: &[u8]) -> Result<(), Error> {
let txn = self.begin_transaction()?;
txn.change_pin(CHREF_ACT_CHANGE_PUK, current_puk, new_puk)
txn.change_ref(ChangeRefAction::ChangePuk, current_puk, new_puk)
}
/// Block PUK: permanently prevent the PIN from becoming unblocked
@@ -456,12 +471,11 @@ impl YubiKey {
let mut tries_remaining: i32 = -1;
let mut flags = [0];
let max_size = yubikey.obj_size_max();
let txn = yubikey.begin_transaction()?;
while tries_remaining != 0 {
// 2 -> change puk
let res = txn.change_pin(CHREF_ACT_CHANGE_PUK, &puk, &puk);
let res = txn.change_ref(ChangeRefAction::ChangePuk, &puk, &puk);
match res {
Ok(()) => puk[0] += 1,
@@ -507,7 +521,7 @@ impl YubiKey {
)
.is_ok()
{
if metadata::write(&txn, TAG_ADMIN, &data[..cb_data], max_size).is_err() {
if metadata::write(&txn, TAG_ADMIN, &data[..cb_data]).is_err() {
error!("could not write admin metadata");
}
} else {
@@ -522,7 +536,7 @@ impl YubiKey {
#[cfg(feature = "untested")]
pub fn unblock_pin(&mut self, puk: &[u8], new_pin: &[u8]) -> Result<(), Error> {
let txn = self.begin_transaction()?;
txn.change_pin(CHREF_ACT_UNBLOCK_PIN, puk, new_pin)
txn.change_ref(ChangeRefAction::UnblockPin, puk, new_pin)
}
/// Fetch an object from the YubiKey
@@ -539,161 +553,13 @@ impl YubiKey {
txn.save_object(object_id, indata)
}
/// Import a private encryption or signing key into the YubiKey
// TODO(tarcieri): refactor this into separate methods per key type
#[cfg(feature = "untested")]
pub fn import_private_key(
&mut self,
key: SlotId,
algorithm: AlgorithmId,
p: Option<&[u8]>,
q: Option<&[u8]>,
dp: Option<&[u8]>,
dq: Option<&[u8]>,
qinv: Option<&[u8]>,
ec_data: Option<&[u8]>,
pin_policy: u8,
touch_policy: u8,
) -> Result<(), Error> {
let mut key_data = Zeroizing::new(vec![0u8; 1024]);
let templ = [0, Ins::ImportKey.code(), algorithm.into(), key.into()];
if pin_policy != YKPIV_PINPOLICY_DEFAULT
&& pin_policy != YKPIV_PINPOLICY_NEVER
&& pin_policy != YKPIV_PINPOLICY_ONCE
&& pin_policy != YKPIV_PINPOLICY_ALWAYS
{
return Err(Error::GenericError);
}
if touch_policy != YKPIV_TOUCHPOLICY_DEFAULT
&& touch_policy != YKPIV_TOUCHPOLICY_NEVER
&& touch_policy != YKPIV_TOUCHPOLICY_ALWAYS
&& touch_policy != YKPIV_TOUCHPOLICY_CACHED
{
return Err(Error::GenericError);
}
let (elem_len, params, param_tag) = match algorithm {
AlgorithmId::Rsa1024 | AlgorithmId::Rsa2048 => match (p, q, dp, dq, qinv) {
(Some(p), Some(q), Some(dp), Some(dq), Some(qinv)) => {
if p.len() + q.len() + dp.len() + dq.len() + qinv.len() >= key_data.len() {
return Err(Error::SizeError);
}
(
match algorithm {
AlgorithmId::Rsa1024 => 64,
AlgorithmId::Rsa2048 => 128,
_ => unreachable!(),
},
vec![p, q, dp, dq, qinv],
0x01,
)
}
_ => return Err(Error::GenericError),
},
AlgorithmId::EccP256 | AlgorithmId::EccP384 => match ec_data {
Some(ec_data) => {
if ec_data.len() >= key_data.len() {
// This can never be true, but check to be explicit.
return Err(Error::SizeError);
}
(
match algorithm {
AlgorithmId::EccP256 => 32,
AlgorithmId::EccP384 => 48,
_ => unreachable!(),
},
vec![ec_data],
0x06,
)
}
_ => return Err(Error::GenericError),
},
};
let mut offset = 0;
for (i, param) in params.into_iter().enumerate() {
key_data[offset] = param_tag + i as u8;
offset += 1;
offset += set_length(&mut key_data[offset..], elem_len);
let padding = elem_len - param.len();
let remaining = key_data.len() - offset;
if padding > remaining {
return Err(Error::AlgorithmError);
}
for b in &mut key_data[offset..offset + padding] {
*b = 0;
}
offset += padding;
key_data[offset..offset + param.len()].copy_from_slice(param);
offset += param.len();
}
if pin_policy != YKPIV_PINPOLICY_DEFAULT {
key_data[offset] = YKPIV_PINPOLICY_TAG;
key_data[offset + 1] = 0x01;
key_data[offset + 2] = pin_policy;
offset += 3;
}
if touch_policy != YKPIV_TOUCHPOLICY_DEFAULT {
key_data[offset] = YKPIV_TOUCHPOLICY_TAG;
key_data[offset + 1] = 0x01;
key_data[offset + 2] = touch_policy;
offset += 3;
}
let txn = self.begin_transaction()?;
let status_words = txn
.transfer_data(&templ, &key_data[..offset], 256)?
.status_words();
match status_words {
StatusWords::Success => Ok(()),
StatusWords::SecurityStatusError => Err(Error::AuthenticationError),
_ => Err(Error::GenericError),
}
}
/// Generate an attestation certificate for a stored key.
/// <https://developers.yubico.com/PIV/Introduction/PIV_attestation.html>
#[cfg(feature = "untested")]
pub fn attest(&mut self, key: SlotId) -> Result<Buffer, Error> {
let templ = [0, Ins::Attest.code(), key.into(), 0];
let txn = self.begin_transaction()?;
let response = txn.transfer_data(&templ, &[], CB_OBJ_MAX)?;
if !response.is_success() {
if response.status_words() == StatusWords::NotSupportedError {
return Err(Error::NotSupported);
} else {
return Err(Error::GenericError);
}
}
if response.data()[0] != 0x30 {
return Err(Error::GenericError);
}
Ok(Buffer::new(response.data().into()))
}
/// Get an auth challenge
#[cfg(feature = "untested")]
pub fn get_auth_challenge(&mut self) -> Result<[u8; 8], Error> {
let txn = self.begin_transaction()?;
let response = APDU::new(Ins::Authenticate)
.params(YKPIV_ALGO_3DES, YKPIV_KEY_CARDMGM)
.params(ALGO_3DES, KEY_CARDMGM)
.data(&[0x7c, 0x02, 0x81, 0x00])
.transmit(&txn, 261)?;
@@ -718,7 +584,7 @@ impl YubiKey {
// send the response to the card and a challenge of our own.
let status_words = APDU::new(Ins::Authenticate)
.params(YKPIV_ALGO_3DES, YKPIV_KEY_CARDMGM)
.params(ALGO_3DES, KEY_CARDMGM)
.data(&data)
.transmit(&txn, 261)?
.status_words();
@@ -747,16 +613,6 @@ impl YubiKey {
Ok(())
}
/// Get max object size supported by this device
#[cfg(feature = "untested")]
pub(crate) fn obj_size_max(&self) -> usize {
if self.is_neo {
CB_OBJ_MAX_NEO
} else {
CB_OBJ_MAX
}
}
}
impl<'a> TryFrom<&'a Reader<'_>> for YubiKey {
@@ -770,43 +626,19 @@ impl<'a> TryFrom<&'a Reader<'_>> for YubiKey {
info!("connected to reader: {}", reader.name());
let mut is_neo = false;
let version: Version;
let serial: Serial;
{
let (version, serial) = {
let txn = Transaction::new(&mut card)?;
let mut atr_buf = [0; CB_ATR_MAX];
let atr = txn.get_attribute(pcsc::Attribute::AtrString, &mut atr_buf)?;
if atr == YKPIV_ATR_NEO_R3 {
is_neo = true;
}
txn.select_application()?;
// now that the PIV application is selected, retrieve the version
// and serial number. Previously the NEO/YK4 required switching
// to the yk applet to retrieve the serial, YK5 implements this
// as a PIV applet command. Unfortunately, this change requires
// that we retrieve the version number first, so that get_serial
// can determine how to get the serial number, which for the NEO/Yk4
// will result in another selection of the PIV applet.
version = txn.get_version().map_err(|e| {
warn!("failed to retrieve version: '{}'", e);
e
})?;
serial = txn.get_serial(version).map_err(|e| {
warn!("failed to retrieve serial number: '{}'", e);
e
})?;
}
let v = txn.get_version()?;
let s = txn.get_serial(v)?;
(v, s)
};
let yubikey = YubiKey {
card,
name: String::from(reader.name()),
pin: None,
is_neo,
version,
serial,
};
+194 -8
View File
@@ -3,18 +3,204 @@
#![forbid(unsafe_code)]
#![warn(missing_docs, rust_2018_idioms, trivial_casts, unused_qualifications)]
use std::env;
use yubikey_piv::YubiKey;
use getrandom::getrandom;
use lazy_static::lazy_static;
use log::trace;
use rsa::{hash::Hash::SHA2_256, PaddingScheme, PublicKey};
use sha2::{Digest, Sha256};
use std::convert::TryInto;
use std::{env, sync::Mutex};
use yubikey_piv::{
certificate::{Certificate, PublicKeyInfo},
key::{self, AlgorithmId, Key, RetiredSlotId, SlotId},
policy::{PinPolicy, TouchPolicy},
Error, MgmKey, YubiKey,
};
#[test]
#[ignore]
fn connect() {
lazy_static! {
/// Provide thread-safe access to a YubiKey
static ref YUBIKEY: Mutex<YubiKey> = init_yubikey();
}
/// One-time test initialization and setup
fn init_yubikey() -> Mutex<YubiKey> {
// Only show logs if `RUST_LOG` is set
if env::var("RUST_LOG").is_ok() {
env_logger::builder().format_timestamp(None).init();
}
let mut yubikey = YubiKey::open().unwrap();
dbg!(&yubikey.version());
dbg!(&yubikey.serial());
let yubikey = YubiKey::open().unwrap();
trace!("serial: {}", yubikey.serial());
trace!("version: {}", yubikey.version());
Mutex::new(yubikey)
}
//
// CCCID support
//
#[test]
#[ignore]
fn test_get_cccid() {
let mut yubikey = YUBIKEY.lock().unwrap();
match yubikey.cccid() {
Ok(cccid) => trace!("CCCID: {:?}", cccid),
Err(Error::NotFound) => trace!("CCCID not found"),
Err(err) => panic!("error getting CCCID: {:?}", err),
}
}
//
// CHUID support
//
#[test]
#[ignore]
fn test_get_chuid() {
let mut yubikey = YUBIKEY.lock().unwrap();
match yubikey.chuid() {
Ok(chuid) => trace!("CHUID: {:?}", chuid),
Err(Error::NotFound) => trace!("CHUID not found"),
Err(err) => panic!("error getting CHUID: {:?}", err),
}
}
//
// Device config support
//
#[test]
#[ignore]
fn test_get_config() {
let mut yubikey = YUBIKEY.lock().unwrap();
let config_result = yubikey.config();
assert!(config_result.is_ok());
trace!("config: {:?}", config_result.unwrap());
}
//
// Cryptographic key support
//
#[test]
#[ignore]
fn test_list_keys() {
let mut yubikey = YUBIKEY.lock().unwrap();
let keys_result = Key::list(&mut yubikey);
assert!(keys_result.is_ok());
trace!("keys: {:?}", keys_result.unwrap());
}
//
// PIN support
//
#[test]
#[ignore]
fn test_verify_pin() {
let mut yubikey = YUBIKEY.lock().unwrap();
assert!(yubikey.verify_pin(b"000000").is_err());
assert!(yubikey.verify_pin(b"123456").is_ok());
}
//
// Certificate support
//
fn generate_self_signed_cert(algorithm: AlgorithmId) -> Certificate {
let mut yubikey = YUBIKEY.lock().unwrap();
assert!(yubikey.verify_pin(b"123456").is_ok());
assert!(yubikey.authenticate(MgmKey::default()).is_ok());
let slot = SlotId::Retired(RetiredSlotId::R1);
// Generate a new key in the selected slot.
let generated = key::generate(
&mut yubikey,
slot,
algorithm,
PinPolicy::Default,
TouchPolicy::Default,
)
.unwrap();
let mut serial = [0u8; 20];
getrandom(&mut serial).unwrap();
// Generate a self-signed certificate for the new key.
let cert_result = Certificate::generate_self_signed(
&mut yubikey,
slot,
serial,
None,
"testSubject".to_owned(),
generated,
);
assert!(cert_result.is_ok());
let cert = cert_result.unwrap();
trace!("cert: {:?}", cert);
cert
}
#[test]
#[ignore]
fn generate_self_signed_rsa_cert() {
let cert = generate_self_signed_cert(AlgorithmId::Rsa1024);
//
// Verify that the certificate is signed correctly
//
let pubkey = match cert.subject_pki() {
PublicKeyInfo::Rsa { pubkey, .. } => pubkey,
_ => unreachable!(),
};
let data = cert.as_ref();
let tbs_cert_len = u16::from_be_bytes(data[6..8].try_into().unwrap()) as usize;
let msg = &data[4..8 + tbs_cert_len];
let sig = &data[data.len() - 128..];
let hash = Sha256::digest(msg);
assert!(pubkey
.verify(
PaddingScheme::PKCS1v15Sign {
hash: Some(SHA2_256)
},
&hash,
sig
)
.is_ok());
}
#[test]
#[ignore]
fn generate_self_signed_ec_cert() {
let cert = generate_self_signed_cert(AlgorithmId::EccP256);
//
// Verify that the certificate is signed correctly
//
let pubkey = match cert.subject_pki() {
PublicKeyInfo::EcP256(pubkey) => pubkey,
_ => unreachable!(),
};
let data = cert.as_ref();
let tbs_cert_len = data[6] as usize;
let sig_algo_len = data[7 + tbs_cert_len + 1] as usize;
let sig_start = 7 + tbs_cert_len + 2 + sig_algo_len + 3;
let msg = &data[4..7 + tbs_cert_len];
let sig = &data[sig_start..];
use ring::signature::{UnparsedPublicKey, ECDSA_P256_SHA256_ASN1};
let ring_pk = UnparsedPublicKey::new(&ECDSA_P256_SHA256_ASN1, pubkey.as_bytes());
assert!(ring_pk.verify(msg, sig).is_ok());
}