Merge remote-tracking branch 'upstream/develop' into develop

This commit is contained in:
Carl Wallace
2019-12-01 14:49:41 -05:00
16 changed files with 832 additions and 563 deletions
+1 -1
View File
@@ -3,7 +3,7 @@
on: on:
pull_request: {} pull_request: {}
push: push:
branches: master branches: develop
name: Rust name: Rust
+11 -11
View File
@@ -19,17 +19,17 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Use `log` crate for logging ([#7]) - Use `log` crate for logging ([#7])
- Replace `ErrorKind::Ok` with `Result` ([#6]) - Replace `ErrorKind::Ok` with `Result` ([#6])
[0.0.2]: https://github.com/tarcieri/yubikey-piv.rs/pull/31 [0.0.2]: https://github.com/iqlusioninc/yubikey-piv.rs/pull/31
[#30]: https://github.com/tarcieri/yubikey-piv.rs/pull/30 [#30]: https://github.com/iqlusioninc/yubikey-piv.rs/pull/30
[#19]: https://github.com/tarcieri/yubikey-piv.rs/pull/19 [#19]: https://github.com/iqlusioninc/yubikey-piv.rs/pull/19
[#17]: https://github.com/tarcieri/yubikey-piv.rs/pull/17 [#17]: https://github.com/iqlusioninc/yubikey-piv.rs/pull/17
[#15]: https://github.com/tarcieri/yubikey-piv.rs/pull/15 [#15]: https://github.com/iqlusioninc/yubikey-piv.rs/pull/15
[#13]: https://github.com/tarcieri/yubikey-piv.rs/pull/13 [#13]: https://github.com/iqlusioninc/yubikey-piv.rs/pull/13
[#10]: https://github.com/tarcieri/yubikey-piv.rs/pull/10 [#10]: https://github.com/iqlusioninc/yubikey-piv.rs/pull/10
[#9]: https://github.com/tarcieri/yubikey-piv.rs/pull/9 [#9]: https://github.com/iqlusioninc/yubikey-piv.rs/pull/9
[#8]: https://github.com/tarcieri/yubikey-piv.rs/pull/8 [#8]: https://github.com/iqlusioninc/yubikey-piv.rs/pull/8
[#7]: https://github.com/tarcieri/yubikey-piv.rs/pull/7 [#7]: https://github.com/iqlusioninc/yubikey-piv.rs/pull/7
[#6]: https://github.com/tarcieri/yubikey-piv.rs/pull/6 [#6]: https://github.com/iqlusioninc/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!
+8 -10
View File
@@ -6,8 +6,8 @@ In the interest of fostering an open and welcoming environment, we as
contributors and maintainers pledge to making participation in our project and contributors and maintainers pledge to making participation in our project and
our community a harassment-free experience for everyone, regardless of age, body our community a harassment-free experience for everyone, regardless of age, body
size, disability, ethnicity, gender identity and expression, level of experience, size, disability, ethnicity, gender identity and expression, level of experience,
nationality, personal appearance, race, religion, or sexual identity and education, socio-economic status, nationality, personal appearance, race,
orientation. religion, or sexual identity and orientation.
## Our Standards ## Our Standards
@@ -23,7 +23,7 @@ include:
Examples of unacceptable behavior by participants include: Examples of unacceptable behavior by participants include:
* The use of sexualized language or imagery and unwelcome sexual attention or * The use of sexualized language or imagery and unwelcome sexual attention or
advances advances
* Trolling, insulting/derogatory comments, and personal or political attacks * Trolling, insulting/derogatory comments, and personal or political attacks
* Public or private harassment * Public or private harassment
* Publishing others' private information, such as a physical or electronic * Publishing others' private information, such as a physical or electronic
@@ -55,8 +55,8 @@ further defined and clarified by project maintainers.
## Enforcement ## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported by contacting the project team at [bascule@gmail.com]. All reported by contacting the project team at [oss@iqlusion.io](mailto:oss@iqlusion.io).
complaints will be reviewed and investigated and will result in a response that All complaints will be reviewed and investigated and will result in a response that
is deemed necessary and appropriate to the circumstances. The project team is is deemed necessary and appropriate to the circumstances. The project team is
obligated to maintain confidentiality with regard to the reporter of an incident. obligated to maintain confidentiality with regard to the reporter of an incident.
Further details of specific enforcement policies may be posted separately. Further details of specific enforcement policies may be posted separately.
@@ -65,12 +65,10 @@ Project maintainers who do not follow or enforce the Code of Conduct in good
faith may face temporary or permanent repercussions as determined by other faith may face temporary or permanent repercussions as determined by other
members of the project's leadership. members of the project's leadership.
[bascule@gmail.com]: mailto:bascule@gmail.com
## Attribution ## Attribution
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
available at [http://contributor-covenant.org/version/1/4][version] available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html
[homepage]: https://www.contributor-covenant.org
[homepage]: http://contributor-covenant.org
[version]: http://contributor-covenant.org/version/1/4/
+7 -1
View File
@@ -10,7 +10,7 @@ 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"
repository = "https://github.com/tarcieri/yubikey-piv.rs" repository = "https://github.com/iqlusioninc/yubikey-piv.rs"
readme = "README.md" 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"]
@@ -19,14 +19,20 @@ keywords = ["ccid", "ecdsa", "rsa", "piv", "yubikey"]
maintenance = { status = "experimental" } maintenance = { status = "experimental" }
[dependencies] [dependencies]
der-parser = "3"
des = "0.3" des = "0.3"
ecdsa = "0.1"
getrandom = "0.1" getrandom = "0.1"
hmac = "0.7" hmac = "0.7"
log = "0.4" log = "0.4"
nom = "5"
pbkdf2 = "0.3" pbkdf2 = "0.3"
pcsc = "2" pcsc = "2"
rsa = "0.1.4"
secrecy = "0.5"
sha-1 = "0.8" sha-1 = "0.8"
subtle = "2" subtle = "2"
x509-parser = "0.6"
zeroize = "1" zeroize = "1"
[dev-dependencies] [dev-dependencies]
+18 -15
View File
@@ -7,6 +7,7 @@
![Apache2/MIT licensed][license-image] ![Apache2/MIT licensed][license-image]
![Rust Version][rustc-image] ![Rust Version][rustc-image]
![Maintenance Status: Experimental][maintenance-image] ![Maintenance Status: Experimental][maintenance-image]
[![Safety Dance][safety-image]][safety-link]
[![Build Status][build-image]][build-link] [![Build Status][build-image]][build-link]
[![Gitter Chat][gitter-image]][gitter-link] [![Gitter Chat][gitter-image]][gitter-link]
@@ -193,10 +194,12 @@ or conditions.
[license-image]: https://img.shields.io/badge/license-BSD-blue.svg [license-image]: https://img.shields.io/badge/license-BSD-blue.svg
[rustc-image]: https://img.shields.io/badge/rustc-1.39+-blue.svg [rustc-image]: https://img.shields.io/badge/rustc-1.39+-blue.svg
[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 [safety-image]: https://img.shields.io/badge/unsafe-forbidden-success.svg
[build-link]: https://github.com/tarcieri/yubikey-piv.rs/actions [safety-link]: https://github.com/rust-secure-code/safety-dance/
[gitter-image]: https://badges.gitter.im/yubihsm-piv-rs.svg [build-image]: https://github.com/iqlusioninc/yubikey-piv.rs/workflows/Rust/badge.svg?branch=develop&event=push
[gitter-link]: https://gitter.im/yubikey-piv-rs/community [build-link]: https://github.com/iqlusioninc/yubikey-piv.rs/actions
[gitter-image]: https://badges.gitter.im/badge.svg
[gitter-link]: https://gitter.im/iqlusioninc/community
[//]: # (general links) [//]: # (general links)
@@ -209,18 +212,18 @@ or conditions.
[yubico-piv-tool]: https://github.com/Yubico/yubico-piv-tool/ [yubico-piv-tool]: https://github.com/Yubico/yubico-piv-tool/
[Corrode]: https://github.com/jameysharp/corrode [Corrode]: https://github.com/jameysharp/corrode
[cc-web]: https://contributor-covenant.org/ [cc-web]: https://contributor-covenant.org/
[cc-md]: https://github.com/tarcieri/yubikey-piv.rs/blob/develop/CODE_OF_CONDUCT.md [cc-md]: https://github.com/iqlusioninc/yubikey-piv.rs/blob/develop/CODE_OF_CONDUCT.md
[BSDL]: https://opensource.org/licenses/BSD-2-Clause [BSDL]: https://opensource.org/licenses/BSD-2-Clause
[//]: # (github issues) [//]: # (github issues)
[#18]: https://github.com/tarcieri/yubikey-piv.rs/issues/18 [#18]: https://github.com/iqlusioninc/yubikey-piv.rs/issues/18
[#20]: https://github.com/tarcieri/yubikey-piv.rs/issues/20 [#20]: https://github.com/iqlusioninc/yubikey-piv.rs/issues/20
[#21]: https://github.com/tarcieri/yubikey-piv.rs/issues/21 [#21]: https://github.com/iqlusioninc/yubikey-piv.rs/issues/21
[#22]: https://github.com/tarcieri/yubikey-piv.rs/issues/22 [#22]: https://github.com/iqlusioninc/yubikey-piv.rs/issues/22
[#23]: https://github.com/tarcieri/yubikey-piv.rs/issues/23 [#23]: https://github.com/iqlusioninc/yubikey-piv.rs/issues/23
[#24]: https://github.com/tarcieri/yubikey-piv.rs/issues/24 [#24]: https://github.com/iqlusioninc/yubikey-piv.rs/issues/24
[#25]: https://github.com/tarcieri/yubikey-piv.rs/issues/25 [#25]: https://github.com/iqlusioninc/yubikey-piv.rs/issues/25
[#26]: https://github.com/tarcieri/yubikey-piv.rs/issues/26 [#26]: https://github.com/iqlusioninc/yubikey-piv.rs/issues/26
[#27]: https://github.com/tarcieri/yubikey-piv.rs/issues/27 [#27]: https://github.com/iqlusioninc/yubikey-piv.rs/issues/27
[#28]: https://github.com/tarcieri/yubikey-piv.rs/issues/28 [#28]: https://github.com/iqlusioninc/yubikey-piv.rs/issues/28
+58 -8
View File
@@ -280,7 +280,7 @@ impl Response {
/// Get the raw [`StatusWords`] code for this response. /// Get the raw [`StatusWords`] code for this response.
#[cfg(feature = "untested")] #[cfg(feature = "untested")]
pub fn code(&self) -> u32 { pub fn code(&self) -> u16 {
self.status_words.code() self.status_words.code()
} }
@@ -317,7 +317,7 @@ impl From<Vec<u8>> for Response {
} }
let sw = StatusWords::from( let sw = StatusWords::from(
(bytes[bytes.len() - 2] as u32) << 8 | (bytes[bytes.len() - 1] as u32), (bytes[bytes.len() - 2] as u16) << 8 | (bytes[bytes.len() - 1] as u16),
); );
let len = bytes.len() - 2; let len = bytes.len() - 2;
@@ -352,15 +352,42 @@ pub(crate) enum StatusWords {
/// Successful execution /// Successful execution
Success, Success,
/// https://github.com/Yubico/yubikey-manager/blob/1f22620b623c6b345dd9f9193ec765a542dddc80/ykman/driver_ccid.py#L53
NoInputDataError,
/// PIN verification failure
VerifyFailError {
/// Remaining verification attempts
tries: u8,
},
/// https://github.com/Yubico/yubikey-manager/blob/1f22620b623c6b345dd9f9193ec765a542dddc80/ykman/driver_ccid.py#L55
WrongLengthError,
/// Security status not satisfied /// Security status not satisfied
SecurityStatusError, SecurityStatusError,
/// Authentication method blocked /// Authentication method blocked
AuthBlockedError, AuthBlockedError,
/// https://github.com/Yubico/yubikey-manager/blob/1f22620b623c6b345dd9f9193ec765a542dddc80/ykman/driver_ccid.py#L58
DataInvalidError,
/// https://github.com/Yubico/yubikey-manager/blob/1f22620b623c6b345dd9f9193ec765a542dddc80/ykman/driver_ccid.py#L59
ConditionsNotSatisfiedError,
/// https://github.com/Yubico/yubikey-manager/blob/1f22620b623c6b345dd9f9193ec765a542dddc80/ykman/driver_ccid.py#L60
CommandNotAllowedError,
/// Incorrect parameter in command data field /// Incorrect parameter in command data field
IncorrectParamError, IncorrectParamError,
/// Data object or application not found
NotFoundError,
/// Not enough memory
NoSpaceError,
// //
// Custom Yubico Status Word extensions // Custom Yubico Status Word extensions
// //
@@ -370,20 +397,32 @@ pub(crate) enum StatusWords {
/// Not supported error /// Not supported error
NotSupportedError, NotSupportedError,
/// https://github.com/Yubico/yubikey-manager/blob/1f22620b623c6b345dd9f9193ec765a542dddc80/ykman/driver_ccid.py#L65
CommandAbortedError,
/// Other/unrecognized status words /// Other/unrecognized status words
Other(u32), Other(u16),
} }
impl StatusWords { impl StatusWords {
/// Get the numerical response code for these status words /// Get the numerical response code for these status words
pub fn code(self) -> u32 { pub fn code(self) -> u16 {
match self { match self {
StatusWords::None => 0, StatusWords::None => 0,
StatusWords::NoInputDataError => 0x6285,
StatusWords::VerifyFailError { tries } => 0x63c0 & tries as u16,
StatusWords::WrongLengthError => 0x6700,
StatusWords::SecurityStatusError => 0x6982, StatusWords::SecurityStatusError => 0x6982,
StatusWords::AuthBlockedError => 0x6983, StatusWords::AuthBlockedError => 0x6983,
StatusWords::DataInvalidError => 0x6984,
StatusWords::ConditionsNotSatisfiedError => 0x6985,
StatusWords::CommandNotAllowedError => 0x6986,
StatusWords::IncorrectParamError => 0x6a80, StatusWords::IncorrectParamError => 0x6a80,
StatusWords::NotFoundError => 0x6a82,
StatusWords::NoSpaceError => 0x6a84,
StatusWords::IncorrectSlotError => 0x6b00, StatusWords::IncorrectSlotError => 0x6b00,
StatusWords::NotSupportedError => 0x6d00, StatusWords::NotSupportedError => 0x6d00,
StatusWords::CommandAbortedError => 0x6f00,
StatusWords::Success => 0x9000, StatusWords::Success => 0x9000,
StatusWords::Other(n) => n, StatusWords::Other(n) => n,
} }
@@ -395,23 +434,34 @@ impl StatusWords {
} }
} }
impl From<u32> for StatusWords { impl From<u16> for StatusWords {
fn from(sw: u32) -> Self { fn from(sw: u16) -> Self {
match sw { match sw {
0x0000 => StatusWords::None, 0x0000 => StatusWords::None,
0x6285 => StatusWords::NoInputDataError,
sw if sw & 0xfff0 == 0x63c0 => StatusWords::VerifyFailError {
tries: (sw & 0x000f) as u8,
},
0x6700 => StatusWords::WrongLengthError,
0x6982 => StatusWords::SecurityStatusError, 0x6982 => StatusWords::SecurityStatusError,
0x6983 => StatusWords::AuthBlockedError, 0x6983 => StatusWords::AuthBlockedError,
0x6984 => StatusWords::DataInvalidError,
0x6985 => StatusWords::ConditionsNotSatisfiedError,
0x6986 => StatusWords::CommandNotAllowedError,
0x6a80 => StatusWords::IncorrectParamError, 0x6a80 => StatusWords::IncorrectParamError,
0x6a82 => StatusWords::NotFoundError,
0x6a84 => StatusWords::NoSpaceError,
0x6b00 => StatusWords::IncorrectSlotError, 0x6b00 => StatusWords::IncorrectSlotError,
0x6d00 => StatusWords::NotSupportedError, 0x6d00 => StatusWords::NotSupportedError,
0x6f00 => StatusWords::CommandAbortedError,
0x9000 => StatusWords::Success, 0x9000 => StatusWords::Success,
_ => StatusWords::Other(sw), _ => StatusWords::Other(sw),
} }
} }
} }
impl From<StatusWords> for u32 { impl From<StatusWords> for u16 {
fn from(sw: StatusWords) -> u32 { fn from(sw: StatusWords) -> u16 {
sw.code() sw.code()
} }
} }
+230 -14
View File
@@ -33,19 +33,141 @@
use crate::{ use crate::{
consts::*, consts::*,
error::Error, error::Error,
key::{self, SlotId}, key::{AlgorithmId, SlotId},
serialization::*, serialization::*,
transaction::Transaction, transaction::Transaction,
yubikey::YubiKey, yubikey::YubiKey,
Buffer, Buffer,
}; };
use ecdsa::{
curve::{CompressedCurvePoint, NistP256, NistP384, UncompressedCurvePoint},
generic_array::GenericArray,
};
use log::error; use log::error;
use std::ptr; use rsa::{PublicKey, RSAPublicKey};
use std::fmt;
use x509_parser::{parse_x509_der, x509::SubjectPublicKeyInfo};
use zeroize::Zeroizing; use zeroize::Zeroizing;
// TODO: Make these der_parser::oid::Oid constants when it has const fn support.
const OID_RSA_ENCRYPTION: &str = "1.2.840.113549.1.1.1";
const OID_EC_PUBLIC_KEY: &str = "1.2.840.10045.2.1";
const OID_NIST_P256: &str = "1.2.840.10045.3.1.7";
const OID_NIST_P384: &str = "1.3.132.0.34";
/// An encoded point on the Nist P-256 curve.
#[derive(Clone, Eq, PartialEq)]
pub enum EcP256Point {
/// Compressed encoding of a point on the curve.
Compressed(CompressedCurvePoint<NistP256>),
/// Uncompressed encoding of a point on the curve.
Uncompressed(UncompressedCurvePoint<NistP256>),
}
/// An encoded point on the Nist P-384 curve.
#[derive(Clone, Eq, PartialEq)]
pub enum EcP384Point {
/// Compressed encoding of a point on the curve.
Compressed(CompressedCurvePoint<NistP384>),
/// Uncompressed encoding of a point on the curve.
Uncompressed(UncompressedCurvePoint<NistP384>),
}
/// Information about a public key within a [`Certificate`].
#[derive(Clone, Eq, PartialEq)]
pub enum PublicKeyInfo {
/// RSA keys
Rsa {
/// RSA algorithm
algorithm: AlgorithmId,
/// Public key
pubkey: RSAPublicKey,
},
/// EC P-256 keys
EcP256(EcP256Point),
/// EC P-384 keys
EcP384(EcP384Point),
}
impl fmt::Debug for PublicKeyInfo {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "PublicKeyInfo({:?})", self.algorithm())
}
}
impl PublicKeyInfo {
fn parse(subject_pki: &SubjectPublicKeyInfo<'_>) -> Result<Self, Error> {
match subject_pki.algorithm.algorithm.to_string().as_str() {
OID_RSA_ENCRYPTION => {
let pubkey = read_pki::rsa_pubkey(subject_pki.subject_public_key.data)?;
Ok(PublicKeyInfo::Rsa {
algorithm: match pubkey.n().bits() {
1024 => AlgorithmId::Rsa1024,
2048 => AlgorithmId::Rsa2048,
_ => return Err(Error::AlgorithmError),
},
pubkey,
})
}
OID_EC_PUBLIC_KEY => {
let key_bytes = &subject_pki.subject_public_key.data;
match read_pki::ec_parameters(&subject_pki.algorithm.parameters)? {
AlgorithmId::EccP256 => match key_bytes.len() {
33 => CompressedCurvePoint::<NistP256>::from_bytes(
GenericArray::clone_from_slice(key_bytes),
)
.map(EcP256Point::Compressed),
65 => UncompressedCurvePoint::<NistP256>::from_bytes(
GenericArray::clone_from_slice(key_bytes),
)
.map(EcP256Point::Uncompressed),
_ => None,
}
.map(PublicKeyInfo::EcP256)
.ok_or(Error::InvalidObject),
AlgorithmId::EccP384 => match key_bytes.len() {
49 => CompressedCurvePoint::<NistP384>::from_bytes(
GenericArray::clone_from_slice(key_bytes),
)
.map(EcP384Point::Compressed),
97 => UncompressedCurvePoint::<NistP384>::from_bytes(
GenericArray::clone_from_slice(key_bytes),
)
.map(EcP384Point::Uncompressed),
_ => None,
}
.map(PublicKeyInfo::EcP384)
.ok_or(Error::InvalidObject),
_ => Err(Error::AlgorithmError),
}
}
_ => Err(Error::InvalidObject),
}
}
/// Returns the algorithm that this public key can be used with.
pub fn algorithm(&self) -> AlgorithmId {
match self {
PublicKeyInfo::Rsa { algorithm, .. } => *algorithm,
PublicKeyInfo::EcP256(_) => AlgorithmId::EccP256,
PublicKeyInfo::EcP384(_) => AlgorithmId::EccP384,
}
}
}
/// Certificates /// Certificates
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub struct Certificate(Buffer); pub struct Certificate {
subject: String,
subject_pki: PublicKeyInfo,
data: Buffer,
}
impl Certificate { impl Certificate {
/// Read a certificate from the given slot in the YubiKey /// Read a certificate from the given slot in the YubiKey
@@ -57,14 +179,14 @@ impl Certificate {
return Err(Error::InvalidObject); return Err(Error::InvalidObject);
} }
Ok(Certificate(buf)) Certificate::new(buf)
} }
/// Write this certificate into the YubiKey in the given slot /// Write this certificate into the YubiKey in the given slot
pub fn write(&self, yubikey: &mut YubiKey, slot: SlotId, certinfo: u8) -> Result<(), Error> { pub fn write(&self, yubikey: &mut YubiKey, slot: SlotId, certinfo: u8) -> Result<(), Error> {
let max_size = yubikey.obj_size_max(); let max_size = yubikey.obj_size_max();
let txn = yubikey.begin_transaction()?; let txn = yubikey.begin_transaction()?;
write_certificate(&txn, slot, Some(&self.0), certinfo, max_size) write_certificate(&txn, slot, Some(&self.data), certinfo, max_size)
} }
/// Delete a certificate located at the given slot of the given YubiKey /// Delete a certificate located at the given slot of the given YubiKey
@@ -83,25 +205,47 @@ impl Certificate {
return Err(Error::SizeError); return Err(Error::SizeError);
} }
Ok(Certificate(cert)) let parsed_cert = match parse_x509_der(&cert) {
Ok((_, cert)) => cert,
_ => return Err(Error::InvalidObject),
};
let subject = format!("{}", parsed_cert.tbs_certificate.subject);
let subject_pki = PublicKeyInfo::parse(&parsed_cert.tbs_certificate.subject_pki)?;
Ok(Certificate {
subject,
subject_pki,
data: cert,
})
}
/// Returns the SubjectName field of the certificate.
pub fn subject(&self) -> &str {
&self.subject
}
/// Returns the SubjectPublicKeyInfo field of the certificate.
pub fn subject_pki(&self) -> &PublicKeyInfo {
&self.subject_pki
} }
/// Extract the inner buffer /// Extract the inner buffer
pub fn into_buffer(self) -> Buffer { pub fn into_buffer(self) -> Buffer {
self.0 self.data
} }
} }
impl AsRef<[u8]> for Certificate { impl AsRef<[u8]> for Certificate {
fn as_ref(&self) -> &[u8] { fn as_ref(&self) -> &[u8] {
self.0.as_ref() self.data.as_ref()
} }
} }
/// Read certificate /// Read certificate
pub(crate) fn read_certificate(txn: &Transaction<'_>, slot: SlotId) -> Result<Buffer, Error> { pub(crate) fn read_certificate(txn: &Transaction<'_>, slot: SlotId) -> Result<Buffer, Error> {
let mut len: usize = 0; let mut len: usize = 0;
let object_id = key::slot_object(slot)?; let object_id = slot.object_id();
let mut buf = match txn.fetch_object(object_id) { let mut buf = match txn.fetch_object(object_id) {
Ok(b) => b, Ok(b) => b,
@@ -124,10 +268,7 @@ pub(crate) fn read_certificate(txn: &Transaction<'_>, slot: SlotId) -> Result<Bu
return Ok(Zeroizing::new(vec![])); return Ok(Zeroizing::new(vec![]));
} }
unsafe { buf.copy_within(offset..offset + len, 0);
ptr::copy(buf.as_ptr().add(offset), buf.as_mut_ptr(), len);
}
buf.truncate(len); buf.truncate(len);
} }
@@ -145,7 +286,7 @@ pub(crate) fn write_certificate(
let mut buf = [0u8; CB_OBJ_MAX]; let mut buf = [0u8; CB_OBJ_MAX];
let mut offset = 0; let mut offset = 0;
let object_id = key::slot_object(slot)?; let object_id = slot.object_id();
if data.is_none() { if data.is_none() {
return txn.save_object(object_id, &[]); return txn.save_object(object_id, &[]);
@@ -184,3 +325,78 @@ pub(crate) fn write_certificate(
txn.save_object(object_id, &buf[..offset]) txn.save_object(object_id, &buf[..offset])
} }
mod read_pki {
use der_parser::{
ber::BerObjectContent,
der::{parse_der_integer, DerObject},
error::BerError,
*,
};
use nom::{combinator, IResult};
use rsa::{BigUint, RSAPublicKey};
use super::{OID_NIST_P256, OID_NIST_P384};
use crate::{error::Error, key::AlgorithmId};
/// From [RFC 8017](https://tools.ietf.org/html/rfc8017#appendix-A.1.1):
/// ```text
/// RSAPublicKey ::= SEQUENCE {
/// modulus INTEGER, -- n
/// publicExponent INTEGER -- e
/// }
/// ```
pub(super) fn rsa_pubkey(encoded: &[u8]) -> Result<RSAPublicKey, Error> {
fn parse_rsa_pubkey(i: &[u8]) -> IResult<&[u8], DerObject<'_>, BerError> {
parse_der_sequence_defined!(i, parse_der_integer >> parse_der_integer)
}
fn rsa_pubkey_parts(i: &[u8]) -> IResult<&[u8], (BigUint, BigUint), BerError> {
combinator::map(parse_rsa_pubkey, |object| {
let seq = object.as_sequence().expect("is DER sequence");
assert_eq!(seq.len(), 2);
let n = match seq[0].content {
BerObjectContent::Integer(s) => BigUint::from_bytes_be(s),
_ => panic!("expected DER integer"),
};
let e = match seq[1].content {
BerObjectContent::Integer(s) => BigUint::from_bytes_be(s),
_ => panic!("expected DER integer"),
};
(n, e)
})(i)
}
let (n, e) = match rsa_pubkey_parts(encoded) {
Ok((_, res)) => res,
_ => return Err(Error::InvalidObject),
};
RSAPublicKey::new(n, e).map_err(|_| Error::InvalidObject)
}
/// From [RFC 5480](https://tools.ietf.org/html/rfc5480#section-2.1.1):
/// ```text
/// ECParameters ::= CHOICE {
/// namedCurve OBJECT IDENTIFIER
/// -- implicitCurve NULL
/// -- specifiedCurve SpecifiedECDomain
/// }
/// ```
pub(super) fn ec_parameters(parameters: &DerObject<'_>) -> Result<AlgorithmId, Error> {
let curve_oid = match parameters.as_context_specific() {
Ok((_, Some(named_curve))) => {
named_curve.as_oid_val().map_err(|_| Error::InvalidObject)
}
_ => Err(Error::InvalidObject),
}?;
match curve_oid.to_string().as_str() {
OID_NIST_P256 => Ok(AlgorithmId::EccP256),
OID_NIST_P384 => Ok(AlgorithmId::EccP384),
_ => Err(Error::AlgorithmError),
}
}
}
-55
View File
@@ -124,10 +124,6 @@ pub const TAG_ECC_POINT: u8 = 0x86;
pub const YKPIV_ALGO_TAG: u8 = 0x80; pub const YKPIV_ALGO_TAG: u8 = 0x80;
pub const YKPIV_ALGO_3DES: u8 = 0x03; pub const YKPIV_ALGO_3DES: u8 = 0x03;
pub const YKPIV_ALGO_RSA1024: u8 = 0x06;
pub const YKPIV_ALGO_RSA2048: u8 = 0x07;
pub const YKPIV_ALGO_ECCP256: u8 = 0x11;
pub const YKPIV_ALGO_ECCP384: u8 = 0x14;
pub const YKPIV_ATR_NEO_R3: &[u8] = b";\xFC\x13\0\0\x811\xFE\x15YubikeyNEOr3\xE1\0"; pub const YKPIV_ATR_NEO_R3: &[u8] = b";\xFC\x13\0\0\x811\xFE\x15YubikeyNEOr3\xE1\0";
@@ -142,72 +138,21 @@ pub const YKPIV_CCC_SIZE: usize = 51;
pub const YKPIV_CERTINFO_UNCOMPRESSED: u8 = 0; pub const YKPIV_CERTINFO_UNCOMPRESSED: u8 = 0;
pub const YKPIV_CERTINFO_GZIP: u8 = 1; pub const YKPIV_CERTINFO_GZIP: u8 = 1;
pub const YKPIV_KEY_AUTHENTICATION: u8 = 0x9a;
pub const YKPIV_KEY_CARDMGM: u8 = 0x9b; pub const YKPIV_KEY_CARDMGM: u8 = 0x9b;
pub const YKPIV_KEY_SIGNATURE: u8 = 0x9c;
pub const YKPIV_KEY_KEYMGM: u8 = 0x9d;
pub const YKPIV_KEY_CARDAUTH: u8 = 0x9e;
pub const YKPIV_KEY_RETIRED1: u8 = 0x82;
pub const YKPIV_KEY_RETIRED2: u8 = 0x83;
pub const YKPIV_KEY_RETIRED3: u8 = 0x84;
pub const YKPIV_KEY_RETIRED4: u8 = 0x85;
pub const YKPIV_KEY_RETIRED5: u8 = 0x86;
pub const YKPIV_KEY_RETIRED6: u8 = 0x87;
pub const YKPIV_KEY_RETIRED7: u8 = 0x88;
pub const YKPIV_KEY_RETIRED8: u8 = 0x89;
pub const YKPIV_KEY_RETIRED9: u8 = 0x8a;
pub const YKPIV_KEY_RETIRED10: u8 = 0x8b;
pub const YKPIV_KEY_RETIRED11: u8 = 0x8c;
pub const YKPIV_KEY_RETIRED12: u8 = 0x8d;
pub const YKPIV_KEY_RETIRED13: u8 = 0x8e;
pub const YKPIV_KEY_RETIRED14: u8 = 0x8f;
pub const YKPIV_KEY_RETIRED15: u8 = 0x90;
pub const YKPIV_KEY_RETIRED16: u8 = 0x91;
pub const YKPIV_KEY_RETIRED17: u8 = 0x92;
pub const YKPIV_KEY_RETIRED18: u8 = 0x93;
pub const YKPIV_KEY_RETIRED19: u8 = 0x94;
pub const YKPIV_KEY_RETIRED20: u8 = 0x95;
pub const YKPIV_KEY_ATTESTATION: u8 = 0xf9;
pub const YKPIV_OBJ_CAPABILITY: u32 = 0x005f_c107; pub const YKPIV_OBJ_CAPABILITY: u32 = 0x005f_c107;
pub const YKPIV_OBJ_CHUID: u32 = 0x005f_c102; pub const YKPIV_OBJ_CHUID: u32 = 0x005f_c102;
pub const YKPIV_OBJ_AUTHENTICATION: u32 = 0x005f_c105; // cert for 9a key
pub const YKPIV_OBJ_FINGERPRINTS: u32 = 0x005f_c103; pub const YKPIV_OBJ_FINGERPRINTS: u32 = 0x005f_c103;
pub const YKPIV_OBJ_SECURITY: u32 = 0x005f_c106; pub const YKPIV_OBJ_SECURITY: u32 = 0x005f_c106;
pub const YKPIV_OBJ_FACIAL: u32 = 0x005f_c108; pub const YKPIV_OBJ_FACIAL: u32 = 0x005f_c108;
pub const YKPIV_OBJ_PRINTED: u32 = 0x005f_c109; pub const YKPIV_OBJ_PRINTED: u32 = 0x005f_c109;
pub const YKPIV_OBJ_SIGNATURE: u32 = 0x005f_c10a; // cert for 9c key
pub const YKPIV_OBJ_KEY_MANAGEMENT: u32 = 0x005f_c10b; // cert for 9d key
pub const YKPIV_OBJ_CARD_AUTH: u32 = 0x005f_c101; // cert for 9e key
pub const YKPIV_OBJ_DISCOVERY: u32 = 0x7e; pub const YKPIV_OBJ_DISCOVERY: u32 = 0x7e;
pub const YKPIV_OBJ_KEY_HISTORY: u32 = 0x005f_c10c; pub const YKPIV_OBJ_KEY_HISTORY: u32 = 0x005f_c10c;
pub const YKPIV_OBJ_IRIS: u32 = 0x005f_c121; pub const YKPIV_OBJ_IRIS: u32 = 0x005f_c121;
pub const YKPIV_OBJ_RETIRED1: u32 = 0x005f_c10d;
pub const YKPIV_OBJ_RETIRED2: u32 = 0x005f_c10e;
pub const YKPIV_OBJ_RETIRED3: u32 = 0x005f_c10f;
pub const YKPIV_OBJ_RETIRED4: u32 = 0x005f_c110;
pub const YKPIV_OBJ_RETIRED5: u32 = 0x005f_c111;
pub const YKPIV_OBJ_RETIRED6: u32 = 0x005f_c112;
pub const YKPIV_OBJ_RETIRED7: u32 = 0x005f_c113;
pub const YKPIV_OBJ_RETIRED8: u32 = 0x005f_c114;
pub const YKPIV_OBJ_RETIRED9: u32 = 0x005f_c115;
pub const YKPIV_OBJ_RETIRED10: u32 = 0x005f_c116;
pub const YKPIV_OBJ_RETIRED11: u32 = 0x005f_c117;
pub const YKPIV_OBJ_RETIRED12: u32 = 0x005f_c118;
pub const YKPIV_OBJ_RETIRED13: u32 = 0x005f_c119;
pub const YKPIV_OBJ_RETIRED14: u32 = 0x005f_c11a;
pub const YKPIV_OBJ_RETIRED15: u32 = 0x005f_c11b;
pub const YKPIV_OBJ_RETIRED16: u32 = 0x005f_c11c;
pub const YKPIV_OBJ_RETIRED17: u32 = 0x005f_c11d;
pub const YKPIV_OBJ_RETIRED18: u32 = 0x005f_c11e;
pub const YKPIV_OBJ_RETIRED19: u32 = 0x005f_c11f;
pub const YKPIV_OBJ_RETIRED20: u32 = 0x005f_c120;
// Internal object IDs // Internal object IDs
pub const YKPIV_OBJ_ADMIN_DATA: u32 = 0x005f_ff00; pub const YKPIV_OBJ_ADMIN_DATA: u32 = 0x005f_ff00;
pub const YKPIV_OBJ_ATTESTATION: u32 = 0x005f_ff01;
pub const YKPIV_OBJ_MSCMAP: u32 = 0x005f_ff10; pub const YKPIV_OBJ_MSCMAP: u32 = 0x005f_ff10;
pub const YKPIV_OBJ_MSROOTS1: u32 = 0x005f_ff11; pub const YKPIV_OBJ_MSROOTS1: u32 = 0x005f_ff11;
pub const YKPIV_OBJ_MSROOTS2: u32 = 0x005f_ff12; pub const YKPIV_OBJ_MSROOTS2: u32 = 0x005f_ff12;
+3 -3
View File
@@ -158,7 +158,7 @@ impl Container {
Ok(Container { Ok(Container {
name, name,
slot: bytes[name_bytes_len], slot: bytes[name_bytes_len].try_into()?,
key_spec: bytes[name_bytes_len + 1], key_spec: bytes[name_bytes_len + 1],
key_size_bits: u16::from_le_bytes( key_size_bits: u16::from_le_bytes(
bytes[(name_bytes_len + 2)..(name_bytes_len + 4)] bytes[(name_bytes_len + 2)..(name_bytes_len + 4)]
@@ -185,7 +185,7 @@ impl Container {
bytes.extend_from_slice(&self.name[i].to_le_bytes()); bytes.extend_from_slice(&self.name[i].to_le_bytes());
} }
bytes.push(self.slot); bytes.push(self.slot.into());
bytes.push(self.key_spec); bytes.push(self.key_spec);
bytes.extend_from_slice(&self.key_size_bits.to_le_bytes()); bytes.extend_from_slice(&self.key_size_bits.to_le_bytes());
bytes.push(self.flags); bytes.push(self.flags);
@@ -204,7 +204,7 @@ impl Debug for Container {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!( write!(
f, f,
"PivContainer {{ name: {:?}, slot: {}, key_spec: {}, key_size_bits: {}, \ "PivContainer {{ name: {:?}, slot: {:?}, key_spec: {}, key_size_bits: {}, \
flags: {}, pin_id: {}, associated_echd_container: {}, cert_fingerprint: {:?} }}", flags: {}, pin_id: {}, associated_echd_container: {}, cert_fingerprint: {:?} }}",
&self.name[..], &self.name[..],
self.slot, self.slot,
+1 -1
View File
@@ -68,7 +68,7 @@ pub enum Error {
/// Wrong PIN /// Wrong PIN
WrongPin { WrongPin {
/// Number of tries remaining /// Number of tries remaining
tries: u32, tries: u8,
}, },
/// Invalid object /// Invalid object
+302 -96
View File
@@ -45,61 +45,272 @@ use crate::{
serialization::*, serialization::*,
settings, settings,
yubikey::YubiKey, yubikey::YubiKey,
AlgorithmId, Buffer, ObjectId, Buffer, ObjectId,
}; };
use log::{debug, error, warn}; use log::{debug, error, warn};
use std::convert::TryFrom;
/// Slot identifiers. /// Slot identifiers.
/// <https://developers.yubico.com/PIV/Introduction/Certificate_slots.html> /// <https://developers.yubico.com/PIV/Introduction/Certificate_slots.html>
// TODO(tarcieri): replace these with enums #[derive(Clone, Copy, Debug, PartialEq)]
pub type SlotId = u8; pub enum SlotId {
/// This certificate and its associated private key is used to authenticate the card
/// and the cardholder. This slot is used for things like system login. The end user
/// PIN is required to perform any private key operations. Once the PIN has been
/// provided successfully, multiple private key operations may be performed without
/// additional cardholder consent.
Authentication,
/// Get the [`ObjectId`] that corresponds to a given [`SlotId`] /// This certificate and its associated private key is used for digital signatures for
// TODO(tarcieri): factor this into a slot ID enum /// the purpose of document signing, or signing files and executables. The end user
pub(crate) fn slot_object(slot: SlotId) -> Result<ObjectId, Error> { /// PIN is required to perform any private key operations. The PIN must be submitted
let id = match slot { /// every time immediately before a sign operation, to ensure cardholder participation
YKPIV_KEY_AUTHENTICATION => YKPIV_OBJ_AUTHENTICATION, /// for every digital signature generated.
YKPIV_KEY_SIGNATURE => YKPIV_OBJ_SIGNATURE, Signature,
YKPIV_KEY_KEYMGM => YKPIV_OBJ_KEY_MANAGEMENT,
YKPIV_KEY_CARDAUTH => YKPIV_OBJ_CARD_AUTH, /// This certificate and its associated private key is used for encryption for the
YKPIV_KEY_ATTESTATION => YKPIV_OBJ_ATTESTATION, /// purpose of confidentiality. This slot is used for things like encrypting e-mails
slot if slot >= YKPIV_KEY_RETIRED1 && (slot <= YKPIV_KEY_RETIRED20) => { /// or files. The end user PIN is required to perform any private key operations. Once
YKPIV_OBJ_RETIRED1 + (slot - YKPIV_KEY_RETIRED1) as u32 /// the PIN has been provided successfully, multiple private key operations may be
/// performed without additional cardholder consent.
KeyManagement,
/// This certificate and its associated private key is used to support additional
/// physical access applications, such as providing physical access to buildings via
/// PIV-enabled door locks. The end user PIN is NOT required to perform private key
/// operations for this slot.
CardAuthentication,
/// These slots are only available on the YubiKey 4 & 5. They are meant for previously
/// used Key Management keys to be able to decrypt earlier encrypted documents or
/// emails. In the YubiKey 4 & 5 all 20 of them are fully available for use.
Retired(RetiredSlotId),
/// This slot is only available on YubiKey version 4.3 and newer. It is only used for
/// attestation of other keys generated on device with instruction `f9`. This slot is
/// not cleared on reset, but can be overwritten.
Attestation,
}
impl TryFrom<u8> for SlotId {
type Error = Error;
fn try_from(value: u8) -> Result<Self, Self::Error> {
match value {
0x9a => Ok(SlotId::Authentication),
0x9c => Ok(SlotId::Signature),
0x9d => Ok(SlotId::KeyManagement),
0x9e => Ok(SlotId::CardAuthentication),
0xf9 => Ok(SlotId::Attestation),
_ => RetiredSlotId::try_from(value).map(SlotId::Retired),
} }
_ => return Err(Error::InvalidObject), }
}; }
Ok(id) impl From<SlotId> for u8 {
fn from(slot: SlotId) -> u8 {
match slot {
SlotId::Authentication => 0x9a,
SlotId::Signature => 0x9c,
SlotId::KeyManagement => 0x9d,
SlotId::CardAuthentication => 0x9e,
SlotId::Retired(retired) => retired.into(),
SlotId::Attestation => 0xf9,
}
}
}
impl SlotId {
/// Returns the [`ObjectId`] that corresponds to a given [`SlotId`].
pub(crate) fn object_id(self) -> ObjectId {
match self {
SlotId::Authentication => 0x005f_c105,
SlotId::Signature => 0x005f_c10a,
SlotId::KeyManagement => 0x005f_c10b,
SlotId::CardAuthentication => 0x005f_c101,
SlotId::Retired(retired) => retired.object_id(),
SlotId::Attestation => 0x005f_ff01,
}
}
}
/// Retired slot IDs.
#[allow(missing_docs)]
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum RetiredSlotId {
R1,
R2,
R3,
R4,
R5,
R6,
R7,
R8,
R9,
R10,
R11,
R12,
R13,
R14,
R15,
R16,
R17,
R18,
R19,
R20,
}
impl TryFrom<u8> for RetiredSlotId {
type Error = Error;
fn try_from(value: u8) -> Result<Self, Self::Error> {
match value {
0x82 => Ok(RetiredSlotId::R1),
0x83 => Ok(RetiredSlotId::R2),
0x84 => Ok(RetiredSlotId::R3),
0x85 => Ok(RetiredSlotId::R4),
0x86 => Ok(RetiredSlotId::R5),
0x87 => Ok(RetiredSlotId::R6),
0x88 => Ok(RetiredSlotId::R7),
0x89 => Ok(RetiredSlotId::R8),
0x8a => Ok(RetiredSlotId::R9),
0x8b => Ok(RetiredSlotId::R10),
0x8c => Ok(RetiredSlotId::R11),
0x8d => Ok(RetiredSlotId::R12),
0x8e => Ok(RetiredSlotId::R13),
0x8f => Ok(RetiredSlotId::R14),
0x90 => Ok(RetiredSlotId::R15),
0x91 => Ok(RetiredSlotId::R16),
0x92 => Ok(RetiredSlotId::R17),
0x93 => Ok(RetiredSlotId::R18),
0x94 => Ok(RetiredSlotId::R19),
0x95 => Ok(RetiredSlotId::R20),
_ => Err(Error::InvalidObject),
}
}
}
impl From<RetiredSlotId> for u8 {
fn from(slot: RetiredSlotId) -> u8 {
match slot {
RetiredSlotId::R1 => 0x82,
RetiredSlotId::R2 => 0x83,
RetiredSlotId::R3 => 0x84,
RetiredSlotId::R4 => 0x85,
RetiredSlotId::R5 => 0x86,
RetiredSlotId::R6 => 0x87,
RetiredSlotId::R7 => 0x88,
RetiredSlotId::R8 => 0x89,
RetiredSlotId::R9 => 0x8a,
RetiredSlotId::R10 => 0x8b,
RetiredSlotId::R11 => 0x8c,
RetiredSlotId::R12 => 0x8d,
RetiredSlotId::R13 => 0x8e,
RetiredSlotId::R14 => 0x8f,
RetiredSlotId::R15 => 0x90,
RetiredSlotId::R16 => 0x91,
RetiredSlotId::R17 => 0x92,
RetiredSlotId::R18 => 0x93,
RetiredSlotId::R19 => 0x94,
RetiredSlotId::R20 => 0x95,
}
}
}
impl RetiredSlotId {
/// Returns the [`ObjectId`] that corresponds to a given [`RetiredSlotId`].
pub(crate) fn object_id(self) -> ObjectId {
match self {
RetiredSlotId::R1 => 0x005f_c10d,
RetiredSlotId::R2 => 0x005f_c10e,
RetiredSlotId::R3 => 0x005f_c10f,
RetiredSlotId::R4 => 0x005f_c110,
RetiredSlotId::R5 => 0x005f_c111,
RetiredSlotId::R6 => 0x005f_c112,
RetiredSlotId::R7 => 0x005f_c113,
RetiredSlotId::R8 => 0x005f_c114,
RetiredSlotId::R9 => 0x005f_c115,
RetiredSlotId::R10 => 0x005f_c116,
RetiredSlotId::R11 => 0x005f_c117,
RetiredSlotId::R12 => 0x005f_c118,
RetiredSlotId::R13 => 0x005f_c119,
RetiredSlotId::R14 => 0x005f_c11a,
RetiredSlotId::R15 => 0x005f_c11b,
RetiredSlotId::R16 => 0x005f_c11c,
RetiredSlotId::R17 => 0x005f_c11d,
RetiredSlotId::R18 => 0x005f_c11e,
RetiredSlotId::R19 => 0x005f_c11f,
RetiredSlotId::R20 => 0x005f_c120,
}
}
} }
/// Personal Identity Verification (PIV) key slots /// Personal Identity Verification (PIV) key slots
pub const SLOTS: [u8; 24] = [ pub const SLOTS: [SlotId; 24] = [
YKPIV_KEY_AUTHENTICATION, SlotId::Authentication,
YKPIV_KEY_SIGNATURE, SlotId::Signature,
YKPIV_KEY_KEYMGM, SlotId::KeyManagement,
YKPIV_KEY_RETIRED1, SlotId::Retired(RetiredSlotId::R1),
YKPIV_KEY_RETIRED2, SlotId::Retired(RetiredSlotId::R2),
YKPIV_KEY_RETIRED3, SlotId::Retired(RetiredSlotId::R3),
YKPIV_KEY_RETIRED4, SlotId::Retired(RetiredSlotId::R4),
YKPIV_KEY_RETIRED5, SlotId::Retired(RetiredSlotId::R5),
YKPIV_KEY_RETIRED6, SlotId::Retired(RetiredSlotId::R6),
YKPIV_KEY_RETIRED7, SlotId::Retired(RetiredSlotId::R7),
YKPIV_KEY_RETIRED8, SlotId::Retired(RetiredSlotId::R8),
YKPIV_KEY_RETIRED9, SlotId::Retired(RetiredSlotId::R9),
YKPIV_KEY_RETIRED10, SlotId::Retired(RetiredSlotId::R10),
YKPIV_KEY_RETIRED11, SlotId::Retired(RetiredSlotId::R11),
YKPIV_KEY_RETIRED12, SlotId::Retired(RetiredSlotId::R12),
YKPIV_KEY_RETIRED13, SlotId::Retired(RetiredSlotId::R13),
YKPIV_KEY_RETIRED14, SlotId::Retired(RetiredSlotId::R14),
YKPIV_KEY_RETIRED15, SlotId::Retired(RetiredSlotId::R15),
YKPIV_KEY_RETIRED16, SlotId::Retired(RetiredSlotId::R16),
YKPIV_KEY_RETIRED17, SlotId::Retired(RetiredSlotId::R17),
YKPIV_KEY_RETIRED18, SlotId::Retired(RetiredSlotId::R18),
YKPIV_KEY_RETIRED19, SlotId::Retired(RetiredSlotId::R19),
YKPIV_KEY_RETIRED20, SlotId::Retired(RetiredSlotId::R20),
YKPIV_KEY_CARDAUTH, SlotId::CardAuthentication,
]; ];
/// Algorithm identifiers
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum AlgorithmId {
/// 1024-bit RSA.
Rsa1024,
/// 2048-bit RSA.
Rsa2048,
/// ECDSA with the NIST P256 curve.
EccP256,
/// ECDSA with the NIST P384 curve.
EccP384,
}
impl TryFrom<u8> for AlgorithmId {
type Error = Error;
fn try_from(value: u8) -> Result<Self, Self::Error> {
match value {
0x06 => Ok(AlgorithmId::Rsa1024),
0x07 => Ok(AlgorithmId::Rsa2048),
0x11 => Ok(AlgorithmId::EccP256),
0x14 => Ok(AlgorithmId::EccP384),
_ => Err(Error::AlgorithmError),
}
}
}
impl From<AlgorithmId> for u8 {
fn from(id: AlgorithmId) -> u8 {
match id {
AlgorithmId::Rsa1024 => 0x06,
AlgorithmId::Rsa2048 => 0x07,
AlgorithmId::EccP256 => 0x11,
AlgorithmId::EccP384 => 0x14,
}
}
}
/// PIV cryptographic keys stored in a YubiKey /// PIV cryptographic keys stored in a YubiKey
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub struct Key { pub struct Key {
@@ -120,13 +331,15 @@ impl Key {
let buf = match certificate::read_certificate(&txn, slot) { let buf = match certificate::read_certificate(&txn, slot) {
Ok(b) => b, Ok(b) => b,
Err(e) => { Err(e) => {
debug!("error reading certificate in slot {}: {}", slot, e); debug!("error reading certificate in slot {:?}: {}", slot, e);
continue; continue;
} }
}; };
let cert = Certificate::new(buf)?; if !buf.is_empty() {
keys.push(Key { slot, cert }); let cert = Certificate::new(buf)?;
keys.push(Key { slot, cert });
}
} }
Ok(keys) Ok(keys)
@@ -202,55 +415,52 @@ pub fn generate(
let mut templ = [0, Ins::GenerateAsymmetric.code(), 0, 0]; let mut templ = [0, Ins::GenerateAsymmetric.code(), 0, 0];
let setting_roca: settings::BoolValue; 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 { match algorithm {
YKPIV_ALGO_RSA1024 | YKPIV_ALGO_RSA2048 | YKPIV_ALGO_ECCP256 | YKPIV_ALGO_ECCP384 => (), AlgorithmId::Rsa1024 | AlgorithmId::Rsa2048 => {
_ => { if yubikey.device_model() == DEVTYPE_YK4
error!("invalid algorithm specified"); && yubikey.version.major == 4
return Err(Error::GenericError); && (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);
}
}
} }
_ => (),
} }
let txn = yubikey.begin_transaction()?; let txn = yubikey.begin_transaction()?;
templ[3] = slot; templ[3] = slot.into();
let mut offset = 5; let mut offset = 5;
in_data[..offset].copy_from_slice(&[ in_data[..offset].copy_from_slice(&[
@@ -258,7 +468,7 @@ pub fn generate(
3, // length sans this 2-byte header 3, // length sans this 2-byte header
YKPIV_ALGO_TAG, YKPIV_ALGO_TAG,
1, 1,
algorithm, algorithm.into(),
]); ]);
if in_data[4] == 0 { if in_data[4] == 0 {
@@ -312,7 +522,7 @@ pub fn generate(
let data = Buffer::new(response.data().into()); let data = Buffer::new(response.data().into());
match algorithm { match algorithm {
YKPIV_ALGO_RSA1024 | YKPIV_ALGO_RSA2048 => { AlgorithmId::Rsa1024 | AlgorithmId::Rsa2048 => {
let mut offset = 5; let mut offset = 5;
let mut len = 0; let mut len = 0;
@@ -340,10 +550,10 @@ pub fn generate(
exp, exp,
}) })
} }
YKPIV_ALGO_ECCP256 | YKPIV_ALGO_ECCP384 => { AlgorithmId::EccP256 | AlgorithmId::EccP384 => {
let mut offset = 3; let mut offset = 3;
let len = if algorithm == YKPIV_ALGO_ECCP256 { let len = if let AlgorithmId::EccP256 = algorithm {
CB_ECC_POINTP256 CB_ECC_POINTP256
} else { } else {
CB_ECC_POINTP384 CB_ECC_POINTP384
@@ -367,9 +577,5 @@ pub fn generate(
let point = data[offset..(offset + len)].to_vec(); let point = data[offset..(offset + len)].to_vec();
Ok(GeneratedKey::Ecc { algorithm, point }) Ok(GeneratedKey::Ecc { algorithm, point })
} }
_ => {
error!("wrong algorithm");
Err(Error::AlgorithmError)
}
} }
} }
+1 -4
View File
@@ -125,6 +125,7 @@
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.2" html_root_url = "https://docs.rs/yubikey-piv/0.0.2"
)] )]
#![forbid(unsafe_code)]
#![warn( #![warn(
missing_docs, missing_docs,
rust_2018_idioms, rust_2018_idioms,
@@ -166,10 +167,6 @@ pub mod yubikey;
pub use self::{key::Key, mgm::MgmKey}; pub use self::{key::Key, mgm::MgmKey};
pub use yubikey::YubiKey; pub use yubikey::YubiKey;
/// Algorithm identifiers
// TODO(tarcieri): make this an enum
pub type AlgorithmId = u8;
/// Object identifiers /// Object identifiers
pub type ObjectId = u32; pub type ObjectId = u32;
+62 -99
View File
@@ -31,48 +31,36 @@
// 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 crate::{consts::*, error::Error, serialization::*, transaction::Transaction, Buffer}; use crate::{consts::*, error::Error, serialization::*, transaction::Transaction, Buffer};
use std::{ptr, slice};
use zeroize::Zeroizing; use zeroize::Zeroizing;
/// Get metadata item /// Get metadata item
pub(crate) fn get_item(data: &[u8], tag: u8) -> Result<&[u8], Error> { 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 cb_temp: usize = 0;
let mut tag_temp: u8; let mut offset = 0;
unsafe { while offset < data.len() {
while p_temp < data.as_ptr().add(data.len()) { let tag_temp = data[offset];
tag_temp = *p_temp; offset += 1;
p_temp = p_temp.add(1);
let p_slice = slice::from_raw_parts( if !has_valid_length(&data[offset..], data.len() - 1) {
p_temp, return Err(Error::SizeError);
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()) { offset += get_length(&data[offset..], &mut cb_temp);
return Ok(slice::from_raw_parts(p_temp, cb_temp));
if tag_temp == tag {
// found tag
break;
} }
offset += cb_temp;
} }
Err(Error::GenericError) if offset < data.len() {
Ok(&data[offset..offset + cb_temp])
} else {
Err(Error::GenericError)
}
} }
/// Set metadata item /// Set metadata item
@@ -83,35 +71,25 @@ pub(crate) fn set_item(
tag: u8, tag: u8,
p_item: &[u8], p_item: &[u8],
) -> Result<(), Error> { ) -> Result<(), Error> {
let mut p_temp: *mut u8 = data.as_mut_ptr();
let mut cb_temp: usize = 0; let mut cb_temp: usize = 0;
let mut tag_temp: u8 = 0; let mut tag_temp: u8 = 0;
let mut cb_len: usize = 0; let mut cb_len: usize = 0;
let cb_item = p_item.len(); 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() { let mut offset = 0;
unsafe {
tag_temp = *p_temp;
p_temp = p_temp.add(1);
cb_len = get_length( while offset < *pcb_data {
slice::from_raw_parts( tag_temp = data[offset];
p_temp, offset += 1;
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 { cb_len = get_length(&data[offset..], &mut cb_temp);
break; offset += cb_len;
}
p_temp = p_temp.add(cb_temp); if tag_temp == tag {
break;
} }
offset += cb_temp;
} }
if tag_temp != tag { if tag_temp != tag {
@@ -120,75 +98,63 @@ pub(crate) fn set_item(
return Ok(()); return Ok(());
} }
unsafe { // We did not find an existing tag, append
p_temp = data.as_mut_ptr().add(*pcb_data); offset = *pcb_data;
cb_len = get_length_size(cb_item); cb_len = get_length_size(cb_item);
if (*pcb_data + cb_len + cb_item) > cb_data_max { // If length would cause buffer overflow, return error
return Err(Error::GenericError); 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);
} }
data[offset] = tag;
offset += 1;
offset += set_length(&mut data[offset..], cb_item);
data[offset..offset + cb_item].copy_from_slice(p_item);
*pcb_data += 1 + cb_len + cb_item; *pcb_data += 1 + cb_len + cb_item;
return Ok(()); return Ok(());
} }
if cb_temp == cb_item { // Found tag
unsafe {
ptr::copy(p_item.as_ptr(), p_temp, cb_item);
}
// Check length, if it matches, overwrite
if cb_temp == cb_item {
data[offset..offset + cb_item].copy_from_slice(p_item);
return Ok(()); return Ok(());
} }
p_next = unsafe { p_temp.add(cb_temp) }; // Length doesn't match, expand/shrink to fit
cb_moved = (cb_item as isize - cb_temp as isize) let next_offset = offset + cb_temp;
// Must be signed to have negative offsets
let cb_moved: isize = (cb_item as isize - cb_temp as isize)
+ if cb_item != 0 { + if cb_item != 0 {
get_length_size(cb_item) as isize get_length_size(cb_item) as isize
} else { } else {
// For tag, if deleting
-1 -1
} }
// Accounts for different length encoding
- cb_len as isize; - cb_len as isize;
if (*pcb_data + cb_moved as usize) > cb_data_max { // If length would cause buffer overflow, return error
if (*pcb_data as isize + cb_moved) as usize > cb_data_max {
return Err(Error::GenericError); return Err(Error::GenericError);
} }
unsafe { // Move remaining data
ptr::copy( data.copy_within(
p_next, next_offset..*pcb_data,
p_next.offset(cb_moved), (next_offset as isize + cb_moved) as usize,
*pcb_data - p_next as usize - data.as_ptr() as usize, );
); *pcb_data = (*pcb_data as isize + cb_moved) as usize;
}
*pcb_data += cb_moved as usize;
// Re-encode item and insert
if cb_item != 0 { if cb_item != 0 {
unsafe { offset -= cb_len;
p_temp = p_temp.offset(-(cb_len as isize)); offset += set_length(&mut data[offset..], cb_item);
p_temp = p_temp.add(set_length( data[offset..offset + cb_item].copy_from_slice(p_item);
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(()) Ok(())
@@ -219,10 +185,7 @@ pub(crate) fn read(txn: &Transaction<'_>, tag: u8) -> Result<Buffer, Error> {
return Err(Error::GenericError); return Err(Error::GenericError);
} }
unsafe { data.copy_within(offset..offset + pcb_data, 0);
ptr::copy(data.as_ptr().add(offset), data.as_mut_ptr(), pcb_data);
}
data.truncate(pcb_data); data.truncate(pcb_data);
Ok(data) Ok(data)
} }
+15 -37
View File
@@ -39,7 +39,6 @@
use crate::{consts::*, error::Error, serialization::*, yubikey::YubiKey}; use crate::{consts::*, error::Error, serialization::*, yubikey::YubiKey};
use log::error; use log::error;
use std::{ptr, slice};
/// `msroots` file: PKCS#7-formatted certificate store for enterprise trust roots /// `msroots` file: PKCS#7-formatted certificate store for enterprise trust roots
pub struct MsRoots(Vec<u8>); pub struct MsRoots(Vec<u8>);
@@ -51,33 +50,22 @@ impl MsRoots {
} }
/// Read `msroots` file from YubiKey /// Read `msroots` file from YubiKey
pub fn read(yubikey: &mut YubiKey) -> Result<Vec<Self>, Error> { pub fn read(yubikey: &mut YubiKey) -> Result<Option<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 cb_data = yubikey.obj_size_max();
let txn = yubikey.begin_transaction()?; let txn = yubikey.begin_transaction()?;
// allocate first page // allocate first page
let mut p_data = vec![0u8; cb_data]; let mut data = Vec::with_capacity(cb_data);
for object_id in YKPIV_OBJ_MSROOTS1..YKPIV_OBJ_MSROOTS5 { for object_id in YKPIV_OBJ_MSROOTS1..YKPIV_OBJ_MSROOTS5 {
let mut buf = txn.fetch_object(object_id)?; let buf = txn.fetch_object(object_id)?;
let cb_buf = buf.len(); let cb_buf = buf.len();
ptr = buf.as_mut_ptr();
if cb_buf < CB_OBJ_TAG_MIN { if cb_buf < CB_OBJ_TAG_MIN {
return Ok(results); return Ok(None);
} }
unsafe { let tag = buf[0];
tag = *ptr;
ptr = ptr.add(1);
}
if (TAG_MSROOTS_MID != tag || YKPIV_OBJ_MSROOTS5 == object_id) if (TAG_MSROOTS_MID != tag || YKPIV_OBJ_MSROOTS5 == object_id)
&& (TAG_MSROOTS_END != tag) && (TAG_MSROOTS_END != tag)
@@ -85,38 +73,28 @@ impl MsRoots {
// the current object doesn't contain a valid part of a msroots file // the current object doesn't contain a valid part of a msroots file
// treat condition as object isn't found // treat condition as object isn't found
return Ok(results); return Ok(None);
} }
unsafe { let mut len: usize = 0;
ptr = ptr.add(get_length( let offset = 1 + get_length(&buf[1..], &mut len);
slice::from_raw_parts(ptr, buf.as_ptr() as usize + buf.len() - ptr as usize),
&mut len,
));
}
// check that decoded length represents object contents // check that decoded length represents object contents
if len > cb_buf - (ptr as isize - buf.as_mut_ptr() as isize) as usize { if len > cb_buf - offset {
return Ok(results); return Ok(None);
} }
unsafe { data.extend_from_slice(&buf[offset..offset + len]);
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 { if tag == TAG_MSROOTS_END {
break; break;
} }
} }
Ok(results) MsRoots::new(&data).map(Some).map_err(|e| {
error!("error parsing msroots: {:?}", e);
e
})
} }
/// Write `msroots` file to YubiKey /// Write `msroots` file to YubiKey
+31 -50
View File
@@ -9,6 +9,7 @@ use crate::{
use crate::{ use crate::{
apdu::{Response, StatusWords}, apdu::{Response, StatusWords},
consts::*, consts::*,
key::{AlgorithmId, SlotId},
mgm::MgmKey, mgm::MgmKey,
serialization::*, serialization::*,
Buffer, ObjectId, Buffer, ObjectId,
@@ -16,8 +17,6 @@ use crate::{
use log::{error, trace}; use log::{error, trace};
use std::convert::TryInto; use std::convert::TryInto;
#[cfg(feature = "untested")] #[cfg(feature = "untested")]
use std::ptr;
#[cfg(feature = "untested")]
use zeroize::Zeroizing; use zeroize::Zeroizing;
/// Exclusive transaction with the YubiKey's PC/SC card. /// Exclusive transaction with the YubiKey's PC/SC card.
@@ -165,20 +164,28 @@ impl<'tx> Transaction<'tx> {
/// Verify device PIN. /// Verify device PIN.
#[cfg(feature = "untested")] #[cfg(feature = "untested")]
pub fn verify_pin(&self, pin: &[u8]) -> Result<(), Error> { 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 {
if pin.len() != CB_PIN_MAX {
return Err(Error::SizeError); return Err(Error::SizeError);
} }
let response = APDU::new(Ins::Verify) let mut query = APDU::new(Ins::Verify);
.params(0x00, 0x80) query.params(0x00, 0x80);
.data(pin)
.transmit(self, 261)?; // Empty pin means we are querying the number of retries. We set no data in this
// case; if we instead sent [0xff; CB_PIN_MAX] it would count as an attempt and
// decrease the retry counter.
if !pin.is_empty() {
let mut data = Zeroizing::new([0xff; CB_PIN_MAX]);
data[0..pin.len()].copy_from_slice(pin);
query.data(data.as_ref());
}
let response = query.transmit(self, 261)?;
match response.status_words() { match response.status_words() {
StatusWords::Success => Ok(()), StatusWords::Success => Ok(()),
StatusWords::AuthBlockedError => Err(Error::WrongPin { tries: 0 }), StatusWords::AuthBlockedError => Err(Error::WrongPin { tries: 0 }),
StatusWords::Other(sw) if sw >> 8 == 0x63 => Err(Error::WrongPin { tries: sw & 0xf }), StatusWords::VerifyFailError { tries } => Err(Error::WrongPin { tries }),
_ => Err(Error::GenericError), _ => Err(Error::GenericError),
} }
} }
@@ -187,7 +194,6 @@ impl<'tx> Transaction<'tx> {
#[cfg(feature = "untested")] #[cfg(feature = "untested")]
pub fn change_pin(&self, action: i32, current_pin: &[u8], new_pin: &[u8]) -> Result<(), Error> { pub fn change_pin(&self, action: i32, current_pin: &[u8], new_pin: &[u8]) -> Result<(), Error> {
let mut templ = [0, Ins::ChangeReference.code(), 0, 0x80]; let mut templ = [0, Ins::ChangeReference.code(), 0, 0x80];
let mut indata = Zeroizing::new([0u8; 16]);
if current_pin.len() > CB_PIN_MAX || new_pin.len() > CB_PIN_MAX { if current_pin.len() > CB_PIN_MAX || new_pin.len() > CB_PIN_MAX {
return Err(Error::SizeError); return Err(Error::SizeError);
@@ -199,31 +205,9 @@ impl<'tx> Transaction<'tx> {
templ[3] = 0x81; templ[3] = 0x81;
} }
unsafe { let mut indata = Zeroizing::new([0xff; CB_PIN_MAX * 2]);
ptr::copy(current_pin.as_ptr(), indata.as_mut_ptr(), current_pin.len()); indata[0..current_pin.len()].copy_from_slice(current_pin);
indata[CB_PIN_MAX..CB_PIN_MAX + new_pin.len()].copy_from_slice(new_pin);
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 let status_words = self
.transfer_data(&templ, indata.as_ref(), 0xFF)? .transfer_data(&templ, indata.as_ref(), 0xFF)?
@@ -232,7 +216,7 @@ impl<'tx> Transaction<'tx> {
match status_words { match status_words {
StatusWords::Success => Ok(()), StatusWords::Success => Ok(()),
StatusWords::AuthBlockedError => Err(Error::PinLocked), StatusWords::AuthBlockedError => Err(Error::PinLocked),
StatusWords::Other(sw) if sw >> 8 == 0x63 => Err(Error::WrongPin { tries: sw & 0xf }), StatusWords::VerifyFailError { tries } => Err(Error::WrongPin { tries }),
_ => { _ => {
error!( error!(
"failed changing pin, token response code: {:x}.", "failed changing pin, token response code: {:x}.",
@@ -283,18 +267,18 @@ impl<'tx> Transaction<'tx> {
pub(crate) fn authenticated_command( pub(crate) fn authenticated_command(
&self, &self,
sign_in: &[u8], sign_in: &[u8],
algorithm: u8, algorithm: AlgorithmId,
key: u8, key: SlotId,
decipher: bool, decipher: bool,
) -> Result<Buffer, Error> { ) -> Result<Buffer, Error> {
let in_len = sign_in.len(); let in_len = sign_in.len();
let mut indata = [0u8; 1024]; let mut indata = [0u8; 1024];
let templ = [0, Ins::Authenticate.code(), algorithm, key]; let templ = [0, Ins::Authenticate.code(), algorithm.into(), key.into()];
let mut len: usize = 0; let mut len: usize = 0;
match algorithm { match algorithm {
YKPIV_ALGO_RSA1024 | YKPIV_ALGO_RSA2048 => { AlgorithmId::Rsa1024 | AlgorithmId::Rsa2048 => {
let key_len = if algorithm == YKPIV_ALGO_RSA1024 { let key_len = if let AlgorithmId::Rsa1024 = algorithm {
128 128
} else { } else {
256 256
@@ -304,8 +288,8 @@ impl<'tx> Transaction<'tx> {
return Err(Error::SizeError); return Err(Error::SizeError);
} }
} }
YKPIV_ALGO_ECCP256 | YKPIV_ALGO_ECCP384 => { AlgorithmId::EccP256 | AlgorithmId::EccP384 => {
let key_len = if algorithm == YKPIV_ALGO_ECCP256 { let key_len = if let AlgorithmId::EccP256 = algorithm {
32 32
} else { } else {
48 48
@@ -316,7 +300,6 @@ impl<'tx> Transaction<'tx> {
return Err(Error::SizeError); return Err(Error::SizeError);
} }
} }
_ => return Err(Error::AlgorithmError),
} }
let bytes = if in_len < 0x80 { let bytes = if in_len < 0x80 {
@@ -331,12 +314,10 @@ impl<'tx> Transaction<'tx> {
let mut offset = 1 + set_length(&mut indata[1..], in_len + bytes + 3); let mut offset = 1 + set_length(&mut indata[1..], in_len + bytes + 3);
indata[offset] = 0x82; indata[offset] = 0x82;
indata[offset + 1] = 0x00; indata[offset + 1] = 0x00;
indata[offset + 2] = indata[offset + 2] = match (algorithm, decipher) {
if (algorithm == YKPIV_ALGO_ECCP256 || algorithm == YKPIV_ALGO_ECCP384) && decipher { (AlgorithmId::EccP256, true) | (AlgorithmId::EccP384, true) => 0x85,
0x85 _ => 0x81,
} else { };
0x81
};
offset += 3; offset += 3;
offset += set_length(&mut indata[offset..], in_len); offset += set_length(&mut indata[offset..], in_len);
+84 -158
View File
@@ -36,22 +36,23 @@
#[cfg(feature = "untested")] #[cfg(feature = "untested")]
use crate::{ use crate::{
apdu::{Ins, StatusWords, APDU}, apdu::{Ins, StatusWords, APDU},
key::SlotId, key::{AlgorithmId, SlotId},
metadata, metadata,
mgm::MgmKey, mgm::MgmKey,
serialization::*, serialization::*,
ObjectId, Buffer, ObjectId,
}; };
use crate::{consts::*, error::Error, transaction::Transaction, Buffer}; use crate::{consts::*, error::Error, transaction::Transaction};
#[cfg(feature = "untested")] #[cfg(feature = "untested")]
use getrandom::getrandom; use getrandom::getrandom;
use log::{error, info, warn}; use log::{error, info, warn};
use pcsc::{Card, Context}; use pcsc::{Card, Context};
#[cfg(feature = "untested")]
use secrecy::ExposeSecret;
use std::fmt::{self, Display}; use std::fmt::{self, Display};
#[cfg(feature = "untested")] #[cfg(feature = "untested")]
use std::{ use std::{
convert::TryInto, convert::TryInto,
ptr, slice,
time::{SystemTime, UNIX_EPOCH}, time::{SystemTime, UNIX_EPOCH},
}; };
#[cfg(feature = "untested")] #[cfg(feature = "untested")]
@@ -64,6 +65,9 @@ pub const AID: [u8; 5] = [0xa0, 0x00, 0x00, 0x03, 0x08];
/// <https://developers.yubico.com/PIV/Introduction/Admin_access.html> /// <https://developers.yubico.com/PIV/Introduction/Admin_access.html>
pub const MGMT_AID: [u8; 8] = [0xa0, 0x00, 0x00, 0x05, 0x27, 0x47, 0x11, 0x17]; pub const MGMT_AID: [u8; 8] = [0xa0, 0x00, 0x00, 0x05, 0x27, 0x47, 0x11, 0x17];
/// Cached YubiKey PIN
pub type CachedPin = secrecy::SecretVec<u8>;
/// YubiKey Serial Number /// YubiKey Serial Number
#[derive(Copy, Clone, Debug, Eq, PartialEq, PartialOrd, Ord)] #[derive(Copy, Clone, Debug, Eq, PartialEq, PartialOrd, Ord)]
pub struct Serial(pub u32); pub struct Serial(pub u32);
@@ -119,7 +123,7 @@ impl Version {
#[cfg_attr(not(feature = "untested"), allow(dead_code))] #[cfg_attr(not(feature = "untested"), allow(dead_code))]
pub struct YubiKey { pub struct YubiKey {
pub(crate) card: Card, pub(crate) card: Card,
pub(crate) pin: Option<Buffer>, pub(crate) pin: Option<CachedPin>,
pub(crate) is_neo: bool, pub(crate) is_neo: bool,
pub(crate) version: Version, pub(crate) version: Version,
pub(crate) serial: Serial, pub(crate) serial: Serial,
@@ -229,8 +233,10 @@ impl YubiKey {
pcsc::Disposition::ResetCard, pcsc::Disposition::ResetCard,
)?; )?;
// TODO(tarcieri): zeroize pin! let pin = self
let pin = self.pin.clone(); .pin
.as_ref()
.map(|p| Buffer::new(p.expose_secret().clone()));
let txn = Transaction::new(&mut self.card)?; let txn = Transaction::new(&mut self.card)?;
txn.select_application()?; txn.select_application()?;
@@ -357,7 +363,7 @@ impl YubiKey {
pub fn sign_data( pub fn sign_data(
&mut self, &mut self,
raw_in: &[u8], raw_in: &[u8],
algorithm: u8, algorithm: AlgorithmId,
key: SlotId, key: SlotId,
) -> Result<Buffer, Error> { ) -> Result<Buffer, Error> {
let txn = self.begin_transaction()?; let txn = self.begin_transaction()?;
@@ -371,7 +377,7 @@ impl YubiKey {
pub fn decrypt_data( pub fn decrypt_data(
&mut self, &mut self,
input: &[u8], input: &[u8],
algorithm: u8, algorithm: AlgorithmId,
key: SlotId, key: SlotId,
) -> Result<Buffer, Error> { ) -> Result<Buffer, Error> {
let txn = self.begin_transaction()?; let txn = self.begin_transaction()?;
@@ -389,7 +395,7 @@ impl YubiKey {
} }
if !pin.is_empty() { if !pin.is_empty() {
self.pin = Some(Buffer::new(pin.into())) self.pin = Some(CachedPin::new(pin.into()))
} }
Ok(()) Ok(())
@@ -397,7 +403,7 @@ impl YubiKey {
/// Get the number of PIN retries /// Get the number of PIN retries
#[cfg(feature = "untested")] #[cfg(feature = "untested")]
pub fn get_pin_retries(&mut self) -> Result<u32, Error> { pub fn get_pin_retries(&mut self) -> Result<u8, Error> {
let txn = self.begin_transaction()?; let txn = self.begin_transaction()?;
// Force a re-select to unverify, because once verified the spec dictates that // Force a re-select to unverify, because once verified the spec dictates that
@@ -415,24 +421,15 @@ impl YubiKey {
/// Set the number of PIN retries /// Set the number of PIN retries
#[cfg(feature = "untested")] #[cfg(feature = "untested")]
pub fn set_pin_retries(&mut self, pin_tries: usize, puk_tries: usize) -> Result<(), Error> { pub fn set_pin_retries(&mut self, pin_tries: u8, puk_tries: u8) -> Result<(), Error> {
// Special case: if either retry count is 0, it's a successful no-op // Special case: if either retry count is 0, it's a successful no-op
if pin_tries == 0 || puk_tries == 0 { if pin_tries == 0 || puk_tries == 0 {
return Ok(()); return Ok(());
} }
if pin_tries > 0xff || puk_tries > 0xff || pin_tries < 1 || puk_tries < 1 {
return Err(Error::RangeError);
}
let txn = self.begin_transaction()?; let txn = self.begin_transaction()?;
let templ = [ let templ = [0, Ins::SetPinRetries.code(), pin_tries, puk_tries];
0,
Ins::SetPinRetries.code(),
pin_tries as u8,
puk_tries as u8,
];
let status_words = txn.transfer_data(&templ, &[], 255)?.status_words(); let status_words = txn.transfer_data(&templ, &[], 255)?.status_words();
@@ -455,7 +452,7 @@ impl YubiKey {
} }
if !new_pin.is_empty() { if !new_pin.is_empty() {
self.pin = Some(Buffer::new(new_pin.into())); self.pin = Some(CachedPin::new(new_pin.into()));
} }
Ok(()) Ok(())
@@ -608,7 +605,7 @@ impl YubiKey {
pub fn import_private_key( pub fn import_private_key(
&mut self, &mut self,
key: SlotId, key: SlotId,
algorithm: u8, algorithm: AlgorithmId,
p: Option<&[u8]>, p: Option<&[u8]>,
q: Option<&[u8]>, q: Option<&[u8]>,
dp: Option<&[u8]>, dp: Option<&[u8]>,
@@ -618,177 +615,106 @@ impl YubiKey {
pin_policy: u8, pin_policy: u8,
touch_policy: u8, touch_policy: u8,
) -> Result<(), Error> { ) -> Result<(), Error> {
// TODO(tarcieri): get rid of legacy pointers
let (p, p_len) = match p {
Some(slice) => (slice.as_ptr(), slice.len()),
None => (ptr::null(), 0),
};
let (q, q_len) = match q {
Some(slice) => (slice.as_ptr(), slice.len()),
None => (ptr::null(), 0),
};
let (dp, dp_len) = match dp {
Some(slice) => (slice.as_ptr(), slice.len()),
None => (ptr::null(), 0),
};
let (dq, dq_len) = match dq {
Some(slice) => (slice.as_ptr(), slice.len()),
None => (ptr::null(), 0),
};
let (qinv, qinv_len) = match qinv {
Some(slice) => (slice.as_ptr(), slice.len()),
None => (ptr::null(), 0),
};
let (ec_data, ec_data_len) = match ec_data {
Some(slice) => (slice.as_ptr(), slice.len()),
None => (ptr::null(), 0),
};
let mut key_data = Zeroizing::new(vec![0u8; 1024]); let mut key_data = Zeroizing::new(vec![0u8; 1024]);
let mut in_ptr: *mut u8 = key_data.as_mut_ptr(); let templ = [0, Ins::ImportKey.code(), algorithm.into(), key.into()];
let templ = [0, Ins::ImportKey.code(), algorithm, key];
let mut elem_len: u32 = 0;
let mut params: [*const u8; 5] = [ptr::null(); 5];
let mut lens = [0usize; 5];
let n_params: u8;
let param_tag: i32;
if key == YKPIV_KEY_CARDMGM
|| key < YKPIV_KEY_RETIRED1
|| key > YKPIV_KEY_RETIRED20 && (key < YKPIV_KEY_AUTHENTICATION)
|| key > YKPIV_KEY_CARDAUTH && (key != YKPIV_KEY_ATTESTATION)
{
return Err(Error::KeyError);
}
if pin_policy != YKPIV_PINPOLICY_DEFAULT if pin_policy != YKPIV_PINPOLICY_DEFAULT
&& (pin_policy != YKPIV_PINPOLICY_NEVER) && pin_policy != YKPIV_PINPOLICY_NEVER
&& (pin_policy != YKPIV_PINPOLICY_ONCE) && pin_policy != YKPIV_PINPOLICY_ONCE
&& (pin_policy != YKPIV_PINPOLICY_ALWAYS) && pin_policy != YKPIV_PINPOLICY_ALWAYS
{ {
return Err(Error::GenericError); return Err(Error::GenericError);
} }
if touch_policy != YKPIV_TOUCHPOLICY_DEFAULT if touch_policy != YKPIV_TOUCHPOLICY_DEFAULT
&& (touch_policy != YKPIV_TOUCHPOLICY_NEVER) && touch_policy != YKPIV_TOUCHPOLICY_NEVER
&& (touch_policy != YKPIV_TOUCHPOLICY_ALWAYS) && touch_policy != YKPIV_TOUCHPOLICY_ALWAYS
&& (touch_policy != YKPIV_TOUCHPOLICY_CACHED) && touch_policy != YKPIV_TOUCHPOLICY_CACHED
{ {
return Err(Error::GenericError); return Err(Error::GenericError);
} }
match algorithm { let (elem_len, params, param_tag) = match algorithm {
YKPIV_ALGO_RSA1024 | YKPIV_ALGO_RSA2048 => { AlgorithmId::Rsa1024 | AlgorithmId::Rsa2048 => match (p, q, dp, dq, qinv) {
if p_len + q_len + dp_len + dq_len + qinv_len >= 1024 { (Some(p), Some(q), Some(dp), Some(dq), Some(qinv)) => {
return Err(Error::SizeError); if p.len() + q.len() + dp.len() + dq.len() + qinv.len() >= key_data.len() {
} else { return Err(Error::SizeError);
if algorithm == YKPIV_ALGO_RSA1024 {
elem_len = 64;
} }
if algorithm == YKPIV_ALGO_RSA2048 { (
elem_len = 128; match algorithm {
AlgorithmId::Rsa1024 => 64,
AlgorithmId::Rsa2048 => 128,
_ => unreachable!(),
},
vec![p, q, dp, dq, qinv],
0x01,
)
}
_ => return Err(Error::GenericError),
},
AlgorithmId::EccP256 | AlgorithmId::EccP384 => match ec_data {
Some(ec_data) => {
if ec_data.len() >= key_data.len() {
// This can never be true, but check to be explicit.
return Err(Error::SizeError);
} }
if p.is_null() || q.is_null() || dp.is_null() || dq.is_null() || qinv.is_null() (
{ match algorithm {
return Err(Error::GenericError); AlgorithmId::EccP256 => 32,
} AlgorithmId::EccP384 => 48,
_ => unreachable!(),
params[0] = p; },
lens[0] = p_len; vec![ec_data],
params[1] = q; 0x06,
lens[1] = q_len; )
params[2] = dp;
lens[2] = dp_len;
params[3] = dq;
lens[3] = dq_len;
params[4] = qinv;
lens[4] = qinv_len;
param_tag = 0x1;
n_params = 5u8;
}
}
YKPIV_ALGO_ECCP256 | YKPIV_ALGO_ECCP384 => {
if ec_data_len >= key_data.len() {
return Err(Error::SizeError);
} }
_ => return Err(Error::GenericError),
},
};
if algorithm == YKPIV_ALGO_ECCP256 { let mut offset = 0;
elem_len = 32;
} else if algorithm == YKPIV_ALGO_ECCP384 {
elem_len = 48;
}
if ec_data.is_null() { for (i, param) in params.into_iter().enumerate() {
return Err(Error::GenericError); key_data[offset] = param_tag + i as u8;
} offset += 1;
params[0] = ec_data; offset += set_length(&mut key_data[offset..], elem_len);
lens[0] = ec_data_len;
param_tag = 0x6;
n_params = 1;
}
_ => return Err(Error::AlgorithmError),
}
for i in 0..n_params { let padding = elem_len - param.len();
unsafe { let remaining = key_data.len() - offset;
*in_ptr = (param_tag + i as i32) as u8;
in_ptr = in_ptr.offset(1);
in_ptr = in_ptr.add(set_length(
slice::from_raw_parts_mut(
in_ptr,
key_data.as_mut_ptr() as usize - in_ptr as usize,
),
elem_len as usize,
));
}
let padding = elem_len as usize - lens[i as usize];
let remaining = (key_data.as_mut_ptr() as usize) + 1024 - in_ptr as usize;
if padding > remaining { if padding > remaining {
return Err(Error::AlgorithmError); return Err(Error::AlgorithmError);
} }
unsafe { for b in &mut key_data[offset..offset + padding] {
ptr::write_bytes(in_ptr, 0, padding); *b = 0;
in_ptr = in_ptr.add(padding);
ptr::copy(params[i as usize], in_ptr, lens[i as usize]);
in_ptr = in_ptr.add(lens[i as usize]);
} }
offset += padding;
key_data[offset..offset + param.len()].copy_from_slice(param);
offset += param.len();
} }
if pin_policy != YKPIV_PINPOLICY_DEFAULT { if pin_policy != YKPIV_PINPOLICY_DEFAULT {
unsafe { key_data[offset] = YKPIV_PINPOLICY_TAG;
*in_ptr = YKPIV_PINPOLICY_TAG; key_data[offset + 1] = 0x01;
*in_ptr.add(1) = 0x01; key_data[offset + 2] = pin_policy;
*in_ptr.add(2) = pin_policy; offset += 3;
in_ptr = in_ptr.add(3);
}
} }
if touch_policy != YKPIV_TOUCHPOLICY_DEFAULT { if touch_policy != YKPIV_TOUCHPOLICY_DEFAULT {
unsafe { key_data[offset] = YKPIV_TOUCHPOLICY_TAG;
*in_ptr = YKPIV_TOUCHPOLICY_TAG; key_data[offset + 1] = 0x01;
*in_ptr.add(1) = 0x01; key_data[offset + 2] = touch_policy;
*in_ptr.add(2) = touch_policy; offset += 3;
in_ptr = in_ptr.add(3);
}
} }
let txn = self.begin_transaction()?; let txn = self.begin_transaction()?;
let len = in_ptr as usize - key_data.as_mut_ptr() as usize;
let status_words = txn let status_words = txn
.transfer_data(&templ, &key_data[..len], 256)? .transfer_data(&templ, &key_data[..offset], 256)?
.status_words(); .status_words();
match status_words { match status_words {
@@ -802,7 +728,7 @@ impl YubiKey {
/// <https://developers.yubico.com/PIV/Introduction/PIV_attestation.html> /// <https://developers.yubico.com/PIV/Introduction/PIV_attestation.html>
#[cfg(feature = "untested")] #[cfg(feature = "untested")]
pub fn attest(&mut self, key: SlotId) -> Result<Buffer, Error> { pub fn attest(&mut self, key: SlotId) -> Result<Buffer, Error> {
let templ = [0, Ins::Attest.code(), key, 0]; let templ = [0, Ins::Attest.code(), key.into(), 0];
let txn = self.begin_transaction()?; let txn = self.begin_transaction()?;
let response = txn.transfer_data(&templ, &[], CB_OBJ_MAX)?; let response = txn.transfer_data(&templ, &[], CB_OBJ_MAX)?;