mirror of
https://github.com/trussed-dev/piv-authenticator.git
synced 2026-06-20 04:16:15 -07:00
Merge branch 'put-data'
This commit is contained in:
@@ -0,0 +1,7 @@
|
||||
Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
|
||||
Upstream-Name: piv-authenticator
|
||||
Source: https://github.com/Nitrokey/piv-authenticator
|
||||
|
||||
Files: tests/default_admin_key
|
||||
Copyright: 2022 Nitrokey GmbH
|
||||
License: LGPL-3.0-only
|
||||
+29
-4
@@ -14,6 +14,11 @@ documentation = "https://docs.rs/piv-authenticator"
|
||||
name = "virtual"
|
||||
required-features = ["virtual"]
|
||||
|
||||
|
||||
[[example]]
|
||||
name = "usbip"
|
||||
required-features = ["apdu-dispatch"]
|
||||
|
||||
[dependencies]
|
||||
apdu-dispatch = { version = "0.1", optional = true }
|
||||
delog = { version = "0.1.5", optional = true }
|
||||
@@ -23,11 +28,13 @@ hex-literal = "0.3"
|
||||
interchange = "0.2.2"
|
||||
iso7816 = "0.1"
|
||||
serde = { version = "1", default-features = false, features = ["derive"] }
|
||||
trussed = "0.1"
|
||||
trussed = { version = "0.1" }
|
||||
untrusted = "0.9"
|
||||
vpicc = { version = "0.1.0", optional = true }
|
||||
log = "0.4"
|
||||
heapless-bytes = "0.3.0"
|
||||
subtle = { version = "2", default-features = false }
|
||||
trussed-rsa-alloc = { git = "https://github.com/Nitrokey/trussed-rsa-backend.git", rev = "e29b26ab3800217b7eb73ecde67134bbb3acb9da", features = ["raw"] }
|
||||
|
||||
[dev-dependencies]
|
||||
littlefs2 = "0.3.2"
|
||||
@@ -37,15 +44,25 @@ env_logger = "0.9"
|
||||
serde = { version = "1", features = ["derive"] }
|
||||
serde_cbor = { version = "0.11", features = ["std"] }
|
||||
hex = "0.4"
|
||||
test-log = "0.2"
|
||||
test-log = "0.2.11"
|
||||
ron = "0.8"
|
||||
des = "0.8"
|
||||
aes = "0.8.2"
|
||||
stoppable_thread = "0.2.1"
|
||||
expectrl = "0.6.0"
|
||||
|
||||
# Examples
|
||||
trussed-usbip = { git = "https://github.com/trussed-dev/pc-usbip-runner", default-features = false, features = ["ccid"], rev = "d2957b6c24c2b0cafbbfacd6fecd62c80943630b"}
|
||||
usbd-ccid = { version = "0.2.0", features = ["highspeed-usb"]}
|
||||
rand = "0.8.5"
|
||||
|
||||
[features]
|
||||
default = []
|
||||
strict-pin = []
|
||||
std = []
|
||||
virtual = ["std", "vpicc","trussed/virt"]
|
||||
virtual = ["std", "vpicc", "trussed-rsa-alloc/virt"]
|
||||
pivy-tests = []
|
||||
opensc-tests = []
|
||||
|
||||
log-all = []
|
||||
log-none = []
|
||||
@@ -55,4 +72,12 @@ log-warn = []
|
||||
log-error = []
|
||||
|
||||
[patch.crates-io]
|
||||
trussed = { git = "https://github.com/trussed-dev/trussed", rev = "28478f8abed11d78c51e6a6a32326821ed61957a"}
|
||||
# trussed = { git = "https://github.com/Nitrokey/trussed", tag = "v0.1.0-nitrokey-4"}
|
||||
trussed = { git = "https://github.com/trussed-dev/trussed", rev = "d9276a689d68ffeb4c5d9ac635b18232be172f45"}
|
||||
# littlefs2 = { git = "https://github.com/Nitrokey/littlefs2", tag = "v0.3.2-nitrokey-1" }
|
||||
|
||||
[profile.dev.package.rsa]
|
||||
opt-level = 2
|
||||
|
||||
[profile.dev.package.num-bigint-dig]
|
||||
opt-level = 2
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
.NOTPARALLEL:
|
||||
|
||||
export RUST_LOG ?= info,cargo_tarpaulin=off
|
||||
TEST_FEATURES ?=virtual,pivy-tests,opensc-tests
|
||||
|
||||
.PHONY: build-cortex-m4
|
||||
build-cortex-m4:
|
||||
@@ -11,24 +12,28 @@ build-cortex-m4:
|
||||
|
||||
.PHONY: test
|
||||
test:
|
||||
cargo test --features virtual
|
||||
cargo test --features $(TEST_FEATURES)
|
||||
|
||||
.PHONY: check
|
||||
check:
|
||||
RUSTLFAGS='-Dwarnings' cargo check --all-features --all-targets
|
||||
|
||||
.PHONY: lint
|
||||
lint:
|
||||
cargo fmt --check
|
||||
cargo check --all-targets --all-features
|
||||
RUSTLFAGS='-Dwarnings' cargo check --all-features --all-targets
|
||||
cargo clippy --all-targets --all-features -- -Dwarnings
|
||||
RUSTDOCFLAGS='-Dwarnings' cargo doc --all-features
|
||||
reuse lint
|
||||
|
||||
.PHONY: tarpaulin
|
||||
tarpaulin:
|
||||
cargo tarpaulin --features virtual -o Html -o Xml
|
||||
cargo tarpaulin --features $(TEST_FEATURES) -o Html -o Xml
|
||||
|
||||
.PHONY: example
|
||||
example:
|
||||
cargo run --example virtual --features virtual
|
||||
|
||||
.PHONY: ci
|
||||
ci: check tarpaulin
|
||||
ci: lint tarpaulin
|
||||
|
||||
|
||||
+11
-1
@@ -3,7 +3,15 @@
|
||||
|
||||
FROM docker.io/rust:latest
|
||||
|
||||
RUN apt update && apt install --yes scdaemon libclang-dev llvm python3-pip vsmartcard-vpcd pkg-config nettle-dev libpcsclite-dev
|
||||
RUN apt update && apt install --yes libpcsclite-dev \
|
||||
&& wget https://github.com/arekinath/pivy/releases/download/v0.10.0/pivy-0.10.0-src.tar.gz \
|
||||
&& tar xvf pivy-0.10.0-src.tar.gz \
|
||||
&& cd pivy-0.10.0 \
|
||||
&& make pivy-tool
|
||||
|
||||
FROM docker.io/rust:latest
|
||||
|
||||
RUN apt update && apt install --yes scdaemon libclang-dev llvm python3-pip vsmartcard-vpcd pkg-config nettle-dev libpcsclite-dev opensc
|
||||
|
||||
RUN python3 -m pip install reuse
|
||||
|
||||
@@ -14,6 +22,8 @@ RUN cargo search
|
||||
|
||||
ENV CARGO_HOME=/app/.cache/cargo
|
||||
|
||||
COPY --from=0 pivy-0.10.0/pivy-tool /bin/pivy-tool
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
COPY entrypoint.sh /entrypoint.sh
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
// Copyright (C) 2022 Nitrokey GmbH
|
||||
// SPDX-License-Identifier: CC0-1.0
|
||||
|
||||
use trussed::virt::{self, Ram, UserInterface};
|
||||
use trussed::{ClientImplementation, Platform};
|
||||
|
||||
use piv_authenticator as piv;
|
||||
use trussed_usbip::Syscall;
|
||||
|
||||
const MANUFACTURER: &str = "Nitrokey";
|
||||
const PRODUCT: &str = "Nitrokey 3";
|
||||
const VID: u16 = 0x20a0;
|
||||
const PID: u16 = 0x42b2;
|
||||
|
||||
struct PivApp {
|
||||
piv: piv::Authenticator<ClientImplementation<Syscall<virt::Platform<Ram>>>>,
|
||||
}
|
||||
|
||||
impl trussed_usbip::Apps<ClientImplementation<Syscall<virt::Platform<Ram>>>, ()> for PivApp {
|
||||
fn new(
|
||||
make_client: impl Fn(&str) -> ClientImplementation<Syscall<virt::Platform<Ram>>>,
|
||||
_data: (),
|
||||
) -> Self {
|
||||
PivApp {
|
||||
piv: piv::Authenticator::new(make_client("piv")),
|
||||
}
|
||||
}
|
||||
|
||||
fn with_ccid_apps<T>(
|
||||
&mut self,
|
||||
f: impl FnOnce(&mut [&mut dyn apdu_dispatch::App<7609, 7609>]) -> T,
|
||||
) -> T {
|
||||
f(&mut [&mut self.piv])
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
env_logger::init();
|
||||
|
||||
let options = trussed_usbip::Options {
|
||||
manufacturer: Some(MANUFACTURER.to_owned()),
|
||||
product: Some(PRODUCT.to_owned()),
|
||||
serial_number: Some("TEST".into()),
|
||||
vid: VID,
|
||||
pid: PID,
|
||||
};
|
||||
trussed_usbip::Runner::new(virt::Ram::default(), options)
|
||||
.init_platform(move |platform| {
|
||||
let ui: Box<dyn trussed::platform::UserInterface + Send + Sync> =
|
||||
Box::new(UserInterface::new());
|
||||
platform.user_interface().set_inner(ui);
|
||||
})
|
||||
.exec::<PivApp, _, _>(|_platform| {});
|
||||
}
|
||||
+1
-1
@@ -14,7 +14,7 @@
|
||||
fn main() {
|
||||
env_logger::init();
|
||||
|
||||
trussed::virt::with_ram_client("piv-authenticator", |client| {
|
||||
trussed_rsa_alloc::virt::with_ram_client("piv-authenticator", |client| {
|
||||
let card = piv_authenticator::Authenticator::new(client);
|
||||
let mut virtual_card = piv_authenticator::vpicc::VirtualCard::new(card);
|
||||
let vpicc = vpicc::connect().expect("failed to connect to vpicc");
|
||||
|
||||
+60
-23
@@ -11,11 +11,13 @@ use core::convert::{TryFrom, TryInto};
|
||||
// use flexiber::Decodable;
|
||||
use iso7816::{Instruction, Status};
|
||||
|
||||
use crate::container::{Container, KeyReference};
|
||||
|
||||
use crate::state::TouchPolicy;
|
||||
pub use crate::{
|
||||
container::{
|
||||
self as containers, AttestKeyReference, AuthenticateKeyReference,
|
||||
ChangeReferenceKeyReference, GenerateAsymmetricKeyReference, VerifyKeyReference,
|
||||
self as containers, AsymmetricKeyReference, AttestKeyReference, AuthenticateKeyReference,
|
||||
ChangeReferenceKeyReference, GenerateKeyReference, VerifyKeyReference,
|
||||
},
|
||||
piv_types, Pin, Puk,
|
||||
};
|
||||
@@ -30,7 +32,7 @@ pub enum YubicoPivExtension {
|
||||
SetPinRetries,
|
||||
Attest(AttestKeyReference),
|
||||
GetSerial, // also used via 0x01
|
||||
GetMetadata,
|
||||
GetMetadata(KeyReference),
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||
@@ -51,14 +53,14 @@ pub enum Command<'l> {
|
||||
/// Change PIN or PUK
|
||||
ChangeReference(ChangeReference),
|
||||
/// If the PIN is blocked, reset it using the PUK
|
||||
ResetPinRetries(ResetPinRetries),
|
||||
ResetRetryCounter(ResetRetryCounter),
|
||||
/// The most general purpose method, performing actual cryptographic operations
|
||||
///
|
||||
/// In particular, this can also decrypt or similar.
|
||||
GeneralAuthenticate(GeneralAuthenticate),
|
||||
/// Store a data object / container.
|
||||
PutData(PutData),
|
||||
GenerateAsymmetric(GenerateAsymmetricKeyReference),
|
||||
PutData(PutData<'l>),
|
||||
GenerateAsymmetric(GenerateKeyReference),
|
||||
|
||||
/* Yubico commands */
|
||||
YkExtension(YubicoPivExtension),
|
||||
@@ -66,8 +68,8 @@ pub enum Command<'l> {
|
||||
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||
pub struct GeneralAuthenticate {
|
||||
algorithm: piv_types::Algorithms,
|
||||
key_reference: AuthenticateKeyReference,
|
||||
pub algorithm: piv_types::Algorithms,
|
||||
pub key_reference: AuthenticateKeyReference,
|
||||
}
|
||||
|
||||
impl<'l> Command<'l> {
|
||||
@@ -111,8 +113,7 @@ impl TryFrom<&[u8]> for GetData {
|
||||
if tagged_slice.tag() != flexiber::Tag::application(0x1C) {
|
||||
return Err(Status::IncorrectDataParameter);
|
||||
}
|
||||
let container: containers::Container = containers::Tag::new(tagged_slice.as_bytes())
|
||||
.try_into()
|
||||
let container = containers::Container::try_from(tagged_slice.as_bytes())
|
||||
.map_err(|_| Status::IncorrectDataParameter)?;
|
||||
|
||||
info!("request to GetData for container {:?}", container);
|
||||
@@ -218,19 +219,19 @@ impl TryFrom<ChangeReferenceArguments<'_>> for ChangeReference {
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||
pub struct ResetPinRetries {
|
||||
pub padded_pin: [u8; 8],
|
||||
pub struct ResetRetryCounter {
|
||||
pub pin: [u8; 8],
|
||||
pub puk: [u8; 8],
|
||||
}
|
||||
|
||||
impl TryFrom<&[u8]> for ResetPinRetries {
|
||||
impl TryFrom<&[u8]> for ResetRetryCounter {
|
||||
type Error = Status;
|
||||
fn try_from(data: &[u8]) -> Result<Self, Self::Error> {
|
||||
if data.len() != 16 {
|
||||
return Err(Status::IncorrectDataParameter);
|
||||
}
|
||||
Ok(Self {
|
||||
padded_pin: data[..8].try_into().unwrap(),
|
||||
pin: data[..8].try_into().unwrap(),
|
||||
puk: data[8..].try_into().unwrap(),
|
||||
})
|
||||
}
|
||||
@@ -246,12 +247,48 @@ pub struct AuthenticateArguments<'l> {
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||
pub struct PutData {}
|
||||
pub enum PutData<'data> {
|
||||
DiscoveryObject(&'data [u8]),
|
||||
BitGroupTemplate(&'data [u8]),
|
||||
Any(Container, &'data [u8]),
|
||||
}
|
||||
|
||||
impl TryFrom<&[u8]> for PutData {
|
||||
impl<'data> TryFrom<&'data [u8]> for PutData<'data> {
|
||||
type Error = Status;
|
||||
fn try_from(_data: &[u8]) -> Result<Self, Self::Error> {
|
||||
todo!();
|
||||
fn try_from(data: &'data [u8]) -> Result<Self, Self::Error> {
|
||||
use crate::tlv::take_do;
|
||||
let (tag, inner, rem) = take_do(data).ok_or_else(|| {
|
||||
warn!("Failed to parse PUT DATA: {:02x?}", data);
|
||||
Status::IncorrectDataParameter
|
||||
})?;
|
||||
if matches!(tag, 0x7E | 0x7F61) && !rem.is_empty() {
|
||||
warn!("Empty remainder expected, got: {:02x?}", rem);
|
||||
}
|
||||
|
||||
let container: Container = match tag {
|
||||
0x7E => return Ok(PutData::DiscoveryObject(inner)),
|
||||
0x7F61 => return Ok(PutData::BitGroupTemplate(inner)),
|
||||
0x5C => Container::try_from(inner).map_err(|_| Status::IncorrectDataParameter)?,
|
||||
_ => return Err(Status::IncorrectDataParameter),
|
||||
};
|
||||
|
||||
let (tag, inner, rem) = take_do(rem).ok_or_else(|| {
|
||||
warn!(
|
||||
"Failed to parse PUT DATA's second field: {:02x?}, {:02x?}",
|
||||
data, rem
|
||||
);
|
||||
Status::IncorrectDataParameter
|
||||
})?;
|
||||
|
||||
if !rem.is_empty() {
|
||||
warn!("Empty second remainder expected, got: {:02x?}", rem);
|
||||
}
|
||||
|
||||
if tag != 0x53 {
|
||||
warn!("Expected 0x53 tag, got: 0x{:02x?}", rem);
|
||||
}
|
||||
|
||||
Ok(PutData::Any(container, inner))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -310,7 +347,7 @@ impl<'l, const C: usize> TryFrom<&'l iso7816::Command<C>> for Command<'l> {
|
||||
}
|
||||
|
||||
(0x00, Instruction::ResetRetryCounter, 0x00, 0x80) => {
|
||||
Self::ResetPinRetries(ResetPinRetries::try_from(data.as_slice())?)
|
||||
Self::ResetRetryCounter(ResetRetryCounter::try_from(data.as_slice())?)
|
||||
}
|
||||
|
||||
(0x00, Instruction::GeneralAuthenticate, p1, p2) => {
|
||||
@@ -327,7 +364,7 @@ impl<'l, const C: usize> TryFrom<&'l iso7816::Command<C>> for Command<'l> {
|
||||
}
|
||||
|
||||
(0x00, Instruction::GenerateAsymmetricKeyPair, 0x00, p2) => {
|
||||
Self::GenerateAsymmetric(GenerateAsymmetricKeyReference::try_from(p2)?)
|
||||
Self::GenerateAsymmetric(GenerateKeyReference::try_from(p2)?)
|
||||
}
|
||||
// (0x00, 0x01, 0x10, 0x00)
|
||||
(0x00, Instruction::Unknown(0x01), 0x00, 0x00) => {
|
||||
@@ -359,9 +396,9 @@ impl<'l, const C: usize> TryFrom<&'l iso7816::Command<C>> for Command<'l> {
|
||||
(0x00, Instruction::Unknown(0xf8), _, _) => {
|
||||
Self::YkExtension(YubicoPivExtension::GetSerial)
|
||||
}
|
||||
(0x00, Instruction::Unknown(0xf7), _, _) => {
|
||||
Self::YkExtension(YubicoPivExtension::GetMetadata)
|
||||
}
|
||||
(0x00, Instruction::Unknown(0xf7), 0x00, reference) => Self::YkExtension(
|
||||
YubicoPivExtension::GetMetadata(KeyReference::try_from(reference)?),
|
||||
),
|
||||
|
||||
_ => return Err(Status::FunctionNotSupported),
|
||||
})
|
||||
|
||||
+36
-2
@@ -5,6 +5,8 @@
|
||||
|
||||
use hex_literal::hex;
|
||||
|
||||
use crate::state::AdministrationAlgorithm;
|
||||
|
||||
pub const RID_LENGTH: usize = 5;
|
||||
|
||||
// top nibble of first byte is "category", here "A" = International
|
||||
@@ -269,6 +271,38 @@ pub const YUBICO_DEFAULT_MANAGEMENT_KEY: &[u8; 24] = &hex!(
|
||||
"
|
||||
);
|
||||
|
||||
pub const YUBICO_DEFAULT_MANAGEMENT_KEY_ALG: AdministrationAlgorithm =
|
||||
AdministrationAlgorithm::Tdes;
|
||||
|
||||
// stolen from le yubico
|
||||
pub const DISCOVERY_OBJECT: &[u8; 20] =
|
||||
b"~\x12O\x0b\xa0\x00\x00\x03\x08\x00\x00\x10\x00\x01\x00_/\x02@\x00";
|
||||
pub const DISCOVERY_OBJECT: [u8; 18] = hex!(
|
||||
"
|
||||
4f 0b // PIV AID
|
||||
a000000308000010000100
|
||||
5f2f 02 // PIN usage Policy
|
||||
4000"
|
||||
);
|
||||
|
||||
use crate::Container;
|
||||
pub const RETIRED_CERTS: [Container; 20] = [
|
||||
Container::RetiredCert01,
|
||||
Container::RetiredCert02,
|
||||
Container::RetiredCert03,
|
||||
Container::RetiredCert04,
|
||||
Container::RetiredCert05,
|
||||
Container::RetiredCert06,
|
||||
Container::RetiredCert07,
|
||||
Container::RetiredCert08,
|
||||
Container::RetiredCert09,
|
||||
Container::RetiredCert10,
|
||||
Container::RetiredCert11,
|
||||
Container::RetiredCert12,
|
||||
Container::RetiredCert13,
|
||||
Container::RetiredCert14,
|
||||
Container::RetiredCert15,
|
||||
Container::RetiredCert16,
|
||||
Container::RetiredCert17,
|
||||
Container::RetiredCert18,
|
||||
Container::RetiredCert19,
|
||||
Container::RetiredCert20,
|
||||
];
|
||||
|
||||
+195
-104
@@ -7,6 +7,7 @@ use hex_literal::hex;
|
||||
|
||||
macro_rules! enum_subset {
|
||||
(
|
||||
|
||||
$(#[$outer:meta])*
|
||||
$vis:vis enum $name:ident: $sup:ident {
|
||||
$($var:ident),+
|
||||
@@ -15,6 +16,7 @@ macro_rules! enum_subset {
|
||||
) => {
|
||||
$(#[$outer])*
|
||||
#[repr(u8)]
|
||||
#[derive(Clone, Copy)]
|
||||
$vis enum $name {
|
||||
$(
|
||||
$var,
|
||||
@@ -45,6 +47,19 @@ macro_rules! enum_subset {
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Copy + Into<$sup>> PartialEq<T> for $name {
|
||||
fn eq(&self, other: &T) -> bool {
|
||||
match (self,(*other).into()) {
|
||||
$(
|
||||
| ($name::$var, $sup::$var)
|
||||
)* => true,
|
||||
_ => false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Eq for $name {}
|
||||
|
||||
impl TryFrom<u8> for $name {
|
||||
type Error = ::iso7816::Status;
|
||||
fn try_from(tag: u8) -> ::core::result::Result<Self, Self::Error> {
|
||||
@@ -60,18 +75,19 @@ macro_rules! enum_subset {
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Tag<'a>(&'a [u8]);
|
||||
impl<'a> Tag<'a> {
|
||||
pub fn new(slice: &'a [u8]) -> Self {
|
||||
Self(slice)
|
||||
}
|
||||
pub(crate) use enum_subset;
|
||||
|
||||
/// Security condition for the use of a given key.
|
||||
pub enum SecurityCondition {
|
||||
Pin,
|
||||
Always,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||
pub struct RetiredIndex(u8);
|
||||
|
||||
crate::enum_u8! {
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||
#[derive(Debug)]
|
||||
pub enum KeyReference {
|
||||
GlobalPin = 0x00,
|
||||
SecureMessaging = 0x04,
|
||||
@@ -110,17 +126,73 @@ crate::enum_u8! {
|
||||
}
|
||||
}
|
||||
|
||||
impl KeyReference {
|
||||
pub fn use_security_condition(self) -> SecurityCondition {
|
||||
match self {
|
||||
Self::SecureMessaging
|
||||
| Self::PivCardApplicationAdministration
|
||||
| Self::KeyManagement => SecurityCondition::Always,
|
||||
_ => SecurityCondition::Pin,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! impl_use_security_condition {
|
||||
($($name:ident),*) => {
|
||||
$(
|
||||
impl $name {
|
||||
pub fn use_security_condition(self) -> SecurityCondition {
|
||||
let tmp: KeyReference = self.into();
|
||||
tmp.use_security_condition()
|
||||
}
|
||||
}
|
||||
)*
|
||||
};
|
||||
}
|
||||
|
||||
enum_subset! {
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||
#[derive(Debug)]
|
||||
pub enum AttestKeyReference: KeyReference {
|
||||
PivAuthentication,
|
||||
}
|
||||
}
|
||||
|
||||
enum_subset! {
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||
pub enum GenerateAsymmetricKeyReference: KeyReference {
|
||||
SecureMessaging,
|
||||
#[derive(Debug)]
|
||||
pub enum AsymmetricKeyReference: KeyReference {
|
||||
// SecureMessaging,
|
||||
PivAuthentication,
|
||||
DigitalSignature,
|
||||
KeyManagement,
|
||||
CardAuthentication,
|
||||
Retired01,
|
||||
Retired02,
|
||||
Retired03,
|
||||
Retired04,
|
||||
Retired05,
|
||||
Retired06,
|
||||
Retired07,
|
||||
Retired08,
|
||||
Retired09,
|
||||
Retired10,
|
||||
Retired11,
|
||||
Retired12,
|
||||
Retired13,
|
||||
Retired14,
|
||||
Retired15,
|
||||
Retired16,
|
||||
Retired17,
|
||||
Retired18,
|
||||
Retired19,
|
||||
Retired20,
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
enum_subset! {
|
||||
#[derive(Debug)]
|
||||
pub enum GenerateKeyReference: AsymmetricKeyReference {
|
||||
// SecureMessaging,
|
||||
PivAuthentication,
|
||||
DigitalSignature,
|
||||
KeyManagement,
|
||||
@@ -129,7 +201,7 @@ enum_subset! {
|
||||
}
|
||||
|
||||
enum_subset! {
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||
#[derive(Debug)]
|
||||
pub enum ChangeReferenceKeyReference: KeyReference {
|
||||
GlobalPin,
|
||||
ApplicationPin,
|
||||
@@ -138,7 +210,7 @@ enum_subset! {
|
||||
}
|
||||
|
||||
enum_subset! {
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||
#[derive(Debug)]
|
||||
pub enum VerifyKeyReference: KeyReference {
|
||||
GlobalPin,
|
||||
ApplicationPin,
|
||||
@@ -151,7 +223,7 @@ enum_subset! {
|
||||
|
||||
enum_subset! {
|
||||
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||
#[derive(Debug)]
|
||||
pub enum AuthenticateKeyReference: KeyReference {
|
||||
SecureMessaging,
|
||||
PivAuthentication,
|
||||
@@ -182,11 +254,38 @@ enum_subset! {
|
||||
}
|
||||
}
|
||||
|
||||
impl_use_security_condition!(
|
||||
AttestKeyReference,
|
||||
AsymmetricKeyReference,
|
||||
ChangeReferenceKeyReference,
|
||||
VerifyKeyReference,
|
||||
AuthenticateKeyReference
|
||||
);
|
||||
|
||||
macro_rules! impl_try_from {
|
||||
($(($left:ident, $right:ident)),*) => {
|
||||
$(
|
||||
impl TryFrom<$left> for $right {
|
||||
type Error = ::iso7816::Status;
|
||||
fn try_from(val: $left) -> Result<Self,Self::Error> {
|
||||
let tmp = KeyReference::from(val);
|
||||
tmp.try_into()
|
||||
}
|
||||
|
||||
}
|
||||
)*
|
||||
};
|
||||
}
|
||||
|
||||
impl_try_from!((AuthenticateKeyReference, AsymmetricKeyReference));
|
||||
|
||||
/// The 36 data objects defined by PIV (SP 800-37-4, Part 1).
|
||||
///
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||
pub enum Container {
|
||||
// static
|
||||
CardCapabilityContainer,
|
||||
// generated at card creation
|
||||
CardHolderUniqueIdentifier,
|
||||
X509CertificateFor9A,
|
||||
CardholderFingerprints,
|
||||
@@ -198,41 +297,32 @@ pub enum Container {
|
||||
PrintedInformation,
|
||||
DiscoveryObject,
|
||||
KeyHistoryObject,
|
||||
RetiredX509Certificate(RetiredIndex),
|
||||
|
||||
RetiredCert01,
|
||||
RetiredCert02,
|
||||
RetiredCert03,
|
||||
RetiredCert04,
|
||||
RetiredCert05,
|
||||
RetiredCert06,
|
||||
RetiredCert07,
|
||||
RetiredCert08,
|
||||
RetiredCert09,
|
||||
RetiredCert10,
|
||||
RetiredCert11,
|
||||
RetiredCert12,
|
||||
RetiredCert13,
|
||||
RetiredCert14,
|
||||
RetiredCert15,
|
||||
RetiredCert16,
|
||||
RetiredCert17,
|
||||
RetiredCert18,
|
||||
RetiredCert19,
|
||||
RetiredCert20,
|
||||
CardholderIrisImages,
|
||||
BiometricInformationTemplatesGroupTemplate,
|
||||
SecureMessagingCertificateSigner,
|
||||
PairingCodeReferenceDataContainer,
|
||||
}
|
||||
|
||||
pub struct ContainerId(u16);
|
||||
|
||||
impl From<Container> for ContainerId {
|
||||
fn from(container: Container) -> Self {
|
||||
use Container::*;
|
||||
Self(match container {
|
||||
CardCapabilityContainer => 0xDB00,
|
||||
CardHolderUniqueIdentifier => 0x3000,
|
||||
X509CertificateFor9A => 0x0101,
|
||||
CardholderFingerprints => 0x6010,
|
||||
SecurityObject => 0x9000,
|
||||
CardholderFacialImage => 0x6030,
|
||||
X509CertificateFor9E => 0x0500,
|
||||
X509CertificateFor9C => 0x0100,
|
||||
X509CertificateFor9D => 0x0102,
|
||||
PrintedInformation => 0x3001,
|
||||
DiscoveryObject => 0x6050,
|
||||
KeyHistoryObject => 0x6060,
|
||||
RetiredX509Certificate(RetiredIndex(i)) => 0x1000u16 + i as u16,
|
||||
CardholderIrisImages => 0x1015,
|
||||
BiometricInformationTemplatesGroupTemplate => 0x1016,
|
||||
SecureMessagingCertificateSigner => 0x1017,
|
||||
PairingCodeReferenceDataContainer => 0x1018,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// these are just the "contact" rules, need to model "contactless" also
|
||||
pub enum ReadAccessRule {
|
||||
Always,
|
||||
@@ -240,46 +330,46 @@ pub enum ReadAccessRule {
|
||||
PinOrOcc,
|
||||
}
|
||||
|
||||
// impl Container {
|
||||
// const fn minimum_capacity(self) -> usize {
|
||||
// use Container::*;
|
||||
// match self {
|
||||
// CardCapabilityContainer => 287,
|
||||
// CardHolderUniqueIdentifier => 2916,
|
||||
// CardholderFingerprints => 4006,
|
||||
// SecurityObject => 1336,
|
||||
// CardholderFacialImage => 12710,
|
||||
// PrintedInformation => 245,
|
||||
// DiscoveryObject => 19,
|
||||
// KeyHistoryObject => 128,
|
||||
// CardholderIrisImages => 7106,
|
||||
// BiometricInformationTemplate => 65,
|
||||
// SecureMessagingCertificateSigner => 2471,
|
||||
// PairingCodeReferenceDataContainer => 12,
|
||||
// // the others are X509 certificates
|
||||
// _ => 1905,
|
||||
// }
|
||||
// }
|
||||
impl Container {
|
||||
// const fn minimum_capacity(self) -> usize {
|
||||
// use Container::*;
|
||||
// match self {
|
||||
// CardCapabilityContainer => 287,
|
||||
// CardHolderUniqueIdentifier => 2916,
|
||||
// CardholderFingerprints => 4006,
|
||||
// SecurityObject => 1336,
|
||||
// CardholderFacialImage => 12710,
|
||||
// PrintedInformation => 245,
|
||||
// DiscoveryObject => 19,
|
||||
// KeyHistoryObject => 128,
|
||||
// CardholderIrisImages => 7106,
|
||||
// BiometricInformationTemplate => 65,
|
||||
// SecureMessagingCertificateSigner => 2471,
|
||||
// PairingCodeReferenceDataContainer => 12,
|
||||
// // the others are X509 certificates
|
||||
// _ => 1905,
|
||||
// }
|
||||
// }
|
||||
|
||||
// const fn contact_access_rule(self) -> {
|
||||
// use Container::*;
|
||||
// use ReadAccessRule::*;
|
||||
// match self {
|
||||
// CardholderFingerprints => Pin,
|
||||
// CardholderFacialImage => Pin,
|
||||
// PrintedInformation => PinOrOcc,
|
||||
// CardholderIrisImages => Pin,
|
||||
// PairingCodeReferenceDataContainer => PinOrOcc,
|
||||
// _ => Always,
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
impl TryFrom<Tag<'_>> for Container {
|
||||
type Error = ();
|
||||
fn try_from(tag: Tag<'_>) -> Result<Self, ()> {
|
||||
pub const fn contact_access_rule(self) -> ReadAccessRule {
|
||||
use Container::*;
|
||||
Ok(match tag.0 {
|
||||
use ReadAccessRule::*;
|
||||
match self {
|
||||
CardholderFingerprints => Pin,
|
||||
CardholderFacialImage => Pin,
|
||||
PrintedInformation => PinOrOcc,
|
||||
CardholderIrisImages => Pin,
|
||||
PairingCodeReferenceDataContainer => PinOrOcc,
|
||||
_ => Always,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<&[u8]> for Container {
|
||||
type Error = ();
|
||||
fn try_from(tag: &[u8]) -> Result<Self, ()> {
|
||||
use Container::*;
|
||||
Ok(match tag {
|
||||
hex!("5FC107") => CardCapabilityContainer,
|
||||
hex!("5FC102") => CardHolderUniqueIdentifier,
|
||||
hex!("5FC105") => X509CertificateFor9A,
|
||||
@@ -287,36 +377,37 @@ impl TryFrom<Tag<'_>> for Container {
|
||||
hex!("5FC106") => SecurityObject,
|
||||
hex!("5FC108") => CardholderFacialImage,
|
||||
hex!("5FC101") => X509CertificateFor9E,
|
||||
hex!("5FC109") => PrintedInformation,
|
||||
hex!("5FC10A") => X509CertificateFor9C,
|
||||
hex!("5FC10B") => X509CertificateFor9D,
|
||||
hex!("5FC109") => PrintedInformation,
|
||||
hex!("7E") => DiscoveryObject,
|
||||
|
||||
hex!("5FC10D") => RetiredX509Certificate(RetiredIndex(1)),
|
||||
hex!("5FC10E") => RetiredX509Certificate(RetiredIndex(2)),
|
||||
hex!("5FC10F") => RetiredX509Certificate(RetiredIndex(3)),
|
||||
hex!("5FC110") => RetiredX509Certificate(RetiredIndex(4)),
|
||||
hex!("5FC111") => RetiredX509Certificate(RetiredIndex(5)),
|
||||
hex!("5FC112") => RetiredX509Certificate(RetiredIndex(6)),
|
||||
hex!("5FC113") => RetiredX509Certificate(RetiredIndex(7)),
|
||||
hex!("5FC114") => RetiredX509Certificate(RetiredIndex(8)),
|
||||
hex!("5FC115") => RetiredX509Certificate(RetiredIndex(9)),
|
||||
hex!("5FC116") => RetiredX509Certificate(RetiredIndex(10)),
|
||||
hex!("5FC117") => RetiredX509Certificate(RetiredIndex(11)),
|
||||
hex!("5FC118") => RetiredX509Certificate(RetiredIndex(12)),
|
||||
hex!("5FC119") => RetiredX509Certificate(RetiredIndex(13)),
|
||||
hex!("5FC11A") => RetiredX509Certificate(RetiredIndex(14)),
|
||||
hex!("5FC11B") => RetiredX509Certificate(RetiredIndex(15)),
|
||||
hex!("5FC11C") => RetiredX509Certificate(RetiredIndex(16)),
|
||||
hex!("5FC11D") => RetiredX509Certificate(RetiredIndex(17)),
|
||||
hex!("5FC11E") => RetiredX509Certificate(RetiredIndex(18)),
|
||||
hex!("5FC11F") => RetiredX509Certificate(RetiredIndex(19)),
|
||||
hex!("5FC120") => RetiredX509Certificate(RetiredIndex(20)),
|
||||
hex!("5FC10C") => KeyHistoryObject,
|
||||
hex!("5FC10D") => RetiredCert01,
|
||||
hex!("5FC10E") => RetiredCert02,
|
||||
hex!("5FC10F") => RetiredCert03,
|
||||
hex!("5FC110") => RetiredCert04,
|
||||
hex!("5FC111") => RetiredCert05,
|
||||
hex!("5FC112") => RetiredCert06,
|
||||
hex!("5FC113") => RetiredCert07,
|
||||
hex!("5FC114") => RetiredCert08,
|
||||
hex!("5FC115") => RetiredCert09,
|
||||
hex!("5FC116") => RetiredCert10,
|
||||
hex!("5FC117") => RetiredCert11,
|
||||
hex!("5FC118") => RetiredCert12,
|
||||
hex!("5FC119") => RetiredCert13,
|
||||
hex!("5FC11A") => RetiredCert14,
|
||||
hex!("5FC11B") => RetiredCert15,
|
||||
hex!("5FC11C") => RetiredCert16,
|
||||
hex!("5FC11D") => RetiredCert17,
|
||||
hex!("5FC11E") => RetiredCert18,
|
||||
hex!("5FC11F") => RetiredCert19,
|
||||
hex!("5FC120") => RetiredCert20,
|
||||
|
||||
hex!("5FC121") => CardholderIrisImages,
|
||||
hex!("7F61") => BiometricInformationTemplatesGroupTemplate,
|
||||
hex!("5FC122") => SecureMessagingCertificateSigner,
|
||||
hex!("5FC123") => PairingCodeReferenceDataContainer,
|
||||
|
||||
hex!("7E") => DiscoveryObject,
|
||||
hex!("7F61") => BiometricInformationTemplatesGroupTemplate,
|
||||
_ => return Err(()),
|
||||
})
|
||||
}
|
||||
|
||||
+11
-5
@@ -23,12 +23,18 @@ impl From<untrusted::EndOfInput> for Error {
|
||||
}
|
||||
|
||||
/// Return the value of the given tag and apply a decoding function to it.
|
||||
pub fn nested<'a, F, R>(input: &mut Reader<'a>, tag: u8, decoder: F) -> Result<R>
|
||||
pub fn nested<'a, F, R, E>(
|
||||
input: &mut Reader<'a>,
|
||||
incomplete_end: E,
|
||||
bad_tag: E,
|
||||
tag: u8,
|
||||
decoder: F,
|
||||
) -> core::result::Result<R, E>
|
||||
where
|
||||
F: FnOnce(&mut untrusted::Reader<'a>) -> Result<R>,
|
||||
F: FnOnce(&mut untrusted::Reader<'a>) -> core::result::Result<R, E>,
|
||||
{
|
||||
let inner = expect_tag_and_get_value(input, tag)?;
|
||||
inner.read_all(Error::Read, decoder)
|
||||
let inner = expect_tag_and_get_value(input, tag).map_err(|_| bad_tag)?;
|
||||
inner.read_all(incomplete_end, decoder)
|
||||
}
|
||||
|
||||
/// Read a tag and return it's value. Errors when the expect and actual tag do not match.
|
||||
@@ -41,7 +47,7 @@ pub fn expect_tag_and_get_value<'a>(input: &mut Reader<'a>, tag: u8) -> Result<I
|
||||
}
|
||||
|
||||
/// Read a tag and its value. Errors when the expected and actual tag and values do not match.
|
||||
pub fn expect_tag_and_value<'a>(input: &mut Reader<'a>, tag: u8, value: &[u8]) -> Result<()> {
|
||||
pub fn expect_tag_and_value(input: &mut Reader, tag: u8, value: &[u8]) -> Result<()> {
|
||||
let (actual_tag, inner) = read_tag_and_get_value(input)?;
|
||||
if usize::from(tag) != usize::from(actual_tag) {
|
||||
return Err(Error::WrongTag);
|
||||
|
||||
+3
-3
@@ -1,7 +1,7 @@
|
||||
// Copyright (C) 2022 Nicolas Stalder AND Nitrokey GmbH
|
||||
// SPDX-License-Identifier: LGPL-3.0-only
|
||||
|
||||
use crate::{Authenticator, /*constants::PIV_AID,*/ Result};
|
||||
use crate::{reply::Reply, Authenticator, /*constants::PIV_AID,*/ Result};
|
||||
|
||||
use apdu_dispatch::{app::App, command, response, Command};
|
||||
use trussed::client;
|
||||
@@ -12,7 +12,7 @@ where
|
||||
T: client::Client + client::Ed255 + client::Tdes,
|
||||
{
|
||||
fn select(&mut self, _apdu: &Command, reply: &mut response::Data) -> Result {
|
||||
self.select(reply)
|
||||
self.select(Reply(reply))
|
||||
}
|
||||
|
||||
fn deselect(&mut self) {
|
||||
@@ -25,6 +25,6 @@ where
|
||||
apdu: &Command,
|
||||
reply: &mut response::Data,
|
||||
) -> Result {
|
||||
self.respond(apdu, reply)
|
||||
self.respond(apdu, &mut Reply(reply))
|
||||
}
|
||||
}
|
||||
|
||||
+609
-335
File diff suppressed because it is too large
Load Diff
+171
-81
@@ -6,6 +6,7 @@ use core::convert::{TryFrom, TryInto};
|
||||
use flexiber::Encodable;
|
||||
use hex_literal::hex;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use trussed::types::Mechanism;
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! enum_u8 {
|
||||
@@ -18,6 +19,7 @@ macro_rules! enum_u8 {
|
||||
) => {
|
||||
$(#[$outer])*
|
||||
#[repr(u8)]
|
||||
#[derive(Clone, Copy)]
|
||||
$vis enum $name {
|
||||
$(
|
||||
$var = $num,
|
||||
@@ -35,6 +37,23 @@ macro_rules! enum_u8 {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq<u8> for $name {
|
||||
fn eq(&self, other: &u8) -> bool {
|
||||
*self as u8 == *other
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Into<$name> + Copy> PartialEq<T> for $name {
|
||||
fn eq(&self, other: &T) -> bool {
|
||||
let other: $name = (*other).into();
|
||||
matches!((self,other), $(
|
||||
| ($name::$var, $name::$var)
|
||||
)*)
|
||||
}
|
||||
}
|
||||
|
||||
impl Eq for $name {}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -42,35 +61,12 @@ macro_rules! enum_u8 {
|
||||
///
|
||||
/// We are more lenient, and allow ASCII 0x20..=0x7E.
|
||||
#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
|
||||
pub struct Pin {
|
||||
padded_pin: [u8; 8],
|
||||
len: usize,
|
||||
}
|
||||
pub struct Pin(pub [u8; 8]);
|
||||
|
||||
impl TryFrom<&[u8]> for Pin {
|
||||
type Error = ();
|
||||
fn try_from(padded_pin: &[u8]) -> Result<Self, Self::Error> {
|
||||
let padded_pin: [u8; 8] = padded_pin.try_into().map_err(|_| ())?;
|
||||
let first_pad_byte = padded_pin[..8].iter().position(|&b| b == 0xff);
|
||||
let unpadded_pin = match first_pad_byte {
|
||||
Some(l) => &padded_pin[..l],
|
||||
None => &padded_pin,
|
||||
};
|
||||
match unpadded_pin.len() {
|
||||
len @ 6..=8 => {
|
||||
let verifier = if cfg!(feature = "strict-pin") {
|
||||
|&byte| (b'0'..=b'9').contains(&byte)
|
||||
} else {
|
||||
|&byte| (32..=127).contains(&byte)
|
||||
};
|
||||
if unpadded_pin.iter().all(verifier) {
|
||||
Ok(Pin { padded_pin, len })
|
||||
} else {
|
||||
Err(())
|
||||
}
|
||||
}
|
||||
_ => Err(()),
|
||||
}
|
||||
Ok(Self(padded_pin.try_into().map_err(|_| ())?))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -86,7 +82,7 @@ impl TryFrom<&[u8]> for Puk {
|
||||
}
|
||||
|
||||
enum_u8! {
|
||||
#[derive(Clone, Copy, Eq, PartialEq, Debug)]
|
||||
#[derive(Debug,Deserialize,Serialize)]
|
||||
// As additional reference, see:
|
||||
// https://globalplatform.org/wp-content/uploads/2014/03/GPC_ISO_Framework_v1.0.pdf#page=15
|
||||
//
|
||||
@@ -123,6 +119,101 @@ enum_u8! {
|
||||
P384Sha384 = 0xF4,
|
||||
}
|
||||
}
|
||||
|
||||
crate::container::enum_subset! {
|
||||
#[derive(Debug,Deserialize,Serialize)]
|
||||
pub enum AsymmetricAlgorithms: Algorithms {
|
||||
Rsa2048,
|
||||
Rsa4096,
|
||||
P256,
|
||||
|
||||
// Not supported
|
||||
// Rsa1024 = 0x6,
|
||||
// Rsa3072 = 0xE0,
|
||||
// P384 = 0x14,
|
||||
// P521 = 0x15,
|
||||
|
||||
// non-standard! in piv-go though!
|
||||
// Ed255_prev = 0x22,
|
||||
// https://globalplatform.org/wp-content/uploads/2014/03/GPC_ISO_Framework_v1.0.pdf#page=15
|
||||
// non-standard!
|
||||
// Ed25519 = 0xE2,
|
||||
// X25519 = 0xE3,
|
||||
// Ed448 = 0xE4,
|
||||
// X448 = 0xE5,
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
impl AsymmetricAlgorithms {
|
||||
pub fn key_mechanism(self) -> Mechanism {
|
||||
match self {
|
||||
Self::Rsa2048 => Mechanism::Rsa2048Raw,
|
||||
Self::Rsa4096 => Mechanism::Rsa4096Raw,
|
||||
Self::P256 => Mechanism::P256,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn ecdh_mechanism(self) -> Option<Mechanism> {
|
||||
use AsymmetricAlgorithms::*;
|
||||
match self {
|
||||
P256 => Some(Mechanism::P256),
|
||||
/* P384 | P521 | X25519 | X448 */
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn sign_mechanism(self) -> Mechanism {
|
||||
match self {
|
||||
Self::Rsa2048 => Mechanism::Rsa2048Raw,
|
||||
Self::Rsa4096 => Mechanism::Rsa4096Raw,
|
||||
Self::P256 => Mechanism::P256Prehashed,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_rsa(self) -> bool {
|
||||
use AsymmetricAlgorithms::*;
|
||||
matches!(self, Rsa2048 | Rsa4096)
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! impl_use_try_into {
|
||||
($sup:ident => {$(($from:ident, $into:ident)),*}) => {
|
||||
$(
|
||||
impl TryFrom<$from> for $into {
|
||||
type Error = iso7816::Status;
|
||||
fn try_from(v: $from) -> core::result::Result<$into, iso7816::Status> {
|
||||
let sup: $sup = v.into();
|
||||
sup.try_into()
|
||||
}
|
||||
}
|
||||
)*
|
||||
};
|
||||
}
|
||||
|
||||
crate::container::enum_subset! {
|
||||
#[derive(Debug,Deserialize,Serialize)]
|
||||
pub enum RsaAlgorithms: Algorithms {
|
||||
Rsa2048,
|
||||
Rsa4096,
|
||||
}
|
||||
}
|
||||
|
||||
impl RsaAlgorithms {
|
||||
pub fn mechanism(self) -> Mechanism {
|
||||
match self {
|
||||
Self::Rsa2048 => Mechanism::Rsa2048Raw,
|
||||
Self::Rsa4096 => Mechanism::Rsa4096Raw,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl_use_try_into!(
|
||||
Algorithms => {
|
||||
(AsymmetricAlgorithms, RsaAlgorithms)
|
||||
}
|
||||
);
|
||||
|
||||
/// TODO:
|
||||
#[derive(Clone, Copy, Default, Eq, PartialEq)]
|
||||
pub struct CryptographicAlgorithmTemplate<'a> {
|
||||
@@ -236,65 +327,64 @@ impl<'a> ApplicationPropertyTemplate<'a> {
|
||||
}
|
||||
}
|
||||
}
|
||||
// /// TODO: This should be an enum of sorts, maybe.
|
||||
// ///
|
||||
// /// The data objects that appear in the dynamic authentication template (tag '7C') in the data field
|
||||
// /// of the GENERAL AUTHENTICATE card command depend on the authentication protocol being executed.
|
||||
// ///
|
||||
// /// Note that the empty tags (i.e., tags with no data) return the same tag with content
|
||||
// /// (they can be seen as “requests for requests”):
|
||||
// /// - '80 00' Returns '80 TL \<encrypted random\>' (as per definition)
|
||||
// /// - '81 00' Returns '81 TL \<random\>' (as per external authenticate example)
|
||||
// #[derive(Clone, Copy, Default, Encodable, Eq, PartialEq)]
|
||||
// #[tlv(application, constructed, number = "0x1C")] // = 0x7C
|
||||
// pub struct DynamicAuthenticationTemplate<'l> {
|
||||
// /// The Witness (tag '80') contains encrypted data (unrevealed fact).
|
||||
// /// This data is decrypted by the card.
|
||||
// #[tlv(simple = "0x80")]
|
||||
// witness: Option<&'l [u8]>,
|
||||
|
||||
/// TODO: This should be an enum of sorts, maybe.
|
||||
///
|
||||
/// The data objects that appear in the dynamic authentication template (tag '7C') in the data field
|
||||
/// of the GENERAL AUTHENTICATE card command depend on the authentication protocol being executed.
|
||||
///
|
||||
/// Note that the empty tags (i.e., tags with no data) return the same tag with content
|
||||
/// (they can be seen as “requests for requests”):
|
||||
/// - '80 00' Returns '80 TL <encrypted random>' (as per definition)
|
||||
/// - '81 00' Returns '81 TL <random>' (as per external authenticate example)
|
||||
#[derive(Clone, Copy, Default, Encodable, Eq, PartialEq)]
|
||||
#[tlv(application, constructed, number = "0x1C")] // = 0x7C
|
||||
pub struct DynamicAuthenticationTemplate<'l> {
|
||||
/// The Witness (tag '80') contains encrypted data (unrevealed fact).
|
||||
/// This data is decrypted by the card.
|
||||
#[tlv(simple = "0x80")]
|
||||
witness: Option<&'l [u8]>,
|
||||
// /// The Challenge (tag '81') contains clear data (byte sequence),
|
||||
// /// which is encrypted by the card.
|
||||
// #[tlv(simple = "0x81")]
|
||||
// challenge: Option<&'l [u8]>,
|
||||
|
||||
/// The Challenge (tag '81') contains clear data (byte sequence),
|
||||
/// which is encrypted by the card.
|
||||
#[tlv(simple = "0x81")]
|
||||
challenge: Option<&'l [u8]>,
|
||||
// /// The Response (tag '82') contains either the decrypted data from tag '80'
|
||||
// /// or the encrypted data from tag '81'.
|
||||
// #[tlv(simple = "0x82")]
|
||||
// response: Option<&'l [u8]>,
|
||||
|
||||
/// The Response (tag '82') contains either the decrypted data from tag '80'
|
||||
/// or the encrypted data from tag '81'.
|
||||
#[tlv(simple = "0x82")]
|
||||
response: Option<&'l [u8]>,
|
||||
// /// Not documented in SP-800-73-4
|
||||
// #[tlv(simple = "0x85")]
|
||||
// exponentiation: Option<&'l [u8]>,
|
||||
// }
|
||||
|
||||
/// Not documented in SP-800-73-4
|
||||
#[tlv(simple = "0x85")]
|
||||
exponentiation: Option<&'l [u8]>,
|
||||
}
|
||||
|
||||
impl<'a> DynamicAuthenticationTemplate<'a> {
|
||||
pub fn with_challenge(challenge: &'a [u8]) -> Self {
|
||||
Self {
|
||||
challenge: Some(challenge),
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
pub fn with_exponentiation(exponentiation: &'a [u8]) -> Self {
|
||||
Self {
|
||||
exponentiation: Some(exponentiation),
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
pub fn with_response(response: &'a [u8]) -> Self {
|
||||
Self {
|
||||
response: Some(response),
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
pub fn with_witness(witness: &'a [u8]) -> Self {
|
||||
Self {
|
||||
witness: Some(witness),
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
}
|
||||
// impl<'a> DynamicAuthenticationTemplate<'a> {
|
||||
// pub fn with_challenge(challenge: &'a [u8]) -> Self {
|
||||
// Self {
|
||||
// challenge: Some(challenge),
|
||||
// ..Default::default()
|
||||
// }
|
||||
// }
|
||||
// pub fn with_exponentiation(exponentiation: &'a [u8]) -> Self {
|
||||
// Self {
|
||||
// exponentiation: Some(exponentiation),
|
||||
// ..Default::default()
|
||||
// }
|
||||
// }
|
||||
// pub fn with_response(response: &'a [u8]) -> Self {
|
||||
// Self {
|
||||
// response: Some(response),
|
||||
// ..Default::default()
|
||||
// }
|
||||
// }
|
||||
// pub fn with_witness(witness: &'a [u8]) -> Self {
|
||||
// Self {
|
||||
// witness: Some(witness),
|
||||
// ..Default::default()
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
/// The Card Holder Unique Identifier (CHUID) data object is defined in accordance with the Technical
|
||||
/// Implementation Guidance: Smart Card Enabled Physical Access Control Systems (TIG SCEPACS)
|
||||
|
||||
+134
@@ -0,0 +1,134 @@
|
||||
// Copyright (C) 2022 Nitrokey GmbH
|
||||
// SPDX-License-Identifier: LGPL-3.0-only
|
||||
|
||||
use iso7816::Status;
|
||||
|
||||
use core::ops::{Deref, DerefMut};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Reply<'v, const R: usize>(pub &'v mut heapless::Vec<u8, R>);
|
||||
|
||||
impl<'v, const R: usize> Deref for Reply<'v, R> {
|
||||
type Target = &'v mut heapless::Vec<u8, R>;
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl<'v, const R: usize> DerefMut for Reply<'v, R> {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
&mut self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl<'v, const R: usize> Reply<'v, R> {
|
||||
/// Extend the reply and return an error otherwise
|
||||
/// The MoreAvailable and GET RESPONSE mechanisms are handled by adpu_dispatch
|
||||
///
|
||||
/// Named expand and not extend to avoid conflicts with Deref
|
||||
pub fn expand(&mut self, data: &[u8]) -> Result<(), Status> {
|
||||
self.0.extend_from_slice(data).map_err(|_| {
|
||||
error!("Buffer full");
|
||||
Status::NotEnoughMemory
|
||||
})
|
||||
}
|
||||
|
||||
fn serialize_len(len: usize) -> Result<heapless::Vec<u8, 3>, Status> {
|
||||
let mut buf = heapless::Vec::new();
|
||||
if let Ok(len) = u8::try_from(len) {
|
||||
if len <= 0x7f {
|
||||
buf.extend_from_slice(&[len]).ok();
|
||||
} else {
|
||||
buf.extend_from_slice(&[0x81, len]).ok();
|
||||
}
|
||||
} else if let Ok(len) = u16::try_from(len) {
|
||||
let arr = len.to_be_bytes();
|
||||
buf.extend_from_slice(&[0x82, arr[0], arr[1]]).ok();
|
||||
} else {
|
||||
error!("Length too long to be encoded");
|
||||
return Err(Status::UnspecifiedNonpersistentExecutionError);
|
||||
}
|
||||
Ok(buf)
|
||||
}
|
||||
|
||||
/// Prepend the length to some data.
|
||||
///
|
||||
/// Input:
|
||||
/// AAAAAAAAAABBBBBBB
|
||||
/// ↑
|
||||
/// offset
|
||||
///
|
||||
/// Output:
|
||||
///
|
||||
/// AAAAAAAAAA 7 BBBBBBB
|
||||
/// (There are seven Bs, the length is encoded as specified in § 4.4.4)
|
||||
pub fn prepend_len(&mut self, offset: usize) -> Result<(), Status> {
|
||||
if self.len() < offset {
|
||||
error!("`prepend_len` called with offset lower than buffer length");
|
||||
return Err(Status::UnspecifiedNonpersistentExecutionError);
|
||||
}
|
||||
let len = self.len() - offset;
|
||||
let encoded = Self::serialize_len(len)?;
|
||||
self.extend_from_slice(&encoded).map_err(|_| {
|
||||
error!("Buffer full");
|
||||
Status::UnspecifiedNonpersistentExecutionError
|
||||
})?;
|
||||
self[offset..].rotate_right(encoded.len());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn append_len(&mut self, len: usize) -> Result<(), Status> {
|
||||
let encoded = Self::serialize_len(len)?;
|
||||
self.extend_from_slice(&encoded).map_err(|_| {
|
||||
error!("Buffer full");
|
||||
Status::UnspecifiedNonpersistentExecutionError
|
||||
})
|
||||
}
|
||||
|
||||
pub fn lend(&mut self) -> Reply<'_, R> {
|
||||
Reply(self.0)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
#![allow(clippy::unwrap_used, clippy::expect_used)]
|
||||
use super::*;
|
||||
#[test]
|
||||
fn prep_length() {
|
||||
let mut tmp = heapless::Vec::<u8, 1000>::new();
|
||||
let mut buf = Reply(&mut tmp);
|
||||
let offset = buf.len();
|
||||
buf.extend_from_slice(&[0; 0]).unwrap();
|
||||
buf.prepend_len(offset).unwrap();
|
||||
assert_eq!(&buf[offset..], [0]);
|
||||
|
||||
let offset = buf.len();
|
||||
buf.extend_from_slice(&[0; 20]).unwrap();
|
||||
buf.prepend_len(offset).unwrap();
|
||||
let mut expected = vec![20];
|
||||
expected.extend_from_slice(&[0; 20]);
|
||||
assert_eq!(&buf[offset..], expected,);
|
||||
|
||||
let offset = buf.len();
|
||||
buf.extend_from_slice(&[1; 127]).unwrap();
|
||||
buf.prepend_len(offset).unwrap();
|
||||
let mut expected = vec![127];
|
||||
expected.extend_from_slice(&[1; 127]);
|
||||
assert_eq!(&buf[offset..], expected);
|
||||
|
||||
let offset = buf.len();
|
||||
buf.extend_from_slice(&[2; 128]).unwrap();
|
||||
buf.prepend_len(offset).unwrap();
|
||||
let mut expected = vec![0x81, 128];
|
||||
expected.extend_from_slice(&[2; 128]);
|
||||
assert_eq!(&buf[offset..], expected);
|
||||
|
||||
let offset = buf.len();
|
||||
buf.extend_from_slice(&[3; 256]).unwrap();
|
||||
buf.prepend_len(offset).unwrap();
|
||||
let mut expected = vec![0x82, 0x01, 0x00];
|
||||
expected.extend_from_slice(&[3; 256]);
|
||||
assert_eq!(&buf[offset..], expected);
|
||||
}
|
||||
}
|
||||
+385
-203
File diff suppressed because it is too large
Load Diff
+100
@@ -0,0 +1,100 @@
|
||||
// Copyright (C) 2022 Nitrokey GmbH
|
||||
// SPDX-License-Identifier: LGPL-3.0-only
|
||||
|
||||
//! Utilities for dealing with TLV (Tag-Length-Value) encoded data
|
||||
|
||||
#[allow(unused)]
|
||||
pub fn get_do<'input>(tag_path: &[u16], data: &'input [u8]) -> Option<&'input [u8]> {
|
||||
let mut to_ret = data;
|
||||
let mut remainder = data;
|
||||
for tag in tag_path {
|
||||
loop {
|
||||
let (cur_tag, cur_value, cur_remainder) = take_do(remainder)?;
|
||||
remainder = cur_remainder;
|
||||
if *tag == cur_tag {
|
||||
to_ret = cur_value;
|
||||
remainder = cur_value;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
Some(to_ret)
|
||||
}
|
||||
|
||||
/// Returns (tag, data, remainder)
|
||||
pub fn take_do(data: &[u8]) -> Option<(u16, &[u8], &[u8])> {
|
||||
let (tag, remainder) = take_tag(data)?;
|
||||
let (len, remainder) = take_len(remainder)?;
|
||||
if remainder.len() < len {
|
||||
warn!("Tried to parse TLV with data length shorter that the length data");
|
||||
None
|
||||
} else {
|
||||
let (value, remainder) = remainder.split_at(len);
|
||||
Some((tag, value, remainder))
|
||||
}
|
||||
}
|
||||
|
||||
// See
|
||||
// https://www.emvco.com/wp-content/uploads/2017/05/EMV_v4.3_Book_3_Application_Specification_20120607062110791.pdf
|
||||
// Annex B1
|
||||
fn take_tag(data: &[u8]) -> Option<(u16, &[u8])> {
|
||||
let b1 = *data.first()?;
|
||||
if (b1 & 0x1f) == 0x1f {
|
||||
let b2 = *data.get(1)?;
|
||||
|
||||
if (b2 & 0b10000000) != 0 {
|
||||
// OpenPGP doesn't have any DO with a tag longer than 2 bytes
|
||||
warn!("Got a tag larger than 2 bytes: {data:x?}");
|
||||
return None;
|
||||
}
|
||||
Some((u16::from_be_bytes([b1, b2]), &data[2..]))
|
||||
} else {
|
||||
Some((u16::from_be_bytes([0, b1]), &data[1..]))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn take_len(data: &[u8]) -> Option<(usize, &[u8])> {
|
||||
let l1 = *data.first()?;
|
||||
if l1 <= 0x7F {
|
||||
Some((l1 as usize, &data[1..]))
|
||||
} else if l1 == 0x81 {
|
||||
Some((*data.get(1)? as usize, &data[2..]))
|
||||
} else {
|
||||
if l1 != 0x82 {
|
||||
warn!(
|
||||
"Got an unexpected length tag: {l1:x}, data: {:x?}",
|
||||
&data[..3]
|
||||
);
|
||||
return None;
|
||||
}
|
||||
let l2 = *data.get(1)?;
|
||||
let l3 = *data.get(2)?;
|
||||
let len = u16::from_be_bytes([l2, l3]) as usize;
|
||||
Some((len, &data[3..]))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use hex_literal::hex;
|
||||
use test_log::test;
|
||||
|
||||
#[test]
|
||||
fn dos() {
|
||||
assert_eq!(
|
||||
get_do(&[0x02], &hex!("02 02 1DB9 02 02 1DB9")),
|
||||
Some(hex!("1DB9").as_slice())
|
||||
);
|
||||
assert_eq!(
|
||||
get_do(&[0xA6, 0x7F49, 0x86], &hex!("A6 26 7F49 23 86 21 04 2525252525252525252525252525252525252525252525252525252525252525")),
|
||||
Some(hex!("04 2525252525252525252525252525252525252525252525252525252525252525").as_slice())
|
||||
);
|
||||
|
||||
// Multiple nested
|
||||
assert_eq!(
|
||||
get_do(&[0xA6, 0x7F49, 0x86], &hex!("A6 2A 02 02 DEAD 7F49 23 86 21 04 2525252525252525252525252525252525252525252525252525252525252525")),
|
||||
Some(hex!("04 2525252525252525252525252525252525252525252525252525252525252525").as_slice())
|
||||
);
|
||||
}
|
||||
}
|
||||
+2
-1
@@ -2,7 +2,8 @@
|
||||
// SPDX-License-Identifier: LGPL-3.0-only
|
||||
|
||||
use iso7816::{command::FromSliceError, Command, Status};
|
||||
use trussed::virt::{Client, Ram};
|
||||
use trussed::virt::Ram;
|
||||
use trussed_rsa_alloc::virt::Client;
|
||||
|
||||
use std::convert::{TryFrom, TryInto};
|
||||
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
// Copyright (C) 2022 Nitrokey GmbH
|
||||
// SPDX-License-Identifier: LGPL-3.0-only
|
||||
|
||||
use piv_authenticator::{vpicc::VirtualCard, Authenticator};
|
||||
|
||||
use std::{sync::mpsc, thread::sleep, time::Duration};
|
||||
use stoppable_thread::spawn;
|
||||
|
||||
use std::sync::Mutex;
|
||||
|
||||
static VSC_MUTEX: Mutex<()> = Mutex::new(());
|
||||
|
||||
pub fn with_vsc<F: FnOnce() -> R, R>(f: F) -> R {
|
||||
let _lock = VSC_MUTEX.lock().unwrap();
|
||||
|
||||
let mut vpicc = vpicc::connect().expect("failed to connect to vpcd");
|
||||
|
||||
let (tx, rx) = mpsc::channel();
|
||||
let handle = spawn(move |stopped| {
|
||||
trussed_rsa_alloc::virt::with_ram_client("opcard", |client| {
|
||||
let card = Authenticator::new(client);
|
||||
let mut virtual_card = VirtualCard::new(card);
|
||||
let mut result = Ok(());
|
||||
while !stopped.get() && result.is_ok() {
|
||||
result = vpicc.poll(&mut virtual_card);
|
||||
if result.is_ok() {
|
||||
tx.send(()).expect("failed to send message");
|
||||
}
|
||||
}
|
||||
result
|
||||
})
|
||||
});
|
||||
|
||||
rx.recv().expect("failed to read message");
|
||||
|
||||
sleep(Duration::from_millis(200));
|
||||
|
||||
let result = f();
|
||||
|
||||
handle
|
||||
.stop()
|
||||
.join()
|
||||
.expect("failed to join vpicc thread")
|
||||
.expect("failed to run virtual smartcard");
|
||||
result
|
||||
}
|
||||
@@ -15,4 +15,116 @@
|
||||
Select
|
||||
]
|
||||
),
|
||||
IoTest(
|
||||
name: "Default management key",
|
||||
cmd_resp: [
|
||||
AuthenticateManagement(
|
||||
key: (
|
||||
algorithm: Tdes,
|
||||
key: "0102030405060708 0102030405060708 0102030405060708"
|
||||
)
|
||||
)
|
||||
]
|
||||
),
|
||||
IoTest(
|
||||
name: "Aes management key",
|
||||
cmd_resp: [
|
||||
AuthenticateManagement(
|
||||
key: (
|
||||
algorithm: Tdes,
|
||||
key: "0102030405060708 0102030405060708 0102030405060708"
|
||||
)
|
||||
),
|
||||
SetManagementKey(
|
||||
key: (
|
||||
algorithm: Aes256,
|
||||
key: "0102030405060708 0102030405060708 0102030405060708 0102030405060708"
|
||||
)
|
||||
),
|
||||
AuthenticateManagement(
|
||||
key: (
|
||||
algorithm: Aes256,
|
||||
key: "0102030405060708 0102030405060708 0102030405060708 0102030405060708"
|
||||
)
|
||||
)
|
||||
]
|
||||
),
|
||||
IoTest(
|
||||
name: "unauthenticated set management key",
|
||||
cmd_resp: [
|
||||
SetManagementKey(
|
||||
key: (
|
||||
algorithm: Aes256,
|
||||
key: "0102030405060708 0102030405060708 0102030405060708 0102030405060708"
|
||||
),
|
||||
expected_status: SecurityStatusNotSatisfied,
|
||||
),
|
||||
AuthenticateManagement(
|
||||
key: (
|
||||
algorithm: Aes256,
|
||||
key: "0102030405060708 0102030405060708 0102030405060708 0102030405060708"
|
||||
),
|
||||
expected_status_challenge: IncorrectP1OrP2Parameter,
|
||||
expected_status_response: IncorrectP1OrP2Parameter,
|
||||
)
|
||||
]
|
||||
),
|
||||
IoTest(
|
||||
name: "Generate key",
|
||||
cmd_resp: [
|
||||
AuthenticateManagement(
|
||||
key: (
|
||||
algorithm: Tdes,
|
||||
key: "0102030405060708 0102030405060708 0102030405060708"
|
||||
)
|
||||
),
|
||||
IoData(
|
||||
input: "00 47 009A 05
|
||||
AC 03
|
||||
80 01 11",
|
||||
output: Len(70),
|
||||
)
|
||||
]
|
||||
),
|
||||
IoTest(
|
||||
name: "PUT DATA",
|
||||
cmd_resp: [
|
||||
GetData(
|
||||
input: "5C 01 7E",
|
||||
output: Data("7e 12 4f 0b a000000308000010000100 5f2f 02 4000")
|
||||
),
|
||||
GetData(
|
||||
input: "5C 03 5FC102",
|
||||
output: Len(61)
|
||||
),
|
||||
PutData(
|
||||
input: "5C 03 5FC102 53 10 000102030405060708090A0B0C0D0E0F",
|
||||
expected_status: SecurityStatusNotSatisfied
|
||||
),
|
||||
GetData(
|
||||
input: "5C 03 5FC102",
|
||||
output: Len(61)
|
||||
),
|
||||
AuthenticateManagement(
|
||||
key: (
|
||||
algorithm: Tdes,
|
||||
key: "0102030405060708 0102030405060708 0102030405060708"
|
||||
)
|
||||
),
|
||||
PutData(
|
||||
input: "5C 03 5FC102 53 10 000102030405060708090A0B0C0D0E0F",
|
||||
),
|
||||
GetData(
|
||||
input: "5C 03 5FC102",
|
||||
output: Data("53 10 000102030405060708090A0B0C0D0E0F")
|
||||
),
|
||||
PutData(
|
||||
input: "5C 01 7E 53 10 000102030405060708090A0B0C0D0E0F",
|
||||
),
|
||||
GetData(
|
||||
input: "5C 01 7E",
|
||||
output: Data("7e 10 000102030405060708090A0B0C0D0E0F")
|
||||
),
|
||||
]
|
||||
)
|
||||
]
|
||||
|
||||
+214
-19
@@ -1,4 +1,4 @@
|
||||
// Copyright (C) 2022 Nitrokey GmbH
|
||||
// Copyright (C) 2022 Nicolas Stalder AND Nitrokey GmbH
|
||||
// SPDX-License-Identifier: LGPL-3.0-only
|
||||
#![cfg(feature = "virtual")]
|
||||
|
||||
@@ -6,8 +6,10 @@ mod setup;
|
||||
|
||||
use std::borrow::Cow;
|
||||
|
||||
use aes::Aes256Enc;
|
||||
use hex_literal::hex;
|
||||
use serde::Deserialize;
|
||||
use trussed::types::GenericArray;
|
||||
|
||||
// iso7816::Status doesn't support serde
|
||||
#[derive(Deserialize, Debug, PartialEq, Clone, Copy)]
|
||||
@@ -36,6 +38,51 @@ enum Status {
|
||||
UnspecifiedCheckingError,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Eq, PartialEq, Debug, Deserialize)]
|
||||
pub enum Algorithm {
|
||||
Tdes = 0x3,
|
||||
Rsa1024 = 0x6,
|
||||
Rsa2048 = 0x7,
|
||||
Aes128 = 0x8,
|
||||
Aes192 = 0xA,
|
||||
Aes256 = 0xC,
|
||||
P256 = 0x11,
|
||||
P384 = 0x14,
|
||||
|
||||
P521 = 0x15,
|
||||
// non-standard!
|
||||
Rsa3072 = 0xE0,
|
||||
Rsa4096 = 0xE1,
|
||||
Ed25519 = 0xE2,
|
||||
X25519 = 0xE3,
|
||||
Ed448 = 0xE4,
|
||||
X448 = 0xE5,
|
||||
|
||||
// non-standard! picked by Alex, but maybe due for removal
|
||||
P256Sha1 = 0xF0,
|
||||
P256Sha256 = 0xF1,
|
||||
P384Sha1 = 0xF2,
|
||||
P384Sha256 = 0xF3,
|
||||
P384Sha384 = 0xF4,
|
||||
}
|
||||
impl Algorithm {
|
||||
pub fn challenge_len(self) -> usize {
|
||||
match self {
|
||||
Self::Tdes => 8,
|
||||
Self::Aes256 => 16,
|
||||
_ => panic!(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn key_len(self) -> usize {
|
||||
match self {
|
||||
Self::Tdes => 24,
|
||||
Self::Aes256 => 32,
|
||||
_ => panic!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn serialize_len(len: usize) -> heapless::Vec<u8, 3> {
|
||||
let mut buf = heapless::Vec::new();
|
||||
if let Ok(len) = u8::try_from(len) {
|
||||
@@ -52,7 +99,6 @@ fn serialize_len(len: usize) -> heapless::Vec<u8, 3> {
|
||||
buf
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
fn tlv(tag: &[u8], data: &[u8]) -> Vec<u8> {
|
||||
let mut buf = Vec::from(tag);
|
||||
buf.extend_from_slice(&serialize_len(data.len()));
|
||||
@@ -60,7 +106,6 @@ fn tlv(tag: &[u8], data: &[u8]) -> Vec<u8> {
|
||||
buf
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
fn build_command(cla: u8, ins: u8, p1: u8, p2: u8, data: &[u8], le: u16) -> Vec<u8> {
|
||||
let mut res = vec![cla, ins, p1, p2];
|
||||
let lc = data.len();
|
||||
@@ -152,8 +197,14 @@ enum OutputMatcher {
|
||||
Len(usize),
|
||||
// The () at the end are here to workaround a compiler bug. See:
|
||||
// https://github.com/rust-lang/rust/issues/89940#issuecomment-1282321806
|
||||
And(Cow<'static, [OutputMatcher]>, #[serde(default)] ()),
|
||||
Or(Cow<'static, [OutputMatcher]>, #[serde(default)] ()),
|
||||
All(
|
||||
#[serde(default)] Cow<'static, [OutputMatcher]>,
|
||||
#[serde(default)] (),
|
||||
),
|
||||
Any(
|
||||
#[serde(default)] Cow<'static, [OutputMatcher]>,
|
||||
#[serde(default)] (),
|
||||
),
|
||||
/// HEX data
|
||||
Data(Cow<'static, str>),
|
||||
Bytes(Cow<'static, [u8]>),
|
||||
@@ -168,7 +219,7 @@ impl Default for OutputMatcher {
|
||||
|
||||
fn parse_hex(data: &str) -> Vec<u8> {
|
||||
let tmp: String = data.split_whitespace().collect();
|
||||
hex::decode(&tmp).unwrap()
|
||||
hex::decode(tmp).unwrap()
|
||||
}
|
||||
|
||||
impl OutputMatcher {
|
||||
@@ -180,16 +231,23 @@ impl OutputMatcher {
|
||||
data == parse_hex(expected)
|
||||
}
|
||||
Self::Bytes(expected) => {
|
||||
println!("Validating output with {expected:x?}");
|
||||
println!("Validating output with {expected:02x?}");
|
||||
data == &**expected
|
||||
}
|
||||
Self::Len(len) => data.len() == *len,
|
||||
Self::And(matchers, _) => matchers.iter().filter(|m| !m.validate(data)).count() == 0,
|
||||
Self::Or(matchers, _) => matchers.iter().filter(|m| m.validate(data)).count() != 0,
|
||||
Self::All(matchers, _) => matchers.iter().filter(|m| !m.validate(data)).count() == 0,
|
||||
Self::Any(matchers, _) => matchers.iter().filter(|m| m.validate(data)).count() != 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Debug)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
struct ManagementKey {
|
||||
algorithm: Algorithm,
|
||||
key: String,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Debug)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
enum IoCmd {
|
||||
@@ -200,6 +258,20 @@ enum IoCmd {
|
||||
#[serde(default)]
|
||||
expected_status: Status,
|
||||
},
|
||||
GetData {
|
||||
input: String,
|
||||
#[serde(default)]
|
||||
output: OutputMatcher,
|
||||
#[serde(default)]
|
||||
expected_status: Status,
|
||||
},
|
||||
PutData {
|
||||
input: String,
|
||||
#[serde(default)]
|
||||
output: OutputMatcher,
|
||||
#[serde(default)]
|
||||
expected_status: Status,
|
||||
},
|
||||
VerifyDefaultApplicationPin {
|
||||
#[serde(default)]
|
||||
expected_status: Status,
|
||||
@@ -208,10 +280,23 @@ enum IoCmd {
|
||||
#[serde(default)]
|
||||
expected_status: Status,
|
||||
},
|
||||
SetManagementKey {
|
||||
key: ManagementKey,
|
||||
#[serde(default)]
|
||||
expected_status: Status,
|
||||
},
|
||||
AuthenticateManagement {
|
||||
key: ManagementKey,
|
||||
#[serde(default)]
|
||||
expected_status_challenge: Status,
|
||||
#[serde(default)]
|
||||
expected_status_response: Status,
|
||||
},
|
||||
Select,
|
||||
}
|
||||
|
||||
const MATCH_EMPTY: OutputMatcher = OutputMatcher::Len(0);
|
||||
const MATCH_ANY: OutputMatcher = OutputMatcher::All(Cow::Borrowed(&[]), ());
|
||||
|
||||
impl IoCmd {
|
||||
fn run(&self, card: &mut setup::Piv) {
|
||||
@@ -221,23 +306,66 @@ impl IoCmd {
|
||||
output,
|
||||
expected_status,
|
||||
} => Self::run_iodata(input, output, *expected_status, card),
|
||||
Self::GetData {
|
||||
input,
|
||||
output,
|
||||
expected_status,
|
||||
} => Self::run_get_data(input, output, *expected_status, card),
|
||||
Self::PutData {
|
||||
input,
|
||||
output,
|
||||
expected_status,
|
||||
} => Self::run_put_data(input, output, *expected_status, card),
|
||||
Self::VerifyDefaultApplicationPin { expected_status } => {
|
||||
Self::run_verify_default_application_pin(*expected_status, card)
|
||||
}
|
||||
Self::VerifyDefaultGlobalPin { expected_status } => {
|
||||
Self::run_verify_default_global_pin(*expected_status, card)
|
||||
}
|
||||
Self::AuthenticateManagement {
|
||||
key,
|
||||
expected_status_challenge,
|
||||
expected_status_response,
|
||||
} => Self::run_authenticate_management(
|
||||
key.algorithm,
|
||||
&key.key,
|
||||
*expected_status_challenge,
|
||||
*expected_status_response,
|
||||
card,
|
||||
),
|
||||
Self::SetManagementKey {
|
||||
key,
|
||||
expected_status,
|
||||
} => Self::run_set_administration_key(key.algorithm, &key.key, *expected_status, card),
|
||||
Self::Select => Self::run_select(card),
|
||||
}
|
||||
}
|
||||
|
||||
fn run_set_administration_key(
|
||||
alg: Algorithm,
|
||||
key: &str,
|
||||
expected_status: Status,
|
||||
card: &mut setup::Piv,
|
||||
) {
|
||||
let mut key_data = parse_hex(key);
|
||||
let mut data = vec![alg as u8, 0x9b, key_data.len() as u8];
|
||||
data.append(&mut key_data);
|
||||
|
||||
Self::run_bytes(
|
||||
&build_command(0x00, 0xff, 0xff, 0xff, &data, 0),
|
||||
&MATCH_ANY,
|
||||
expected_status,
|
||||
card,
|
||||
);
|
||||
}
|
||||
|
||||
fn run_bytes(
|
||||
input: &[u8],
|
||||
output: &OutputMatcher,
|
||||
expected_status: Status,
|
||||
card: &mut setup::Piv,
|
||||
) {
|
||||
println!("Command: {:x?}", input);
|
||||
) -> heapless::Vec<u8, 1024> {
|
||||
println!("Command: {input:x?}");
|
||||
let mut rep: heapless::Vec<u8, 1024> = heapless::Vec::new();
|
||||
let cmd: iso7816::Command<{ setup::COMMAND_SIZE }> = iso7816::Command::try_from(input)
|
||||
.unwrap_or_else(|err| {
|
||||
@@ -252,11 +380,12 @@ impl IoCmd {
|
||||
println!("Output: {:?}\nStatus: {status:?}", hex::encode(&rep));
|
||||
|
||||
if !output.validate(&rep) {
|
||||
panic!("Bad output. Expected {:?}", output);
|
||||
panic!("Bad output. Expected {output:02x?}");
|
||||
}
|
||||
if status != expected_status {
|
||||
panic!("Bad status. Expected {:?}", expected_status);
|
||||
panic!("Bad status. Expected {expected_status:?}");
|
||||
}
|
||||
rep
|
||||
}
|
||||
|
||||
fn run_iodata(
|
||||
@@ -265,7 +394,71 @@ impl IoCmd {
|
||||
expected_status: Status,
|
||||
card: &mut setup::Piv,
|
||||
) {
|
||||
Self::run_bytes(&parse_hex(input), output, expected_status, card)
|
||||
Self::run_bytes(&parse_hex(input), output, expected_status, card);
|
||||
}
|
||||
|
||||
fn run_get_data(
|
||||
input: &str,
|
||||
output: &OutputMatcher,
|
||||
expected_status: Status,
|
||||
card: &mut setup::Piv,
|
||||
) {
|
||||
Self::run_bytes(
|
||||
&build_command(0x00, 0xCB, 0x3F, 0xFF, &parse_hex(input), 0),
|
||||
output,
|
||||
expected_status,
|
||||
card,
|
||||
);
|
||||
}
|
||||
|
||||
fn run_put_data(
|
||||
input: &str,
|
||||
output: &OutputMatcher,
|
||||
expected_status: Status,
|
||||
card: &mut setup::Piv,
|
||||
) {
|
||||
Self::run_bytes(
|
||||
&build_command(0x00, 0xDB, 0x3F, 0xFF, &parse_hex(input), 0),
|
||||
output,
|
||||
expected_status,
|
||||
card,
|
||||
);
|
||||
}
|
||||
|
||||
fn run_authenticate_management(
|
||||
alg: Algorithm,
|
||||
key: &str,
|
||||
expected_status_challenge: Status,
|
||||
expected_status_response: Status,
|
||||
card: &mut setup::Piv,
|
||||
) {
|
||||
use des::{
|
||||
cipher::{BlockEncrypt, KeyInit},
|
||||
TdesEde3,
|
||||
};
|
||||
let command = build_command(0x00, 0x87, alg as u8, 0x9B, &hex!("7C 02 81 00"), 0);
|
||||
let mut res = Self::run_bytes(&command, &MATCH_ANY, expected_status_challenge, card);
|
||||
let key = parse_hex(key);
|
||||
if expected_status_challenge != Status::Success {
|
||||
res = heapless::Vec::from_slice(&vec![0; alg.challenge_len() + 6]).unwrap();
|
||||
}
|
||||
|
||||
// Remove header
|
||||
let challenge = &mut res[4..][..alg.challenge_len()];
|
||||
match alg {
|
||||
Algorithm::Tdes => {
|
||||
let cipher = TdesEde3::new(GenericArray::from_slice(&key));
|
||||
cipher.encrypt_block(GenericArray::from_mut_slice(challenge));
|
||||
}
|
||||
Algorithm::Aes256 => {
|
||||
let cipher = Aes256Enc::new(GenericArray::from_slice(&key));
|
||||
cipher.encrypt_block(GenericArray::from_mut_slice(challenge));
|
||||
}
|
||||
_ => panic!(),
|
||||
}
|
||||
let second_data = tlv(&[0x7C], &tlv(&[0x82], challenge));
|
||||
let command = build_command(0x00, 0x87, alg as u8, 0x9B, &second_data, 0);
|
||||
Self::run_bytes(&command, &MATCH_ANY, expected_status_response, card);
|
||||
}
|
||||
|
||||
fn run_verify_default_global_pin(expected_status: Status, card: &mut setup::Piv) {
|
||||
@@ -274,21 +467,22 @@ impl IoCmd {
|
||||
&MATCH_EMPTY,
|
||||
expected_status,
|
||||
card,
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
fn run_verify_default_application_pin(expected_status: Status, card: &mut setup::Piv) {
|
||||
Self::run_bytes(
|
||||
&hex!("00 20 00 80 08 313233343536FFFF"),
|
||||
&MATCH_EMPTY,
|
||||
expected_status,
|
||||
card,
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
fn run_select(card: &mut setup::Piv) {
|
||||
let matcher = OutputMatcher::Bytes(Cow::Borrowed(&hex!(
|
||||
"
|
||||
61 63 // Card application property template
|
||||
61 66 // Card application property template
|
||||
4f 06 000010000100 // Application identifier
|
||||
50 0c 536f6c6f4b65797320504956 // Application label = b\"Solokeys PIV\"
|
||||
|
||||
@@ -296,12 +490,13 @@ impl IoCmd {
|
||||
5f50 2d 68747470733a2f2f6769746875622e636f6d2f736f6c6f6b6579732f7069762d61757468656e74696361746f72
|
||||
|
||||
// Cryptographic Algorithm Identifier Template
|
||||
ac 12
|
||||
ac 15
|
||||
80 01 03 // TDES - ECB
|
||||
80 01 0c // AES256 - ECB
|
||||
80 01 11 // P-256
|
||||
80 01 e2 // Ed25519
|
||||
80 01 e3 // X25519
|
||||
80 01 07 // RSA 2048
|
||||
06 01 00
|
||||
// Coexistent Tag Allocation Authority Template
|
||||
79 07
|
||||
@@ -313,7 +508,7 @@ impl IoCmd {
|
||||
&matcher,
|
||||
Status::Success,
|
||||
card,
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user