Compare commits
58 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 77d9dd6e97 | |||
| aeb4e6c3fc | |||
| a23af7dc31 | |||
| 9083194c3b | |||
| cf8f3c88cf | |||
| eb399cbecc | |||
| fd77e9f844 | |||
| 4039560d97 | |||
| 63d7a21c9d | |||
| 79b1142f21 | |||
| 67ed32cbf9 | |||
| c54f66acb4 | |||
| 6e4819bad1 | |||
| a9d7996aa6 | |||
| 9367218c7d | |||
| e18828d048 | |||
| ebbf043bc9 | |||
| 96cd5d080b | |||
| bd485eb912 | |||
| 64bf135f6c | |||
| b5bee1aa2f | |||
| 7b1d98f695 | |||
| f372cfc2a7 | |||
| 9b6fb7a39c | |||
| d01d2dec84 | |||
| 7412c02892 | |||
| 6e24660a80 | |||
| ad21eaea81 | |||
| a71389a820 | |||
| 35cc1bbf72 | |||
| 86fde50c2d | |||
| 634740d751 | |||
| c5a486cb4b | |||
| 87c00a9b61 | |||
| c0bbf9aa06 | |||
| ffdb114ae5 | |||
| c3d5df1643 | |||
| f25eed1a86 | |||
| 683e463824 | |||
| ce55e08af8 | |||
| 88ec6bcb32 | |||
| b23ed1d48a | |||
| 6324f7a75d | |||
| 9252765940 | |||
| a43bddb531 | |||
| 71a334a9b8 | |||
| b750b9cbbb | |||
| 31ef465571 | |||
| 90bdda85cb | |||
| c394511c60 | |||
| 4e710da32c | |||
| 7add9bfa41 | |||
| 6c03ea89ec | |||
| 5733d0b0af | |||
| 943dd6f146 | |||
| 65ec5aad63 | |||
| 012d164e12 | |||
| 9bcd85bce0 |
@@ -21,20 +21,59 @@ jobs:
|
|||||||
toolchain: stable
|
toolchain: stable
|
||||||
override: true
|
override: true
|
||||||
|
|
||||||
|
- name: Install libpcsclite-dev
|
||||||
|
run: sudo apt-get install libpcsclite-dev
|
||||||
|
|
||||||
- name: Run cargo check
|
- name: Run cargo check
|
||||||
uses: actions-rs/cargo@v1
|
uses: actions-rs/cargo@v1
|
||||||
with:
|
with:
|
||||||
command: check
|
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: --release
|
||||||
|
|
||||||
|
- name: Run cargo build --all-features
|
||||||
|
uses: actions-rs/cargo@v1
|
||||||
|
env:
|
||||||
|
RUSTFLAGS: -D warnings
|
||||||
|
with:
|
||||||
|
command: build
|
||||||
|
args: --all-features
|
||||||
|
|
||||||
test:
|
test:
|
||||||
name: Test Suite
|
name: Test Suite
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
platform:
|
platform:
|
||||||
- ubuntu-latest
|
|
||||||
- macos-latest
|
- macos-latest
|
||||||
# TODO: support Windows after eliminating C legacy
|
- windows-latest
|
||||||
# - windows-latest
|
|
||||||
toolchain:
|
toolchain:
|
||||||
- 1.39.0
|
- 1.39.0
|
||||||
- stable
|
- stable
|
||||||
@@ -57,6 +96,14 @@ jobs:
|
|||||||
command: test
|
command: test
|
||||||
args: --release
|
args: --release
|
||||||
|
|
||||||
|
- name: Run cargo build --all-features
|
||||||
|
uses: actions-rs/cargo@v1
|
||||||
|
env:
|
||||||
|
RUSTFLAGS: -D warnings
|
||||||
|
with:
|
||||||
|
command: build
|
||||||
|
args: --all-features
|
||||||
|
|
||||||
fmt:
|
fmt:
|
||||||
name: Rustfmt
|
name: Rustfmt
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
@@ -92,6 +139,9 @@ jobs:
|
|||||||
toolchain: stable
|
toolchain: stable
|
||||||
override: true
|
override: true
|
||||||
|
|
||||||
|
- name: Install libpcsclite-dev
|
||||||
|
run: sudo apt-get install libpcsclite-dev
|
||||||
|
|
||||||
- name: Install clippy
|
- name: Install clippy
|
||||||
run: rustup component add clippy
|
run: rustup component add clippy
|
||||||
|
|
||||||
|
|||||||
@@ -4,5 +4,32 @@ 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/),
|
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).
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||||
|
|
||||||
|
## [0.0.2] (2019-11-25)
|
||||||
|
### Added
|
||||||
|
- `untested` Cargo feature to mark untested functionality ([#30])
|
||||||
|
- Initial connect test and docs ([#19])
|
||||||
|
- Clean up APDU construction with builder API ([#15])
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
- Rewrite translated code to use the `pcsc` crate ([#17])
|
||||||
|
- Rename ErrorKind to Error ([#13])
|
||||||
|
- Use `des` crate for 3DES operations ([#10])
|
||||||
|
- Replace `PKCS5_PBKDF2_HMAC_SHA1` with `pbkdf2` et al crates ([#9])
|
||||||
|
- Replace `RAND_bytes` with `getrandom` crate ([#8])
|
||||||
|
- Use `log` crate for logging ([#7])
|
||||||
|
- Replace `ErrorKind::Ok` with `Result` ([#6])
|
||||||
|
|
||||||
|
[0.0.2]: https://github.com/tarcieri/yubikey-piv.rs/pull/31
|
||||||
|
[#30]: https://github.com/tarcieri/yubikey-piv.rs/pull/30
|
||||||
|
[#19]: https://github.com/tarcieri/yubikey-piv.rs/pull/19
|
||||||
|
[#17]: https://github.com/tarcieri/yubikey-piv.rs/pull/17
|
||||||
|
[#15]: https://github.com/tarcieri/yubikey-piv.rs/pull/15
|
||||||
|
[#13]: https://github.com/tarcieri/yubikey-piv.rs/pull/13
|
||||||
|
[#10]: https://github.com/tarcieri/yubikey-piv.rs/pull/10
|
||||||
|
[#9]: https://github.com/tarcieri/yubikey-piv.rs/pull/9
|
||||||
|
[#8]: https://github.com/tarcieri/yubikey-piv.rs/pull/8
|
||||||
|
[#7]: https://github.com/tarcieri/yubikey-piv.rs/pull/7
|
||||||
|
[#6]: https://github.com/tarcieri/yubikey-piv.rs/pull/6
|
||||||
|
|
||||||
## 0.0.1 (2019-11-18)
|
## 0.0.1 (2019-11-18)
|
||||||
- It typechecks, ship it!
|
- It typechecks, ship it!
|
||||||
|
|||||||
+22
-4
@@ -1,13 +1,12 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "yubikey-piv"
|
name = "yubikey-piv"
|
||||||
version = "0.0.1" # Also update html_root_url in lib.rs when bumping this
|
version = "0.0.2" # Also update html_root_url in lib.rs when bumping this
|
||||||
description = """
|
description = """
|
||||||
Pure Rust host-side driver for the YubiKey Personal Identity Verification (PIV)
|
Pure Rust host-side driver for the YubiKey Personal Identity Verification (PIV)
|
||||||
CCID application providing general-purpose public-key signing and encryption
|
application providing general-purpose public-key signing and encryption
|
||||||
with hardware-backed private keys for RSA (2048/1024) and ECC (P-256/P-384)
|
with hardware-backed private keys for RSA (2048/1024) and ECC (P-256/P-384)
|
||||||
algorithms (e.g, PKCS#1v1.5, ECDSA)
|
algorithms (e.g, PKCS#1v1.5, ECDSA)
|
||||||
"""
|
"""
|
||||||
|
|
||||||
authors = ["Tony Arcieri <bascule@gmail.com>", "Yubico AB"]
|
authors = ["Tony Arcieri <bascule@gmail.com>", "Yubico AB"]
|
||||||
edition = "2018"
|
edition = "2018"
|
||||||
license = "BSD-2-Clause"
|
license = "BSD-2-Clause"
|
||||||
@@ -16,6 +15,25 @@ readme = "README.md"
|
|||||||
categories = ["api-bindings", "cryptography", "hardware-support"]
|
categories = ["api-bindings", "cryptography", "hardware-support"]
|
||||||
keywords = ["ccid", "ecdsa", "rsa", "piv", "yubikey"]
|
keywords = ["ccid", "ecdsa", "rsa", "piv", "yubikey"]
|
||||||
|
|
||||||
|
[badges]
|
||||||
|
maintenance = { status = "experimental" }
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
libc = "0.2"
|
des = "0.3"
|
||||||
|
getrandom = "0.1"
|
||||||
|
hmac = "0.7"
|
||||||
|
log = "0.4"
|
||||||
|
pbkdf2 = "0.3"
|
||||||
|
pcsc = "2"
|
||||||
|
sha-1 = "0.8"
|
||||||
|
subtle = "2"
|
||||||
zeroize = "1"
|
zeroize = "1"
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
env_logger = "0.7"
|
||||||
|
|
||||||
|
[features]
|
||||||
|
untested = []
|
||||||
|
|
||||||
|
[package.metadata.docs.rs]
|
||||||
|
all-features = true
|
||||||
|
|||||||
@@ -10,7 +10,7 @@
|
|||||||
[![Build Status][build-image]][build-link]
|
[![Build Status][build-image]][build-link]
|
||||||
[![Gitter Chat][gitter-image]][gitter-link]
|
[![Gitter Chat][gitter-image]][gitter-link]
|
||||||
|
|
||||||
Pure Rust host-side YubiKey [Personal Identity Verification (PIV)][1] driver
|
Pure Rust host-side YubiKey [Personal Identity Verification (PIV)][PIV] driver
|
||||||
with general-purpose public-key encryption and signing support.
|
with general-purpose public-key encryption and signing support.
|
||||||
|
|
||||||
[Documentation][docs-link]
|
[Documentation][docs-link]
|
||||||
@@ -22,52 +22,129 @@ to store a number of RSA (2048/1024) and ECC (NIST P-256/P-384) private keys
|
|||||||
with configurable access control policies. Both the signing (RSASSA/ECDSA) and
|
with configurable access control policies. Both the signing (RSASSA/ECDSA) and
|
||||||
encryption (PKCS#1v1.5/ECIES) use cases are supported for either key type.
|
encryption (PKCS#1v1.5/ECIES) use cases are supported for either key type.
|
||||||
|
|
||||||
|
See [Yubico's guide to PIV-enabled YubiKeys][yk-guide] for more information
|
||||||
|
on which devices support PIV and the available functionality.
|
||||||
|
|
||||||
If you've been wanting to use Rust to sign and/or encrypt stuff using a
|
If you've been wanting to use Rust to sign and/or encrypt stuff using a
|
||||||
private key generated and stored on a Yubikey (with option PIN-based access),
|
private key generated and stored on a Yubikey (with option PIN-based access),
|
||||||
this is the crate you've been after!
|
this is the crate you've been after!
|
||||||
|
|
||||||
One small problem, it's not done yet... 😫
|
Note that while this project started as a fork of a [Yubico] project,
|
||||||
|
|
||||||
But it might be close?
|
|
||||||
|
|
||||||
## History
|
|
||||||
|
|
||||||
This library is a Rust translation of the [yubico-piv-tool][2] utility by
|
|
||||||
Yubico, which was originally written in C. It was mechanically translated
|
|
||||||
from C into Rust using [Corrode][3], and then subsequently heavily
|
|
||||||
refactored into safer, more idiomatic Rust§.
|
|
||||||
|
|
||||||
Note that while this project started as a fork of a [Yubico][4] project,
|
|
||||||
this fork is **NOT** an official Yubico project and is in no way supported or
|
this fork is **NOT** an official Yubico project and is in no way supported or
|
||||||
endorsed by Yubico.
|
endorsed by Yubico.
|
||||||
|
|
||||||
§ *NOTE*: This section is actually full of lies and notes aspirations/goals,
|
## Minimum Supported Rust Version
|
||||||
not history. That said, there's been a decent amount of work cleaning up the
|
|
||||||
mechanically translated code, and at ~5klocs it's not that much.
|
- Rust **1.39+**
|
||||||
|
|
||||||
|
## 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
|
||||||
|
|
||||||
## Security Warning
|
## Security Warning
|
||||||
|
|
||||||
No security audits of this crate have ever been performed, and it has not been
|
No security audits of this crate have ever been performed. Presently it is in
|
||||||
thoroughly assessed to ensure its operation is constant-time on common CPU
|
an experimental stage and may still contain high-severity issues.
|
||||||
architectures.
|
|
||||||
|
|
||||||
USE AT YOUR OWN RISK!
|
USE AT YOUR OWN RISK!
|
||||||
|
|
||||||
## Requirements
|
## Status
|
||||||
|
|
||||||
- Rust 1.39+
|
This project is a largely incomplete work-in-progress. So far the only
|
||||||
|
functionality which has actually been tested is connecting to Yubikeys.
|
||||||
|
|
||||||
|
If you're interested helping test functionality, the table below documents
|
||||||
|
the current status of the project and relevant GitHub issues for various
|
||||||
|
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
|
||||||
|
| ⚠️ | `msroots` | [#28] | `msroots` file: PKCS#7 formatted certificate store for enterprise trusted roots |
|
||||||
|
|
||||||
|
Legend:
|
||||||
|
|
||||||
|
| | Description |
|
||||||
|
|----|------------------------------------|
|
||||||
|
| 🚧 | Testing and validation in progress |
|
||||||
|
| ⚠️ | Untested support |
|
||||||
|
|
||||||
|
NOTE: Commands marked ⚠️ are disabled by default as they have have not been properly tested and may contain bugs or
|
||||||
|
not work at all. USE AT YOUR OWN RISK!
|
||||||
|
|
||||||
|
Enable the `untested` feature in your `Cargo.toml` to enable features marked ⚠️
|
||||||
|
above.
|
||||||
|
|
||||||
|
## Testing
|
||||||
|
|
||||||
|
To run the full test suite, you'll need a connected YubiKey NEO/4/5 device in
|
||||||
|
the default state (i.e. default PIN/PUK).
|
||||||
|
|
||||||
|
Tests which run live against a YubiKey device are marked as `#[ignore]` by
|
||||||
|
default in order to pass when running in a CI environment. To run these
|
||||||
|
tests locally, invoke the following command:
|
||||||
|
|
||||||
|
```
|
||||||
|
cargo test -- --ignored
|
||||||
|
```
|
||||||
|
|
||||||
|
This crate makes extensive use of the `log` facade to provide detailed
|
||||||
|
information about what is happening. If you'd like to print this logging
|
||||||
|
information while running the tests, set the `RUST_LOG` environment variable
|
||||||
|
to a relevant loglevel (e.g. `error`, `warn`, `info`, `debug`, `trace`):
|
||||||
|
|
||||||
|
```
|
||||||
|
RUST_LOG=info cargo test -- --ignored
|
||||||
|
```
|
||||||
|
|
||||||
|
To trace every message sent to/from the card i.e. the raw
|
||||||
|
Application Protocol Data Unit (APDU) messages, use the `trace` log level:
|
||||||
|
|
||||||
|
```
|
||||||
|
running 1 test
|
||||||
|
[INFO yubikey_piv::yubikey] trying to connect to reader 'Yubico YubiKey OTP+FIDO+CCID'
|
||||||
|
[INFO yubikey_piv::yubikey] connected to 'Yubico YubiKey OTP+FIDO+CCID' successfully
|
||||||
|
[TRACE yubikey_piv::transaction] >>> [0, 164, 4, 0, 5, 160, 0, 0, 3, 8]
|
||||||
|
[TRACE yubikey_piv::transaction] <<< [97, 17, 79, 6, 0, 0, 16, 0, 1, 0, 121, 7, 79, 5, 160, 0, 0, 3, 8, 144, 0]
|
||||||
|
[TRACE yubikey_piv::transaction] >>> [0, 253, 0, 0, 0]
|
||||||
|
[TRACE yubikey_piv::transaction] <<< [5, 1, 2, 144, 0]
|
||||||
|
[TRACE yubikey_piv::transaction] >>> [0, 248, 0, 0, 0]
|
||||||
|
[TRACE yubikey_piv::transaction] <<< [0, 115, 0, 178, 144, 0]
|
||||||
|
test connect ... ok
|
||||||
|
```
|
||||||
|
|
||||||
|
APDU messages labeled `>>>` are being sent to the YubiKey's internal SmartCard,
|
||||||
|
and ones labeled `<<<` are the responses.
|
||||||
|
|
||||||
|
## History
|
||||||
|
|
||||||
|
This library is a Rust translation of the [yubico-piv-tool] utility by
|
||||||
|
Yubico, which was originally written in C. It was mechanically translated
|
||||||
|
from C into Rust using [Corrode], and then subsequently heavily
|
||||||
|
refactored into safer, more idiomatic Rust.
|
||||||
|
|
||||||
## Code of Conduct
|
## Code of Conduct
|
||||||
|
|
||||||
We abide by the [Contributor Covenant][5] and ask that you do as well.
|
We abide by the [Contributor Covenant][cc-md] and ask that you do as well.
|
||||||
|
|
||||||
For more information, please see [CODE_OF_CONDUCT.md][6].
|
For more information, please see [CODE_OF_CONDUCT.md][cc-md].
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
**yubikey-piv.rs** is a fork of and originally a mechanical translation from
|
**yubikey-piv.rs** is a fork of and originally a mechanical translation from
|
||||||
Yubico's [`yubico-piv-tool`][2], a C library/CLI program. The original library
|
Yubico's [yubico-piv-tool], a C library/CLI program. The original library
|
||||||
was licensed under a [2-Clause BSD License][5], which this library inherits
|
was licensed under a [2-Clause BSD License][BSDL], which this library inherits
|
||||||
as a derived work.
|
as a derived work.
|
||||||
|
|
||||||
Copyright (c) 2014-2019 Yubico AB, Tony Arcieri
|
Copyright (c) 2014-2019 Yubico AB, Tony Arcieri
|
||||||
@@ -101,7 +178,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|||||||
|
|
||||||
Unless you explicitly state otherwise, any contribution intentionally
|
Unless you explicitly state otherwise, any contribution intentionally
|
||||||
submitted for inclusion in the work by you shall be licensed under the
|
submitted for inclusion in the work by you shall be licensed under the
|
||||||
[2-Clause BSD License][5] as shown above, without any additional terms
|
[2-Clause BSD License][BSDL] as shown above, without any additional terms
|
||||||
or conditions.
|
or conditions.
|
||||||
|
|
||||||
[//]: # (badges)
|
[//]: # (badges)
|
||||||
@@ -115,15 +192,32 @@ or conditions.
|
|||||||
[maintenance-image]: https://img.shields.io/badge/maintenance-experimental-blue.svg
|
[maintenance-image]: https://img.shields.io/badge/maintenance-experimental-blue.svg
|
||||||
[build-image]: https://github.com/tarcieri/yubikey-piv.rs/workflows/Rust/badge.svg
|
[build-image]: https://github.com/tarcieri/yubikey-piv.rs/workflows/Rust/badge.svg
|
||||||
[build-link]: https://github.com/tarcieri/yubikey-piv.rs/actions
|
[build-link]: https://github.com/tarcieri/yubikey-piv.rs/actions
|
||||||
[gitter-image]: https://badges.gitter.im/tarcieri/yubihsm-piv.rs.svg
|
[gitter-image]: https://badges.gitter.im/yubihsm-piv-rs.svg
|
||||||
[gitter-link]: https://gitter.im/tarcieri/community
|
[gitter-link]: https://gitter.im/yubikey-piv-rs/community
|
||||||
|
|
||||||
[//]: # (general links)
|
[//]: # (general links)
|
||||||
|
|
||||||
[1]: https://piv.idmanagement.gov/
|
[PIV]: https://piv.idmanagement.gov/
|
||||||
[2]: https://github.com/Yubico/yubico-piv-tool/
|
[yk-guide]: https://developers.yubico.com/PIV/Introduction/YubiKey_and_PIV.html
|
||||||
[3]: https://github.com/jameysharp/corrode
|
[Yubico]: https://www.yubico.com/
|
||||||
[4]: https://www.yubico.com/
|
[YubiKey NEO]: https://support.yubico.com/support/solutions/articles/15000006494-yubikey-neo
|
||||||
[5]: https://contributor-covenant.org/
|
[YubiKey 4]: https://support.yubico.com/support/solutions/articles/15000006486-yubikey-4
|
||||||
[6]: https://github.com/tarcieri/yubikey-piv.rs/blob/develop/CODE_OF_CONDUCT.md
|
[YubiKey 5]: https://www.yubico.com/products/yubikey-5-overview/
|
||||||
[7]: https://opensource.org/licenses/BSD-2-Clause
|
[yubico-piv-tool]: https://github.com/Yubico/yubico-piv-tool/
|
||||||
|
[Corrode]: https://github.com/jameysharp/corrode
|
||||||
|
[cc-web]: https://contributor-covenant.org/
|
||||||
|
[cc-md]: https://github.com/tarcieri/yubikey-piv.rs/blob/develop/CODE_OF_CONDUCT.md
|
||||||
|
[BSDL]: https://opensource.org/licenses/BSD-2-Clause
|
||||||
|
|
||||||
|
[//]: # (github issues)
|
||||||
|
|
||||||
|
[#18]: https://github.com/tarcieri/yubikey-piv.rs/issues/18
|
||||||
|
[#20]: https://github.com/tarcieri/yubikey-piv.rs/issues/20
|
||||||
|
[#21]: https://github.com/tarcieri/yubikey-piv.rs/issues/21
|
||||||
|
[#22]: https://github.com/tarcieri/yubikey-piv.rs/issues/22
|
||||||
|
[#23]: https://github.com/tarcieri/yubikey-piv.rs/issues/23
|
||||||
|
[#24]: https://github.com/tarcieri/yubikey-piv.rs/issues/24
|
||||||
|
[#25]: https://github.com/tarcieri/yubikey-piv.rs/issues/25
|
||||||
|
[#26]: https://github.com/tarcieri/yubikey-piv.rs/issues/26
|
||||||
|
[#27]: https://github.com/tarcieri/yubikey-piv.rs/issues/27
|
||||||
|
[#28]: https://github.com/tarcieri/yubikey-piv.rs/issues/28
|
||||||
|
|||||||
+127
-32
@@ -1,56 +1,152 @@
|
|||||||
//! Application Protocol Data Unit (APDU)
|
//! Application Protocol Data Unit (APDU)
|
||||||
|
|
||||||
use zeroize::Zeroize;
|
// 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.
|
||||||
|
|
||||||
|
use crate::{error::Error, response::Response, transaction::Transaction, Buffer};
|
||||||
|
use std::fmt::{self, Debug};
|
||||||
|
use zeroize::{Zeroize, Zeroizing};
|
||||||
|
|
||||||
|
/// Maximum amount of command data that can be included in an APDU
|
||||||
|
const APDU_DATA_MAX: usize = 0xFF;
|
||||||
|
|
||||||
/// Application Protocol Data Unit (APDU).
|
/// Application Protocol Data Unit (APDU).
|
||||||
///
|
///
|
||||||
/// These messages are packets used to communicate with the YubiKey using the
|
/// These messages are packets used to communicate with the YubiKey.
|
||||||
/// Chip Card Interface Device (CCID) protocol.
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct APDU {
|
pub(crate) struct APDU {
|
||||||
/// Instruction class - indicates the type of command, e.g. interindustry or proprietary
|
/// Instruction class: indicates the type of command (e.g. inter-industry or proprietary)
|
||||||
pub cla: u8,
|
cla: u8,
|
||||||
|
|
||||||
/// Instruction code - indicates the specific command, e.g. "write data"
|
/// Instruction code: indicates the specific command (e.g. "write data")
|
||||||
pub ins: u8,
|
ins: u8,
|
||||||
|
|
||||||
/// Instruction parameter 1 for the command, e.g. offset into file at which to write the data
|
/// Instruction parameter 1 for the command (e.g. offset into file at which to write the data)
|
||||||
pub p1: u8,
|
p1: u8,
|
||||||
|
|
||||||
/// Instruction parameter 2 for the command
|
/// Instruction parameter 2 for the command
|
||||||
pub p2: u8,
|
p2: u8,
|
||||||
|
|
||||||
/// Length of command - encodes the number of bytes of command data to follow
|
/// Command data to be sent (`lc` is calculated as `data.len()`)
|
||||||
pub lc: u8,
|
data: Vec<u8>,
|
||||||
|
|
||||||
/// Command data
|
|
||||||
pub data: [u8; 255],
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl APDU {
|
impl APDU {
|
||||||
/// Get a const pointer to this APDU
|
/// Create a new APDU with the given instruction code
|
||||||
// TODO(tarcieri): eliminate pointers and use all safe references
|
pub fn new(ins: u8) -> Self {
|
||||||
pub(crate) fn as_ptr(&self) -> *const APDU {
|
Self {
|
||||||
|
cla: 0,
|
||||||
|
ins,
|
||||||
|
p1: 0,
|
||||||
|
p2: 0,
|
||||||
|
data: vec![],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set this APDU's class
|
||||||
|
#[cfg(feature = "untested")]
|
||||||
|
pub fn cla(&mut self, value: u8) -> &mut Self {
|
||||||
|
self.cla = value;
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get a mut pointer to this APDU
|
/// Set this APDU's first parameter only
|
||||||
// TODO(tarcieri): eliminate pointers and use all safe references
|
pub fn p1(&mut self, value: u8) -> &mut Self {
|
||||||
pub(crate) fn as_mut_ptr(&mut self) -> *mut APDU {
|
self.p1 = value;
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// 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;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set the command data for this APDU.
|
||||||
|
///
|
||||||
|
/// Panics if the byte slice is more than 255 bytes!
|
||||||
|
pub fn data(&mut self, bytes: impl AsRef<[u8]>) -> &mut Self {
|
||||||
|
assert!(self.data.is_empty(), "APDU command already set!");
|
||||||
|
|
||||||
|
let bytes = bytes.as_ref();
|
||||||
|
|
||||||
|
assert!(
|
||||||
|
bytes.len() <= APDU_DATA_MAX,
|
||||||
|
"APDU command data too long: {} (max: {})",
|
||||||
|
bytes.len(),
|
||||||
|
APDU_DATA_MAX
|
||||||
|
);
|
||||||
|
|
||||||
|
self.data.extend_from_slice(bytes);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Transmit this APDU using the given card transaction
|
||||||
|
pub fn transmit(&self, txn: &Transaction<'_>, recv_len: usize) -> Result<Response, Error> {
|
||||||
|
let response_bytes = txn.transmit(&self.to_bytes(), recv_len)?;
|
||||||
|
Ok(Response::from_bytes(response_bytes))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Consume this APDU and return a self-zeroizing buffer
|
||||||
|
pub fn to_bytes(&self) -> Buffer {
|
||||||
|
let mut bytes = Vec::with_capacity(5 + self.data.len());
|
||||||
|
bytes.push(self.cla);
|
||||||
|
bytes.push(self.ins);
|
||||||
|
bytes.push(self.p1);
|
||||||
|
bytes.push(self.p2);
|
||||||
|
bytes.push(self.data.len() as u8);
|
||||||
|
bytes.extend_from_slice(self.data.as_ref());
|
||||||
|
Zeroizing::new(bytes)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for APDU {
|
impl Debug for APDU {
|
||||||
fn default() -> Self {
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
Self {
|
write!(
|
||||||
cla: 0,
|
f,
|
||||||
ins: 0,
|
"APDU {{ cla: {}, ins: {}, p1: {}, p2: {}, lc: {}, data: {:?} }}",
|
||||||
p1: 0,
|
self.cla,
|
||||||
p2: 0,
|
self.ins,
|
||||||
lc: 0,
|
self.p1,
|
||||||
data: [0u8; 255],
|
self.p2,
|
||||||
|
self.data.len(),
|
||||||
|
self.data.as_slice()
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Drop for APDU {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
self.zeroize();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -60,7 +156,6 @@ impl Zeroize for APDU {
|
|||||||
self.ins.zeroize();
|
self.ins.zeroize();
|
||||||
self.p1.zeroize();
|
self.p1.zeroize();
|
||||||
self.p2.zeroize();
|
self.p2.zeroize();
|
||||||
self.lc.zeroize();
|
|
||||||
self.data.zeroize();
|
self.data.zeroize();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,85 @@
|
|||||||
|
//! Cardholder Capability Container (CCC) ID Support
|
||||||
|
|
||||||
|
// 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.
|
||||||
|
|
||||||
|
use crate::{consts::*, error::Error, yubikey::YubiKey};
|
||||||
|
use getrandom::getrandom;
|
||||||
|
|
||||||
|
/// Cardholder Capability Container (CCC) Template
|
||||||
|
///
|
||||||
|
/// f0: Card Identifier
|
||||||
|
///
|
||||||
|
/// - 0xa000000116 == GSC-IS RID
|
||||||
|
/// - 0xff == Manufacturer ID (dummy)
|
||||||
|
/// - 0x02 == Card type (javaCard)
|
||||||
|
/// - next 14 bytes: card ID
|
||||||
|
const CCC_TMPL: &[u8] = &[
|
||||||
|
0xf0, 0x15, 0xa0, 0x00, 0x00, 0x01, 0x16, 0xff, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf1, 0x01, 0x21, 0xf2, 0x01, 0x21, 0xf3, 0x00, 0xf4,
|
||||||
|
0x01, 0x00, 0xf5, 0x01, 0x10, 0xf6, 0x00, 0xf7, 0x00, 0xfa, 0x00, 0xfb, 0x00, 0xfc, 0x00, 0xfd,
|
||||||
|
0x00, 0xfe, 0x00,
|
||||||
|
];
|
||||||
|
|
||||||
|
/// Cardholder Capability Container (CCC) Identifier
|
||||||
|
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
|
||||||
|
pub struct CCCID([u8; YKPIV_CCCID_SIZE]);
|
||||||
|
|
||||||
|
impl CCCID {
|
||||||
|
/// Generate a random CCCID
|
||||||
|
pub fn generate() -> Result<Self, Error> {
|
||||||
|
let mut id = [0u8; YKPIV_CCCID_SIZE];
|
||||||
|
getrandom(&mut id).map_err(|_| Error::RandomnessError)?;
|
||||||
|
Ok(CCCID(id))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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)?;
|
||||||
|
|
||||||
|
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))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get Cardholder Capability Container (CCC) ID
|
||||||
|
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);
|
||||||
|
|
||||||
|
let txn = yubikey.begin_transaction()?;
|
||||||
|
txn.save_object(YKPIV_OBJ_CAPABILITY, &buf)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,186 @@
|
|||||||
|
//! YubiKey Certificates
|
||||||
|
|
||||||
|
// 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.
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
consts::*,
|
||||||
|
error::Error,
|
||||||
|
key::{self, SlotId},
|
||||||
|
serialization::*,
|
||||||
|
transaction::Transaction,
|
||||||
|
yubikey::YubiKey,
|
||||||
|
Buffer,
|
||||||
|
};
|
||||||
|
use log::error;
|
||||||
|
use std::ptr;
|
||||||
|
use zeroize::Zeroizing;
|
||||||
|
|
||||||
|
/// Certificates
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub struct Certificate(Buffer);
|
||||||
|
|
||||||
|
impl Certificate {
|
||||||
|
/// 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()?;
|
||||||
|
let buf = read_certificate(&txn, slot)?;
|
||||||
|
|
||||||
|
if buf.is_empty() {
|
||||||
|
return Err(Error::InvalidObject);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(Certificate(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();
|
||||||
|
let txn = yubikey.begin_transaction()?;
|
||||||
|
write_certificate(&txn, slot, Some(&self.0), certinfo, max_size)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Delete a certificate located at the given slot of the given YubiKey
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Initialize a local certificate struct from the given bytebuffer
|
||||||
|
pub fn new(cert: impl Into<Buffer>) -> Result<Self, Error> {
|
||||||
|
let cert = cert.into();
|
||||||
|
|
||||||
|
if cert.is_empty() {
|
||||||
|
error!("certificate cannot be empty");
|
||||||
|
return Err(Error::SizeError);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(Certificate(cert))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Extract the inner buffer
|
||||||
|
pub fn into_buffer(self) -> Buffer {
|
||||||
|
self.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AsRef<[u8]> for Certificate {
|
||||||
|
fn as_ref(&self) -> &[u8] {
|
||||||
|
self.0.as_ref()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Read certificate
|
||||||
|
pub(crate) fn read_certificate(txn: &Transaction<'_>, slot: SlotId) -> Result<Buffer, Error> {
|
||||||
|
let mut len: usize = 0;
|
||||||
|
let object_id = key::slot_object(slot)?;
|
||||||
|
|
||||||
|
let mut buf = match txn.fetch_object(object_id) {
|
||||||
|
Ok(b) => b,
|
||||||
|
Err(_) => {
|
||||||
|
// TODO(tarcieri): is this really ok?
|
||||||
|
return Ok(Zeroizing::new(vec![]));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if buf.len() < CB_OBJ_TAG_MIN {
|
||||||
|
// TODO(tarcieri): is this really ok?
|
||||||
|
return Ok(Zeroizing::new(vec![]));
|
||||||
|
}
|
||||||
|
|
||||||
|
if buf[0] == TAG_CERT {
|
||||||
|
let offset = 1 + get_length(&buf[1..], &mut len);
|
||||||
|
|
||||||
|
if len > buf.len() - offset {
|
||||||
|
// TODO(tarcieri): is this really ok?
|
||||||
|
return Ok(Zeroizing::new(vec![]));
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe {
|
||||||
|
ptr::copy(buf.as_ptr().add(offset), buf.as_mut_ptr(), len);
|
||||||
|
}
|
||||||
|
|
||||||
|
buf.truncate(len);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(buf)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Write certificate
|
||||||
|
pub(crate) fn write_certificate(
|
||||||
|
txn: &Transaction<'_>,
|
||||||
|
slot: SlotId,
|
||||||
|
data: Option<&[u8]>,
|
||||||
|
certinfo: u8,
|
||||||
|
max_size: usize,
|
||||||
|
) -> Result<(), Error> {
|
||||||
|
let mut buf = [0u8; CB_OBJ_MAX];
|
||||||
|
let mut offset = 0;
|
||||||
|
|
||||||
|
let object_id = key::slot_object(slot)?;
|
||||||
|
|
||||||
|
if data.is_none() {
|
||||||
|
return txn.save_object(object_id, &[]);
|
||||||
|
}
|
||||||
|
|
||||||
|
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();
|
||||||
|
|
||||||
|
// 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;
|
||||||
|
|
||||||
|
txn.save_object(object_id, &buf[..offset])
|
||||||
|
}
|
||||||
@@ -0,0 +1,92 @@
|
|||||||
|
//! Cardholder Unique Identifier (CHUID) Support
|
||||||
|
|
||||||
|
// 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.
|
||||||
|
|
||||||
|
use crate::{consts::*, error::Error, yubikey::YubiKey};
|
||||||
|
use getrandom::getrandom;
|
||||||
|
|
||||||
|
/// Cardholder Unique Identifier (CHUID) Template
|
||||||
|
///
|
||||||
|
/// Format defined in SP-800-73-4, Appendix A, Table 9
|
||||||
|
///
|
||||||
|
/// FASC-N containing S9999F9999F999999F0F1F0000000000300001E encoded in
|
||||||
|
/// 4-bit BCD with 1 bit parity. run through the tools/fasc.pl script to get
|
||||||
|
/// bytes. This CHUID has an expiry of 2030-01-01.
|
||||||
|
///
|
||||||
|
/// Defined fields:
|
||||||
|
///
|
||||||
|
/// - 0x30: FASC-N (hard-coded)
|
||||||
|
/// - 0x34: Card UUID / GUID (settable)
|
||||||
|
/// - 0x35: Exp. Date (hard-coded)
|
||||||
|
/// - 0x3e: Signature (hard-coded, empty)
|
||||||
|
/// - 0xfe: Error Detection Code (hard-coded)
|
||||||
|
const CHUID_TMPL: &[u8] = &[
|
||||||
|
0x30, 0x19, 0xd4, 0xe7, 0x39, 0xda, 0x73, 0x9c, 0xed, 0x39, 0xce, 0x73, 0x9d, 0x83, 0x68, 0x58,
|
||||||
|
0x21, 0x08, 0x42, 0x10, 0x84, 0x21, 0xc8, 0x42, 0x10, 0xc3, 0xeb, 0x34, 0x10, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x35, 0x08, 0x32,
|
||||||
|
0x30, 0x33, 0x30, 0x30, 0x31, 0x30, 0x31, 0x3e, 0x00, 0xfe, 0x00,
|
||||||
|
];
|
||||||
|
|
||||||
|
/// Cardholder Unique Identifier (CHUID)
|
||||||
|
#[derive(Copy, Clone, Debug)]
|
||||||
|
pub struct CHUID([u8; YKPIV_CARDID_SIZE]);
|
||||||
|
|
||||||
|
impl CHUID {
|
||||||
|
/// Generate a random Cardholder Unique Identifier (CHUID)
|
||||||
|
pub fn generate() -> Result<Self, Error> {
|
||||||
|
let mut id = [0u8; YKPIV_CARDID_SIZE];
|
||||||
|
getrandom(&mut id).map_err(|_| Error::RandomnessError)?;
|
||||||
|
Ok(CHUID(id))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get Cardholder Unique Identifier (CHUID)
|
||||||
|
pub fn get(yubikey: &mut YubiKey) -> Result<Self, Error> {
|
||||||
|
let txn = yubikey.begin_transaction()?;
|
||||||
|
let response = txn.fetch_object(YKPIV_OBJ_CHUID)?;
|
||||||
|
|
||||||
|
if response.len() != CHUID_TMPL.len() {
|
||||||
|
return Err(Error::GenericError);
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut cardid = [0u8; YKPIV_CARDID_SIZE];
|
||||||
|
cardid.copy_from_slice(&response[CHUID_GUID_OFFS..(CHUID_GUID_OFFS + YKPIV_CARDID_SIZE)]);
|
||||||
|
Ok(CHUID(cardid))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set Cardholder Unique Identifier (CHUID)
|
||||||
|
pub fn set(&self, yubikey: &mut YubiKey) -> Result<(), Error> {
|
||||||
|
let mut buf = CHUID_TMPL.to_vec();
|
||||||
|
buf[CHUID_GUID_OFFS..(CHUID_GUID_OFFS + self.0.len())].copy_from_slice(&self.0);
|
||||||
|
|
||||||
|
let txn = yubikey.begin_transaction()?;
|
||||||
|
txn.save_object(YKPIV_OBJ_CHUID, &buf)
|
||||||
|
}
|
||||||
|
}
|
||||||
+127
@@ -0,0 +1,127 @@
|
|||||||
|
//! YubiKey Configuration Values
|
||||||
|
|
||||||
|
// 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.
|
||||||
|
|
||||||
|
use crate::{consts::*, error::Error, metadata, mgm::MgmType, yubikey::YubiKey};
|
||||||
|
use log::error;
|
||||||
|
use std::convert::TryInto;
|
||||||
|
|
||||||
|
/// Config
|
||||||
|
#[derive(Copy, Clone)]
|
||||||
|
pub struct Config {
|
||||||
|
/// Protected data available
|
||||||
|
protected_data_available: bool,
|
||||||
|
|
||||||
|
/// PUK blocked
|
||||||
|
puk_blocked: bool,
|
||||||
|
|
||||||
|
/// No block on upgrade
|
||||||
|
puk_noblock_on_upgrade: bool,
|
||||||
|
|
||||||
|
/// PIN last changed
|
||||||
|
pin_last_changed: u32,
|
||||||
|
|
||||||
|
/// MGM type
|
||||||
|
mgm_type: MgmType,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Config {
|
||||||
|
/// Get YubiKey config
|
||||||
|
pub fn get(yubikey: &mut YubiKey) -> Result<Config, Error> {
|
||||||
|
let mut config = Config {
|
||||||
|
protected_data_available: false,
|
||||||
|
puk_blocked: false,
|
||||||
|
puk_noblock_on_upgrade: false,
|
||||||
|
pin_last_changed: 0,
|
||||||
|
mgm_type: MgmType::Manual,
|
||||||
|
};
|
||||||
|
|
||||||
|
let txn = yubikey.begin_transaction()?;
|
||||||
|
|
||||||
|
if let Ok(data) = metadata::read(&txn, TAG_ADMIN) {
|
||||||
|
if let Ok(item) = metadata::get_item(&data, TAG_ADMIN_FLAGS_1) {
|
||||||
|
if item.is_empty() {
|
||||||
|
error!("empty response for admin flags metadata item! ignoring");
|
||||||
|
} else {
|
||||||
|
if item[0] & ADMIN_FLAGS_1_PUK_BLOCKED != 0 {
|
||||||
|
config.puk_blocked = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if item[0] & ADMIN_FLAGS_1_PROTECTED_MGM != 0 {
|
||||||
|
config.mgm_type = MgmType::Protected;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if metadata::get_item(&data, TAG_ADMIN_SALT).is_ok() {
|
||||||
|
if config.mgm_type != MgmType::Manual {
|
||||||
|
error!("conflicting types of MGM key administration configured");
|
||||||
|
} else {
|
||||||
|
config.mgm_type = MgmType::Derived;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Ok(item) = metadata::get_item(&data, TAG_ADMIN_TIMESTAMP) {
|
||||||
|
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());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Ok(data) = metadata::read(&txn, TAG_PROTECTED) {
|
||||||
|
config.protected_data_available = true;
|
||||||
|
|
||||||
|
if let Ok(item) = metadata::get_item(&data, TAG_PROTECTED_FLAGS_1) {
|
||||||
|
if item.is_empty() {
|
||||||
|
error!("empty response for protected flags metadata item! ignoring");
|
||||||
|
} else if item[0] & PROTECTED_FLAGS_1_PUK_NOBLOCK != 0 {
|
||||||
|
config.puk_noblock_on_upgrade = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if metadata::get_item(&data, TAG_PROTECTED_MGM).is_ok() {
|
||||||
|
if config.mgm_type != MgmType::Protected {
|
||||||
|
error!(
|
||||||
|
"conflicting types of mgm key administration configured: protected MGM exists"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Always favor protected MGM
|
||||||
|
config.mgm_type = MgmType::Protected;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(config)
|
||||||
|
}
|
||||||
|
}
|
||||||
+29
-5
@@ -36,14 +36,32 @@
|
|||||||
|
|
||||||
pub const szLOG_SOURCE: &str = "yubikey-piv.rs";
|
pub const szLOG_SOURCE: &str = "yubikey-piv.rs";
|
||||||
|
|
||||||
pub const CB_OBJ_MAX: usize = 3063;
|
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_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_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 CB_PIN_MAX: usize = 8;
|
||||||
pub const CB_ECC_POINTP256: usize = 65;
|
|
||||||
pub const CB_ECC_POINTP384: usize = 97;
|
pub const CCC_ID_OFFS: usize = 9;
|
||||||
|
|
||||||
pub const CHUID_GUID_OFFS: usize = 29;
|
pub const CHUID_GUID_OFFS: usize = 29;
|
||||||
|
|
||||||
@@ -51,6 +69,9 @@ pub const CHREF_ACT_CHANGE_PIN: i32 = 0;
|
|||||||
pub const CHREF_ACT_UNBLOCK_PIN: i32 = 1;
|
pub const CHREF_ACT_UNBLOCK_PIN: i32 = 1;
|
||||||
pub const CHREF_ACT_CHANGE_PUK: i32 = 2;
|
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_TYPE_3DES: u8 = 1;
|
||||||
|
|
||||||
pub const DES_LEN_DES: usize = 8;
|
pub const DES_LEN_DES: usize = 8;
|
||||||
@@ -65,6 +86,10 @@ pub const DEVTYPE_NEOr3: u32 = (DEVTYPE_NEO | 0x0000_7233); //"r3"
|
|||||||
pub const DEVTYPE_YK4: u32 = (DEVTYPE_YK | 0x0000_0034); // "4"
|
pub const DEVTYPE_YK4: u32 = (DEVTYPE_YK | 0x0000_0034); // "4"
|
||||||
pub const DEVYTPE_YK5: u32 = (DEVTYPE_YK | 0x0000_0035); // "5"
|
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
|
// sw is status words, see NIST special publication 800-73-4, section 5.6
|
||||||
|
|
||||||
pub const SW_SUCCESS: i32 = 0x9000;
|
pub const SW_SUCCESS: i32 = 0x9000;
|
||||||
@@ -122,6 +147,7 @@ pub const YKPIV_INS_SELECT_APPLICATION: u8 = 0xa4;
|
|||||||
pub const YKPIV_INS_GET_RESPONSE_APDU: u8 = 0xc0;
|
pub const YKPIV_INS_GET_RESPONSE_APDU: u8 = 0xc0;
|
||||||
|
|
||||||
// Yubico vendor specific instructions
|
// Yubico vendor specific instructions
|
||||||
|
// <https://developers.yubico.com/PIV/Introduction/Yubico_extensions.html>
|
||||||
pub const YKPIV_INS_SET_MGMKEY: u8 = 0xff;
|
pub const YKPIV_INS_SET_MGMKEY: u8 = 0xff;
|
||||||
pub const YKPIV_INS_IMPORT_KEY: u8 = 0xfe;
|
pub const YKPIV_INS_IMPORT_KEY: u8 = 0xfe;
|
||||||
pub const YKPIV_INS_GET_VERSION: u8 = 0xfd;
|
pub const YKPIV_INS_GET_VERSION: u8 = 0xfd;
|
||||||
@@ -203,8 +229,6 @@ pub const YKPIV_OBJ_MSROOTS3: u32 = 0x005f_ff13;
|
|||||||
pub const YKPIV_OBJ_MSROOTS4: u32 = 0x005f_ff14;
|
pub const YKPIV_OBJ_MSROOTS4: u32 = 0x005f_ff14;
|
||||||
pub const YKPIV_OBJ_MSROOTS5: u32 = 0x005f_ff15;
|
pub const YKPIV_OBJ_MSROOTS5: u32 = 0x005f_ff15;
|
||||||
|
|
||||||
pub const YKPIV_OBJ_MAX_SIZE: usize = 3072;
|
|
||||||
|
|
||||||
pub const YKPIV_PINPOLICY_TAG: u8 = 0xaa;
|
pub const YKPIV_PINPOLICY_TAG: u8 = 0xaa;
|
||||||
pub const YKPIV_PINPOLICY_DEFAULT: u8 = 0;
|
pub const YKPIV_PINPOLICY_DEFAULT: u8 = 0;
|
||||||
pub const YKPIV_PINPOLICY_NEVER: u8 = 1;
|
pub const YKPIV_PINPOLICY_NEVER: u8 = 1;
|
||||||
|
|||||||
@@ -0,0 +1,227 @@
|
|||||||
|
//! MS Container Map Records
|
||||||
|
//!
|
||||||
|
//! These appear(?) to be defined in Microsoft's Smart Card Minidriver Specification:
|
||||||
|
//! <https://docs.microsoft.com/en-us/previous-versions/windows/hardware/design/dn631754(v=vs.85)>
|
||||||
|
|
||||||
|
// 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.
|
||||||
|
|
||||||
|
use crate::{consts::*, error::Error, key::SlotId, serialization::*, yubikey::YubiKey};
|
||||||
|
use log::error;
|
||||||
|
use std::{
|
||||||
|
convert::{TryFrom, TryInto},
|
||||||
|
fmt::{self, Debug},
|
||||||
|
};
|
||||||
|
|
||||||
|
/// MS Container Map(?) Records
|
||||||
|
#[derive(Copy, Clone)]
|
||||||
|
pub struct Container {
|
||||||
|
/// Container name
|
||||||
|
pub name: [u16; CONTAINER_NAME_LEN],
|
||||||
|
|
||||||
|
/// Card slot
|
||||||
|
pub slot: SlotId,
|
||||||
|
|
||||||
|
/// Key spec
|
||||||
|
pub key_spec: u8,
|
||||||
|
|
||||||
|
/// Key size in bits
|
||||||
|
pub key_size_bits: u16,
|
||||||
|
|
||||||
|
/// Flags
|
||||||
|
pub flags: u8,
|
||||||
|
|
||||||
|
/// PIN ID
|
||||||
|
pub pin_id: u8,
|
||||||
|
|
||||||
|
/// Associated ECHD(?) container (typo of "ecdh" perhaps?)
|
||||||
|
pub associated_echd_container: u8,
|
||||||
|
|
||||||
|
/// Cert fingerprint
|
||||||
|
pub cert_fingerprint: [u8; 20],
|
||||||
|
}
|
||||||
|
|
||||||
|
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 mut containers = vec![];
|
||||||
|
|
||||||
|
if response.len() < CB_OBJ_TAG_MIN {
|
||||||
|
// TODO(tarcieri): is this really OK?
|
||||||
|
return Ok(containers);
|
||||||
|
}
|
||||||
|
|
||||||
|
if response[0] != 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) {
|
||||||
|
containers.push(Container::new(chunk)?);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(containers)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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, &[]);
|
||||||
|
}
|
||||||
|
|
||||||
|
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()
|
||||||
|
{
|
||||||
|
chunk.copy_from_slice(&containers[i].to_bytes());
|
||||||
|
}
|
||||||
|
|
||||||
|
offset += data_len;
|
||||||
|
txn.save_object(YKPIV_OBJ_MSCMAP, &buf[..offset])
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Parse a container record from a byte slice
|
||||||
|
pub fn new(bytes: &[u8]) -> Result<Self, Error> {
|
||||||
|
if bytes.len() != CONTAINER_REC_LEN {
|
||||||
|
error!(
|
||||||
|
"couldn't parse PIV container: expected {}-bytes, got {}-bytes",
|
||||||
|
CONTAINER_REC_LEN,
|
||||||
|
bytes.len()
|
||||||
|
);
|
||||||
|
return Err(Error::ParseError);
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut name = [0u16; CONTAINER_NAME_LEN];
|
||||||
|
let name_bytes_len = CONTAINER_NAME_LEN * 2;
|
||||||
|
|
||||||
|
for (i, chunk) in bytes[..name_bytes_len].chunks_exact(2).enumerate() {
|
||||||
|
name[i] = u16::from_le_bytes(chunk.try_into().unwrap());
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut cert_fingerprint = [0u8; 20];
|
||||||
|
cert_fingerprint.copy_from_slice(&bytes[(bytes.len() - 20)..]);
|
||||||
|
|
||||||
|
Ok(Container {
|
||||||
|
name,
|
||||||
|
slot: bytes[name_bytes_len],
|
||||||
|
key_spec: bytes[name_bytes_len + 1],
|
||||||
|
key_size_bits: u16::from_le_bytes(
|
||||||
|
bytes[(name_bytes_len + 2)..(name_bytes_len + 4)]
|
||||||
|
.try_into()
|
||||||
|
.unwrap(),
|
||||||
|
),
|
||||||
|
flags: bytes[name_bytes_len + 4],
|
||||||
|
pin_id: bytes[name_bytes_len + 5],
|
||||||
|
associated_echd_container: bytes[name_bytes_len + 6],
|
||||||
|
cert_fingerprint,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Parse the container name as a UTF-16 string
|
||||||
|
pub fn parse_name(&self) -> Result<String, Error> {
|
||||||
|
String::from_utf16(&self.name).map_err(|_| Error::ParseError)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Serialize a container record as a byte size
|
||||||
|
pub fn to_bytes(&self) -> [u8; CONTAINER_REC_LEN] {
|
||||||
|
let mut bytes = Vec::with_capacity(CONTAINER_REC_LEN);
|
||||||
|
|
||||||
|
for i in 0..CONTAINER_NAME_LEN {
|
||||||
|
bytes.extend_from_slice(&self.name[i].to_le_bytes());
|
||||||
|
}
|
||||||
|
|
||||||
|
bytes.push(self.slot);
|
||||||
|
bytes.push(self.key_spec);
|
||||||
|
bytes.extend_from_slice(&self.key_size_bits.to_le_bytes());
|
||||||
|
bytes.push(self.flags);
|
||||||
|
bytes.push(self.pin_id);
|
||||||
|
bytes.push(self.associated_echd_container);
|
||||||
|
bytes.extend_from_slice(&self.cert_fingerprint);
|
||||||
|
|
||||||
|
// TODO(tarcieri): use TryInto here when const generics are available
|
||||||
|
let mut result = [0u8; CONTAINER_REC_LEN];
|
||||||
|
result.copy_from_slice(&bytes);
|
||||||
|
result
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Debug for Container {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
write!(
|
||||||
|
f,
|
||||||
|
"PivContainer {{ name: {:?}, slot: {}, key_spec: {}, key_size_bits: {}, \
|
||||||
|
flags: {}, pin_id: {}, associated_echd_container: {}, cert_fingerprint: {:?} }}",
|
||||||
|
&self.name[..],
|
||||||
|
self.slot,
|
||||||
|
self.key_spec,
|
||||||
|
self.key_size_bits,
|
||||||
|
self.flags,
|
||||||
|
self.pin_id,
|
||||||
|
self.associated_echd_container,
|
||||||
|
&self.cert_fingerprint[..]
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> TryFrom<&'a [u8]> for Container {
|
||||||
|
type Error = Error;
|
||||||
|
|
||||||
|
fn try_from(bytes: &'a [u8]) -> Result<Self, Error> {
|
||||||
|
Self::new(bytes)
|
||||||
|
}
|
||||||
|
}
|
||||||
+58
-54
@@ -30,20 +30,19 @@
|
|||||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
|
||||||
use std::fmt;
|
use std::fmt::{self, Display};
|
||||||
|
|
||||||
/// Kinds of errors
|
/// Kinds of errors
|
||||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||||
pub enum ErrorKind {
|
pub enum Error {
|
||||||
/// OK
|
|
||||||
// TODO(tarcieri): replace this with proper result types
|
|
||||||
Ok,
|
|
||||||
|
|
||||||
/// Memory error
|
/// Memory error
|
||||||
MemoryError,
|
MemoryError,
|
||||||
|
|
||||||
/// PCSC error
|
/// PCSC error
|
||||||
PcscError,
|
PcscError {
|
||||||
|
/// Original PC/SC error
|
||||||
|
inner: Option<pcsc::Error>,
|
||||||
|
},
|
||||||
|
|
||||||
/// Size error
|
/// Size error
|
||||||
SizeError,
|
SizeError,
|
||||||
@@ -67,7 +66,10 @@ pub enum ErrorKind {
|
|||||||
ParseError,
|
ParseError,
|
||||||
|
|
||||||
/// Wrong PIN
|
/// Wrong PIN
|
||||||
WrongPin,
|
WrongPin {
|
||||||
|
/// Number of tries remaining
|
||||||
|
tries: u32,
|
||||||
|
},
|
||||||
|
|
||||||
/// Invalid object
|
/// Invalid object
|
||||||
InvalidObject,
|
InvalidObject,
|
||||||
@@ -88,73 +90,75 @@ pub enum ErrorKind {
|
|||||||
NotSupported,
|
NotSupported,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ErrorKind {
|
impl Error {
|
||||||
/// Name of the error.
|
/// Name of the error.
|
||||||
///
|
///
|
||||||
/// These names map to the legacy names from the Yubico C library, to
|
/// These names map to the legacy names from the Yubico C library, to
|
||||||
/// assist in web searches for relevant information for these errors.
|
/// assist in web searches for relevant information for these errors.
|
||||||
pub fn name(self) -> &'static str {
|
pub fn name(self) -> &'static str {
|
||||||
match self {
|
match self {
|
||||||
ErrorKind::Ok => "YKPIV_OK",
|
Error::MemoryError => "YKPIV_MEMORY_ERROR",
|
||||||
ErrorKind::MemoryError => "YKPIV_MEMORY_ERROR",
|
Error::PcscError { .. } => "YKPIV_PCSC_ERROR",
|
||||||
ErrorKind::PcscError => "YKPIV_PCSC_ERROR",
|
Error::SizeError => "YKPIV_SIZE_ERROR",
|
||||||
ErrorKind::SizeError => "YKPIV_SIZE_ERROR",
|
Error::AppletError => "YKPIV_APPLET_ERROR",
|
||||||
ErrorKind::AppletError => "YKPIV_APPLET_ERROR",
|
Error::AuthenticationError => "YKPIV_AUTHENTICATION_ERROR",
|
||||||
ErrorKind::AuthenticationError => "YKPIV_AUTHENTICATION_ERROR",
|
Error::RandomnessError => "YKPIV_RANDOMNESS_ERROR",
|
||||||
ErrorKind::RandomnessError => "YKPIV_RANDOMNESS_ERROR",
|
Error::GenericError => "YKPIV_GENERIC_ERROR",
|
||||||
ErrorKind::GenericError => "YKPIV_GENERIC_ERROR",
|
Error::KeyError => "YKPIV_KEY_ERROR",
|
||||||
ErrorKind::KeyError => "YKPIV_KEY_ERROR",
|
Error::ParseError => "YKPIV_PARSE_ERROR",
|
||||||
ErrorKind::ParseError => "YKPIV_PARSE_ERROR",
|
Error::WrongPin { .. } => "YKPIV_WRONG_PIN",
|
||||||
ErrorKind::WrongPin => "YKPIV_WRONG_PIN",
|
Error::InvalidObject => "YKPIV_INVALID_OBJECT",
|
||||||
ErrorKind::InvalidObject => "YKPIV_INVALID_OBJECT",
|
Error::AlgorithmError => "YKPIV_ALGORITHM_ERROR",
|
||||||
ErrorKind::AlgorithmError => "YKPIV_ALGORITHM_ERROR",
|
Error::PinLocked => "YKPIV_PIN_LOCKED",
|
||||||
ErrorKind::PinLocked => "YKPIV_PIN_LOCKED",
|
Error::ArgumentError => "YKPIV_ARGUMENT_ERROR",
|
||||||
ErrorKind::ArgumentError => "YKPIV_ARGUMENT_ERROR",
|
Error::RangeError => "YKPIV_RANGE_ERROR",
|
||||||
ErrorKind::RangeError => "YKPIV_RANGE_ERROR",
|
Error::NotSupported => "YKPIV_NOT_SUPPORTED",
|
||||||
ErrorKind::NotSupported => "YKPIV_NOT_SUPPORTED",
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Error message
|
/// Error message
|
||||||
pub fn msg(self) -> &'static str {
|
pub fn msg(self) -> &'static str {
|
||||||
match self {
|
match self {
|
||||||
ErrorKind::Ok => "OK",
|
Error::MemoryError => "memory error",
|
||||||
ErrorKind::MemoryError => "memory error",
|
Error::PcscError { .. } => "PCSC error",
|
||||||
ErrorKind::PcscError => "PCSC error",
|
Error::SizeError => "size error",
|
||||||
ErrorKind::SizeError => "size error",
|
Error::AppletError => "applet error",
|
||||||
ErrorKind::AppletError => "applet error",
|
Error::AuthenticationError => "authentication error",
|
||||||
ErrorKind::AuthenticationError => "authentication error",
|
Error::RandomnessError => "randomness error",
|
||||||
ErrorKind::RandomnessError => "randomness error",
|
Error::GenericError => "generic error",
|
||||||
ErrorKind::GenericError => "generic error",
|
Error::KeyError => "key error",
|
||||||
ErrorKind::KeyError => "key error",
|
Error::ParseError => "parse error",
|
||||||
ErrorKind::ParseError => "parse error",
|
Error::WrongPin { .. } => "wrong pin",
|
||||||
ErrorKind::WrongPin => "wrong pin",
|
Error::InvalidObject => "invalid object",
|
||||||
ErrorKind::InvalidObject => "invalid object",
|
Error::AlgorithmError => "algorithm error",
|
||||||
ErrorKind::AlgorithmError => "algorithm error",
|
Error::PinLocked => "PIN locked",
|
||||||
ErrorKind::PinLocked => "PIN locked",
|
Error::ArgumentError => "argument error",
|
||||||
ErrorKind::ArgumentError => "argument error",
|
Error::RangeError => "range error",
|
||||||
ErrorKind::RangeError => "range error",
|
Error::NotSupported => "not supported",
|
||||||
ErrorKind::NotSupported => "not supported",
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Display for ErrorKind {
|
impl Display for Error {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
f.write_str(self.msg())
|
f.write_str(self.msg())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl std::error::Error for ErrorKind {}
|
impl From<pcsc::Error> for Error {
|
||||||
|
fn from(err: pcsc::Error) -> Error {
|
||||||
/// Get a string representation of this error
|
Error::PcscError { inner: Some(err) }
|
||||||
// TODO(tarcieri): completely replace this with `Display`
|
}
|
||||||
pub fn ykpiv_strerror(err: ErrorKind) -> &'static str {
|
|
||||||
err.msg()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get the name of this error
|
impl std::error::Error for Error {
|
||||||
// TODO(tarcieri): completely replace this with debug
|
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
|
||||||
pub fn ykpiv_strerror_name(err: ErrorKind) -> &'static str {
|
match self {
|
||||||
err.name()
|
#[allow(trivial_casts)] // why doesn't this work without the cast???
|
||||||
|
Error::PcscError { inner } => inner
|
||||||
|
.as_ref()
|
||||||
|
.map(|err| err as &(dyn std::error::Error + 'static)),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
-427
@@ -1,427 +0,0 @@
|
|||||||
//! Internal functions (mostly 3DES)
|
|
||||||
|
|
||||||
// TODO(tarcieri): replace OpenSSL extern "C" invocations with `des` crate
|
|
||||||
// - crate: https://crates.io/crates/des
|
|
||||||
// - docs: https://docs.rs/des/0
|
|
||||||
|
|
||||||
// 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): investigate and remove dead code
|
|
||||||
#![allow(non_upper_case_globals, dead_code)]
|
|
||||||
#![allow(clippy::missing_safety_doc)]
|
|
||||||
|
|
||||||
use crate::consts::*;
|
|
||||||
use libc::{
|
|
||||||
c_char, c_int, fclose, feof, fgets, fopen, free, getenv, malloc, memcpy, memset, sscanf,
|
|
||||||
strcasecmp, strcmp,
|
|
||||||
};
|
|
||||||
use std::{
|
|
||||||
ffi::{CStr, CString},
|
|
||||||
mem,
|
|
||||||
os::raw::c_void,
|
|
||||||
};
|
|
||||||
|
|
||||||
extern "C" {
|
|
||||||
fn DES_ecb3_encrypt(
|
|
||||||
input: *mut [u8; 8],
|
|
||||||
output: *mut [u8; 8],
|
|
||||||
ks1: *mut DesSubKey,
|
|
||||||
ks2: *mut DesSubKey,
|
|
||||||
ks3: *mut DesSubKey,
|
|
||||||
enc: i32,
|
|
||||||
);
|
|
||||||
fn DES_is_weak_key(key: *mut [u8; 8]) -> i32;
|
|
||||||
fn DES_set_key_unchecked(key: *mut [u8; 8], schedule: *mut DesSubKey);
|
|
||||||
fn PKCS5_PBKDF2_HMAC_SHA1(
|
|
||||||
pass: *const u8,
|
|
||||||
passlen: i32,
|
|
||||||
salt: *const u8,
|
|
||||||
saltlen: i32,
|
|
||||||
iter: i32,
|
|
||||||
keylen: i32,
|
|
||||||
out: *mut u8,
|
|
||||||
) -> i32;
|
|
||||||
fn RAND_bytes(buf: *mut u8, num: i32) -> i32;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// DES-related errors
|
|
||||||
#[allow(non_camel_case_types)]
|
|
||||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
|
||||||
#[repr(i32)]
|
|
||||||
pub enum DesErrorKind {
|
|
||||||
/// Ok
|
|
||||||
Ok = 0,
|
|
||||||
|
|
||||||
/// Invalid parameter
|
|
||||||
InvalidParameter = -1,
|
|
||||||
|
|
||||||
/// Buffer too small
|
|
||||||
BufferTooSmall = -2,
|
|
||||||
|
|
||||||
/// Memory error
|
|
||||||
MemoryError = -3,
|
|
||||||
|
|
||||||
/// General error
|
|
||||||
GeneralError = -4,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 3DES subkeys
|
|
||||||
#[derive(Copy, Clone)]
|
|
||||||
#[repr(C)]
|
|
||||||
pub struct DesSubKey([u8; 16]);
|
|
||||||
|
|
||||||
/// 3DES keys
|
|
||||||
#[derive(Copy, Clone)]
|
|
||||||
pub struct DesKey {
|
|
||||||
/// subkey 1
|
|
||||||
pub ks1: DesSubKey,
|
|
||||||
|
|
||||||
/// subkey 2
|
|
||||||
pub ks2: DesSubKey,
|
|
||||||
|
|
||||||
/// subkey 3
|
|
||||||
pub ks3: DesSubKey,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Import DES key
|
|
||||||
pub unsafe fn des_import_key(
|
|
||||||
key_type: i32,
|
|
||||||
keyraw: *const u8,
|
|
||||||
keyrawlen: usize,
|
|
||||||
key: *mut *mut DesKey,
|
|
||||||
) -> DesErrorKind {
|
|
||||||
let mut key_tmp = [0u8; 8];
|
|
||||||
let cb_expectedkey: usize;
|
|
||||||
let cb_keysize: usize;
|
|
||||||
|
|
||||||
if key_type != DES_TYPE_3DES as i32 {
|
|
||||||
return DesErrorKind::InvalidParameter;
|
|
||||||
}
|
|
||||||
|
|
||||||
cb_expectedkey = (8i32 * 3i32) as (usize);
|
|
||||||
cb_keysize = 8usize;
|
|
||||||
|
|
||||||
if cb_keysize > 8 {
|
|
||||||
return DesErrorKind::MemoryError;
|
|
||||||
}
|
|
||||||
|
|
||||||
if key.is_null() || keyraw.is_null() || keyrawlen != cb_expectedkey {
|
|
||||||
return DesErrorKind::InvalidParameter;
|
|
||||||
}
|
|
||||||
|
|
||||||
*key = malloc(mem::size_of::<DesKey>()) as (*mut DesKey);
|
|
||||||
|
|
||||||
if (*key).is_null() {
|
|
||||||
return DesErrorKind::MemoryError;
|
|
||||||
}
|
|
||||||
|
|
||||||
memset(*key as (*mut c_void), 0i32, mem::size_of::<DesKey>());
|
|
||||||
|
|
||||||
memcpy(
|
|
||||||
key_tmp.as_mut_ptr() as (*mut c_void),
|
|
||||||
keyraw as (*const c_void),
|
|
||||||
cb_keysize,
|
|
||||||
);
|
|
||||||
|
|
||||||
DES_set_key_unchecked(&mut key_tmp, &mut (**key).ks1);
|
|
||||||
|
|
||||||
memcpy(
|
|
||||||
key_tmp.as_mut_ptr() as (*mut c_void),
|
|
||||||
keyraw.add(cb_keysize) as (*const c_void),
|
|
||||||
cb_keysize,
|
|
||||||
);
|
|
||||||
|
|
||||||
DES_set_key_unchecked(&mut key_tmp, &mut (**key).ks2);
|
|
||||||
|
|
||||||
memcpy(
|
|
||||||
key_tmp.as_mut_ptr() as (*mut c_void),
|
|
||||||
keyraw.add(2usize.wrapping_mul(cb_keysize)) as (*const c_void),
|
|
||||||
cb_keysize,
|
|
||||||
);
|
|
||||||
|
|
||||||
DES_set_key_unchecked(&mut key_tmp, &mut (**key).ks3);
|
|
||||||
|
|
||||||
DesErrorKind::Ok
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Destroy DES key
|
|
||||||
pub unsafe fn des_destroy_key(key: *mut DesKey) -> DesErrorKind {
|
|
||||||
if !key.is_null() {
|
|
||||||
free(key as (*mut c_void));
|
|
||||||
}
|
|
||||||
|
|
||||||
DesErrorKind::Ok
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Encrypt with DES key
|
|
||||||
pub unsafe fn des_encrypt(
|
|
||||||
key: *mut DesKey,
|
|
||||||
input: *const u8,
|
|
||||||
inputlen: usize,
|
|
||||||
out: *mut u8,
|
|
||||||
outlen: *mut usize,
|
|
||||||
) -> DesErrorKind {
|
|
||||||
if key.is_null() || outlen.is_null() || *outlen < inputlen || input.is_null() || out.is_null() {
|
|
||||||
return DesErrorKind::InvalidParameter;
|
|
||||||
}
|
|
||||||
|
|
||||||
DES_ecb3_encrypt(
|
|
||||||
input as *mut [u8; 8],
|
|
||||||
out as *mut [u8; 8],
|
|
||||||
&mut (*key).ks1,
|
|
||||||
&mut (*key).ks2,
|
|
||||||
&mut (*key).ks3,
|
|
||||||
1,
|
|
||||||
);
|
|
||||||
|
|
||||||
DesErrorKind::Ok
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Decrypt with DES key
|
|
||||||
pub unsafe fn des_decrypt(
|
|
||||||
key: *mut DesKey,
|
|
||||||
in_: *const u8,
|
|
||||||
inlen: usize,
|
|
||||||
out: *mut u8,
|
|
||||||
outlen: *mut usize,
|
|
||||||
) -> DesErrorKind {
|
|
||||||
if key.is_null() || outlen.is_null() || *outlen < inlen || in_.is_null() || out.is_null() {
|
|
||||||
return DesErrorKind::InvalidParameter;
|
|
||||||
}
|
|
||||||
|
|
||||||
DES_ecb3_encrypt(
|
|
||||||
in_ as *mut [u8; 8],
|
|
||||||
out as *mut [u8; 8],
|
|
||||||
&mut (*key).ks1,
|
|
||||||
&mut (*key).ks2,
|
|
||||||
&mut (*key).ks3,
|
|
||||||
0,
|
|
||||||
);
|
|
||||||
|
|
||||||
DesErrorKind::Ok
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Is the given DES key weak?
|
|
||||||
pub unsafe fn yk_des_is_weak_key(key: *const u8, _cb_key: usize) -> bool {
|
|
||||||
DES_is_weak_key(key as (*mut [u8; 8])) != 0
|
|
||||||
}
|
|
||||||
|
|
||||||
/// PRNG errors/results
|
|
||||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
|
||||||
#[repr(i32)]
|
|
||||||
pub enum PRngErrorKind {
|
|
||||||
/// Ok
|
|
||||||
Ok = 0,
|
|
||||||
|
|
||||||
/// General error
|
|
||||||
GeneralError = -1,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Generate bytes with the PRNG
|
|
||||||
pub unsafe fn _ykpiv_prng_generate(buffer: *mut u8, cb_req: usize) -> PRngErrorKind {
|
|
||||||
if RAND_bytes(buffer, cb_req as (i32)) != -1 {
|
|
||||||
PRngErrorKind::Ok
|
|
||||||
} else {
|
|
||||||
PRngErrorKind::GeneralError
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// PKCS#5 error types
|
|
||||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
|
||||||
#[repr(i32)]
|
|
||||||
pub enum Pkcs5ErrorKind {
|
|
||||||
/// OK
|
|
||||||
Ok = 0,
|
|
||||||
|
|
||||||
/// General error
|
|
||||||
GeneralError = -1,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Decrypt a PKCS#5 key
|
|
||||||
pub unsafe fn pkcs5_pbkdf2_sha1(
|
|
||||||
password: *const u8,
|
|
||||||
cb_password: usize,
|
|
||||||
salt: *const u8,
|
|
||||||
cb_salt: usize,
|
|
||||||
iterations: usize,
|
|
||||||
key: *const u8,
|
|
||||||
cb_key: usize,
|
|
||||||
) -> Pkcs5ErrorKind {
|
|
||||||
PKCS5_PBKDF2_HMAC_SHA1(
|
|
||||||
password,
|
|
||||||
cb_password as (i32),
|
|
||||||
salt,
|
|
||||||
cb_salt as (i32),
|
|
||||||
iterations as (i32),
|
|
||||||
cb_key as (i32),
|
|
||||||
key as (*mut u8),
|
|
||||||
);
|
|
||||||
|
|
||||||
Pkcs5ErrorKind::Ok
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Strip whitespace
|
|
||||||
// TODO(tarcieri): implement this
|
|
||||||
pub unsafe fn _strip_ws(sz: *mut c_char) -> *mut c_char {
|
|
||||||
sz
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Source of how a setting was configured
|
|
||||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
|
||||||
pub enum SettingSource {
|
|
||||||
/// User-specified setting
|
|
||||||
User,
|
|
||||||
|
|
||||||
/// Admin-specified setting
|
|
||||||
Admin,
|
|
||||||
|
|
||||||
/// Default setting
|
|
||||||
Default,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Setting booleans
|
|
||||||
#[derive(Copy, Clone, Debug)]
|
|
||||||
pub struct SettingBool {
|
|
||||||
/// Boolean value
|
|
||||||
pub value: bool,
|
|
||||||
|
|
||||||
/// Source of the configuration setting (user/admin/default)
|
|
||||||
pub source: SettingSource,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get a boolean config value
|
|
||||||
pub unsafe fn _get_bool_config(sz_setting: *const c_char) -> SettingBool {
|
|
||||||
let mut setting: SettingBool = SettingBool {
|
|
||||||
value: false,
|
|
||||||
source: SettingSource::Default,
|
|
||||||
};
|
|
||||||
let mut sz_line = [0u8; 256];
|
|
||||||
let mut psz_name: *mut c_char;
|
|
||||||
let mut psz_value: *mut c_char;
|
|
||||||
let mut sz_name = [0u8; 256];
|
|
||||||
let mut sz_value = [0u8; 256];
|
|
||||||
|
|
||||||
let pf = fopen(
|
|
||||||
b"/etc/yubico/yubikeypiv.conf\0".as_ptr() as *const c_char,
|
|
||||||
b"r\0".as_ptr() as *const c_char,
|
|
||||||
);
|
|
||||||
|
|
||||||
if pf.is_null() {
|
|
||||||
return setting;
|
|
||||||
}
|
|
||||||
|
|
||||||
while feof(pf) == 0 {
|
|
||||||
if fgets(
|
|
||||||
sz_line.as_mut_ptr() as *mut c_char,
|
|
||||||
sz_line.len() as c_int,
|
|
||||||
pf,
|
|
||||||
)
|
|
||||||
.is_null()
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if sz_line[0] == b'#' {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if sz_line[0] == b'\r' {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if sz_line[0] == b'\n' {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if sscanf(
|
|
||||||
sz_line.as_ptr() as *const c_char,
|
|
||||||
b"%255[^=]=%255s\0".as_ptr() as *const c_char,
|
|
||||||
sz_name.as_mut_ptr(),
|
|
||||||
sz_value.as_mut_ptr(),
|
|
||||||
) != 2
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
psz_name = _strip_ws(sz_name.as_mut_ptr() as *mut c_char);
|
|
||||||
|
|
||||||
if strcasecmp(psz_name, sz_setting) != 0 {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
psz_value = _strip_ws(sz_value.as_mut_ptr() as *mut c_char);
|
|
||||||
setting.source = SettingSource::Admin;
|
|
||||||
setting.value = strcmp(psz_value, b"1\0".as_ptr() as *const c_char) == 0
|
|
||||||
|| strcasecmp(psz_value, b"true\0".as_ptr() as *const c_char) == 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
fclose(pf);
|
|
||||||
setting
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get a setting boolean from an environment variable
|
|
||||||
pub unsafe fn _get_bool_env(sz_setting: *const c_char) -> SettingBool {
|
|
||||||
let mut setting: SettingBool = SettingBool {
|
|
||||||
value: false,
|
|
||||||
source: SettingSource::Default,
|
|
||||||
};
|
|
||||||
|
|
||||||
let sz_name = CString::new(format!(
|
|
||||||
"YUBIKEY_PIV_{}",
|
|
||||||
CStr::from_ptr(sz_setting).to_string_lossy()
|
|
||||||
))
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
let psz_value = getenv(sz_name.as_ptr());
|
|
||||||
|
|
||||||
if !psz_value.is_null() {
|
|
||||||
setting.source = SettingSource::User;
|
|
||||||
setting.value = strcmp(psz_value, b"1\0".as_ptr() as *const c_char) == 0
|
|
||||||
|| strcasecmp(psz_value, b"true\0".as_ptr() as *const c_char) == 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
setting
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get a setting boolean
|
|
||||||
pub unsafe fn setting_get_bool(sz_setting: *const c_char, def: bool) -> SettingBool {
|
|
||||||
let mut setting = _get_bool_config(sz_setting);
|
|
||||||
|
|
||||||
if setting.source == SettingSource::Default {
|
|
||||||
setting = _get_bool_env(sz_setting);
|
|
||||||
}
|
|
||||||
|
|
||||||
if setting.source == SettingSource::Default {
|
|
||||||
setting.value = def;
|
|
||||||
}
|
|
||||||
|
|
||||||
setting
|
|
||||||
}
|
|
||||||
+375
@@ -0,0 +1,375 @@
|
|||||||
|
//! PIV cryptographic keys stored in a YubiKey.
|
||||||
|
//!
|
||||||
|
//! Supported algorithms:
|
||||||
|
//!
|
||||||
|
//! - **Encryption**: `RSA1024`, `RSA2048`, `ECCP256`, `ECCP384`
|
||||||
|
//! - **Signatures**:
|
||||||
|
//! - RSASSA-PKCS#1v1.5: `RSA1024`, `RSA2048`
|
||||||
|
//! - ECDSA: `ECCP256`, `ECCP384`
|
||||||
|
|
||||||
|
// 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.
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
certificate::{self, Certificate},
|
||||||
|
consts::*,
|
||||||
|
error::Error,
|
||||||
|
response::StatusWords,
|
||||||
|
serialization::*,
|
||||||
|
settings,
|
||||||
|
yubikey::YubiKey,
|
||||||
|
AlgorithmId, ObjectId,
|
||||||
|
};
|
||||||
|
use log::{debug, error, warn};
|
||||||
|
|
||||||
|
/// Slot identifiers.
|
||||||
|
/// <https://developers.yubico.com/PIV/Introduction/Certificate_slots.html>
|
||||||
|
// TODO(tarcieri): replace these with enums
|
||||||
|
pub type SlotId = u8;
|
||||||
|
|
||||||
|
/// Get the [`ObjectId`] that corresponds to a given [`SlotId`]
|
||||||
|
// TODO(tarcieri): factor this into a slot ID enum
|
||||||
|
pub(crate) fn slot_object(slot: SlotId) -> Result<ObjectId, Error> {
|
||||||
|
let id = match slot {
|
||||||
|
YKPIV_KEY_AUTHENTICATION => YKPIV_OBJ_AUTHENTICATION,
|
||||||
|
YKPIV_KEY_SIGNATURE => YKPIV_OBJ_SIGNATURE,
|
||||||
|
YKPIV_KEY_KEYMGM => YKPIV_OBJ_KEY_MANAGEMENT,
|
||||||
|
YKPIV_KEY_CARDAUTH => YKPIV_OBJ_CARD_AUTH,
|
||||||
|
YKPIV_KEY_ATTESTATION => YKPIV_OBJ_ATTESTATION,
|
||||||
|
slot if slot >= YKPIV_KEY_RETIRED1 && (slot <= YKPIV_KEY_RETIRED20) => {
|
||||||
|
YKPIV_OBJ_RETIRED1 + (slot - YKPIV_KEY_RETIRED1) as u32
|
||||||
|
}
|
||||||
|
_ => return Err(Error::InvalidObject),
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(id)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Personal Identity Verification (PIV) key slots
|
||||||
|
pub const SLOTS: [u8; 24] = [
|
||||||
|
YKPIV_KEY_AUTHENTICATION,
|
||||||
|
YKPIV_KEY_SIGNATURE,
|
||||||
|
YKPIV_KEY_KEYMGM,
|
||||||
|
YKPIV_KEY_RETIRED1,
|
||||||
|
YKPIV_KEY_RETIRED2,
|
||||||
|
YKPIV_KEY_RETIRED3,
|
||||||
|
YKPIV_KEY_RETIRED4,
|
||||||
|
YKPIV_KEY_RETIRED5,
|
||||||
|
YKPIV_KEY_RETIRED6,
|
||||||
|
YKPIV_KEY_RETIRED7,
|
||||||
|
YKPIV_KEY_RETIRED8,
|
||||||
|
YKPIV_KEY_RETIRED9,
|
||||||
|
YKPIV_KEY_RETIRED10,
|
||||||
|
YKPIV_KEY_RETIRED11,
|
||||||
|
YKPIV_KEY_RETIRED12,
|
||||||
|
YKPIV_KEY_RETIRED13,
|
||||||
|
YKPIV_KEY_RETIRED14,
|
||||||
|
YKPIV_KEY_RETIRED15,
|
||||||
|
YKPIV_KEY_RETIRED16,
|
||||||
|
YKPIV_KEY_RETIRED17,
|
||||||
|
YKPIV_KEY_RETIRED18,
|
||||||
|
YKPIV_KEY_RETIRED19,
|
||||||
|
YKPIV_KEY_RETIRED20,
|
||||||
|
YKPIV_KEY_CARDAUTH,
|
||||||
|
];
|
||||||
|
|
||||||
|
/// PIV cryptographic keys stored in a YubiKey
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub struct Key {
|
||||||
|
/// Card slot
|
||||||
|
slot: SlotId,
|
||||||
|
|
||||||
|
/// Cert
|
||||||
|
cert: Certificate,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Key {
|
||||||
|
/// List Personal Identity Verification (PIV) keys stored in a YubiKey
|
||||||
|
pub fn list(yubikey: &mut YubiKey) -> Result<Vec<Self>, Error> {
|
||||||
|
let mut keys = vec![];
|
||||||
|
let txn = yubikey.begin_transaction()?;
|
||||||
|
|
||||||
|
for slot in SLOTS.iter().cloned() {
|
||||||
|
let buf = match certificate::read_certificate(&txn, slot) {
|
||||||
|
Ok(b) => b,
|
||||||
|
Err(e) => {
|
||||||
|
debug!("error reading certificate in slot {}: {}", slot, e);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let cert = Certificate::new(buf)?;
|
||||||
|
keys.push(Key { slot, cert });
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(keys)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the slot ID for this key
|
||||||
|
pub fn slot(&self) -> SlotId {
|
||||||
|
self.slot
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the certificate for this key
|
||||||
|
pub fn certificate(&self) -> &Certificate {
|
||||||
|
&self.cert
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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, YKPIV_INS_GENERATE_ASYMMETRIC, 0, 0];
|
||||||
|
let setting_roca: settings::BoolValue;
|
||||||
|
|
||||||
|
if yubikey.device_model() == DEVTYPE_YK4
|
||||||
|
&& (algorithm == YKPIV_ALGO_RSA1024 || algorithm == YKPIV_ALGO_RSA2048)
|
||||||
|
&& yubikey.version.major == 4
|
||||||
|
&& (yubikey.version.minor < 3 || yubikey.version.minor == 3 && (yubikey.version.patch < 5))
|
||||||
|
{
|
||||||
|
setting_roca = settings::BoolValue::get(SZ_SETTING_ROCA, true);
|
||||||
|
|
||||||
|
let psz_msg = match setting_roca.source {
|
||||||
|
settings::Source::User => {
|
||||||
|
if setting_roca.value {
|
||||||
|
SZ_ROCA_ALLOW_USER
|
||||||
|
} else {
|
||||||
|
SZ_ROCA_BLOCK_USER
|
||||||
|
}
|
||||||
|
}
|
||||||
|
settings::Source::Admin => {
|
||||||
|
if setting_roca.value {
|
||||||
|
SZ_ROCA_ALLOW_ADMIN
|
||||||
|
} else {
|
||||||
|
SZ_ROCA_BLOCK_ADMIN
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => SZ_ROCA_DEFAULT,
|
||||||
|
};
|
||||||
|
|
||||||
|
warn!(
|
||||||
|
"YubiKey serial number {} is affected by vulnerability CVE-2017-15361 \
|
||||||
|
(ROCA) and should be replaced. On-chip key generation {} See \
|
||||||
|
YSA-2017-01 <https://www.yubico.com/support/security-advisories/ysa-2017-01/> \
|
||||||
|
for additional information on device replacement and mitigation assistance",
|
||||||
|
yubikey.serial, psz_msg
|
||||||
|
);
|
||||||
|
|
||||||
|
if !setting_roca.value {
|
||||||
|
return Err(Error::NotSupported);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
match algorithm {
|
||||||
|
YKPIV_ALGO_RSA1024 | YKPIV_ALGO_RSA2048 | YKPIV_ALGO_ECCP256 | YKPIV_ALGO_ECCP384 => (),
|
||||||
|
_ => {
|
||||||
|
error!("invalid algorithm specified");
|
||||||
|
return Err(Error::GenericError);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let txn = yubikey.begin_transaction()?;
|
||||||
|
|
||||||
|
templ[3] = slot;
|
||||||
|
|
||||||
|
let mut offset = 5;
|
||||||
|
in_data[..offset].copy_from_slice(&[
|
||||||
|
0xac,
|
||||||
|
3, // length sans this 2-byte header
|
||||||
|
YKPIV_ALGO_TAG,
|
||||||
|
1,
|
||||||
|
algorithm,
|
||||||
|
]);
|
||||||
|
|
||||||
|
if in_data[4] == 0 {
|
||||||
|
error!("unexpected algorithm");
|
||||||
|
return Err(Error::AlgorithmError);
|
||||||
|
}
|
||||||
|
|
||||||
|
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 response = txn.transfer_data(&templ, &in_data[..offset], 1024)?;
|
||||||
|
|
||||||
|
if !response.is_success() {
|
||||||
|
let err_msg = "failed to generate new key";
|
||||||
|
|
||||||
|
match response.status_words() {
|
||||||
|
StatusWords::IncorrectSlotError => {
|
||||||
|
error!("{} (incorrect slot)", err_msg);
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Err(Error::AlgorithmError);
|
||||||
|
}
|
||||||
|
StatusWords::SecurityStatusError => {
|
||||||
|
error!("{} (not authenticated)", err_msg);
|
||||||
|
return Err(Error::AuthenticationError);
|
||||||
|
}
|
||||||
|
other => {
|
||||||
|
error!("{} (error {:x})", err_msg, other.code());
|
||||||
|
return Err(Error::GenericError);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let data = response.into_buffer();
|
||||||
|
|
||||||
|
match algorithm {
|
||||||
|
YKPIV_ALGO_RSA1024 | YKPIV_ALGO_RSA2048 => {
|
||||||
|
let mut offset = 5;
|
||||||
|
let mut len = 0;
|
||||||
|
|
||||||
|
if data[offset] != TAG_RSA_MODULUS {
|
||||||
|
error!("Failed to parse public key structure (modulus)");
|
||||||
|
return Err(Error::ParseError);
|
||||||
|
}
|
||||||
|
|
||||||
|
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 {
|
||||||
|
error!("failed to parse public key structure (public exponent)");
|
||||||
|
return Err(Error::ParseError);
|
||||||
|
}
|
||||||
|
|
||||||
|
offset += 1;
|
||||||
|
offset += get_length(&data[offset..], &mut len);
|
||||||
|
let exp = data[offset..(offset + len)].to_vec();
|
||||||
|
Ok(GeneratedKey::Rsa {
|
||||||
|
algorithm,
|
||||||
|
modulus,
|
||||||
|
exp,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
YKPIV_ALGO_ECCP256 | YKPIV_ALGO_ECCP384 => {
|
||||||
|
let mut offset = 3;
|
||||||
|
|
||||||
|
let len = if algorithm == YKPIV_ALGO_ECCP256 {
|
||||||
|
CB_ECC_POINTP256
|
||||||
|
} else {
|
||||||
|
CB_ECC_POINTP384
|
||||||
|
};
|
||||||
|
|
||||||
|
if data[offset] != 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 {
|
||||||
|
error!("unexpected length");
|
||||||
|
return Err(Error::AlgorithmError);
|
||||||
|
}
|
||||||
|
|
||||||
|
let point = data[offset..(offset + len)].to_vec();
|
||||||
|
Ok(GeneratedKey::Ecc { algorithm, point })
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
error!("wrong algorithm");
|
||||||
|
Err(Error::AlgorithmError)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+107
-24
@@ -1,15 +1,30 @@
|
|||||||
//! [YubiKey][1] PIV: [Personal Identity Verification][2] support for
|
//! [YubiKey] PIV: [Personal Identity Verification][PIV] support for
|
||||||
//! [Yubico][3] devices using the Chip Card Interface Device ([CCID][4])
|
//! [Yubico] devices using the Personal Computer/Smart Card ([PC/SC])
|
||||||
//! protocol.
|
//! interface as provided by the [`pcsc` crate].
|
||||||
//!
|
//!
|
||||||
//! **PIV** is a [NIST][5] standard for both *signing* and *encryption*
|
//! **PIV** is a [NIST] standard for both *signing* and *encryption*
|
||||||
//! using SmartCards and SmartCard-based hardware tokens like YubiKeys.
|
//! using SmartCards and SmartCard-based hardware tokens like YubiKeys.
|
||||||
//!
|
//!
|
||||||
//! This library natively implements the CCID protocol used to manage and
|
//! This library natively implements the protocol used to manage and
|
||||||
//! utilize PIV encryption and signing keys which can be generated, imported,
|
//! utilize PIV encryption and signing keys which can be generated, imported,
|
||||||
//! and stored on YubiKey devices.
|
//! and stored on YubiKey devices.
|
||||||
//!
|
//!
|
||||||
//! Supported algorithms:
|
//! See [Yubico's guide to PIV-enabled YubiKeys][yk-guide] for more information
|
||||||
|
//! on which devices support PIV and the available functionality.
|
||||||
|
//!
|
||||||
|
//! ## Minimum Supported Rust Version
|
||||||
|
//!
|
||||||
|
//! Rust 1.39+
|
||||||
|
//!
|
||||||
|
//! ## Supported YubiKeys
|
||||||
|
//!
|
||||||
|
//! - [YubiKey NEO] series
|
||||||
|
//! - [YubiKey 4] series
|
||||||
|
//! - [YubiKey 5] series
|
||||||
|
//!
|
||||||
|
//! NOTE: Nano and USB-C variants of the above are also supported
|
||||||
|
//!
|
||||||
|
//! ## Supported Algorithms
|
||||||
//!
|
//!
|
||||||
//! - **Authentication**: `3DES`
|
//! - **Authentication**: `3DES`
|
||||||
//! - **Encryption**: `RSA1024`, `RSA2048`, `ECCP256`, `ECCP384`
|
//! - **Encryption**: `RSA1024`, `RSA2048`, `ECCP256`, `ECCP384`
|
||||||
@@ -17,30 +32,64 @@
|
|||||||
//! - RSASSA-PKCS#1v1.5: `RSA1024`, `RSA2048`
|
//! - RSASSA-PKCS#1v1.5: `RSA1024`, `RSA2048`
|
||||||
//! - ECDSA: `ECCP256`, `ECCP384`
|
//! - ECDSA: `ECCP256`, `ECCP384`
|
||||||
//!
|
//!
|
||||||
|
//! NOTE: RSASSA-PSS signatures and RSA-OAEP encryption may be supportable (TBD)
|
||||||
|
//!
|
||||||
//! ## Status
|
//! ## Status
|
||||||
//!
|
//!
|
||||||
//! This library is a work-in-progress translation and is not yet usable.
|
//! This is a work-in-progress effort, and while much of the library-level
|
||||||
//! Check back later for updates.
|
//! code from upstream [yubico-piv-tool] has been translated into Rust
|
||||||
|
//! presenting a safe interface, much of it is still untested.
|
||||||
|
//!
|
||||||
|
//! Please see the [project's README.md for a complete status][status].
|
||||||
//!
|
//!
|
||||||
//! ## History
|
//! ## History
|
||||||
//!
|
//!
|
||||||
//! This library is a Rust translation of the [yubico-piv-tool][6] utility by
|
//! This library is a Rust translation of the [yubico-piv-tool] utility by
|
||||||
//! Yubico, which was originally written in C. It was mechanically translated
|
//! Yubico, which was originally written in C. It was mechanically translated
|
||||||
//! from C into Rust using [Corrode][7], and then subsequently heavily
|
//! from C into Rust using [Corrode], and then subsequently heavily
|
||||||
//! refactored into safer, more idiomatic Rust.
|
//! refactored into safer, more idiomatic Rust.
|
||||||
//!
|
//!
|
||||||
//! For more information on `yubico-piv-tool` and background information on how
|
//! For more information on [yubico-piv-tool] and background information on how
|
||||||
//! the YubiKey implementation of PIV works in general, see the
|
//! the YubiKey implementation of PIV works in general, see the
|
||||||
//! [Yubico PIV Tool Command Line Guide][8].
|
//! [Yubico PIV Tool Command Line Guide][piv-tool-guide].
|
||||||
//!
|
//!
|
||||||
//! [1]: https://www.yubico.com/products/yubikey-hardware/
|
//! ## Security Warning
|
||||||
//! [2]: https://nvlpubs.nist.gov/nistpubs/SpecialPublications/NIST.SP.800-73-4.pdf
|
//!
|
||||||
//! [3]: https://www.yubico.com/
|
//! No security audits of this crate have ever been performed. Presently it is in
|
||||||
//! [4]: https://en.wikipedia.org/wiki/CCID_(protocol)
|
//! an experimental stage and may still contain high-severity issues.
|
||||||
//! [5]: https://www.nist.gov/
|
//!
|
||||||
//! [6]: https://github.com/Yubico/yubico-piv-tool/
|
//! USE AT YOUR OWN RISK!
|
||||||
//! [7]: https://github.com/jameysharp/corrode
|
//!
|
||||||
//! [8]: https://www.yubico.com/wp-content/uploads/2016/05/Yubico_PIV_Tool_Command_Line_Guide_en.pdf
|
//! ## Code of Conduct
|
||||||
|
//!
|
||||||
|
//! We abide by the [Contributor Covenant][cc-md] and ask that you do as well.
|
||||||
|
//!
|
||||||
|
//! For more information, please see [CODE_OF_CONDUCT.md][cc-md].
|
||||||
|
//!
|
||||||
|
//! ## License
|
||||||
|
//!
|
||||||
|
//! **yubikey-piv.rs** is a fork of and originally a mechanical translation from
|
||||||
|
//! 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.
|
||||||
|
//!
|
||||||
|
//! [YubiKey]: https://www.yubico.com/products/yubikey-hardware/
|
||||||
|
//! [PIV]: https://nvlpubs.nist.gov/nistpubs/SpecialPublications/NIST.SP.800-73-4.pdf
|
||||||
|
//! [Yubico]: https://www.yubico.com/
|
||||||
|
//! [PC/SC]: https://en.wikipedia.org/wiki/PC/SC
|
||||||
|
//! [`pcsc` crate]: https://github.com/bluetech/pcsc-rust
|
||||||
|
//! [NIST]: https://www.nist.gov/
|
||||||
|
//! [yk-guide]: https://developers.yubico.com/PIV/Introduction/YubiKey_and_PIV.html
|
||||||
|
//! [YubiKey NEO]: https://support.yubico.com/support/solutions/articles/15000006494-yubikey-neo
|
||||||
|
//! [YubiKey 4]: https://support.yubico.com/support/solutions/articles/15000006486-yubikey-4
|
||||||
|
//! [YubiKey 5]: https://www.yubico.com/products/yubikey-5-overview/
|
||||||
|
//! [status]: https://github.com/tarcieri/yubikey-piv.rs#status
|
||||||
|
//! [yubico-piv-tool]: https://github.com/Yubico/yubico-piv-tool/
|
||||||
|
//! [Corrode]: https://github.com/jameysharp/corrode
|
||||||
|
//! [piv-tool-guide]: https://www.yubico.com/wp-content/uploads/2016/05/Yubico_PIV_Tool_Command_Line_Guide_en.pdf
|
||||||
|
//! [cc-web]: https://contributor-covenant.org/
|
||||||
|
//! [cc-md]: https://github.com/tarcieri/yubikey-piv.rs/blob/develop/CODE_OF_CONDUCT.md
|
||||||
|
//! [BSDL]: https://opensource.org/licenses/BSD-2-Clause
|
||||||
|
|
||||||
// Adapted from yubico-piv-tool:
|
// Adapted from yubico-piv-tool:
|
||||||
// <https://github.com/Yubico/yubico-piv-tool/>
|
// <https://github.com/Yubico/yubico-piv-tool/>
|
||||||
@@ -74,7 +123,7 @@
|
|||||||
|
|
||||||
#![doc(
|
#![doc(
|
||||||
html_logo_url = "https://raw.githubusercontent.com/tarcieri/yubikey-piv.rs/develop/img/logo.png",
|
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.1"
|
html_root_url = "https://docs.rs/yubikey-piv/0.0.2"
|
||||||
)]
|
)]
|
||||||
#![warn(
|
#![warn(
|
||||||
missing_docs,
|
missing_docs,
|
||||||
@@ -86,10 +135,44 @@
|
|||||||
)]
|
)]
|
||||||
|
|
||||||
mod apdu;
|
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;
|
pub mod consts;
|
||||||
|
#[cfg(feature = "untested")]
|
||||||
|
pub mod container;
|
||||||
pub mod error;
|
pub mod error;
|
||||||
mod internal;
|
#[cfg(feature = "untested")]
|
||||||
pub mod util;
|
pub mod key;
|
||||||
|
#[cfg(feature = "untested")]
|
||||||
|
mod metadata;
|
||||||
|
#[cfg(feature = "untested")]
|
||||||
|
pub mod mgm;
|
||||||
|
#[cfg(feature = "untested")]
|
||||||
|
pub mod msroots;
|
||||||
|
mod response;
|
||||||
|
#[cfg(feature = "untested")]
|
||||||
|
mod serialization;
|
||||||
|
#[cfg(feature = "untested")]
|
||||||
|
pub mod settings;
|
||||||
|
mod transaction;
|
||||||
pub mod yubikey;
|
pub mod yubikey;
|
||||||
|
|
||||||
pub use self::yubikey::YubiKey;
|
#[cfg(feature = "untested")]
|
||||||
|
pub use self::{key::Key, mgm::MgmKey};
|
||||||
|
pub use yubikey::YubiKey;
|
||||||
|
|
||||||
|
/// Algorithm identifiers
|
||||||
|
// TODO(tarcieri): make this an enum
|
||||||
|
pub type AlgorithmId = u8;
|
||||||
|
|
||||||
|
/// Object identifiers
|
||||||
|
pub type ObjectId = u32;
|
||||||
|
|
||||||
|
/// Buffer type (self-zeroizing byte vector)
|
||||||
|
pub(crate) type Buffer = zeroize::Zeroizing<Vec<u8>>;
|
||||||
|
|||||||
+271
@@ -0,0 +1,271 @@
|
|||||||
|
//! YubiKey Device Metadata
|
||||||
|
|
||||||
|
// 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.
|
||||||
|
|
||||||
|
use crate::{consts::*, error::Error, serialization::*, transaction::Transaction, Buffer};
|
||||||
|
use std::{ptr, slice};
|
||||||
|
use zeroize::Zeroizing;
|
||||||
|
|
||||||
|
/// Get metadata item
|
||||||
|
pub(crate) fn get_item(data: &[u8], tag: u8) -> Result<&[u8], Error> {
|
||||||
|
let mut p_temp: *const u8 = data.as_ptr();
|
||||||
|
let mut cb_temp: usize = 0;
|
||||||
|
let mut tag_temp: u8;
|
||||||
|
|
||||||
|
unsafe {
|
||||||
|
while p_temp < data.as_ptr().add(data.len()) {
|
||||||
|
tag_temp = *p_temp;
|
||||||
|
p_temp = p_temp.add(1);
|
||||||
|
|
||||||
|
let p_slice = slice::from_raw_parts(
|
||||||
|
p_temp,
|
||||||
|
data.as_ptr() as usize + data.len() - p_temp as usize,
|
||||||
|
);
|
||||||
|
|
||||||
|
if !has_valid_length(
|
||||||
|
p_slice,
|
||||||
|
data.as_ptr().add(data.len()) as usize - p_temp as usize,
|
||||||
|
) {
|
||||||
|
return Err(Error::SizeError);
|
||||||
|
}
|
||||||
|
|
||||||
|
p_temp = p_temp.add(get_length(p_slice, &mut cb_temp));
|
||||||
|
|
||||||
|
if tag_temp == tag {
|
||||||
|
// found tag
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
p_temp = p_temp.add(cb_temp);
|
||||||
|
}
|
||||||
|
|
||||||
|
if p_temp < data.as_ptr().add(data.len()) {
|
||||||
|
return Ok(slice::from_raw_parts(p_temp, cb_temp));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Err(Error::GenericError)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set metadata item
|
||||||
|
pub(crate) fn set_item(
|
||||||
|
data: &mut [u8],
|
||||||
|
pcb_data: &mut usize,
|
||||||
|
cb_data_max: usize,
|
||||||
|
tag: u8,
|
||||||
|
p_item: &[u8],
|
||||||
|
) -> Result<(), Error> {
|
||||||
|
let mut p_temp: *mut u8 = data.as_mut_ptr();
|
||||||
|
let mut cb_temp: usize = 0;
|
||||||
|
let mut tag_temp: u8 = 0;
|
||||||
|
let mut cb_len: usize = 0;
|
||||||
|
let cb_item = p_item.len();
|
||||||
|
// Must be signed to have negative offsets
|
||||||
|
let cb_moved: isize;
|
||||||
|
let p_next: *mut u8;
|
||||||
|
|
||||||
|
while p_temp < data[*pcb_data..].as_mut_ptr() {
|
||||||
|
unsafe {
|
||||||
|
tag_temp = *p_temp;
|
||||||
|
p_temp = p_temp.add(1);
|
||||||
|
|
||||||
|
cb_len = get_length(
|
||||||
|
slice::from_raw_parts(
|
||||||
|
p_temp,
|
||||||
|
data.as_mut_ptr() as usize + data.len() - p_temp as usize,
|
||||||
|
),
|
||||||
|
&mut cb_temp,
|
||||||
|
);
|
||||||
|
p_temp = p_temp.add(cb_len);
|
||||||
|
|
||||||
|
if tag_temp == tag {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
p_temp = p_temp.add(cb_temp);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if tag_temp != tag {
|
||||||
|
if cb_item == 0 {
|
||||||
|
// We've been asked to delete an existing item that isn't in the blob
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe {
|
||||||
|
p_temp = data.as_mut_ptr().add(*pcb_data);
|
||||||
|
cb_len = get_length_size(cb_item);
|
||||||
|
|
||||||
|
if (*pcb_data + cb_len + cb_item) > cb_data_max {
|
||||||
|
return Err(Error::GenericError);
|
||||||
|
}
|
||||||
|
|
||||||
|
*p_temp = tag;
|
||||||
|
p_temp = p_temp.add(1);
|
||||||
|
p_temp = p_temp.add(set_length(
|
||||||
|
slice::from_raw_parts_mut(
|
||||||
|
p_temp,
|
||||||
|
data.as_ptr() as usize + data.len() - p_temp as usize,
|
||||||
|
),
|
||||||
|
cb_item,
|
||||||
|
));
|
||||||
|
|
||||||
|
ptr::copy(p_item.as_ptr(), p_temp, cb_item);
|
||||||
|
}
|
||||||
|
|
||||||
|
*pcb_data += 1 + cb_len + cb_item;
|
||||||
|
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
if cb_temp == cb_item {
|
||||||
|
unsafe {
|
||||||
|
ptr::copy(p_item.as_ptr(), p_temp, cb_item);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
p_next = unsafe { p_temp.add(cb_temp) };
|
||||||
|
cb_moved = (cb_item as isize - cb_temp as isize)
|
||||||
|
+ if cb_item != 0 {
|
||||||
|
get_length_size(cb_item) as isize
|
||||||
|
} else {
|
||||||
|
-1
|
||||||
|
}
|
||||||
|
- cb_len as isize;
|
||||||
|
|
||||||
|
if (*pcb_data + cb_moved as usize) > cb_data_max {
|
||||||
|
return Err(Error::GenericError);
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe {
|
||||||
|
ptr::copy(
|
||||||
|
p_next,
|
||||||
|
p_next.offset(cb_moved),
|
||||||
|
*pcb_data - p_next as usize - data.as_ptr() as usize,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
*pcb_data += cb_moved as usize;
|
||||||
|
|
||||||
|
if cb_item != 0 {
|
||||||
|
unsafe {
|
||||||
|
p_temp = p_temp.offset(-(cb_len as isize));
|
||||||
|
p_temp = p_temp.add(set_length(
|
||||||
|
slice::from_raw_parts_mut(
|
||||||
|
p_temp,
|
||||||
|
data.as_ptr() as usize + data.len() - p_temp as usize,
|
||||||
|
),
|
||||||
|
cb_item,
|
||||||
|
));
|
||||||
|
ptr::copy(p_item.as_ptr(), p_temp, cb_item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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,
|
||||||
|
_ => 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe {
|
||||||
|
ptr::copy(data.as_ptr().add(offset), data.as_mut_ptr(), pcb_data);
|
||||||
|
}
|
||||||
|
|
||||||
|
data.truncate(pcb_data);
|
||||||
|
Ok(data)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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 {
|
||||||
|
return Err(Error::GenericError);
|
||||||
|
}
|
||||||
|
|
||||||
|
let obj_id = match tag {
|
||||||
|
TAG_ADMIN => YKPIV_OBJ_ADMIN_DATA,
|
||||||
|
TAG_PROTECTED => YKPIV_OBJ_PRINTED,
|
||||||
|
_ => return Err(Error::InvalidObject),
|
||||||
|
};
|
||||||
|
|
||||||
|
if data.is_empty() {
|
||||||
|
// Deleting metadata
|
||||||
|
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();
|
||||||
|
|
||||||
|
txn.save_object(obj_id, &buf[..offset])
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the size of a length tag for the given length
|
||||||
|
fn get_length_size(length: usize) -> usize {
|
||||||
|
if length < 0x80 {
|
||||||
|
1
|
||||||
|
} else if length < 0xff {
|
||||||
|
2
|
||||||
|
} else {
|
||||||
|
3
|
||||||
|
}
|
||||||
|
}
|
||||||
+363
@@ -0,0 +1,363 @@
|
|||||||
|
//! Management Key (MGM) for authenticating to the YubiKey management applet
|
||||||
|
|
||||||
|
// 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.
|
||||||
|
|
||||||
|
use crate::{consts::*, error::Error, metadata, yubikey::YubiKey};
|
||||||
|
use des::{
|
||||||
|
block_cipher_trait::{generic_array::GenericArray, BlockCipher},
|
||||||
|
TdesEde3,
|
||||||
|
};
|
||||||
|
use getrandom::getrandom;
|
||||||
|
use hmac::Hmac;
|
||||||
|
use log::error;
|
||||||
|
use pbkdf2::pbkdf2;
|
||||||
|
use sha1::Sha1;
|
||||||
|
use std::convert::{TryFrom, TryInto};
|
||||||
|
use zeroize::{Zeroize, Zeroizing};
|
||||||
|
|
||||||
|
/// 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,
|
||||||
|
];
|
||||||
|
|
||||||
|
/// 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,
|
||||||
|
|
||||||
|
/// Derived
|
||||||
|
Derived = 1,
|
||||||
|
|
||||||
|
/// Protected
|
||||||
|
Protected = 2,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Management Key (MGM).
|
||||||
|
///
|
||||||
|
/// This key is used to authenticate to the management applet running on
|
||||||
|
/// a YubiKey in order to perform administrative functions.
|
||||||
|
///
|
||||||
|
/// The only supported algorithm for MGM keys is 3DES.
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct MgmKey([u8; DES_LEN_3DES]);
|
||||||
|
|
||||||
|
impl MgmKey {
|
||||||
|
/// Generate a random MGM key
|
||||||
|
pub fn generate() -> Result<Self, Error> {
|
||||||
|
let mut key_bytes = [0u8; DES_LEN_3DES];
|
||||||
|
|
||||||
|
if getrandom(&mut key_bytes).is_err() {
|
||||||
|
return Err(Error::RandomnessError);
|
||||||
|
}
|
||||||
|
|
||||||
|
MgmKey::new(key_bytes)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create an MGM key from byte slice.
|
||||||
|
///
|
||||||
|
/// Returns an error if the slice is the wrong size or the key is weak.
|
||||||
|
pub fn from_bytes(bytes: impl AsRef<[u8]>) -> Result<Self, Error> {
|
||||||
|
bytes.as_ref().try_into()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create an MGM key from the given byte array.
|
||||||
|
///
|
||||||
|
/// Returns an error if the key is weak.
|
||||||
|
pub fn new(key_bytes: [u8; DES_LEN_3DES]) -> Result<Self, Error> {
|
||||||
|
if is_weak_key(&key_bytes) {
|
||||||
|
error!(
|
||||||
|
"blacklisting key '{:?}' since it's weak (with odd parity)",
|
||||||
|
&key_bytes
|
||||||
|
);
|
||||||
|
|
||||||
|
return Err(Error::KeyError);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(MgmKey(key_bytes))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get derived management key (MGM)
|
||||||
|
pub fn get_derived(yubikey: &mut YubiKey, pin: &[u8]) -> Result<Self, Error> {
|
||||||
|
let txn = yubikey.begin_transaction()?;
|
||||||
|
|
||||||
|
// recover management key
|
||||||
|
let data = metadata::read(&txn, TAG_ADMIN)?;
|
||||||
|
let salt = metadata::get_item(&data, TAG_ADMIN_SALT)?;
|
||||||
|
|
||||||
|
if salt.len() != CB_ADMIN_SALT {
|
||||||
|
error!(
|
||||||
|
"derived MGM salt exists, but is incorrect size: {} (expected {})",
|
||||||
|
salt.len(),
|
||||||
|
CB_ADMIN_SALT
|
||||||
|
);
|
||||||
|
|
||||||
|
return Err(Error::GenericError);
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut mgm = [0u8; DES_LEN_3DES];
|
||||||
|
pbkdf2::<Hmac<Sha1>>(pin, &salt, ITER_MGM_PBKDF2, &mut mgm);
|
||||||
|
|
||||||
|
MgmKey::from_bytes(mgm)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get protected management key (MGM)
|
||||||
|
pub fn get_protected(yubikey: &mut YubiKey) -> Result<Self, Error> {
|
||||||
|
let txn = yubikey.begin_transaction()?;
|
||||||
|
|
||||||
|
let data = metadata::read(&txn, TAG_PROTECTED).map_err(|e| {
|
||||||
|
error!("could not read protected data (err: {:?})", e);
|
||||||
|
e
|
||||||
|
})?;
|
||||||
|
|
||||||
|
let item = metadata::get_item(&data, TAG_PROTECTED_MGM).map_err(|e| {
|
||||||
|
error!("could not read protected MGM from metadata (err: {:?})", e);
|
||||||
|
e
|
||||||
|
})?;
|
||||||
|
|
||||||
|
if item.len() != DES_LEN_3DES {
|
||||||
|
error!(
|
||||||
|
"protected data contains MGM, but is the wrong size: {} (expected {})",
|
||||||
|
item.len(),
|
||||||
|
DES_LEN_3DES
|
||||||
|
);
|
||||||
|
|
||||||
|
return Err(Error::AuthenticationError);
|
||||||
|
}
|
||||||
|
|
||||||
|
MgmKey::from_bytes(item)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set the management key (MGM)
|
||||||
|
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)
|
||||||
|
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| {
|
||||||
|
// log a warning, since the device mgm key is corrupt or we're in
|
||||||
|
// a state where we can't set the mgm key
|
||||||
|
error!("could not set new derived mgm key, err = {}", e);
|
||||||
|
e
|
||||||
|
})?;
|
||||||
|
|
||||||
|
// after this point, we've set the mgm key, so the function should
|
||||||
|
// succeed, regardless of being able to set the metadata
|
||||||
|
|
||||||
|
// set the new mgm key in protected data
|
||||||
|
let buffer = match metadata::read(&txn, TAG_PROTECTED) {
|
||||||
|
Ok(b) => b,
|
||||||
|
Err(_) => {
|
||||||
|
// set current metadata blob size to zero, we'll add to the blank blob
|
||||||
|
Zeroizing::new(vec![])
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let mut cb_data = buffer.len();
|
||||||
|
data[..cb_data].copy_from_slice(&buffer);
|
||||||
|
|
||||||
|
if let Err(e) = metadata::set_item(
|
||||||
|
data.as_mut_slice(),
|
||||||
|
&mut cb_data,
|
||||||
|
CB_OBJ_MAX,
|
||||||
|
TAG_PROTECTED_MGM,
|
||||||
|
self.as_ref(),
|
||||||
|
) {
|
||||||
|
error!("could not set protected mgm item, err = {:?}", e);
|
||||||
|
} else {
|
||||||
|
metadata::write(&txn, TAG_PROTECTED, &data, max_size).map_err(|e| {
|
||||||
|
error!("could not write protected data, err = {:?}", e);
|
||||||
|
e
|
||||||
|
})?;
|
||||||
|
}
|
||||||
|
|
||||||
|
// set the protected mgm flag in admin data
|
||||||
|
cb_data = data.len();
|
||||||
|
|
||||||
|
let mut flags_1 = [0u8; 1];
|
||||||
|
|
||||||
|
if let Ok(buffer) = metadata::read(&txn, TAG_ADMIN) {
|
||||||
|
if let Ok(item) = metadata::get_item(&buffer, TAG_ADMIN_FLAGS_1) {
|
||||||
|
if item.len() == flags_1.len() {
|
||||||
|
flags_1.copy_from_slice(item);
|
||||||
|
} else {
|
||||||
|
error!(
|
||||||
|
"admin data flags are an incorrect size: {} (expected {})",
|
||||||
|
item.len(),
|
||||||
|
flags_1.len()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// flags are not set
|
||||||
|
error!("admin data exists, but flags are not present");
|
||||||
|
}
|
||||||
|
|
||||||
|
// remove any existing salt
|
||||||
|
if let Err(e) =
|
||||||
|
metadata::set_item(&mut data, &mut cb_data, CB_OBJ_MAX, TAG_ADMIN_SALT, &[])
|
||||||
|
{
|
||||||
|
error!("could not unset derived mgm salt (err = {})", e)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
cb_data = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
flags_1[0] |= ADMIN_FLAGS_1_PROTECTED_MGM;
|
||||||
|
|
||||||
|
if let Err(e) = metadata::set_item(
|
||||||
|
data.as_mut_slice(),
|
||||||
|
&mut cb_data,
|
||||||
|
CB_OBJ_MAX,
|
||||||
|
TAG_ADMIN_FLAGS_1,
|
||||||
|
&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) {
|
||||||
|
error!("could not write admin data, err = {}", e);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Encrypt with 3DES key
|
||||||
|
#[allow(clippy::trivially_copy_pass_by_ref)]
|
||||||
|
pub(crate) fn encrypt(&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));
|
||||||
|
output
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Decrypt with 3DES key
|
||||||
|
#[allow(clippy::trivially_copy_pass_by_ref)]
|
||||||
|
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));
|
||||||
|
output
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AsRef<[u8; DES_LEN_3DES]> for MgmKey {
|
||||||
|
fn as_ref(&self) -> &[u8; DES_LEN_3DES] {
|
||||||
|
&self.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for MgmKey {
|
||||||
|
fn default() -> Self {
|
||||||
|
MgmKey(DEFAULT_MGM_KEY)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Drop for MgmKey {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
self.0.zeroize();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> TryFrom<&'a [u8]> for MgmKey {
|
||||||
|
type Error = Error;
|
||||||
|
|
||||||
|
fn try_from(key_bytes: &'a [u8]) -> Result<Self, Error> {
|
||||||
|
Self::new(key_bytes.try_into().map_err(|_| Error::SizeError)?)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Weak and semi weak DES keys as taken from:
|
||||||
|
/// %A D.W. Davies
|
||||||
|
/// %A W.L. Price
|
||||||
|
/// %T Security for Computer Networks
|
||||||
|
/// %I John Wiley & Sons
|
||||||
|
/// %D 1984
|
||||||
|
const WEAK_DES_KEYS: &[[u8; DES_LEN_DES]] = &[
|
||||||
|
// weak keys
|
||||||
|
[0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01],
|
||||||
|
[0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE],
|
||||||
|
[0x1F, 0x1F, 0x1F, 0x1F, 0x0E, 0x0E, 0x0E, 0x0E],
|
||||||
|
[0xE0, 0xE0, 0xE0, 0xE0, 0xF1, 0xF1, 0xF1, 0xF1],
|
||||||
|
// semi-weak keys
|
||||||
|
[0x01, 0xFE, 0x01, 0xFE, 0x01, 0xFE, 0x01, 0xFE],
|
||||||
|
[0xFE, 0x01, 0xFE, 0x01, 0xFE, 0x01, 0xFE, 0x01],
|
||||||
|
[0x1F, 0xE0, 0x1F, 0xE0, 0x0E, 0xF1, 0x0E, 0xF1],
|
||||||
|
[0xE0, 0x1F, 0xE0, 0x1F, 0xF1, 0x0E, 0xF1, 0x0E],
|
||||||
|
[0x01, 0xE0, 0x01, 0xE0, 0x01, 0xF1, 0x01, 0xF1],
|
||||||
|
[0xE0, 0x01, 0xE0, 0x01, 0xF1, 0x01, 0xF1, 0x01],
|
||||||
|
[0x1F, 0xFE, 0x1F, 0xFE, 0x0E, 0xFE, 0x0E, 0xFE],
|
||||||
|
[0xFE, 0x1F, 0xFE, 0x1F, 0xFE, 0x0E, 0xFE, 0x0E],
|
||||||
|
[0x01, 0x1F, 0x01, 0x1F, 0x01, 0x0E, 0x01, 0x0E],
|
||||||
|
[0x1F, 0x01, 0x1F, 0x01, 0x0E, 0x01, 0x0E, 0x01],
|
||||||
|
[0xE0, 0xFE, 0xE0, 0xFE, 0xF1, 0xFE, 0xF1, 0xFE],
|
||||||
|
[0xFE, 0xE0, 0xFE, 0xE0, 0xFE, 0xF1, 0xFE, 0xF1],
|
||||||
|
];
|
||||||
|
|
||||||
|
/// Is this 3DES key weak?
|
||||||
|
///
|
||||||
|
/// This check is performed automatically when the key is instantiated to
|
||||||
|
/// ensure no such keys are used.
|
||||||
|
fn is_weak_key(key: &[u8; DES_LEN_3DES]) -> bool {
|
||||||
|
// set odd parity of key
|
||||||
|
let mut tmp = Zeroizing::new([0u8; DES_LEN_3DES]);
|
||||||
|
|
||||||
|
for i in 0..DES_LEN_3DES {
|
||||||
|
// count number of set bits in byte, excluding the low-order bit - SWAR method
|
||||||
|
let mut c = key[i] & 0xFE;
|
||||||
|
|
||||||
|
c = (c & 0x55) + ((c >> 1) & 0x55);
|
||||||
|
c = (c & 0x33) + ((c >> 2) & 0x33);
|
||||||
|
c = (c & 0x0F) + ((c >> 4) & 0x0F);
|
||||||
|
|
||||||
|
// if count is even, set low key bit to 1, otherwise 0
|
||||||
|
tmp[i] = (key[i] & 0xFE) | (if c & 0x01 == 0x01 { 0x00 } else { 0x01 });
|
||||||
|
}
|
||||||
|
|
||||||
|
// check odd parity key against table by DES key block
|
||||||
|
let mut is_weak = false;
|
||||||
|
|
||||||
|
for weak_key in WEAK_DES_KEYS.iter() {
|
||||||
|
if weak_key == &tmp[0..DES_LEN_DES]
|
||||||
|
|| weak_key == &tmp[DES_LEN_DES..2 * DES_LEN_DES]
|
||||||
|
|| weak_key == &tmp[2 * DES_LEN_DES..3 * DES_LEN_DES]
|
||||||
|
{
|
||||||
|
is_weak = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
is_weak
|
||||||
|
}
|
||||||
+178
@@ -0,0 +1,178 @@
|
|||||||
|
//! `msroots`: PKCS#7 formatted certificate store for enterprise trusted roots.
|
||||||
|
//!
|
||||||
|
//! This `msroots` file contains a bag of certificates with empty content and
|
||||||
|
//! an empty signature, allowing an enterprise root certificate truststore to
|
||||||
|
//! be written to and read from a YubiKey.
|
||||||
|
//!
|
||||||
|
//! For more information, see:
|
||||||
|
//! <https://docs.microsoft.com/en-us/windows-hardware/drivers/smartcard/developer-guidelines#-interoperability-with-msroots>
|
||||||
|
|
||||||
|
// 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.
|
||||||
|
|
||||||
|
use crate::{consts::*, error::Error, serialization::*, yubikey::YubiKey};
|
||||||
|
use log::error;
|
||||||
|
use std::{ptr, slice};
|
||||||
|
|
||||||
|
/// `msroots` file: PKCS#7-formatted certificate store for enterprise trust roots
|
||||||
|
pub struct MsRoots(Vec<u8>);
|
||||||
|
|
||||||
|
impl MsRoots {
|
||||||
|
/// Initialize a local certificate struct from the given bytebuffer
|
||||||
|
pub fn new(msroots: impl AsRef<[u8]>) -> Result<Self, Error> {
|
||||||
|
Ok(MsRoots(msroots.as_ref().into()))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Read `msroots` file from YubiKey
|
||||||
|
pub fn read(yubikey: &mut YubiKey) -> Result<Vec<Self>, Error> {
|
||||||
|
let mut len: usize = 0;
|
||||||
|
let mut ptr: *mut u8;
|
||||||
|
let mut tag: u8;
|
||||||
|
let mut offset: usize = 0;
|
||||||
|
|
||||||
|
let mut results = vec![];
|
||||||
|
let cb_data = yubikey.obj_size_max();
|
||||||
|
let txn = yubikey.begin_transaction()?;
|
||||||
|
|
||||||
|
// allocate first page
|
||||||
|
let mut p_data = vec![0u8; cb_data];
|
||||||
|
|
||||||
|
for object_id in YKPIV_OBJ_MSROOTS1..YKPIV_OBJ_MSROOTS5 {
|
||||||
|
let mut buf = txn.fetch_object(object_id)?;
|
||||||
|
let cb_buf = buf.len();
|
||||||
|
|
||||||
|
ptr = buf.as_mut_ptr();
|
||||||
|
|
||||||
|
if cb_buf < CB_OBJ_TAG_MIN {
|
||||||
|
return Ok(results);
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe {
|
||||||
|
tag = *ptr;
|
||||||
|
ptr = ptr.add(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (TAG_MSROOTS_MID != tag || YKPIV_OBJ_MSROOTS5 == object_id)
|
||||||
|
&& (TAG_MSROOTS_END != tag)
|
||||||
|
{
|
||||||
|
// the current object doesn't contain a valid part of a msroots file
|
||||||
|
|
||||||
|
// treat condition as object isn't found
|
||||||
|
return Ok(results);
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe {
|
||||||
|
ptr = ptr.add(get_length(
|
||||||
|
slice::from_raw_parts(ptr, buf.as_ptr() as usize + buf.len() - ptr as usize),
|
||||||
|
&mut len,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
// check that decoded length represents object contents
|
||||||
|
if len > cb_buf - (ptr as isize - buf.as_mut_ptr() as isize) as usize {
|
||||||
|
return Ok(results);
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe {
|
||||||
|
ptr::copy(ptr, p_data.as_mut_ptr().add(offset), len);
|
||||||
|
}
|
||||||
|
|
||||||
|
offset += len;
|
||||||
|
|
||||||
|
match MsRoots::new(&p_data[..offset]) {
|
||||||
|
Ok(msroots) => results.push(msroots),
|
||||||
|
Err(res) => error!("error parsing msroots: {:?}", res),
|
||||||
|
}
|
||||||
|
|
||||||
|
if tag == TAG_MSROOTS_END {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(results)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Write `msroots` file to YubiKey
|
||||||
|
pub fn write(&self, yubikey: &mut YubiKey) -> Result<(), Error> {
|
||||||
|
let mut buf = [0u8; CB_OBJ_MAX];
|
||||||
|
let mut offset: usize;
|
||||||
|
let mut data_offset: usize = 0;
|
||||||
|
let mut data_chunk: usize;
|
||||||
|
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, &[]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calculate number of objects required to store blob
|
||||||
|
n_objs = (data_len / (cb_obj_max - CB_OBJ_TAG_MAX)) + 1;
|
||||||
|
|
||||||
|
if n_objs > 5 {
|
||||||
|
return Err(Error::SizeError);
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
} else {
|
||||||
|
data_len - data_offset
|
||||||
|
};
|
||||||
|
|
||||||
|
buf[offset] = if i == n_objs - 1 {
|
||||||
|
TAG_MSROOTS_END
|
||||||
|
} else {
|
||||||
|
TAG_MSROOTS_MID
|
||||||
|
};
|
||||||
|
|
||||||
|
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])?;
|
||||||
|
|
||||||
|
data_offset += data_chunk;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AsRef<[u8]> for MsRoots {
|
||||||
|
fn as_ref(&self) -> &[u8] {
|
||||||
|
self.0.as_ref()
|
||||||
|
}
|
||||||
|
}
|
||||||
+186
@@ -0,0 +1,186 @@
|
|||||||
|
//! Responses to issued commands
|
||||||
|
|
||||||
|
// 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.
|
||||||
|
|
||||||
|
use crate::Buffer;
|
||||||
|
|
||||||
|
/// Parsed response to a command
|
||||||
|
pub(crate) struct Response {
|
||||||
|
/// Status words
|
||||||
|
status_words: StatusWords,
|
||||||
|
|
||||||
|
/// Buffer
|
||||||
|
buffer: Buffer,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Response {
|
||||||
|
/// Parse a response from the given buffer
|
||||||
|
pub fn from_bytes(mut buffer: Buffer) -> Self {
|
||||||
|
if buffer.len() >= 2 {
|
||||||
|
let sw = StatusWords::from(
|
||||||
|
(buffer[buffer.len() - 2] as u32) << 8 | (buffer[buffer.len() - 1] as u32),
|
||||||
|
);
|
||||||
|
|
||||||
|
let len = buffer.len() - 2;
|
||||||
|
buffer.truncate(len);
|
||||||
|
Response {
|
||||||
|
status_words: sw,
|
||||||
|
buffer,
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Response {
|
||||||
|
status_words: StatusWords::None,
|
||||||
|
buffer,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create a new response from the given status words and buffer
|
||||||
|
#[cfg(feature = "untested")]
|
||||||
|
pub fn new(status_words: StatusWords, buffer: Buffer) -> Response {
|
||||||
|
Response {
|
||||||
|
status_words,
|
||||||
|
buffer,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the [`StatusWords`] for this response.
|
||||||
|
pub fn status_words(&self) -> StatusWords {
|
||||||
|
self.status_words
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the raw [`StatusWords`] code for this response.
|
||||||
|
#[cfg(feature = "untested")]
|
||||||
|
pub fn code(&self) -> u32 {
|
||||||
|
self.status_words.code()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Do the status words for this response indicate success?
|
||||||
|
pub fn is_success(&self) -> bool {
|
||||||
|
self.status_words.is_success()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Borrow the response buffer
|
||||||
|
pub fn buffer(&self) -> &[u8] {
|
||||||
|
self.buffer.as_ref()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Consume this response, returning its buffer
|
||||||
|
#[cfg(feature = "untested")]
|
||||||
|
pub fn into_buffer(self) -> Buffer {
|
||||||
|
self.buffer
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AsRef<[u8]> for Response {
|
||||||
|
fn as_ref(&self) -> &[u8] {
|
||||||
|
self.buffer()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Status Words (SW) are 2-byte values returned by a card command.
|
||||||
|
///
|
||||||
|
/// The first byte of a status word is referred to as SW1 and the second byte
|
||||||
|
/// of a status word is referred to as SW2.
|
||||||
|
///
|
||||||
|
/// See NIST special publication 800-73-4, section 5.6:
|
||||||
|
/// <https://csrc.nist.gov/publications/detail/sp/800-73/4/final>
|
||||||
|
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
|
||||||
|
pub(crate) enum StatusWords {
|
||||||
|
/// No status words present in response
|
||||||
|
None,
|
||||||
|
|
||||||
|
/// Successful execution
|
||||||
|
Success,
|
||||||
|
|
||||||
|
/// Security status not satisfied
|
||||||
|
SecurityStatusError,
|
||||||
|
|
||||||
|
/// Authentication method blocked
|
||||||
|
AuthBlockedError,
|
||||||
|
|
||||||
|
/// Incorrect parameter in command data field
|
||||||
|
IncorrectParamError,
|
||||||
|
|
||||||
|
//
|
||||||
|
// Custom Yubico Status Word extensions
|
||||||
|
//
|
||||||
|
/// Incorrect card slot error
|
||||||
|
IncorrectSlotError,
|
||||||
|
|
||||||
|
/// Not supported error
|
||||||
|
NotSupportedError,
|
||||||
|
|
||||||
|
/// Other/unrecognized status words
|
||||||
|
Other(u32),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl StatusWords {
|
||||||
|
/// Get the numerical response code for these status words
|
||||||
|
pub fn code(self) -> u32 {
|
||||||
|
match self {
|
||||||
|
StatusWords::None => 0,
|
||||||
|
StatusWords::SecurityStatusError => 0x6982,
|
||||||
|
StatusWords::AuthBlockedError => 0x6983,
|
||||||
|
StatusWords::IncorrectParamError => 0x6a80,
|
||||||
|
StatusWords::IncorrectSlotError => 0x6b00,
|
||||||
|
StatusWords::NotSupportedError => 0x6d00,
|
||||||
|
StatusWords::Success => 0x9000,
|
||||||
|
StatusWords::Other(n) => n,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Do these status words indicate success?
|
||||||
|
pub fn is_success(self) -> bool {
|
||||||
|
self == StatusWords::Success
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<u32> for StatusWords {
|
||||||
|
fn from(sw: u32) -> Self {
|
||||||
|
match sw {
|
||||||
|
0x0000 => StatusWords::None,
|
||||||
|
0x6982 => StatusWords::SecurityStatusError,
|
||||||
|
0x6983 => StatusWords::AuthBlockedError,
|
||||||
|
0x6a80 => StatusWords::IncorrectParamError,
|
||||||
|
0x6b00 => StatusWords::IncorrectSlotError,
|
||||||
|
0x6d00 => StatusWords::NotSupportedError,
|
||||||
|
0x9000 => StatusWords::Success,
|
||||||
|
_ => StatusWords::Other(sw),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<StatusWords> for u32 {
|
||||||
|
fn from(sw: StatusWords) -> u32 {
|
||||||
|
sw.code()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,99 @@
|
|||||||
|
//! Serialization functions
|
||||||
|
|
||||||
|
// 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.
|
||||||
|
|
||||||
|
use crate::{consts::*, ObjectId};
|
||||||
|
|
||||||
|
// TODO(tarcieri): refactor these into better serializers/message builders
|
||||||
|
|
||||||
|
/// Set length
|
||||||
|
pub(crate) fn set_length(buffer: &mut [u8], length: usize) -> usize {
|
||||||
|
if length < 0x80 {
|
||||||
|
buffer[0] = length as u8;
|
||||||
|
1
|
||||||
|
} else if length < 0x100 {
|
||||||
|
buffer[0] = 0x81;
|
||||||
|
buffer[1] = length as u8;
|
||||||
|
2
|
||||||
|
} else {
|
||||||
|
buffer[0] = 0x82;
|
||||||
|
buffer[1] = ((length >> 8) & 0xff) as u8;
|
||||||
|
buffer[2] = (length & 0xff) as u8;
|
||||||
|
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 {
|
||||||
|
if buffer[0] < 0x81 {
|
||||||
|
*len = buffer[0] as usize;
|
||||||
|
1
|
||||||
|
} else if (buffer[0] & 0x7f) == 1 {
|
||||||
|
*len = buffer[1] as usize;
|
||||||
|
2
|
||||||
|
} else if (buffer[0] & 0x7f) == 2 {
|
||||||
|
let tmp = buffer[1] as usize;
|
||||||
|
*len = (tmp << 8) + buffer[2] as usize;
|
||||||
|
3
|
||||||
|
} else {
|
||||||
|
0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Is length valid?
|
||||||
|
pub(crate) fn has_valid_length(buffer: &[u8], len: usize) -> bool {
|
||||||
|
(buffer[0] < 0x81 && len > 0)
|
||||||
|
|| ((buffer[0] & 0x7f) == 1 && len > 1)
|
||||||
|
|| ((buffer[0] & 0x7f == 2) && (len > 2))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set an object ID header value in the given buffer, returning a mutable
|
||||||
|
/// slice immediately after the header.
|
||||||
|
///
|
||||||
|
/// Panics if the buffer is too small to contain the header.
|
||||||
|
pub(crate) fn set_object(object_id: ObjectId, mut buffer: &mut [u8]) -> &mut [u8] {
|
||||||
|
buffer[0] = 0x5c;
|
||||||
|
|
||||||
|
if object_id == YKPIV_OBJ_DISCOVERY {
|
||||||
|
buffer[1] = 1;
|
||||||
|
buffer[2] = YKPIV_OBJ_DISCOVERY as u8;
|
||||||
|
buffer = &mut buffer[3..];
|
||||||
|
} else if object_id > 0xffff && object_id <= 0x00ff_ffff {
|
||||||
|
buffer[1] = 3;
|
||||||
|
buffer[2] = ((object_id >> 16) & 0xff) as u8;
|
||||||
|
buffer[3] = ((object_id >> 8) & 0xff) as u8;
|
||||||
|
buffer[4] = (object_id & 0xff) as u8;
|
||||||
|
buffer = &mut buffer[5..];
|
||||||
|
}
|
||||||
|
|
||||||
|
buffer
|
||||||
|
}
|
||||||
+138
@@ -0,0 +1,138 @@
|
|||||||
|
//! Configuration setting values parsed from the environment and config file:
|
||||||
|
//! `/etc/yubico/yubikeypiv.conf`
|
||||||
|
|
||||||
|
// 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.
|
||||||
|
|
||||||
|
/// Default location of the YubiKey PIV configuration file
|
||||||
|
pub const DEFAULT_CONFIG_FILE: &str = "/etc/yubico/yubikeypiv.conf";
|
||||||
|
|
||||||
|
use std::{
|
||||||
|
env,
|
||||||
|
fs::File,
|
||||||
|
io::{BufRead, BufReader},
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Source of how a setting was configured
|
||||||
|
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||||
|
pub enum Source {
|
||||||
|
/// User-specified setting
|
||||||
|
User,
|
||||||
|
|
||||||
|
/// Admin-specified setting
|
||||||
|
Admin,
|
||||||
|
|
||||||
|
/// Default setting
|
||||||
|
Default,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Setting booleans
|
||||||
|
#[derive(Copy, Clone, Debug)]
|
||||||
|
pub struct BoolValue {
|
||||||
|
/// Boolean value
|
||||||
|
pub value: bool,
|
||||||
|
|
||||||
|
/// Source of the configuration setting (user/admin/default)
|
||||||
|
pub source: Source,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl BoolValue {
|
||||||
|
/// Get a [`BoolValue`] value
|
||||||
|
pub fn get(key: &str, def: bool) -> Self {
|
||||||
|
let mut setting = get_setting_from_file(key);
|
||||||
|
|
||||||
|
if setting.source == Source::Default {
|
||||||
|
setting = get_setting_from_env(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
if setting.source == Source::Default {
|
||||||
|
setting.value = def;
|
||||||
|
}
|
||||||
|
|
||||||
|
setting
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get a boolean config value
|
||||||
|
fn get_setting_from_file(key: &str) -> BoolValue {
|
||||||
|
let mut setting: BoolValue = BoolValue {
|
||||||
|
value: false,
|
||||||
|
source: Source::Default,
|
||||||
|
};
|
||||||
|
|
||||||
|
let file = match File::open(DEFAULT_CONFIG_FILE) {
|
||||||
|
Ok(f) => f,
|
||||||
|
Err(_) => return setting,
|
||||||
|
};
|
||||||
|
|
||||||
|
for line in BufReader::new(file).lines() {
|
||||||
|
let line = match line {
|
||||||
|
Ok(line) => line,
|
||||||
|
_ => continue,
|
||||||
|
};
|
||||||
|
|
||||||
|
if line.starts_with('#') || line.starts_with('\r') || line.starts_with('\n') {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let (name, value) = {
|
||||||
|
let mut parts = line.splitn(1, '=');
|
||||||
|
let name = parts.next();
|
||||||
|
let value = parts.next();
|
||||||
|
match (name, value, parts.next()) {
|
||||||
|
(Some(name), Some(value), None) => (name.trim(), value.trim()),
|
||||||
|
_ => continue,
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if name == key {
|
||||||
|
setting.source = Source::Admin;
|
||||||
|
setting.value = value == "1" || value == "true";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setting
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get a setting boolean from an environment variable
|
||||||
|
fn get_setting_from_env(key: &str) -> BoolValue {
|
||||||
|
let mut setting: BoolValue = BoolValue {
|
||||||
|
value: false,
|
||||||
|
source: Source::Default,
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Ok(value) = env::var(format!("YUBIKEY_PIV_{}", key)) {
|
||||||
|
setting.source = Source::User;
|
||||||
|
setting.value = value == "1" || value == "true";
|
||||||
|
}
|
||||||
|
|
||||||
|
setting
|
||||||
|
}
|
||||||
@@ -0,0 +1,561 @@
|
|||||||
|
//! YubiKey PC/SC transactions
|
||||||
|
|
||||||
|
use crate::{apdu::APDU, consts::*, error::Error, yubikey::*, Buffer};
|
||||||
|
#[cfg(feature = "untested")]
|
||||||
|
use crate::{
|
||||||
|
mgm::MgmKey,
|
||||||
|
response::{Response, StatusWords},
|
||||||
|
serialization::*,
|
||||||
|
ObjectId,
|
||||||
|
};
|
||||||
|
use log::{error, trace};
|
||||||
|
use std::convert::TryInto;
|
||||||
|
#[cfg(feature = "untested")]
|
||||||
|
use std::ptr;
|
||||||
|
use zeroize::Zeroizing;
|
||||||
|
|
||||||
|
/// 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
|
||||||
|
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.
|
||||||
|
///
|
||||||
|
/// This is a wrapper for the raw `SCardTransmit` function and operates on
|
||||||
|
/// single APDU messages at a time. For larger messages that need to be
|
||||||
|
/// split into multiple APDUs, use the [`Transaction::transfer_data`]
|
||||||
|
/// method instead.
|
||||||
|
pub fn transmit(&self, send_buffer: &[u8], recv_len: usize) -> Result<Buffer, Error> {
|
||||||
|
trace!(">>> {:?}", send_buffer);
|
||||||
|
|
||||||
|
let mut recv_buffer = Zeroizing::new(vec![0u8; recv_len]);
|
||||||
|
|
||||||
|
let len = self
|
||||||
|
.inner
|
||||||
|
.transmit(send_buffer, recv_buffer.as_mut())?
|
||||||
|
.len();
|
||||||
|
|
||||||
|
recv_buffer.truncate(len);
|
||||||
|
|
||||||
|
trace!("<<< {:?}", recv_buffer.as_slice());
|
||||||
|
|
||||||
|
Ok(recv_buffer)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Select application.
|
||||||
|
pub fn select_application(&self) -> Result<(), Error> {
|
||||||
|
let response = APDU::new(YKPIV_INS_SELECT_APPLICATION)
|
||||||
|
.p1(0x04)
|
||||||
|
.data(&AID)
|
||||||
|
.transmit(self, 0xFF)
|
||||||
|
.map_err(|e| {
|
||||||
|
error!("failed communicating with card: '{}'", e);
|
||||||
|
e
|
||||||
|
})?;
|
||||||
|
|
||||||
|
if !response.is_success() {
|
||||||
|
error!(
|
||||||
|
"failed selecting application: {:04x}",
|
||||||
|
response.status_words().code()
|
||||||
|
);
|
||||||
|
return Err(Error::GenericError);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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(YKPIV_INS_GET_VERSION).transmit(self, 261)?;
|
||||||
|
|
||||||
|
if !response.is_success() {
|
||||||
|
return Err(Error::GenericError);
|
||||||
|
}
|
||||||
|
|
||||||
|
if response.buffer().len() < 3 {
|
||||||
|
return Err(Error::SizeError);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(Version {
|
||||||
|
major: response.buffer()[0],
|
||||||
|
minor: response.buffer()[1],
|
||||||
|
patch: response.buffer()[2],
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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
|
||||||
|
let sw = APDU::new(YKPIV_INS_SELECT_APPLICATION)
|
||||||
|
.p1(0x04)
|
||||||
|
.data(&yk_applet)
|
||||||
|
.transmit(self, 0xFF)?
|
||||||
|
.status_words();
|
||||||
|
|
||||||
|
if !sw.is_success() {
|
||||||
|
error!("failed selecting yk application: {:04x}", sw.code());
|
||||||
|
return Err(Error::GenericError);
|
||||||
|
}
|
||||||
|
|
||||||
|
let resp = APDU::new(0x01).p1(0x10).transmit(self, 0xFF)?;
|
||||||
|
|
||||||
|
if !resp.is_success() {
|
||||||
|
error!(
|
||||||
|
"failed retrieving serial number: {:04x}",
|
||||||
|
resp.status_words().code()
|
||||||
|
);
|
||||||
|
return Err(Error::GenericError);
|
||||||
|
}
|
||||||
|
|
||||||
|
// reselect the PIV applet
|
||||||
|
let sw = APDU::new(YKPIV_INS_SELECT_APPLICATION)
|
||||||
|
.p1(0x04)
|
||||||
|
.data(&AID)
|
||||||
|
.transmit(self, 0xFF)?
|
||||||
|
.status_words();
|
||||||
|
|
||||||
|
if !sw.is_success() {
|
||||||
|
error!("failed selecting application: {:04x}", sw.code());
|
||||||
|
return Err(Error::GenericError);
|
||||||
|
}
|
||||||
|
|
||||||
|
resp
|
||||||
|
} else {
|
||||||
|
// get serial from yk5 and later devices using the f8 command
|
||||||
|
let resp = APDU::new(YKPIV_INS_GET_SERIAL).transmit(self, 0xFF)?;
|
||||||
|
|
||||||
|
if !resp.is_success() {
|
||||||
|
error!(
|
||||||
|
"failed retrieving serial number: {:04x}",
|
||||||
|
resp.status_words().code()
|
||||||
|
);
|
||||||
|
return Err(Error::GenericError);
|
||||||
|
}
|
||||||
|
|
||||||
|
resp
|
||||||
|
};
|
||||||
|
|
||||||
|
response.buffer()[..4]
|
||||||
|
.try_into()
|
||||||
|
.map(|serial| Serial::from(u32::from_be_bytes(serial)))
|
||||||
|
.map_err(|_| Error::SizeError)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Verify device PIN.
|
||||||
|
#[cfg(feature = "untested")]
|
||||||
|
pub fn verify_pin(&self, pin: &[u8]) -> Result<(), Error> {
|
||||||
|
// TODO(tarcieri): allow unpadded (with `0xFF`) PIN shorter than CB_PIN_MAX?
|
||||||
|
if pin.len() != CB_PIN_MAX {
|
||||||
|
return Err(Error::SizeError);
|
||||||
|
}
|
||||||
|
|
||||||
|
let response = APDU::new(YKPIV_INS_VERIFY)
|
||||||
|
.params(0x00, 0x80)
|
||||||
|
.data(pin)
|
||||||
|
.transmit(self, 261)?;
|
||||||
|
|
||||||
|
match response.status_words() {
|
||||||
|
StatusWords::Success => Ok(()),
|
||||||
|
StatusWords::AuthBlockedError => Err(Error::WrongPin { tries: 0 }),
|
||||||
|
StatusWords::Other(sw) if sw >> 8 == 0x63 => Err(Error::WrongPin { tries: sw & 0xf }),
|
||||||
|
_ => Err(Error::GenericError),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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, YKPIV_INS_CHANGE_REFERENCE, 0, 0x80];
|
||||||
|
let mut indata = Zeroizing::new([0u8; 16]);
|
||||||
|
|
||||||
|
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] = YKPIV_INS_RESET_RETRY;
|
||||||
|
} else if action == CHREF_ACT_CHANGE_PUK {
|
||||||
|
templ[3] = 0x81;
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe {
|
||||||
|
ptr::copy(current_pin.as_ptr(), indata.as_mut_ptr(), current_pin.len());
|
||||||
|
|
||||||
|
if current_pin.len() < CB_PIN_MAX {
|
||||||
|
ptr::write_bytes(
|
||||||
|
indata.as_mut_ptr().add(current_pin.len()),
|
||||||
|
0xff,
|
||||||
|
CB_PIN_MAX - current_pin.len(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
ptr::copy(
|
||||||
|
new_pin.as_ptr(),
|
||||||
|
indata.as_mut_ptr().offset(8),
|
||||||
|
new_pin.len(),
|
||||||
|
);
|
||||||
|
|
||||||
|
if new_pin.len() < CB_PIN_MAX {
|
||||||
|
ptr::write_bytes(
|
||||||
|
indata.as_mut_ptr().offset(8).add(new_pin.len()),
|
||||||
|
0xff,
|
||||||
|
CB_PIN_MAX - new_pin.len(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let status_words = self
|
||||||
|
.transfer_data(&templ, indata.as_ref(), 0xFF)?
|
||||||
|
.status_words();
|
||||||
|
|
||||||
|
match status_words {
|
||||||
|
StatusWords::Success => Ok(()),
|
||||||
|
StatusWords::AuthBlockedError => Err(Error::PinLocked),
|
||||||
|
StatusWords::Other(sw) if sw >> 8 == 0x63 => Err(Error::WrongPin { tries: sw & 0xf }),
|
||||||
|
_ => {
|
||||||
|
error!(
|
||||||
|
"failed changing pin, token response code: {:x}.",
|
||||||
|
status_words.code()
|
||||||
|
);
|
||||||
|
Err(Error::GenericError)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set the management key (MGM).
|
||||||
|
#[cfg(feature = "untested")]
|
||||||
|
pub fn set_mgm_key(&self, new_key: &MgmKey, touch: Option<u8>) -> Result<(), Error> {
|
||||||
|
let p2 = match touch.unwrap_or_default() {
|
||||||
|
0 => 0xff,
|
||||||
|
1 => 0xfe,
|
||||||
|
_ => {
|
||||||
|
return Err(Error::GenericError);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut data = [0u8; DES_LEN_3DES + 3];
|
||||||
|
data[0] = YKPIV_ALGO_3DES;
|
||||||
|
data[1] = YKPIV_KEY_CARDMGM;
|
||||||
|
data[2] = DES_LEN_3DES as u8;
|
||||||
|
data[3..3 + DES_LEN_3DES].copy_from_slice(new_key.as_ref());
|
||||||
|
|
||||||
|
let status_words = APDU::new(YKPIV_INS_SET_MGMKEY)
|
||||||
|
.params(0xff, p2)
|
||||||
|
.data(&data)
|
||||||
|
.transmit(self, 261)?
|
||||||
|
.status_words();
|
||||||
|
|
||||||
|
if !status_words.is_success() {
|
||||||
|
return Err(Error::GenericError);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Perform a YubiKey operation which requires authentication.
|
||||||
|
///
|
||||||
|
/// 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,
|
||||||
|
sign_in: &[u8],
|
||||||
|
out: &mut [u8],
|
||||||
|
out_len: &mut usize,
|
||||||
|
algorithm: u8,
|
||||||
|
key: u8,
|
||||||
|
decipher: bool,
|
||||||
|
) -> Result<(), Error> {
|
||||||
|
let in_len = sign_in.len();
|
||||||
|
let mut indata = [0u8; 1024];
|
||||||
|
let templ = [0, YKPIV_INS_AUTHENTICATE, algorithm, key];
|
||||||
|
let mut len: usize = 0;
|
||||||
|
|
||||||
|
match algorithm {
|
||||||
|
YKPIV_ALGO_RSA1024 | YKPIV_ALGO_RSA2048 => {
|
||||||
|
let key_len = if algorithm == YKPIV_ALGO_RSA1024 {
|
||||||
|
128
|
||||||
|
} else {
|
||||||
|
256
|
||||||
|
};
|
||||||
|
|
||||||
|
if in_len != key_len {
|
||||||
|
return Err(Error::SizeError);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
YKPIV_ALGO_ECCP256 | YKPIV_ALGO_ECCP384 => {
|
||||||
|
let key_len = if algorithm == YKPIV_ALGO_ECCP256 {
|
||||||
|
32
|
||||||
|
} else {
|
||||||
|
48
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!decipher && (in_len > key_len)) || (decipher && (in_len != (key_len * 2) + 1))
|
||||||
|
{
|
||||||
|
return Err(Error::SizeError);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => return Err(Error::AlgorithmError),
|
||||||
|
}
|
||||||
|
|
||||||
|
let bytes = if in_len < 0x80 {
|
||||||
|
1
|
||||||
|
} else if in_len < 0xff {
|
||||||
|
2
|
||||||
|
} else {
|
||||||
|
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] =
|
||||||
|
if (algorithm == YKPIV_ALGO_ECCP256 || algorithm == YKPIV_ALGO_ECCP384) && decipher {
|
||||||
|
0x85
|
||||||
|
} else {
|
||||||
|
0x81
|
||||||
|
};
|
||||||
|
|
||||||
|
offset += 3;
|
||||||
|
offset += set_length(&mut indata[offset..], in_len);
|
||||||
|
indata[offset..(offset + in_len)].copy_from_slice(sign_in);
|
||||||
|
offset += in_len;
|
||||||
|
|
||||||
|
let response = self
|
||||||
|
.transfer_data(&templ, &indata[..offset], 1024)
|
||||||
|
.map_err(|e| {
|
||||||
|
error!("sign command failed to communicate: {}", e);
|
||||||
|
e
|
||||||
|
})?;
|
||||||
|
|
||||||
|
if !response.is_success() {
|
||||||
|
error!("failed sign command with code {:x}", response.code());
|
||||||
|
|
||||||
|
if response.status_words() == StatusWords::SecurityStatusError {
|
||||||
|
return Err(Error::AuthenticationError);
|
||||||
|
} else {
|
||||||
|
return Err(Error::GenericError);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let data = response.buffer();
|
||||||
|
|
||||||
|
// skip the first 7c tag
|
||||||
|
if data[0] != 0x7c {
|
||||||
|
error!("failed parsing signature reply (0x7c byte)");
|
||||||
|
return Err(Error::ParseError);
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut offset = 1 + get_length(&data[1..], &mut len);
|
||||||
|
|
||||||
|
// skip the 82 tag
|
||||||
|
if data[offset] != 0x82 {
|
||||||
|
error!("failed parsing signature reply (0x82 byte)");
|
||||||
|
return Err(Error::ParseError);
|
||||||
|
}
|
||||||
|
|
||||||
|
offset += 1;
|
||||||
|
offset += get_length(&data[offset..], &mut len);
|
||||||
|
|
||||||
|
if len > *out_len {
|
||||||
|
error!("wrong size on output buffer");
|
||||||
|
return Err(Error::SizeError);
|
||||||
|
}
|
||||||
|
|
||||||
|
*out_len = len;
|
||||||
|
out[..len].copy_from_slice(&data[offset..(offset + len)]);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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],
|
||||||
|
in_data: &[u8],
|
||||||
|
max_out: usize,
|
||||||
|
) -> Result<Response, Error> {
|
||||||
|
let mut in_offset = 0;
|
||||||
|
let mut out_data = Zeroizing::new(vec![]);
|
||||||
|
let mut sw = 0;
|
||||||
|
|
||||||
|
loop {
|
||||||
|
let mut this_size = 0xff;
|
||||||
|
|
||||||
|
let cla = if in_offset + 0xff < in_data.len() {
|
||||||
|
0x10
|
||||||
|
} else {
|
||||||
|
this_size = in_data.len() - in_offset;
|
||||||
|
templ[0]
|
||||||
|
};
|
||||||
|
|
||||||
|
trace!("going to send {} bytes in this go", this_size);
|
||||||
|
|
||||||
|
let response = APDU::new(templ[1])
|
||||||
|
.cla(cla)
|
||||||
|
.params(templ[2], templ[3])
|
||||||
|
.data(&in_data[in_offset..(in_offset + this_size)])
|
||||||
|
.transmit(self, 261)?;
|
||||||
|
|
||||||
|
if !response.is_success() && (response.status_words().code() >> 8 != 0x61) {
|
||||||
|
// TODO(tarcieri): is this really OK?
|
||||||
|
return Ok(Response::new(sw.into(), out_data));
|
||||||
|
}
|
||||||
|
|
||||||
|
sw = response.status_words().code();
|
||||||
|
|
||||||
|
if out_data.len() - response.buffer().len() - 2 > max_out {
|
||||||
|
error!(
|
||||||
|
"output buffer too small: wanted to write {}, max was {}",
|
||||||
|
out_data.len() - response.buffer().len() - 2,
|
||||||
|
max_out
|
||||||
|
);
|
||||||
|
|
||||||
|
return Err(Error::SizeError);
|
||||||
|
}
|
||||||
|
|
||||||
|
out_data.extend_from_slice(&response.buffer()[..response.buffer().len() - 2]);
|
||||||
|
|
||||||
|
in_offset += this_size;
|
||||||
|
if in_offset >= in_data.len() {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
while sw >> 8 == 0x61 {
|
||||||
|
trace!(
|
||||||
|
"The card indicates there is {} bytes more data for us",
|
||||||
|
sw & 0xff
|
||||||
|
);
|
||||||
|
|
||||||
|
let response = APDU::new(YKPIV_INS_GET_RESPONSE_APDU).transmit(self, 261)?;
|
||||||
|
sw = response.status_words().code();
|
||||||
|
|
||||||
|
if sw != StatusWords::Success.code() && (sw >> 8 != 0x61) {
|
||||||
|
return Ok(Response::new(sw.into(), Zeroizing::new(vec![])));
|
||||||
|
}
|
||||||
|
|
||||||
|
if out_data.len() + response.buffer().len() - 2 > max_out {
|
||||||
|
error!(
|
||||||
|
"output buffer too small: wanted to write {}, max was {}",
|
||||||
|
out_data.len() + response.buffer().len() - 2,
|
||||||
|
max_out
|
||||||
|
);
|
||||||
|
|
||||||
|
return Err(Error::SizeError);
|
||||||
|
}
|
||||||
|
|
||||||
|
out_data.extend_from_slice(&response.buffer()[..response.buffer().len() - 2]);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(Response::new(sw.into(), out_data))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Fetch an object
|
||||||
|
#[cfg(feature = "untested")]
|
||||||
|
pub fn fetch_object(&self, object_id: ObjectId) -> Result<Buffer, Error> {
|
||||||
|
let mut indata = [0u8; 5];
|
||||||
|
let templ = [0, YKPIV_INS_GET_DATA, 0x3f, 0xff];
|
||||||
|
|
||||||
|
let mut inlen = indata.len();
|
||||||
|
let indata_remaining = set_object(object_id, &mut indata);
|
||||||
|
inlen -= indata_remaining.len();
|
||||||
|
|
||||||
|
let response = self.transfer_data(&templ, &indata[..inlen], CB_BUF_MAX)?;
|
||||||
|
|
||||||
|
if !response.is_success() {
|
||||||
|
return Err(Error::GenericError);
|
||||||
|
}
|
||||||
|
|
||||||
|
let data = response.into_buffer();
|
||||||
|
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);
|
||||||
|
|
||||||
|
if offs == 0 {
|
||||||
|
return Err(Error::SizeError);
|
||||||
|
}
|
||||||
|
|
||||||
|
if outlen + offs + 1 != data.len() {
|
||||||
|
error!(
|
||||||
|
"invalid length indicated in object: total len is {} but indicated length is {}",
|
||||||
|
data.len(),
|
||||||
|
outlen
|
||||||
|
);
|
||||||
|
|
||||||
|
return Err(Error::SizeError);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(Zeroizing::new(
|
||||||
|
data[(1 + offs)..(1 + offs + outlen)].to_vec(),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Save an object
|
||||||
|
#[cfg(feature = "untested")]
|
||||||
|
pub fn save_object(&self, object_id: ObjectId, indata: &[u8]) -> Result<(), Error> {
|
||||||
|
let templ = [0, YKPIV_INS_PUT_DATA, 0x3f, 0xff];
|
||||||
|
|
||||||
|
// TODO(tarcieri): replace with vector
|
||||||
|
let mut data = [0u8; CB_BUF_MAX];
|
||||||
|
|
||||||
|
if indata.len() > CB_OBJ_MAX {
|
||||||
|
return Err(Error::SizeError);
|
||||||
|
}
|
||||||
|
|
||||||
|
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());
|
||||||
|
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
|
||||||
|
.transfer_data(&templ, &data[..len], 255)?
|
||||||
|
.status_words();
|
||||||
|
|
||||||
|
match status_words {
|
||||||
|
StatusWords::Success => Ok(()),
|
||||||
|
StatusWords::SecurityStatusError => Err(Error::AuthenticationError),
|
||||||
|
_ => Err(Error::GenericError),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
-2354
File diff suppressed because it is too large
Load Diff
+571
-2237
File diff suppressed because it is too large
Load Diff
@@ -1,46 +0,0 @@
|
|||||||
# 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.
|
|
||||||
|
|
||||||
AM_CFLAGS = $(WARN_CFLAGS) @CHECK_CFLAGS@ $(OPENSSL_CFLAGS)
|
|
||||||
AM_CPPFLAGS = -I$(top_srcdir)/lib -I$(top_builddir)/lib $(OPENSSL_CFLAGS) $(PCSC_CFLAGS)
|
|
||||||
|
|
||||||
AM_LDFLAGS = @CHECK_LIBS@
|
|
||||||
|
|
||||||
if COMPILER_CLANG
|
|
||||||
AM_LDFLAGS += -no-fast-install
|
|
||||||
else
|
|
||||||
AM_LDFLAGS += -no-install
|
|
||||||
endif
|
|
||||||
|
|
||||||
LDADD = ../libykpiv.la $(OPENSSL_LIBS)
|
|
||||||
|
|
||||||
api_LDADD = ../../tool/libpiv_util.la
|
|
||||||
api_SOURCES = api.c
|
|
||||||
check_PROGRAMS = basic parse_key api
|
|
||||||
TESTS = $(check_PROGRAMS)
|
|
||||||
|
|
||||||
LOG_COMPILER = $(VALGRIND)
|
|
||||||
-993
@@ -1,993 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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.
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include "ykpiv.h"
|
|
||||||
#include "internal.h"
|
|
||||||
#include "../../tool/openssl-compat.h"
|
|
||||||
|
|
||||||
#include <stddef.h>
|
|
||||||
#include <stdio.h>
|
|
||||||
#include <stdlib.h>
|
|
||||||
#include <string.h>
|
|
||||||
|
|
||||||
#include <check.h>
|
|
||||||
|
|
||||||
#ifdef __MINGW32__
|
|
||||||
#define dprintf(fd, ...) fprintf(stdout, __VA_ARGS__)
|
|
||||||
#endif
|
|
||||||
|
|
||||||
int destruction_confirmed(void);
|
|
||||||
|
|
||||||
// only defined in libcheck 0.11+ (linux distros still shipping 0.10)
|
|
||||||
#ifndef ck_assert_ptr_nonnull
|
|
||||||
#define ck_assert_ptr_nonnull(a) ck_assert((a) != NULL)
|
|
||||||
#endif
|
|
||||||
#ifndef ck_assert_mem_eq
|
|
||||||
#define ck_assert_mem_eq(a,b,n) ck_assert(memcmp((a), (b), (n)) == 0)
|
|
||||||
#endif
|
|
||||||
// only defined in libcheck 0.10+ (RHEL7 is still shipping 0.9)
|
|
||||||
#ifndef ck_assert_ptr_eq
|
|
||||||
#define ck_assert_ptr_eq(a,b) ck_assert((void *)(a) == (void *)(b))
|
|
||||||
#endif
|
|
||||||
|
|
||||||
ykpiv_state *g_state;
|
|
||||||
const uint8_t g_cert[] = {
|
|
||||||
"0123456789ABCDEFGHIK0123456789ABCDEFGHIK0123456789ABCDEFGHIK0123456789ABCDEFGHIK"
|
|
||||||
"0123456789ABCDEFGHIK0123456789ABCDEFGHIK0123456789ABCDEFGHIK0123456789ABCDEFGHIK"
|
|
||||||
"0123456789ABCDEFGHIK0123456789ABCDEFGHIK0123456789ABCDEFGHIK0123456789ABCDEFGHIK"
|
|
||||||
"0123456789ABCDEFGHIK0123456789ABCDEFGHIK0123456789ABCDEFGHIK0123456789ABCDEFGHIK"
|
|
||||||
"0123456789ABCDEFGHIK0123456789ABCDEFGHIK0123456789ABCDEFGHIK0123456789ABCDEFGHIK"
|
|
||||||
};
|
|
||||||
|
|
||||||
void setup(void) {
|
|
||||||
ykpiv_rc res;
|
|
||||||
|
|
||||||
// Require user confirmation to continue, since this test suite will clear
|
|
||||||
// any data stored on connected keys.
|
|
||||||
if (!destruction_confirmed())
|
|
||||||
exit(77); // exit code 77 == skipped tests
|
|
||||||
|
|
||||||
res = ykpiv_init(&g_state, true);
|
|
||||||
ck_assert_int_eq(res, YKPIV_OK);
|
|
||||||
ck_assert_ptr_nonnull(g_state);
|
|
||||||
|
|
||||||
res = ykpiv_connect(g_state, NULL);
|
|
||||||
ck_assert_int_eq(res, YKPIV_OK);
|
|
||||||
}
|
|
||||||
|
|
||||||
void teardown(void) {
|
|
||||||
ykpiv_rc res;
|
|
||||||
|
|
||||||
// This is the expected case, if the allocator test ran, since it de-inits.
|
|
||||||
if (NULL == g_state)
|
|
||||||
return;
|
|
||||||
|
|
||||||
res = ykpiv_disconnect(g_state);
|
|
||||||
ck_assert_int_eq(res, YKPIV_OK);
|
|
||||||
|
|
||||||
res = ykpiv_done(g_state);
|
|
||||||
ck_assert_int_eq(res, YKPIV_OK);
|
|
||||||
}
|
|
||||||
|
|
||||||
#ifdef HW_TESTS
|
|
||||||
START_TEST(test_devicemodel) {
|
|
||||||
ykpiv_rc res;
|
|
||||||
ykpiv_devmodel model;
|
|
||||||
char version[256];
|
|
||||||
char reader_buf[2048];
|
|
||||||
size_t num_readers = sizeof(reader_buf);
|
|
||||||
|
|
||||||
res = ykpiv_get_version(g_state, version, sizeof(version));
|
|
||||||
ck_assert_int_eq(res, YKPIV_OK);
|
|
||||||
fprintf(stderr, "Version: %s\n", version);
|
|
||||||
model = ykpiv_util_devicemodel(g_state);
|
|
||||||
fprintf(stdout, "Model: %u\n", model);
|
|
||||||
ck_assert(model == DEVTYPE_YK4 || model == DEVTYPE_NEOr3);
|
|
||||||
|
|
||||||
res = ykpiv_list_readers(g_state, reader_buf, &num_readers);
|
|
||||||
ck_assert_int_eq(res, YKPIV_OK);
|
|
||||||
ck_assert_int_gt(num_readers, 0);
|
|
||||||
if (model == DEVTYPE_YK4) {
|
|
||||||
ck_assert_ptr_nonnull(strstr(reader_buf, "Yubikey 4"));
|
|
||||||
ck_assert(version[0] == '4'); // Verify app version 4.x
|
|
||||||
ck_assert(version[1] == '.');
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
ck_assert_ptr_nonnull(strstr(reader_buf, "Yubikey NEO"));
|
|
||||||
ck_assert(version[0] == '1'); // Verify app version 1.x
|
|
||||||
ck_assert(version[1] == '.');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
END_TEST
|
|
||||||
|
|
||||||
START_TEST(test_get_set_cardid) {
|
|
||||||
ykpiv_rc res;
|
|
||||||
ykpiv_cardid set_id;
|
|
||||||
ykpiv_cardid get_id;
|
|
||||||
|
|
||||||
memset(&set_id.data, 'i', sizeof(set_id.data));
|
|
||||||
memset(&get_id.data, 0, sizeof(get_id.data));
|
|
||||||
|
|
||||||
res = ykpiv_util_set_cardid(g_state, &set_id);
|
|
||||||
ck_assert_int_eq(res, YKPIV_OK);
|
|
||||||
|
|
||||||
res = ykpiv_util_get_cardid(g_state, &get_id);
|
|
||||||
ck_assert_int_eq(res, YKPIV_OK);
|
|
||||||
ck_assert_mem_eq(&set_id.data, &get_id.data, sizeof(set_id.data));
|
|
||||||
}
|
|
||||||
END_TEST
|
|
||||||
|
|
||||||
START_TEST(test_list_readers) {
|
|
||||||
ykpiv_rc res;
|
|
||||||
char reader_buf[2048];
|
|
||||||
size_t num_readers = sizeof(reader_buf);
|
|
||||||
char *reader_ptr;
|
|
||||||
res = ykpiv_list_readers(g_state, reader_buf, &num_readers);
|
|
||||||
ck_assert_int_eq(res, YKPIV_OK);
|
|
||||||
ck_assert_int_gt(num_readers, 0);
|
|
||||||
for(reader_ptr = reader_buf; *reader_ptr != '\0'; reader_ptr += strlen(reader_ptr) + 1) {
|
|
||||||
fprintf(stdout, "Found device: %s\n", reader_ptr);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
END_TEST
|
|
||||||
|
|
||||||
START_TEST(test_read_write_list_delete_cert) {
|
|
||||||
ykpiv_rc res;
|
|
||||||
uint8_t *read_cert = NULL;
|
|
||||||
size_t read_cert_len = 0;
|
|
||||||
|
|
||||||
{
|
|
||||||
res = ykpiv_util_write_cert(g_state, YKPIV_KEY_AUTHENTICATION, (uint8_t*)g_cert, sizeof(g_cert), YKPIV_CERTINFO_UNCOMPRESSED);
|
|
||||||
ck_assert_int_eq(res, YKPIV_OK);
|
|
||||||
|
|
||||||
res = ykpiv_util_read_cert(g_state, YKPIV_KEY_AUTHENTICATION, &read_cert, &read_cert_len);
|
|
||||||
ck_assert_int_eq(res, YKPIV_OK);
|
|
||||||
ck_assert_ptr_nonnull(read_cert);
|
|
||||||
ck_assert_int_eq(read_cert_len, sizeof(g_cert));
|
|
||||||
ck_assert_mem_eq(g_cert, read_cert, sizeof(g_cert));
|
|
||||||
|
|
||||||
res = ykpiv_util_free(g_state, read_cert);
|
|
||||||
ck_assert_int_eq(res, YKPIV_OK);
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
ykpiv_key *keys = NULL;
|
|
||||||
size_t data_len;
|
|
||||||
uint8_t key_count;
|
|
||||||
res = ykpiv_util_list_keys(g_state, &key_count, &keys, &data_len);
|
|
||||||
ck_assert_int_eq(res, YKPIV_OK);
|
|
||||||
ck_assert_ptr_nonnull(keys);
|
|
||||||
ck_assert_int_gt(key_count, 0);
|
|
||||||
|
|
||||||
res = ykpiv_util_free(g_state, keys);
|
|
||||||
ck_assert_int_eq(res, YKPIV_OK);
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
res = ykpiv_util_delete_cert(g_state, YKPIV_KEY_AUTHENTICATION);
|
|
||||||
ck_assert_int_eq(res, YKPIV_OK);
|
|
||||||
|
|
||||||
res = ykpiv_util_read_cert(g_state, YKPIV_KEY_AUTHENTICATION, &read_cert, &read_cert_len);
|
|
||||||
ck_assert_int_eq(res, YKPIV_GENERIC_ERROR);
|
|
||||||
|
|
||||||
res = ykpiv_util_free(g_state, read_cert);
|
|
||||||
ck_assert_int_eq(res, YKPIV_OK);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
END_TEST
|
|
||||||
|
|
||||||
#include <openssl/des.h>
|
|
||||||
#include <openssl/pem.h>
|
|
||||||
#include <openssl/pkcs12.h>
|
|
||||||
#include <openssl/rand.h>
|
|
||||||
|
|
||||||
// RSA2048 private key, generated with: `openssl genrsa 2048 -out private.pem`
|
|
||||||
static const char *private_key_pem =
|
|
||||||
"-----BEGIN RSA PRIVATE KEY-----\n"
|
|
||||||
"MIIEpAIBAAKCAQEAwVUwmVbc+ffOy2+RivxBpgleTVN6bUa0q7jNYB+AseFQYaYq\n"
|
|
||||||
"EGfa+VGdxSGo+8DV1KT9+fNEd5243gXn/tcjtMItKeB+oAQc64s9lIFlYuR8bpq1\n"
|
|
||||||
"ibr33iW2elnnv9mpecqohdCVwM2McWveoPyb7MwlwVuhqexOzJO29bqJcazLbtkf\n"
|
|
||||||
"ZETK0oBx53/ylA4Y6nE9Pa46jW2qhj+KShf1iBg+gAyt3eI+wI2Wmub1WxLLH8D2\n"
|
|
||||||
"w+kow8QhQOa8dHCkRRw771JxVO5+d+Y/Y+x9B1HgF4q0q9xUlhWLK2TR4ChBFzXe\n"
|
|
||||||
"47sAHsSqi/pl5JbwYrHPOE/VEBLukmjL8NFCSQIDAQABAoIBADmEyOK2DyRnb6Ti\n"
|
|
||||||
"2qBJEJb/boj+7wuX36S/ZIrWlIlXiXyj3RvoaiOG/rNpokbURknvlIhKsfIMgLW9\n"
|
|
||||||
"eBo/k6Xxp1IwMjwVPS1uzbFjFfDoHYUijiQd9iSnf7TDDsnrThqoCp9VQViNTt1n\n"
|
|
||||||
"xGKNBS7cRddTFbPiVEdVIzfUeZPR2oRrc4maBCRCrQgg8WNknawmc8zhkf2NiPj3\n"
|
|
||||||
"tWLQHMy1/MgW2W1LM9sgzllEtS5CZUnyGy2HbbhS2tbZ6j9kPzOp0pPxxTTzJmmV\n"
|
|
||||||
"fi1vkJcVW4+MdXjWmhALcPA4dO7Y2Ljiu6VxIxQORRO1DyiCjAs1AVMQxgPAAY41\n"
|
|
||||||
"YR4Q2EkCgYEA4zE0oytg97aVaBY9CKi7/PqR+NI/uEvfoQCnT+ddaJgp/qsspuXo\n"
|
|
||||||
"tJt94p13ANd8O7suqQTVNvbZq1rX10xQjJZ9nvlqQa6iHkN6Epq31XBK3Z+acjIV\n"
|
|
||||||
"A2rAgKBByjz9/CpKHqnOsrTWU1Y7x416IG4BZt42hHdrxRH98/wiDH8CgYEA2djj\n"
|
|
||||||
"AjwgK+MwDnshwT1NNgCSP/2ZHatBAykZ5BCs9BJ6MNYqqXVGYoqs5Z5kSkow+Db3\n"
|
|
||||||
"pipkEieo5w2Rd5zkolTThaVCvRkSe5wRiBpZhaeY+b0UFwavGCb6zU/MmJIMDPiI\n"
|
|
||||||
"2iRGeCXgQDvIS/icIqzbTtp6dZaoMgG7LdSR7TcCgYBtxGhaLas8A8tL7vKuLFgn\n"
|
|
||||||
"cij0vyBqOr5hW596y54l2t7vXGTGfm5gVIAN7WaB0ZsEgPuaTet2Eu44DDwcmZKR\n"
|
|
||||||
"WmR3Wqor8eQCGzfvpTEMvqRtT5+fbPMaI4m+m68ttyo/m28UQZbMYPLscM2RLJnE\n"
|
|
||||||
"8WFcAiD0/33iST8ZksggoQKBgQDE/7Yhsj+hkHxHzB+1QPtOp2uaBHnvc4uCESwB\n"
|
|
||||||
"qvbMbN0kxrejsJLqz98UcozdBYSNIiAHmvQN2uGJuCJhGXdEORNjGxRkLoUhVPwh\n"
|
|
||||||
"qTplfC8BQHQncnrqi21oNw6ctg3BuQsAwaccRZwqWiWCVhrT3J8iCr6NEaWeOySK\n"
|
|
||||||
"iF1CNwKBgQCRpkkZArlccwS0kMvkK+tQ1rG2xWm7c05G34gP/g6dHFRy0gPNMyvi\n"
|
|
||||||
"SkiLTJmQIEZSAEiq0FFgcVwM6o556ftvQZuwDp5rHUbwqnHCpMJKpD9aJpStvfPi\n"
|
|
||||||
"4p9JbYdaGqnq4eoNKemmGnbUof0dR9Zr0lGmcMTwwzBib+4E1d7soA==\n"
|
|
||||||
"-----END RSA PRIVATE KEY-----\n";
|
|
||||||
|
|
||||||
// Certificate signed with key above:
|
|
||||||
// `openssl req -x509 -key private.pem -out cert.pem -subj "/CN=bar/OU=test/O=example.com/" -new`
|
|
||||||
static const char *certificate_pem =
|
|
||||||
"-----BEGIN CERTIFICATE-----\n"
|
|
||||||
"MIIC5zCCAc+gAwIBAgIJAOq8A/cmpxF5MA0GCSqGSIb3DQEBCwUAMDMxDDAKBgNV\n"
|
|
||||||
"BAMMA2JhcjENMAsGA1UECwwEdGVzdDEUMBIGA1UECgwLZXhhbXBsZS5jb20wHhcN\n"
|
|
||||||
"MTcwODAzMTE1MDI2WhcNMTgwODAzMTE1MDI2WjAzMQwwCgYDVQQDDANiYXIxDTAL\n"
|
|
||||||
"BgNVBAsMBHRlc3QxFDASBgNVBAoMC2V4YW1wbGUuY29tMIIBIjANBgkqhkiG9w0B\n"
|
|
||||||
"AQEFAAOCAQ8AMIIBCgKCAQEAwVUwmVbc+ffOy2+RivxBpgleTVN6bUa0q7jNYB+A\n"
|
|
||||||
"seFQYaYqEGfa+VGdxSGo+8DV1KT9+fNEd5243gXn/tcjtMItKeB+oAQc64s9lIFl\n"
|
|
||||||
"YuR8bpq1ibr33iW2elnnv9mpecqohdCVwM2McWveoPyb7MwlwVuhqexOzJO29bqJ\n"
|
|
||||||
"cazLbtkfZETK0oBx53/ylA4Y6nE9Pa46jW2qhj+KShf1iBg+gAyt3eI+wI2Wmub1\n"
|
|
||||||
"WxLLH8D2w+kow8QhQOa8dHCkRRw771JxVO5+d+Y/Y+x9B1HgF4q0q9xUlhWLK2TR\n"
|
|
||||||
"4ChBFzXe47sAHsSqi/pl5JbwYrHPOE/VEBLukmjL8NFCSQIDAQABMA0GCSqGSIb3\n"
|
|
||||||
"DQEBCwUAA4IBAQCamrwdEhNmY2GCQWq6U90Q3XQT6w0HHW/JmtuGeF+BTpVr12gN\n"
|
|
||||||
"/UvEXTo9geWbGcCTjaMMURTa7mUjVUIttIWEVHZMKqBuvsUM1RcuOEX/vitaJJ8K\n"
|
|
||||||
"Sw4upjCNa3ZxUXmSA1FBixZgDzFqjEeSiaJjMU0yX5W2p1T4iNYtF3YqzMF5AWSI\n"
|
|
||||||
"qCO7gP5ezPyg5kDnrO3V7DBgnDiqawq7Pyn9DynKNULX/hc1yls/R+ebb2u8Z+h5\n"
|
|
||||||
"W4YXbzGZb8qdT27qIZaHD638tL6liLkI6UE4KCXH8X8e3fqdbmqvwrq403nOGmsP\n"
|
|
||||||
"cbJb2PEXibNEQG234riKxm7x7vNDLL79Jwtc\n"
|
|
||||||
"-----END CERTIFICATE-----\n";
|
|
||||||
|
|
||||||
static bool set_component(unsigned char *in_ptr, const BIGNUM *bn, int element_len) {
|
|
||||||
int real_len = BN_num_bytes(bn);
|
|
||||||
|
|
||||||
if(real_len > element_len) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
memset(in_ptr, 0, (size_t)(element_len - real_len));
|
|
||||||
in_ptr += element_len - real_len;
|
|
||||||
BN_bn2bin(bn, in_ptr);
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
static bool prepare_rsa_signature(const unsigned char *in, unsigned int in_len, unsigned char *out, unsigned int *out_len, int nid) {
|
|
||||||
X509_SIG *digestInfo;
|
|
||||||
X509_ALGOR *algor;
|
|
||||||
ASN1_OCTET_STRING *digest;
|
|
||||||
unsigned char data[1024];
|
|
||||||
|
|
||||||
memcpy(data, in, in_len);
|
|
||||||
|
|
||||||
digestInfo = X509_SIG_new();
|
|
||||||
X509_SIG_getm(digestInfo, &algor, &digest);
|
|
||||||
algor->algorithm = OBJ_nid2obj(nid);
|
|
||||||
X509_ALGOR_set0(algor, OBJ_nid2obj(nid), V_ASN1_NULL, NULL);
|
|
||||||
ASN1_STRING_set(digest, data, in_len);
|
|
||||||
*out_len = (unsigned int)i2d_X509_SIG(digestInfo, &out);
|
|
||||||
X509_SIG_free(digestInfo);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void import_key(unsigned char slot, unsigned char pin_policy) {
|
|
||||||
ykpiv_rc res;
|
|
||||||
{
|
|
||||||
unsigned char pp = pin_policy;
|
|
||||||
unsigned char tp = YKPIV_TOUCHPOLICY_DEFAULT;
|
|
||||||
EVP_PKEY *private_key = NULL;
|
|
||||||
BIO *bio = NULL;
|
|
||||||
RSA *rsa_private_key = NULL;
|
|
||||||
unsigned char e[4];
|
|
||||||
unsigned char p[128];
|
|
||||||
unsigned char q[128];
|
|
||||||
unsigned char dmp1[128];
|
|
||||||
unsigned char dmq1[128];
|
|
||||||
unsigned char iqmp[128];
|
|
||||||
int element_len = 128;
|
|
||||||
const BIGNUM *bn_e, *bn_p, *bn_q, *bn_dmp1, *bn_dmq1, *bn_iqmp;
|
|
||||||
|
|
||||||
bio = BIO_new_mem_buf(private_key_pem, strlen(private_key_pem));
|
|
||||||
private_key = PEM_read_bio_PrivateKey(bio, NULL, NULL, NULL);
|
|
||||||
ck_assert_ptr_nonnull(private_key);
|
|
||||||
BIO_free(bio);
|
|
||||||
rsa_private_key = EVP_PKEY_get1_RSA(private_key);
|
|
||||||
ck_assert_ptr_nonnull(rsa_private_key);
|
|
||||||
RSA_get0_key(rsa_private_key, NULL, &bn_e, NULL);
|
|
||||||
RSA_get0_factors(rsa_private_key, &bn_p, &bn_q);
|
|
||||||
RSA_get0_crt_params(rsa_private_key, &bn_dmp1, &bn_dmq1, &bn_iqmp);
|
|
||||||
ck_assert(set_component(e, bn_e, 3));
|
|
||||||
ck_assert(set_component(p, bn_p, element_len));
|
|
||||||
ck_assert(set_component(q, bn_q, element_len));
|
|
||||||
ck_assert(set_component(dmp1, bn_dmp1, element_len));
|
|
||||||
ck_assert(set_component(dmq1, bn_dmq1, element_len));
|
|
||||||
ck_assert(set_component(iqmp, bn_iqmp, element_len));
|
|
||||||
|
|
||||||
// Try wrong algorithm, fail.
|
|
||||||
res = ykpiv_import_private_key(g_state,
|
|
||||||
slot,
|
|
||||||
YKPIV_ALGO_RSA1024,
|
|
||||||
p, element_len,
|
|
||||||
q, element_len,
|
|
||||||
dmp1, element_len,
|
|
||||||
dmq1, element_len,
|
|
||||||
iqmp, element_len,
|
|
||||||
NULL, 0,
|
|
||||||
pp, tp);
|
|
||||||
ck_assert_int_eq(res, YKPIV_ALGORITHM_ERROR);
|
|
||||||
|
|
||||||
// Try right algorithm
|
|
||||||
res = ykpiv_import_private_key(g_state,
|
|
||||||
slot,
|
|
||||||
YKPIV_ALGO_RSA2048,
|
|
||||||
p, element_len,
|
|
||||||
q, element_len,
|
|
||||||
dmp1, element_len,
|
|
||||||
dmq1, element_len,
|
|
||||||
iqmp, element_len,
|
|
||||||
NULL, 0,
|
|
||||||
pp, tp);
|
|
||||||
ck_assert_int_eq(res, YKPIV_OK);
|
|
||||||
RSA_free(rsa_private_key);
|
|
||||||
EVP_PKEY_free(private_key);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Use imported key to decrypt a thing. See that it works.
|
|
||||||
{
|
|
||||||
BIO *bio = NULL;
|
|
||||||
X509 *cert = NULL;
|
|
||||||
EVP_PKEY *pub_key = NULL;
|
|
||||||
unsigned char secret[32];
|
|
||||||
unsigned char secret2[32];
|
|
||||||
unsigned char data[256];
|
|
||||||
int len;
|
|
||||||
size_t len2 = sizeof(data);
|
|
||||||
RSA *rsa = NULL;
|
|
||||||
bio = BIO_new_mem_buf(certificate_pem, strlen(certificate_pem));
|
|
||||||
cert = PEM_read_bio_X509(bio, NULL, NULL, NULL);
|
|
||||||
ck_assert_ptr_nonnull(cert);
|
|
||||||
BIO_free(bio);
|
|
||||||
pub_key = X509_get_pubkey(cert);
|
|
||||||
ck_assert_ptr_nonnull(pub_key);
|
|
||||||
rsa = EVP_PKEY_get1_RSA(pub_key);
|
|
||||||
ck_assert_ptr_nonnull(rsa);
|
|
||||||
EVP_PKEY_free(pub_key);
|
|
||||||
|
|
||||||
ck_assert_int_gt(RAND_bytes(secret, sizeof(secret)), 0);
|
|
||||||
len = RSA_public_encrypt(sizeof(secret), secret, data, rsa, RSA_PKCS1_PADDING);
|
|
||||||
ck_assert_int_ge(len, 0);
|
|
||||||
res = ykpiv_verify(g_state, "123456", NULL);
|
|
||||||
ck_assert_int_eq(res, YKPIV_OK);
|
|
||||||
res = ykpiv_decipher_data(g_state, data, (size_t)len, data, &len2, YKPIV_ALGO_RSA2048, slot);
|
|
||||||
ck_assert_int_eq(res, YKPIV_OK);
|
|
||||||
len = RSA_padding_check_PKCS1_type_2(secret2, sizeof(secret2), data + 1, len2 - 1, RSA_size(rsa));
|
|
||||||
ck_assert_int_eq(len, sizeof(secret));
|
|
||||||
ck_assert_int_eq(memcmp(secret, secret2, sizeof(secret)), 0);
|
|
||||||
RSA_free(rsa);
|
|
||||||
X509_free(cert);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
START_TEST(test_import_key) {
|
|
||||||
ykpiv_rc res;
|
|
||||||
|
|
||||||
import_key(0x9a, YKPIV_PINPOLICY_DEFAULT);
|
|
||||||
|
|
||||||
// Verify certificate
|
|
||||||
{
|
|
||||||
BIO *bio = NULL;
|
|
||||||
X509 *cert = NULL;
|
|
||||||
RSA *rsa = NULL;
|
|
||||||
EVP_PKEY *pub_key = NULL;
|
|
||||||
const EVP_MD *md = EVP_sha256();
|
|
||||||
EVP_MD_CTX *mdctx;
|
|
||||||
|
|
||||||
unsigned char signature[1024];
|
|
||||||
unsigned char encoded[1024];
|
|
||||||
unsigned char data[1024];
|
|
||||||
unsigned char signinput[1024];
|
|
||||||
unsigned char rand[128];
|
|
||||||
|
|
||||||
size_t sig_len = sizeof(signature);
|
|
||||||
size_t padlen = 256;
|
|
||||||
unsigned int enc_len;
|
|
||||||
unsigned int data_len;
|
|
||||||
|
|
||||||
bio = BIO_new_mem_buf(certificate_pem, strlen(certificate_pem));
|
|
||||||
cert = PEM_read_bio_X509(bio, NULL, NULL, NULL);
|
|
||||||
ck_assert_ptr_nonnull(cert);
|
|
||||||
BIO_free(bio);
|
|
||||||
pub_key = X509_get_pubkey(cert);
|
|
||||||
ck_assert_ptr_nonnull(pub_key);
|
|
||||||
rsa = EVP_PKEY_get1_RSA(pub_key);
|
|
||||||
ck_assert_ptr_nonnull(rsa);
|
|
||||||
EVP_PKEY_free(pub_key);
|
|
||||||
|
|
||||||
ck_assert_int_gt(RAND_bytes(rand, 128), 0);
|
|
||||||
mdctx = EVP_MD_CTX_create();
|
|
||||||
EVP_DigestInit_ex(mdctx, md, NULL);
|
|
||||||
EVP_DigestUpdate(mdctx, rand, 128);
|
|
||||||
EVP_DigestFinal_ex(mdctx, data, &data_len);
|
|
||||||
|
|
||||||
prepare_rsa_signature(data, data_len, encoded, &enc_len, EVP_MD_type(md));
|
|
||||||
ck_assert_int_ne(RSA_padding_add_PKCS1_type_1(signinput, padlen, encoded, enc_len), 0);
|
|
||||||
res = ykpiv_sign_data(g_state, signinput, padlen, signature, &sig_len, YKPIV_ALGO_RSA2048, 0x9a);
|
|
||||||
ck_assert_int_eq(res, YKPIV_OK);
|
|
||||||
|
|
||||||
ck_assert_int_eq(RSA_verify(EVP_MD_type(md), data, data_len, signature, sig_len, rsa), 1);
|
|
||||||
|
|
||||||
RSA_free(rsa);
|
|
||||||
X509_free(cert);
|
|
||||||
EVP_MD_CTX_destroy(mdctx);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Verify that imported key can not be attested
|
|
||||||
{
|
|
||||||
unsigned char attest[2048];
|
|
||||||
size_t attest_len = sizeof(attest);
|
|
||||||
ykpiv_devmodel model;
|
|
||||||
model = ykpiv_util_devicemodel(g_state);
|
|
||||||
res = ykpiv_attest(g_state, 0x9a, attest, &attest_len);
|
|
||||||
if (model == DEVTYPE_YK4) {
|
|
||||||
ck_assert_int_eq(res, YKPIV_GENERIC_ERROR);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
ck_assert_int_eq(res, YKPIV_NOT_SUPPORTED);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
END_TEST
|
|
||||||
|
|
||||||
START_TEST(test_pin_policy_always) {
|
|
||||||
ykpiv_rc res;
|
|
||||||
|
|
||||||
{
|
|
||||||
ykpiv_devmodel model;
|
|
||||||
model = ykpiv_util_devicemodel(g_state);
|
|
||||||
// Only works with YK4. NEO should skip.
|
|
||||||
if (model != DEVTYPE_YK4) {
|
|
||||||
fprintf(stderr, "WARNING: Not supported with Yubikey NEO. Test skipped.\n");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
import_key(0x9e, YKPIV_PINPOLICY_ALWAYS);
|
|
||||||
|
|
||||||
// Verify certificate
|
|
||||||
{
|
|
||||||
BIO *bio = NULL;
|
|
||||||
X509 *cert = NULL;
|
|
||||||
RSA *rsa = NULL;
|
|
||||||
EVP_PKEY *pub_key = NULL;
|
|
||||||
const EVP_MD *md = EVP_sha256();
|
|
||||||
EVP_MD_CTX *mdctx;
|
|
||||||
|
|
||||||
unsigned char signature[1024];
|
|
||||||
unsigned char encoded[1024];
|
|
||||||
unsigned char data[1024];
|
|
||||||
unsigned char signinput[1024];
|
|
||||||
unsigned char rand[128];
|
|
||||||
|
|
||||||
size_t sig_len = sizeof(signature);
|
|
||||||
size_t padlen = 256;
|
|
||||||
unsigned int enc_len;
|
|
||||||
unsigned int data_len;
|
|
||||||
|
|
||||||
bio = BIO_new_mem_buf(certificate_pem, strlen(certificate_pem));
|
|
||||||
cert = PEM_read_bio_X509(bio, NULL, NULL, NULL);
|
|
||||||
ck_assert_ptr_nonnull(cert);
|
|
||||||
BIO_free(bio);
|
|
||||||
pub_key = X509_get_pubkey(cert);
|
|
||||||
ck_assert_ptr_nonnull(pub_key);
|
|
||||||
rsa = EVP_PKEY_get1_RSA(pub_key);
|
|
||||||
ck_assert_ptr_nonnull(rsa);
|
|
||||||
EVP_PKEY_free(pub_key);
|
|
||||||
|
|
||||||
ck_assert_int_gt(RAND_bytes(rand, 128), 0);
|
|
||||||
mdctx = EVP_MD_CTX_create();
|
|
||||||
EVP_DigestInit_ex(mdctx, md, NULL);
|
|
||||||
EVP_DigestUpdate(mdctx, rand, 128);
|
|
||||||
EVP_DigestFinal_ex(mdctx, data, &data_len);
|
|
||||||
|
|
||||||
prepare_rsa_signature(data, data_len, encoded, &enc_len, EVP_MD_type(md));
|
|
||||||
ck_assert_int_ne(RSA_padding_add_PKCS1_type_1(signinput, padlen, encoded, enc_len), 0);
|
|
||||||
|
|
||||||
// Sign without verify: fail
|
|
||||||
res = ykpiv_sign_data(g_state, signinput, padlen, signature, &sig_len, YKPIV_ALGO_RSA2048, 0x9e);
|
|
||||||
ck_assert_int_eq(res, YKPIV_AUTHENTICATION_ERROR);
|
|
||||||
|
|
||||||
// Sign with verify: pass
|
|
||||||
res = ykpiv_verify(g_state, "123456", NULL);
|
|
||||||
ck_assert_int_eq(res, YKPIV_OK);
|
|
||||||
res = ykpiv_sign_data(g_state, signinput, padlen, signature, &sig_len, YKPIV_ALGO_RSA2048, 0x9e);
|
|
||||||
ck_assert_int_eq(res, YKPIV_OK);
|
|
||||||
|
|
||||||
// Sign again without verify: fail
|
|
||||||
res = ykpiv_sign_data(g_state, signinput, padlen, signature, &sig_len, YKPIV_ALGO_RSA2048, 0x9e);
|
|
||||||
ck_assert_int_eq(res, YKPIV_AUTHENTICATION_ERROR);
|
|
||||||
|
|
||||||
// Sign again with verify: pass
|
|
||||||
res = ykpiv_verify(g_state, "123456", NULL);
|
|
||||||
ck_assert_int_eq(res, YKPIV_OK);
|
|
||||||
res = ykpiv_sign_data(g_state, signinput, padlen, signature, &sig_len, YKPIV_ALGO_RSA2048, 0x9e);
|
|
||||||
ck_assert_int_eq(res, YKPIV_OK);
|
|
||||||
|
|
||||||
ck_assert_int_eq(RSA_verify(EVP_MD_type(md), data, data_len, signature, sig_len, rsa), 1);
|
|
||||||
|
|
||||||
RSA_free(rsa);
|
|
||||||
X509_free(cert);
|
|
||||||
EVP_MD_CTX_destroy(mdctx);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
END_TEST
|
|
||||||
|
|
||||||
START_TEST(test_generate_key) {
|
|
||||||
ykpiv_rc res;
|
|
||||||
uint8_t *mod, *exp;
|
|
||||||
size_t mod_len, exp_len;
|
|
||||||
res = ykpiv_util_write_cert(g_state, YKPIV_KEY_AUTHENTICATION, (uint8_t*)g_cert, sizeof(g_cert), YKPIV_CERTINFO_UNCOMPRESSED);
|
|
||||||
ck_assert_int_eq(res, YKPIV_OK);
|
|
||||||
res = ykpiv_util_generate_key(g_state,
|
|
||||||
YKPIV_KEY_AUTHENTICATION,
|
|
||||||
YKPIV_ALGO_RSA2048,
|
|
||||||
YKPIV_PINPOLICY_DEFAULT,
|
|
||||||
YKPIV_TOUCHPOLICY_DEFAULT,
|
|
||||||
&mod,
|
|
||||||
&mod_len,
|
|
||||||
&exp,
|
|
||||||
&exp_len,
|
|
||||||
NULL,
|
|
||||||
NULL);
|
|
||||||
ck_assert_int_eq(res, YKPIV_OK);
|
|
||||||
res = ykpiv_util_free(g_state, mod);
|
|
||||||
ck_assert_int_eq(res, YKPIV_OK);
|
|
||||||
res = ykpiv_util_free(g_state, exp);
|
|
||||||
ck_assert_int_eq(res, YKPIV_OK);
|
|
||||||
|
|
||||||
// Verify that imported key can be attested
|
|
||||||
{
|
|
||||||
ykpiv_devmodel model;
|
|
||||||
unsigned char attest[2048];
|
|
||||||
size_t attest_len = sizeof(attest);
|
|
||||||
model = ykpiv_util_devicemodel(g_state);
|
|
||||||
res = ykpiv_attest(g_state, YKPIV_KEY_AUTHENTICATION, attest, &attest_len);
|
|
||||||
// Only works with YK4. NEO should error.
|
|
||||||
if (model == DEVTYPE_YK4) {
|
|
||||||
ck_assert_int_eq(res, YKPIV_OK);
|
|
||||||
ck_assert_int_gt(attest_len, 0);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
ck_assert_int_eq(res, YKPIV_NOT_SUPPORTED);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
END_TEST
|
|
||||||
|
|
||||||
START_TEST(test_authenticate) {
|
|
||||||
ykpiv_rc res;
|
|
||||||
const char *default_mgm_key = "010203040506070801020304050607080102030405060708";
|
|
||||||
const char *mgm_key = "112233445566778811223344556677881122334455667788";
|
|
||||||
const char *weak_mgm_key = "FEFEFEFEFEFEFEFEFEFEFEFEFEFEFEFEFEFEFEFEFEFEFEFE";
|
|
||||||
unsigned char key[24];
|
|
||||||
size_t key_len = sizeof(key);
|
|
||||||
|
|
||||||
// Try new key, fail.
|
|
||||||
res = ykpiv_hex_decode(mgm_key, strlen(mgm_key), key, &key_len);
|
|
||||||
ck_assert_int_eq(res, YKPIV_OK);
|
|
||||||
res = ykpiv_authenticate(g_state, key);
|
|
||||||
ck_assert_int_eq(res, YKPIV_AUTHENTICATION_ERROR);
|
|
||||||
|
|
||||||
// Try default key, succeed
|
|
||||||
res = ykpiv_hex_decode(default_mgm_key, strlen(default_mgm_key), key, &key_len);
|
|
||||||
ck_assert_int_eq(res, YKPIV_OK);
|
|
||||||
res = ykpiv_authenticate(g_state, key);
|
|
||||||
ck_assert_int_eq(res, YKPIV_OK);
|
|
||||||
|
|
||||||
// Verify same key works twice
|
|
||||||
res = ykpiv_hex_decode(default_mgm_key, strlen(default_mgm_key), key, &key_len);
|
|
||||||
ck_assert_int_eq(res, YKPIV_OK);
|
|
||||||
res = ykpiv_authenticate(g_state, key);
|
|
||||||
ck_assert_int_eq(res, YKPIV_OK);
|
|
||||||
|
|
||||||
// Change to new key
|
|
||||||
res = ykpiv_hex_decode(mgm_key, strlen(mgm_key), key, &key_len);
|
|
||||||
ck_assert_int_eq(res, YKPIV_OK);
|
|
||||||
res = ykpiv_set_mgmkey(g_state, key);
|
|
||||||
ck_assert_int_eq(res, YKPIV_OK);
|
|
||||||
|
|
||||||
// Try new key, succeed.
|
|
||||||
res = ykpiv_hex_decode(mgm_key, strlen(mgm_key), key, &key_len);
|
|
||||||
ck_assert_int_eq(res, YKPIV_OK);
|
|
||||||
res = ykpiv_authenticate(g_state, key);
|
|
||||||
ck_assert_int_eq(res, YKPIV_OK);
|
|
||||||
|
|
||||||
// Change back to default key
|
|
||||||
res = ykpiv_hex_decode(default_mgm_key, strlen(default_mgm_key), key, &key_len);
|
|
||||||
ck_assert_int_eq(res, YKPIV_OK);
|
|
||||||
res = ykpiv_set_mgmkey(g_state, key);
|
|
||||||
ck_assert_int_eq(res, YKPIV_OK);
|
|
||||||
|
|
||||||
// Try default key, succeed
|
|
||||||
res = ykpiv_hex_decode(default_mgm_key, strlen(default_mgm_key), key, &key_len);
|
|
||||||
ck_assert_int_eq(res, YKPIV_OK);
|
|
||||||
res = ykpiv_authenticate(g_state, key);
|
|
||||||
ck_assert_int_eq(res, YKPIV_OK);
|
|
||||||
|
|
||||||
// Try to set a weak key, fail
|
|
||||||
res = ykpiv_hex_decode(weak_mgm_key, strlen(weak_mgm_key), key, &key_len);
|
|
||||||
ck_assert_int_eq(res, YKPIV_OK);
|
|
||||||
res = ykpiv_set_mgmkey(g_state, key);
|
|
||||||
ck_assert_int_eq(res, YKPIV_KEY_ERROR);
|
|
||||||
|
|
||||||
// Try default key, succeed
|
|
||||||
res = ykpiv_hex_decode(default_mgm_key, strlen(default_mgm_key), key, &key_len);
|
|
||||||
ck_assert_int_eq(res, YKPIV_OK);
|
|
||||||
res = ykpiv_authenticate(g_state, key);
|
|
||||||
ck_assert_int_eq(res, YKPIV_OK);
|
|
||||||
}
|
|
||||||
END_TEST
|
|
||||||
|
|
||||||
START_TEST(test_change_pin) {
|
|
||||||
ykpiv_rc res;
|
|
||||||
|
|
||||||
res = ykpiv_verify(g_state, "123456", NULL);
|
|
||||||
ck_assert_int_eq(res, YKPIV_OK);
|
|
||||||
|
|
||||||
res = ykpiv_change_pin(g_state, "123456", 6, "ABCDEF", 6, NULL);
|
|
||||||
ck_assert_int_eq(res, YKPIV_OK);
|
|
||||||
|
|
||||||
res = ykpiv_verify(g_state, "123456", NULL);
|
|
||||||
ck_assert_int_eq(res, YKPIV_WRONG_PIN);
|
|
||||||
|
|
||||||
res = ykpiv_verify(g_state, "ABCDEF", NULL);
|
|
||||||
ck_assert_int_eq(res, YKPIV_OK);
|
|
||||||
|
|
||||||
res = ykpiv_change_pin(g_state, "ABCDEF", 6, "123456", 6, NULL);
|
|
||||||
ck_assert_int_eq(res, YKPIV_OK);
|
|
||||||
|
|
||||||
res = ykpiv_verify(g_state, "ABCDEF", NULL);
|
|
||||||
ck_assert_int_eq(res, YKPIV_WRONG_PIN);
|
|
||||||
|
|
||||||
res = ykpiv_verify(g_state, "123456", NULL);
|
|
||||||
ck_assert_int_eq(res, YKPIV_OK);
|
|
||||||
}
|
|
||||||
END_TEST
|
|
||||||
|
|
||||||
START_TEST(test_change_puk) {
|
|
||||||
ykpiv_rc res;
|
|
||||||
|
|
||||||
res = ykpiv_unblock_pin(g_state, "12345678", 8, "123456", 6, NULL);
|
|
||||||
ck_assert_int_eq(res, YKPIV_OK);
|
|
||||||
|
|
||||||
res = ykpiv_change_puk(g_state, "12345678", 8, "ABCDEFGH", 8, NULL);
|
|
||||||
ck_assert_int_eq(res, YKPIV_OK);
|
|
||||||
|
|
||||||
res = ykpiv_unblock_pin(g_state, "12345678", 8, "123456", 6, NULL);
|
|
||||||
ck_assert_int_eq(res, YKPIV_WRONG_PIN);
|
|
||||||
|
|
||||||
res = ykpiv_unblock_pin(g_state, "ABCDEFGH", 8, "123456", 6, NULL);
|
|
||||||
ck_assert_int_eq(res, YKPIV_OK);
|
|
||||||
|
|
||||||
res = ykpiv_change_puk(g_state, "ABCDEFGH", 8, "12345678", 8, NULL);
|
|
||||||
ck_assert_int_eq(res, YKPIV_OK);
|
|
||||||
|
|
||||||
res = ykpiv_unblock_pin(g_state, "ABCDEFGH", 8, "123456", 6, NULL);
|
|
||||||
ck_assert_int_eq(res, YKPIV_WRONG_PIN);
|
|
||||||
|
|
||||||
res = ykpiv_unblock_pin(g_state, "12345678", 8, "123456", 6, NULL);
|
|
||||||
ck_assert_int_eq(res, YKPIV_OK);
|
|
||||||
}
|
|
||||||
END_TEST
|
|
||||||
|
|
||||||
static int block_and_reset() {
|
|
||||||
ykpiv_rc res;
|
|
||||||
int tries = 100;
|
|
||||||
int tries_until_blocked;
|
|
||||||
|
|
||||||
tries_until_blocked = 0;
|
|
||||||
while (tries) {
|
|
||||||
res = ykpiv_verify(g_state, "AAAAAA", &tries);
|
|
||||||
if (res == YKPIV_PIN_LOCKED)
|
|
||||||
break;
|
|
||||||
ck_assert_int_eq(res, YKPIV_WRONG_PIN);
|
|
||||||
tries_until_blocked++;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Verify no PIN retries remaining
|
|
||||||
tries = 100;
|
|
||||||
res = ykpiv_get_pin_retries(g_state, &tries);
|
|
||||||
ck_assert_int_eq(res, YKPIV_OK);
|
|
||||||
ck_assert_int_eq(tries, 0);
|
|
||||||
|
|
||||||
tries = 100;
|
|
||||||
while (tries) {
|
|
||||||
res = ykpiv_change_puk(g_state, "AAAAAAAA", 8, "AAAAAAAA", 8, &tries);
|
|
||||||
if (res == YKPIV_PIN_LOCKED)
|
|
||||||
break;
|
|
||||||
ck_assert_int_eq(res, YKPIV_WRONG_PIN);
|
|
||||||
}
|
|
||||||
res = ykpiv_util_reset(g_state);
|
|
||||||
ck_assert_int_eq(res, YKPIV_OK);
|
|
||||||
return tries_until_blocked;
|
|
||||||
}
|
|
||||||
|
|
||||||
START_TEST(test_reset) {
|
|
||||||
ykpiv_rc res;
|
|
||||||
int tries = 100;
|
|
||||||
int tries_until_blocked;
|
|
||||||
|
|
||||||
// Block and reset, with default PIN retries
|
|
||||||
tries_until_blocked = block_and_reset();
|
|
||||||
ck_assert_int_eq(tries_until_blocked, 3);
|
|
||||||
|
|
||||||
// Authenticate and increase PIN retries
|
|
||||||
test_authenticate(0);
|
|
||||||
res = ykpiv_verify(g_state, "123456", NULL);
|
|
||||||
ck_assert_int_eq(res, YKPIV_OK);
|
|
||||||
res = ykpiv_set_pin_retries(g_state, 8, 3);
|
|
||||||
ck_assert_int_eq(res, YKPIV_OK);
|
|
||||||
|
|
||||||
// Block and reset again, verifying increased PIN retries
|
|
||||||
tries_until_blocked = block_and_reset();
|
|
||||||
ck_assert_int_eq(tries_until_blocked, 8);
|
|
||||||
// Note: defaults back to 3 retries after reset
|
|
||||||
|
|
||||||
// Verify default (3) PIN retries remaining
|
|
||||||
tries = 0;
|
|
||||||
res = ykpiv_get_pin_retries(g_state, &tries);
|
|
||||||
ck_assert_int_eq(res, YKPIV_OK);
|
|
||||||
ck_assert_int_eq(tries, 3);
|
|
||||||
|
|
||||||
// Verify still (3) PIN retries remaining
|
|
||||||
tries = 0;
|
|
||||||
res = ykpiv_get_pin_retries(g_state, &tries);
|
|
||||||
ck_assert_int_eq(res, YKPIV_OK);
|
|
||||||
ck_assert_int_eq(tries, 3);
|
|
||||||
|
|
||||||
// Try wrong PIN
|
|
||||||
res = ykpiv_verify(g_state, "AAAAAA", &tries);
|
|
||||||
ck_assert_int_eq(res, YKPIV_WRONG_PIN);
|
|
||||||
|
|
||||||
// Verify 2 PIN retries remaining
|
|
||||||
tries = 0;
|
|
||||||
res = ykpiv_get_pin_retries(g_state, &tries);
|
|
||||||
ck_assert_int_eq(res, YKPIV_OK);
|
|
||||||
ck_assert_int_eq(tries, 2);
|
|
||||||
|
|
||||||
// Verify correct PIN
|
|
||||||
tries = 100;
|
|
||||||
res = ykpiv_verify(g_state, "123456", &tries);
|
|
||||||
ck_assert_int_eq(res, YKPIV_OK);
|
|
||||||
|
|
||||||
// Verify back to 3 PIN retries remaining
|
|
||||||
tries = 0;
|
|
||||||
res = ykpiv_get_pin_retries(g_state, &tries);
|
|
||||||
ck_assert_int_eq(res, YKPIV_OK);
|
|
||||||
ck_assert_int_eq(tries, 3);
|
|
||||||
}
|
|
||||||
END_TEST
|
|
||||||
|
|
||||||
|
|
||||||
struct t_alloc_data{
|
|
||||||
uint32_t count;
|
|
||||||
} g_alloc_data;
|
|
||||||
|
|
||||||
static void* _test_alloc(void *data, size_t cb) {
|
|
||||||
ck_assert_ptr_eq(data, &g_alloc_data);
|
|
||||||
((struct t_alloc_data*)data)->count++;
|
|
||||||
return calloc(cb, 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void * _test_realloc(void *data, void *p, size_t cb) {
|
|
||||||
ck_assert_ptr_eq(data, &g_alloc_data);
|
|
||||||
return realloc(p, cb);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void _test_free(void *data, void *p) {
|
|
||||||
fflush(stderr);
|
|
||||||
ck_assert_ptr_eq(data, &g_alloc_data);
|
|
||||||
((struct t_alloc_data*)data)->count--;
|
|
||||||
free(p);
|
|
||||||
}
|
|
||||||
|
|
||||||
ykpiv_allocator test_allocator_cbs = {
|
|
||||||
.pfn_alloc = _test_alloc,
|
|
||||||
.pfn_realloc = _test_realloc,
|
|
||||||
.pfn_free = _test_free,
|
|
||||||
.alloc_data = &g_alloc_data
|
|
||||||
};
|
|
||||||
|
|
||||||
uint8_t *alloc_auth_cert() {
|
|
||||||
ykpiv_rc res;
|
|
||||||
uint8_t *read_cert = NULL;
|
|
||||||
size_t read_cert_len = 0;
|
|
||||||
|
|
||||||
res = ykpiv_util_write_cert(g_state, YKPIV_KEY_AUTHENTICATION, (uint8_t*)g_cert, sizeof(g_cert), YKPIV_CERTINFO_UNCOMPRESSED);
|
|
||||||
ck_assert_int_eq(res, YKPIV_OK);
|
|
||||||
|
|
||||||
res = ykpiv_util_read_cert(g_state, YKPIV_KEY_AUTHENTICATION, &read_cert, &read_cert_len);
|
|
||||||
ck_assert_int_eq(res, YKPIV_OK);
|
|
||||||
ck_assert_ptr_nonnull(read_cert);
|
|
||||||
ck_assert_int_eq(read_cert_len, sizeof(g_cert));
|
|
||||||
ck_assert_mem_eq(g_cert, read_cert, sizeof(g_cert));
|
|
||||||
return read_cert;
|
|
||||||
}
|
|
||||||
|
|
||||||
START_TEST(test_allocator) {
|
|
||||||
ykpiv_rc res;
|
|
||||||
const ykpiv_allocator allocator;
|
|
||||||
uint8_t *cert1, *cert2;
|
|
||||||
|
|
||||||
res = ykpiv_done(g_state);
|
|
||||||
ck_assert_int_eq(res, YKPIV_OK);
|
|
||||||
g_state = NULL;
|
|
||||||
|
|
||||||
res = ykpiv_init_with_allocator(&g_state, false, &test_allocator_cbs);
|
|
||||||
ck_assert_int_eq(res, YKPIV_OK);
|
|
||||||
ck_assert_ptr_nonnull(g_state);
|
|
||||||
|
|
||||||
// Verify we can communicate with device and make some allocations
|
|
||||||
res = ykpiv_connect(g_state, NULL);
|
|
||||||
ck_assert_int_eq(res, YKPIV_OK);
|
|
||||||
test_authenticate(0);
|
|
||||||
cert1 = alloc_auth_cert();
|
|
||||||
cert2 = alloc_auth_cert();
|
|
||||||
|
|
||||||
// Verify allocations went through custom allocator, and still live
|
|
||||||
ck_assert_int_gt(g_alloc_data.count, 1);
|
|
||||||
|
|
||||||
// Free and shutdown everything
|
|
||||||
ykpiv_util_free(g_state, cert2);
|
|
||||||
ykpiv_util_free(g_state, cert1);
|
|
||||||
res = ykpiv_disconnect(g_state);
|
|
||||||
ck_assert_int_eq(res, YKPIV_OK);
|
|
||||||
res = ykpiv_done(g_state);
|
|
||||||
ck_assert_int_eq(res, YKPIV_OK);
|
|
||||||
|
|
||||||
// Verify equal number of frees as allocations
|
|
||||||
ck_assert_int_eq(g_alloc_data.count, 0);
|
|
||||||
|
|
||||||
// Clear g_state so teardown() is skipped
|
|
||||||
g_state = NULL;
|
|
||||||
}
|
|
||||||
END_TEST
|
|
||||||
|
|
||||||
START_TEST(test_pin_cache) {
|
|
||||||
ykpiv_rc res;
|
|
||||||
ykpiv_state *local_state;
|
|
||||||
unsigned char data[256];
|
|
||||||
unsigned char data_in[256] = {0};
|
|
||||||
int len = sizeof(data);
|
|
||||||
size_t len2 = sizeof(data);
|
|
||||||
|
|
||||||
import_key(0x9a, YKPIV_PINPOLICY_DEFAULT);
|
|
||||||
|
|
||||||
// Disconnect and reconnect to device to guarantee it is not authed
|
|
||||||
res = ykpiv_disconnect(g_state);
|
|
||||||
ck_assert_int_eq(res, YKPIV_OK);
|
|
||||||
res = ykpiv_done(g_state);
|
|
||||||
ck_assert_int_eq(res, YKPIV_OK);
|
|
||||||
res = ykpiv_init(&g_state, true);
|
|
||||||
ck_assert_int_eq(res, YKPIV_OK);
|
|
||||||
ck_assert_ptr_nonnull(g_state);
|
|
||||||
res = ykpiv_connect(g_state, NULL);
|
|
||||||
ck_assert_int_eq(res, YKPIV_OK);
|
|
||||||
|
|
||||||
// Verify decryption does not work without auth
|
|
||||||
res = ykpiv_decipher_data(g_state, data_in, (size_t)len, data, &len2, YKPIV_ALGO_RSA2048, 0x9a);
|
|
||||||
ck_assert_int_eq(res, YKPIV_AUTHENTICATION_ERROR);
|
|
||||||
|
|
||||||
// Verify decryption does work when authed
|
|
||||||
res = ykpiv_verify_select(g_state, "123456", 6, NULL, true);
|
|
||||||
ck_assert_int_eq(res, YKPIV_OK);
|
|
||||||
res = ykpiv_decipher_data(g_state, data_in, (size_t)len, data, &len2, YKPIV_ALGO_RSA2048, 0x9a);
|
|
||||||
ck_assert_int_eq(res, YKPIV_OK);
|
|
||||||
|
|
||||||
// Verify PIN policy allows continuing to decrypt without re-verifying
|
|
||||||
res = ykpiv_decipher_data(g_state, data_in, (size_t)len, data, &len2, YKPIV_ALGO_RSA2048, 0x9a);
|
|
||||||
ck_assert_int_eq(res, YKPIV_OK);
|
|
||||||
|
|
||||||
// Create a new ykpiv state, connect, and close it.
|
|
||||||
// This forces a card reset from another context, so the original global
|
|
||||||
// context will require a reconnect for its next transaction.
|
|
||||||
res = ykpiv_init(&local_state, true);
|
|
||||||
ck_assert_int_eq(res, YKPIV_OK);
|
|
||||||
ck_assert_ptr_nonnull(local_state);
|
|
||||||
res = ykpiv_connect(local_state, NULL);
|
|
||||||
ck_assert_int_eq(res, YKPIV_OK);
|
|
||||||
res = ykpiv_disconnect(local_state);
|
|
||||||
ck_assert_int_eq(res, YKPIV_OK);
|
|
||||||
res = ykpiv_done(local_state);
|
|
||||||
ck_assert_int_eq(res, YKPIV_OK);
|
|
||||||
|
|
||||||
// Verify we are still authenticated on the global context. This will
|
|
||||||
// require an automatic reconnect and re-verify with the cached PIN.
|
|
||||||
//
|
|
||||||
// Note that you can verify that this fails by rebuilding with
|
|
||||||
// DISABLE_PIN_CACHE set to 1.
|
|
||||||
res = ykpiv_decipher_data(g_state, data_in, (size_t)len, data, &len2, YKPIV_ALGO_RSA2048, 0x9a);
|
|
||||||
ck_assert_int_eq(res, YKPIV_OK);
|
|
||||||
}
|
|
||||||
END_TEST
|
|
||||||
#endif
|
|
||||||
|
|
||||||
int destruction_confirmed(void) {
|
|
||||||
char *confirmed = getenv("YKPIV_ENV_HWTESTS_CONFIRMED");
|
|
||||||
if (confirmed && confirmed[0] == '1')
|
|
||||||
return 1;
|
|
||||||
// Use dprintf() to write directly to stdout, since automake eats the standard stdout/stderr pointers.
|
|
||||||
dprintf(0, "\n***\n*** Hardware tests skipped. Run \"make hwcheck\".\n***\n\n");
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
Suite *test_suite(void) {
|
|
||||||
Suite *s;
|
|
||||||
TCase *tc;
|
|
||||||
|
|
||||||
s = suite_create("libykpiv api");
|
|
||||||
tc = tcase_create("api");
|
|
||||||
#ifdef HW_TESTS
|
|
||||||
tcase_add_unchecked_fixture(tc, setup, teardown);
|
|
||||||
|
|
||||||
// Must be first: Reset device. Tests run serially, and depend on a clean slate.
|
|
||||||
tcase_add_test(tc, test_reset);
|
|
||||||
|
|
||||||
// Authenticate after reset.
|
|
||||||
tcase_add_test(tc, test_authenticate);
|
|
||||||
|
|
||||||
// Test API functionality
|
|
||||||
tcase_add_test(tc, test_change_pin);
|
|
||||||
tcase_add_test(tc, test_change_puk);
|
|
||||||
tcase_add_test(tc, test_devicemodel);
|
|
||||||
tcase_add_test(tc, test_get_set_cardid);
|
|
||||||
tcase_add_test(tc, test_list_readers);
|
|
||||||
tcase_add_test(tc, test_read_write_list_delete_cert);
|
|
||||||
tcase_add_test(tc, test_import_key);
|
|
||||||
tcase_add_test(tc, test_pin_policy_always);
|
|
||||||
tcase_add_test(tc, test_generate_key);
|
|
||||||
tcase_add_test(tc, test_pin_cache);
|
|
||||||
|
|
||||||
// Must be last: tear down and re-test with custom memory allocator
|
|
||||||
tcase_add_test(tc, test_allocator);
|
|
||||||
#endif
|
|
||||||
suite_add_tcase(s, tc);
|
|
||||||
|
|
||||||
return s;
|
|
||||||
}
|
|
||||||
|
|
||||||
int main(void)
|
|
||||||
{
|
|
||||||
int number_failed;
|
|
||||||
Suite *s;
|
|
||||||
SRunner *sr;
|
|
||||||
|
|
||||||
s = test_suite();
|
|
||||||
sr = srunner_create(s);
|
|
||||||
srunner_set_fork_status(sr, CK_NOFORK);
|
|
||||||
srunner_run_all(sr, CK_VERBOSE);
|
|
||||||
number_failed = srunner_ntests_failed(sr);
|
|
||||||
srunner_free(sr);
|
|
||||||
|
|
||||||
return (number_failed == 0) ? EXIT_SUCCESS : EXIT_FAILURE;
|
|
||||||
}
|
|
||||||
@@ -1,98 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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.
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include "ykpiv.h"
|
|
||||||
|
|
||||||
#include <stddef.h>
|
|
||||||
#include <stdio.h>
|
|
||||||
#include <stdlib.h>
|
|
||||||
#include <string.h>
|
|
||||||
|
|
||||||
#include <check.h>
|
|
||||||
|
|
||||||
START_TEST(test_version_string) {
|
|
||||||
if (strcmp(YKPIV_VERSION_STRING, ykpiv_check_version(NULL)) != 0) {
|
|
||||||
ck_abort_msg("version mismatch %s != %s\n", YKPIV_VERSION_STRING,
|
|
||||||
ykpiv_check_version(NULL));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ykpiv_check_version(YKPIV_VERSION_STRING) == NULL) {
|
|
||||||
ck_abort_msg("version NULL?\n");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ykpiv_check_version("99.99.99") != NULL) {
|
|
||||||
ck_abort_msg("version not NULL?\n");
|
|
||||||
}
|
|
||||||
|
|
||||||
fprintf(stderr, "ykpiv version: header %s library %s\n",
|
|
||||||
YKPIV_VERSION_STRING, ykpiv_check_version (NULL));
|
|
||||||
}
|
|
||||||
END_TEST
|
|
||||||
|
|
||||||
START_TEST(test_strerror) {
|
|
||||||
const char *s;
|
|
||||||
|
|
||||||
if (ykpiv_strerror(YKPIV_OK) == NULL) {
|
|
||||||
ck_abort_msg("ykpiv_strerror NULL\n");
|
|
||||||
}
|
|
||||||
|
|
||||||
s = ykpiv_strerror_name(YKPIV_OK);
|
|
||||||
if (s == NULL || strcmp(s, "YKPIV_OK") != 0) {
|
|
||||||
ck_abort_msg("ykpiv_strerror_name %s\n", s);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
END_TEST
|
|
||||||
|
|
||||||
Suite *basic_suite(void) {
|
|
||||||
Suite *s;
|
|
||||||
TCase *tc;
|
|
||||||
|
|
||||||
s = suite_create("libykpiv basic");
|
|
||||||
tc = tcase_create("basic");
|
|
||||||
tcase_add_test(tc, test_version_string);
|
|
||||||
tcase_add_test(tc, test_strerror);
|
|
||||||
suite_add_tcase(s, tc);
|
|
||||||
|
|
||||||
return s;
|
|
||||||
}
|
|
||||||
|
|
||||||
int main(void)
|
|
||||||
{
|
|
||||||
int number_failed;
|
|
||||||
Suite *s;
|
|
||||||
SRunner *sr;
|
|
||||||
|
|
||||||
s = basic_suite();
|
|
||||||
sr = srunner_create(s);
|
|
||||||
srunner_run_all(sr, CK_NORMAL);
|
|
||||||
number_failed = srunner_ntests_failed(sr);
|
|
||||||
srunner_free(sr);
|
|
||||||
return (number_failed == 0) ? EXIT_SUCCESS : EXIT_FAILURE;
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
//! Integration tests
|
||||||
|
|
||||||
|
#![forbid(unsafe_code)]
|
||||||
|
#![warn(missing_docs, rust_2018_idioms, trivial_casts, unused_qualifications)]
|
||||||
|
|
||||||
|
use std::env;
|
||||||
|
use yubikey_piv::YubiKey;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[ignore]
|
||||||
|
fn connect() {
|
||||||
|
// 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(None).unwrap();
|
||||||
|
dbg!(&yubikey.version());
|
||||||
|
dbg!(&yubikey.serial());
|
||||||
|
}
|
||||||
@@ -1,101 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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.
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include <stdio.h>
|
|
||||||
#include <stdlib.h>
|
|
||||||
#include <string.h>
|
|
||||||
|
|
||||||
#include <check.h>
|
|
||||||
|
|
||||||
#include "ykpiv.h"
|
|
||||||
|
|
||||||
struct key {
|
|
||||||
const char text[49];
|
|
||||||
const unsigned char formatted[24];
|
|
||||||
int valid;
|
|
||||||
} keys[] = {
|
|
||||||
{"010203040506070801020304050607080102030405060708",
|
|
||||||
{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08,0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08},
|
|
||||||
1},
|
|
||||||
{"a1a2a3a4a5a6a7a8a1a2a3a4a5a6a7a8a1a2a3a4a5a6a7a8",
|
|
||||||
{0xa1, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7, 0xa8, 0xa1, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7, 0xa8,0xa1, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7, 0xa8},
|
|
||||||
1},
|
|
||||||
{"A1A2A3A4A5A6A7A8A1A2A3A4A5A6A7A8A1A2A3A4A5A6A7A8",
|
|
||||||
{0xa1, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7, 0xa8, 0xa1, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7, 0xa8,0xa1, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7, 0xa8},
|
|
||||||
1},
|
|
||||||
{"This is not something considered valid hex......",
|
|
||||||
{},
|
|
||||||
0},
|
|
||||||
};
|
|
||||||
|
|
||||||
static int parse_key(const char *text, const unsigned char *expected, int valid) {
|
|
||||||
unsigned char key[24];
|
|
||||||
size_t len = sizeof(key);
|
|
||||||
ykpiv_rc res = ykpiv_hex_decode(text, strlen(text), key, &len);
|
|
||||||
if (valid) {
|
|
||||||
ck_assert(res == YKPIV_OK);
|
|
||||||
ck_assert(memcmp(expected, key, 24) == 0);
|
|
||||||
} else {
|
|
||||||
ck_assert(res != YKPIV_OK);
|
|
||||||
}
|
|
||||||
return EXIT_SUCCESS;
|
|
||||||
}
|
|
||||||
|
|
||||||
START_TEST(test_parse_key) {
|
|
||||||
int res = parse_key(keys[_i].text, keys[_i].formatted, keys[_i].valid);
|
|
||||||
ck_assert(res == EXIT_SUCCESS);
|
|
||||||
}
|
|
||||||
END_TEST
|
|
||||||
|
|
||||||
Suite *parsekey_suite(void) {
|
|
||||||
Suite *s;
|
|
||||||
TCase *tc;
|
|
||||||
|
|
||||||
s = suite_create("libykpiv parsekey");
|
|
||||||
tc = tcase_create("parsekey");
|
|
||||||
tcase_add_loop_test(tc, test_parse_key, 0, sizeof(keys) / sizeof(struct key));
|
|
||||||
suite_add_tcase(s, tc);
|
|
||||||
|
|
||||||
return s;
|
|
||||||
}
|
|
||||||
|
|
||||||
int main(void)
|
|
||||||
{
|
|
||||||
int number_failed;
|
|
||||||
Suite *s;
|
|
||||||
SRunner *sr;
|
|
||||||
|
|
||||||
s = parsekey_suite();
|
|
||||||
sr = srunner_create(s);
|
|
||||||
srunner_run_all(sr, CK_NORMAL);
|
|
||||||
number_failed = srunner_ntests_failed(sr);
|
|
||||||
srunner_free(sr);
|
|
||||||
return (number_failed == 0) ? EXIT_SUCCESS : EXIT_FAILURE;
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user