#![forbid(unsafe_code)]
use keyroost_proto::apdu::{build_apdu, build_apdu_get};
pub mod spki;
pub mod x509;
pub mod x509_parse;
pub const AID: [u8; 5] = [0xA0, 0x00, 0x00, 0x03, 0x08];
pub const SW_OK: u16 = 0x9000;
pub const SW_MORE_DATA: u8 = 0x61;
pub const SW_NOT_FOUND: u16 = 0x6A82;
pub const SW_SECURITY_NOT_SATISFIED: u16 = 0x6982;
pub const SW_AUTH_BLOCKED: u16 = 0x6983;
pub const SW_REFERENCE_NOT_FOUND: u16 = 0x6A88;
pub const PIN_REF_APPLICATION: u8 = 0x80;
pub const PIN_REF_PUK: u8 = 0x81;
pub const KEY_REF_MANAGEMENT: u8 = 0x9B;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(u8)]
pub enum Instruction {
Select = 0xA4,
Verify = 0x20,
GetData = 0xCB,
GetResponse = 0xC0,
GeneralAuthenticate = 0x87,
GenerateKeyPair = 0x47,
PutData = 0xDB,
ChangeReference = 0x24,
ResetRetryCounter = 0x2C,
GetVersion = 0xFD,
GetSerial = 0xF8,
GetMetadata = 0xF7,
MoveKey = 0xF6,
SetManagementKey = 0xFF,
SetPinRetries = 0xFA,
Reset = 0xFB,
}
impl Instruction {
#[must_use]
pub const fn code(self) -> u8 {
self as u8
}
}
const INS_SELECT_P1_BY_AID: u8 = 0x04;
const GET_DATA_P1: u8 = 0x3F;
const GET_DATA_P2: u8 = 0xFF;
const TAG_OBJECT_SELECTOR: u8 = 0x5C;
const TAG_DATA_TEMPLATE: u8 = 0x53;
const TAG_DYN_AUTH: u8 = 0x7C;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Slot {
Authentication,
Signature,
KeyManagement,
CardAuthentication,
}
impl Slot {
#[must_use]
pub const fn key_ref(self) -> u8 {
match self {
Slot::Authentication => 0x9A,
Slot::Signature => 0x9C,
Slot::KeyManagement => 0x9D,
Slot::CardAuthentication => 0x9E,
}
}
#[must_use]
pub const fn cert_object_tag(self) -> [u8; 3] {
match self {
Slot::Authentication => [0x5F, 0xC1, 0x05],
Slot::Signature => [0x5F, 0xC1, 0x0A],
Slot::KeyManagement => [0x5F, 0xC1, 0x0B],
Slot::CardAuthentication => [0x5F, 0xC1, 0x01],
}
}
#[must_use]
pub const fn label(self) -> &'static str {
match self {
Slot::Authentication => "authentication (9A)",
Slot::Signature => "signature (9C)",
Slot::KeyManagement => "key management (9D)",
Slot::CardAuthentication => "card authentication (9E)",
}
}
#[must_use]
pub const fn all() -> [Slot; 4] {
[
Slot::Authentication,
Slot::Signature,
Slot::KeyManagement,
Slot::CardAuthentication,
]
}
}
pub const OBJECT_CHUID: [u8; 3] = [0x5F, 0xC1, 0x02];
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum MgmtAlg {
TripleDes,
Aes128,
Aes192,
Aes256,
}
impl MgmtAlg {
#[must_use]
pub const fn id(self) -> u8 {
match self {
MgmtAlg::TripleDes => 0x03,
MgmtAlg::Aes128 => 0x08,
MgmtAlg::Aes192 => 0x0A,
MgmtAlg::Aes256 => 0x0C,
}
}
#[must_use]
pub const fn from_id(id: u8) -> Option<Self> {
match id {
0x03 => Some(MgmtAlg::TripleDes),
0x08 => Some(MgmtAlg::Aes128),
0x0A => Some(MgmtAlg::Aes192),
0x0C => Some(MgmtAlg::Aes256),
_ => None,
}
}
#[must_use]
pub const fn block_size(self) -> usize {
match self {
MgmtAlg::TripleDes => 8,
_ => 16,
}
}
#[must_use]
pub const fn key_len(self) -> usize {
match self {
MgmtAlg::TripleDes | MgmtAlg::Aes192 => 24,
MgmtAlg::Aes128 => 16,
MgmtAlg::Aes256 => 32,
}
}
#[must_use]
pub const fn label(self) -> &'static str {
match self {
MgmtAlg::TripleDes => "3DES",
MgmtAlg::Aes128 => "AES-128",
MgmtAlg::Aes192 => "AES-192",
MgmtAlg::Aes256 => "AES-256",
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum KeyAlg {
Rsa1024,
Rsa2048,
Rsa3072,
Rsa4096,
EccP256,
EccP384,
Ed25519,
X25519,
}
impl KeyAlg {
#[must_use]
pub const fn id(self) -> u8 {
match self {
KeyAlg::Rsa1024 => 0x06,
KeyAlg::Rsa2048 => 0x07,
KeyAlg::Rsa3072 => 0x05,
KeyAlg::Rsa4096 => 0x16,
KeyAlg::EccP256 => 0x11,
KeyAlg::EccP384 => 0x14,
KeyAlg::Ed25519 => 0xE0,
KeyAlg::X25519 => 0xE1,
}
}
#[must_use]
pub const fn from_id(id: u8) -> Option<Self> {
match id {
0x06 => Some(KeyAlg::Rsa1024),
0x07 => Some(KeyAlg::Rsa2048),
0x05 => Some(KeyAlg::Rsa3072),
0x16 => Some(KeyAlg::Rsa4096),
0x11 => Some(KeyAlg::EccP256),
0x14 => Some(KeyAlg::EccP384),
0xE0 => Some(KeyAlg::Ed25519),
0xE1 => Some(KeyAlg::X25519),
_ => None,
}
}
#[must_use]
pub const fn label(self) -> &'static str {
match self {
KeyAlg::Rsa1024 => "RSA-1024",
KeyAlg::Rsa2048 => "RSA-2048",
KeyAlg::Rsa3072 => "RSA-3072",
KeyAlg::Rsa4096 => "RSA-4096",
KeyAlg::EccP256 => "ECC P-256",
KeyAlg::EccP384 => "ECC P-384",
KeyAlg::Ed25519 => "Ed25519",
KeyAlg::X25519 => "X25519",
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum PinPolicy {
Default,
Never,
Once,
Always,
}
impl PinPolicy {
#[must_use]
pub const fn id(self) -> u8 {
match self {
PinPolicy::Default => 0x00,
PinPolicy::Never => 0x01,
PinPolicy::Once => 0x02,
PinPolicy::Always => 0x03,
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum TouchPolicy {
Default,
Never,
Always,
Cached,
}
impl TouchPolicy {
#[must_use]
pub const fn id(self) -> u8 {
match self {
TouchPolicy::Default => 0x00,
TouchPolicy::Never => 0x01,
TouchPolicy::Always => 0x02,
TouchPolicy::Cached => 0x03,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum PublicKey {
Rsa { modulus: Vec<u8>, exponent: Vec<u8> },
Ecc { point: Vec<u8> },
}
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct Metadata {
pub algorithm: Option<u8>,
pub is_default: Option<bool>,
pub retries: Option<(u8, u8)>,
pub policy: Option<(u8, u8)>,
pub origin: Option<u8>,
pub public_key: Option<Vec<u8>>,
}
#[derive(Debug, PartialEq, Eq)]
pub enum ParseError {
Truncated,
NotDataObject,
BadResponse(&'static str),
NotAuthTemplate,
NotPublicKey,
}
impl core::fmt::Display for ParseError {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
ParseError::Truncated => write!(f, "PIV response truncated"),
ParseError::NotDataObject => write!(f, "PIV response is not a 0x53 data object"),
ParseError::BadResponse(w) => write!(f, "malformed PIV response: {w}"),
ParseError::NotAuthTemplate => {
write!(f, "PIV response is not a 0x7C dynamic-auth template")
}
ParseError::NotPublicKey => {
write!(f, "PIV response is not a 0x7F49 public-key template")
}
}
}
}
impl std::error::Error for ParseError {}
#[must_use]
pub fn select() -> Vec<u8> {
let mut apdu = build_apdu(
0x00,
Instruction::Select.code(),
INS_SELECT_P1_BY_AID,
0x00,
&AID,
);
apdu.push(0x00); apdu
}
#[must_use]
pub fn get_data(tag: &[u8]) -> Vec<u8> {
assert!(tag.len() <= 0x7F, "GET DATA object tag too long");
let mut selector = Vec::with_capacity(2 + tag.len());
selector.push(TAG_OBJECT_SELECTOR);
selector.push(tag.len() as u8);
selector.extend_from_slice(tag);
let mut apdu = build_apdu(
0x00,
Instruction::GetData.code(),
GET_DATA_P1,
GET_DATA_P2,
&selector,
);
apdu.push(0x00); apdu
}
#[must_use]
pub fn verify_pin(pin: &[u8]) -> Vec<u8> {
build_apdu(
0x00,
Instruction::Verify.code(),
0x00,
PIN_REF_APPLICATION,
&pad_pin(pin),
)
}
#[must_use]
pub fn verify_pin_status() -> Vec<u8> {
vec![0x00, Instruction::Verify.code(), 0x00, PIN_REF_APPLICATION]
}
#[must_use]
pub fn get_version() -> Vec<u8> {
build_apdu_get(0x00, Instruction::GetVersion.code(), 0x00, 0x00, 0x00)
}
#[must_use]
pub fn get_serial() -> Vec<u8> {
build_apdu_get(0x00, Instruction::GetSerial.code(), 0x00, 0x00, 0x00)
}
#[must_use]
pub fn get_response() -> Vec<u8> {
build_apdu_get(0x00, Instruction::GetResponse.code(), 0x00, 0x00, 0x00)
}
fn push_ber_len(out: &mut Vec<u8>, len: usize) {
assert!(len <= 0xFFFF, "BER-TLV value too large");
if len < 0x80 {
out.push(len as u8);
} else if len <= 0xFF {
out.push(0x81);
out.push(len as u8);
} else {
out.push(0x82);
out.push((len >> 8) as u8);
out.push(len as u8);
}
}
fn push_tlv(out: &mut Vec<u8>, tag: &[u8], value: &[u8]) {
out.extend_from_slice(tag);
push_ber_len(out, value.len());
out.extend_from_slice(value);
}
fn build_apdu_ext(cla: u8, ins: u8, p1: u8, p2: u8, data: &[u8], le: Option<u16>) -> Vec<u8> {
assert!(data.len() <= 0xFFFF, "extended APDU body too large");
if data.len() <= 255 && le.map_or(true, |v| v <= 256) {
let mut out = Vec::with_capacity(6 + data.len());
out.extend_from_slice(&[cla, ins, p1, p2]);
if !data.is_empty() {
out.push(data.len() as u8);
out.extend_from_slice(data);
}
if let Some(le) = le {
out.push(if le == 256 { 0x00 } else { le as u8 });
}
return out;
}
let mut out = Vec::with_capacity(9 + data.len());
out.extend_from_slice(&[cla, ins, p1, p2, 0x00]);
if !data.is_empty() {
out.push((data.len() >> 8) as u8);
out.push(data.len() as u8);
out.extend_from_slice(data);
}
if let Some(le) = le {
out.push((le >> 8) as u8);
out.push(le as u8);
}
out
}
#[must_use]
pub fn general_auth_request_witness(alg: MgmtAlg, key_ref: u8) -> Vec<u8> {
let data = [TAG_DYN_AUTH, 0x02, 0x80, 0x00];
build_apdu_ext(
0x00,
Instruction::GeneralAuthenticate.code(),
alg.id(),
key_ref,
&data,
Some(256),
)
}
#[must_use]
pub fn general_auth_mutual(
alg: MgmtAlg,
key_ref: u8,
decrypted_witness: &[u8],
challenge: &[u8],
) -> Vec<u8> {
let mut inner = Vec::with_capacity(decrypted_witness.len() + challenge.len() + 6);
push_tlv(&mut inner, &[0x80], decrypted_witness); push_tlv(&mut inner, &[0x81], challenge); let mut data = Vec::with_capacity(inner.len() + 4);
push_tlv(&mut data, &[TAG_DYN_AUTH], &inner);
build_apdu_ext(
0x00,
Instruction::GeneralAuthenticate.code(),
alg.id(),
key_ref,
&data,
Some(256),
)
}
#[must_use]
pub fn general_auth_sign(key_alg: KeyAlg, key_ref: u8, payload: &[u8]) -> Vec<u8> {
let mut inner = Vec::with_capacity(payload.len() + 6);
inner.extend_from_slice(&[0x82, 0x00]); push_tlv(&mut inner, &[0x81], payload); let mut data = Vec::with_capacity(inner.len() + 4);
push_tlv(&mut data, &[TAG_DYN_AUTH], &inner);
build_apdu_ext(
0x00,
Instruction::GeneralAuthenticate.code(),
key_alg.id(),
key_ref,
&data,
Some(0), )
}
#[must_use]
pub fn generate_key(
slot: Slot,
alg: KeyAlg,
pin_policy: PinPolicy,
touch_policy: TouchPolicy,
) -> Vec<u8> {
let mut control = Vec::with_capacity(9);
push_tlv(&mut control, &[0x80], &[alg.id()]); if pin_policy != PinPolicy::Default {
push_tlv(&mut control, &[0xAA], &[pin_policy.id()]);
}
if touch_policy != TouchPolicy::Default {
push_tlv(&mut control, &[0xAB], &[touch_policy.id()]);
}
let mut data = Vec::with_capacity(control.len() + 3);
push_tlv(&mut data, &[0xAC], &control); build_apdu_ext(
0x00,
Instruction::GenerateKeyPair.code(),
0x00,
slot.key_ref(),
&data,
Some(0),
)
}
#[must_use]
pub fn put_data(tag: &[u8], value: &[u8]) -> Vec<u8> {
let mut data = Vec::with_capacity(tag.len() + value.len() + 8);
push_tlv(&mut data, &[TAG_OBJECT_SELECTOR], tag); push_tlv(&mut data, &[TAG_DATA_TEMPLATE], value); build_apdu_ext(
0x00,
Instruction::PutData.code(),
GET_DATA_P1,
GET_DATA_P2,
&data,
None,
)
}
#[must_use]
pub fn encode_certificate(der: &[u8]) -> Vec<u8> {
let mut out = Vec::with_capacity(der.len() + 8);
push_tlv(&mut out, &[0x70], der); push_tlv(&mut out, &[0x71], &[0x00]); push_tlv(&mut out, &[0xFE], &[]); out
}
#[must_use]
pub fn change_reference(reference: u8, old: &[u8], new: &[u8]) -> Vec<u8> {
let mut body = pad_pin(old);
body.extend_from_slice(&pad_pin(new));
build_apdu(
0x00,
Instruction::ChangeReference.code(),
0x00,
reference,
&body,
)
}
#[must_use]
pub fn unblock_pin(puk: &[u8], new_pin: &[u8]) -> Vec<u8> {
let mut body = pad_pin(puk);
body.extend_from_slice(&pad_pin(new_pin));
build_apdu(
0x00,
Instruction::ResetRetryCounter.code(),
0x00,
PIN_REF_APPLICATION,
&body,
)
}
#[must_use]
pub fn set_management_key(alg: MgmtAlg, key: &[u8], require_touch: bool) -> Vec<u8> {
assert!(key.len() <= 255, "management key too long");
let mut body = Vec::with_capacity(3 + key.len());
body.push(alg.id());
body.push(KEY_REF_MANAGEMENT);
body.push(key.len() as u8);
body.extend_from_slice(key);
build_apdu(
0x00,
Instruction::SetManagementKey.code(),
0xFF,
if require_touch { 0xFE } else { 0xFF },
&body,
)
}
#[must_use]
pub fn set_pin_retries(pin_tries: u8, puk_tries: u8) -> Vec<u8> {
vec![
0x00,
Instruction::SetPinRetries.code(),
pin_tries,
puk_tries,
]
}
#[must_use]
pub fn get_metadata(key_ref: u8) -> Vec<u8> {
vec![0x00, Instruction::GetMetadata.code(), 0x00, key_ref]
}
#[must_use]
pub fn reset() -> Vec<u8> {
vec![0x00, Instruction::Reset.code(), 0x00, 0x00]
}
#[must_use]
pub fn delete_key(slot: Slot) -> Vec<u8> {
vec![0x00, Instruction::MoveKey.code(), 0xFF, slot.key_ref()]
}
#[must_use]
pub fn clear_certificate(slot: Slot) -> Vec<u8> {
put_data(&slot.cert_object_tag(), &[])
}
fn pad_pin(pin: &[u8]) -> Vec<u8> {
let mut out = [0xFFu8; 8].to_vec();
let n = pin.len().min(8);
out[..n].copy_from_slice(&pin[..n]);
out
}
pub fn unwrap_data_object(buf: &[u8]) -> Result<&[u8], ParseError> {
if buf.first() != Some(&TAG_DATA_TEMPLATE) {
return Err(ParseError::NotDataObject);
}
let (len, header) = read_ber_len(&buf[1..])?;
let start = 1 + header;
let end = start.checked_add(len).ok_or(ParseError::Truncated)?;
buf.get(start..end).ok_or(ParseError::Truncated)
}
pub fn parse_version(buf: &[u8]) -> Result<(u8, u8, u8), ParseError> {
match buf {
[a, b, c] => Ok((*a, *b, *c)),
_ => Err(ParseError::BadResponse("version is not 3 bytes")),
}
}
pub fn parse_serial(buf: &[u8]) -> Result<u32, ParseError> {
match buf {
[a, b, c, d] => Ok(u32::from_be_bytes([*a, *b, *c, *d])),
_ => Err(ParseError::BadResponse("serial is not 4 bytes")),
}
}
pub fn parse_general_auth(buf: &[u8], inner_tag: u8) -> Result<&[u8], ParseError> {
if buf.first() != Some(&TAG_DYN_AUTH) {
return Err(ParseError::NotAuthTemplate);
}
let (len, header) = read_ber_len(&buf[1..])?;
let start = 1 + header;
let end = start.checked_add(len).ok_or(ParseError::Truncated)?;
let inner = buf.get(start..end).ok_or(ParseError::Truncated)?;
find_tlv(inner, inner_tag).ok_or(ParseError::NotAuthTemplate)
}
pub fn parse_public_key(buf: &[u8]) -> Result<PublicKey, ParseError> {
if buf.get(..2) != Some(&[0x7F, 0x49][..]) {
return Err(ParseError::NotPublicKey);
}
let (len, header) = read_ber_len(&buf[2..])?;
let start = 2 + header;
let end = start.checked_add(len).ok_or(ParseError::Truncated)?;
let inner = buf.get(start..end).ok_or(ParseError::Truncated)?;
if let Some(point) = find_tlv(inner, 0x86) {
return Ok(PublicKey::Ecc {
point: point.to_vec(),
});
}
let modulus = find_tlv(inner, 0x81).ok_or(ParseError::NotPublicKey)?;
let exponent = find_tlv(inner, 0x82).ok_or(ParseError::NotPublicKey)?;
Ok(PublicKey::Rsa {
modulus: modulus.to_vec(),
exponent: exponent.to_vec(),
})
}
pub fn parse_metadata(buf: &[u8]) -> Result<Metadata, ParseError> {
let mut md = Metadata::default();
let mut i = 0;
while i < buf.len() {
let tag = buf[i];
let (len, header) = read_ber_len(&buf[i + 1..])?;
let vstart = i + 1 + header;
let vend = vstart.checked_add(len).ok_or(ParseError::Truncated)?;
let value = buf.get(vstart..vend).ok_or(ParseError::Truncated)?;
match tag {
0x01 => md.algorithm = value.first().copied(),
0x02 if value.len() >= 2 => md.policy = Some((value[0], value[1])),
0x03 => md.origin = value.first().copied(),
0x04 => md.public_key = Some(value.to_vec()),
0x05 => md.is_default = value.first().map(|&b| b != 0),
0x06 if value.len() >= 2 => md.retries = Some((value[0], value[1])),
_ => {}
}
i = vend;
}
Ok(md)
}
#[must_use]
pub fn find_tlv(buf: &[u8], tag: u8) -> Option<&[u8]> {
let mut i = 0;
while i < buf.len() {
let t = buf[i];
let (len, header) = read_ber_len(buf.get(i + 1..)?).ok()?;
let vstart = i + 1 + header;
let vend = vstart.checked_add(len)?;
let value = buf.get(vstart..vend)?;
if t == tag {
return Some(value);
}
i = vend;
}
None
}
pub fn read_ber_len(buf: &[u8]) -> Result<(usize, usize), ParseError> {
let first = *buf.first().ok_or(ParseError::Truncated)?;
if first < 0x80 {
return Ok((first as usize, 1));
}
let n = (first & 0x7F) as usize;
if n == 0 || n > 2 {
return Err(ParseError::BadResponse("unsupported BER length form"));
}
let bytes = buf.get(1..1 + n).ok_or(ParseError::Truncated)?;
let len = bytes.iter().fold(0usize, |acc, &b| (acc << 8) | b as usize);
Ok((len, 1 + n))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn select_bytes() {
assert_eq!(
select(),
vec![0x00, 0xA4, 0x04, 0x00, 0x05, 0xA0, 0x00, 0x00, 0x03, 0x08, 0x00]
);
}
#[test]
fn get_data_auth_cert_bytes() {
assert_eq!(
get_data(&Slot::Authentication.cert_object_tag()),
vec![0x00, 0xCB, 0x3F, 0xFF, 0x05, 0x5C, 0x03, 0x5F, 0xC1, 0x05, 0x00]
);
}
#[test]
fn slot_key_refs_and_tags() {
assert_eq!(Slot::Authentication.key_ref(), 0x9A);
assert_eq!(Slot::Signature.key_ref(), 0x9C);
assert_eq!(Slot::KeyManagement.key_ref(), 0x9D);
assert_eq!(Slot::CardAuthentication.key_ref(), 0x9E);
assert_eq!(Slot::Signature.cert_object_tag(), [0x5F, 0xC1, 0x0A]);
assert_eq!(
Slot::CardAuthentication.cert_object_tag(),
[0x5F, 0xC1, 0x01]
);
}
#[test]
fn verify_pin_pads_to_eight() {
assert_eq!(
verify_pin(b"123456"),
vec![0x00, 0x20, 0x00, 0x80, 0x08, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0xFF, 0xFF]
);
}
#[test]
fn verify_status_is_case1() {
assert_eq!(verify_pin_status(), vec![0x00, 0x20, 0x00, 0x80]);
}
#[test]
fn version_and_serial_apdus() {
assert_eq!(get_version(), vec![0x00, 0xFD, 0x00, 0x00, 0x00]);
assert_eq!(get_serial(), vec![0x00, 0xF8, 0x00, 0x00, 0x00]);
}
#[test]
fn unwrap_short_data_object() {
assert_eq!(
unwrap_data_object(&[0x53, 0x03, 0xAA, 0xBB, 0xCC]).unwrap(),
&[0xAA, 0xBB, 0xCC]
);
}
#[test]
fn unwrap_long_form_data_object() {
let mut buf = vec![0x53, 0x81, 0x80];
buf.extend(std::iter::repeat(0x11).take(128));
let inner = unwrap_data_object(&buf).unwrap();
assert_eq!(inner.len(), 128);
assert!(inner.iter().all(|&b| b == 0x11));
}
#[test]
fn unwrap_rejects_non_template_and_truncation() {
assert_eq!(
unwrap_data_object(&[0x70, 0x01, 0x00]),
Err(ParseError::NotDataObject)
);
assert_eq!(
unwrap_data_object(&[0x53, 0x05, 0x00]),
Err(ParseError::Truncated)
);
}
#[test]
fn parse_version_and_serial_values() {
assert_eq!(parse_version(&[5, 7, 1]).unwrap(), (5, 7, 1));
assert!(parse_version(&[5, 7]).is_err());
assert_eq!(parse_serial(&[0x02, 0x40, 0x8A, 0x1B]).unwrap(), 0x02408A1B);
assert!(parse_serial(&[0x00, 0x01]).is_err());
}
#[test]
fn mgmt_alg_round_trips_and_sizes() {
for a in [
MgmtAlg::TripleDes,
MgmtAlg::Aes128,
MgmtAlg::Aes192,
MgmtAlg::Aes256,
] {
assert_eq!(MgmtAlg::from_id(a.id()), Some(a));
}
assert_eq!(MgmtAlg::Aes192.id(), 0x0A);
assert_eq!(MgmtAlg::Aes192.block_size(), 16);
assert_eq!(MgmtAlg::Aes192.key_len(), 24);
assert_eq!(MgmtAlg::TripleDes.block_size(), 8);
assert_eq!(MgmtAlg::Aes256.key_len(), 32);
assert_eq!(MgmtAlg::from_id(0x99), None);
}
#[test]
fn key_alg_round_trips() {
for a in [
KeyAlg::Rsa1024,
KeyAlg::Rsa2048,
KeyAlg::Rsa3072,
KeyAlg::Rsa4096,
KeyAlg::EccP256,
KeyAlg::EccP384,
KeyAlg::Ed25519,
KeyAlg::X25519,
] {
assert_eq!(KeyAlg::from_id(a.id()), Some(a));
}
assert_eq!(KeyAlg::Rsa2048.id(), 0x07);
assert_eq!(KeyAlg::EccP256.id(), 0x11);
}
#[test]
fn witness_request_bytes() {
assert_eq!(
general_auth_request_witness(MgmtAlg::Aes192, KEY_REF_MANAGEMENT),
vec![0x00, 0x87, 0x0A, 0x9B, 0x04, 0x7C, 0x02, 0x80, 0x00, 0x00]
);
}
#[test]
fn mutual_auth_bytes_aes() {
let w = [0xAAu8; 16];
let c = [0xBBu8; 16];
let apdu = general_auth_mutual(MgmtAlg::Aes192, KEY_REF_MANAGEMENT, &w, &c);
assert_eq!(&apdu[..5], &[0x00, 0x87, 0x0A, 0x9B, 0x26]); assert_eq!(&apdu[5..9], &[0x7C, 0x24, 0x80, 0x10]);
assert_eq!(&apdu[9..25], &w);
assert_eq!(&apdu[25..27], &[0x81, 0x10]);
assert_eq!(&apdu[27..43], &c);
assert_eq!(apdu[43], 0x00); }
#[test]
fn generate_key_bytes_default_policy() {
assert_eq!(
generate_key(
Slot::Authentication,
KeyAlg::EccP256,
PinPolicy::Default,
TouchPolicy::Default
),
vec![0x00, 0x47, 0x00, 0x9A, 0x05, 0xAC, 0x03, 0x80, 0x01, 0x11, 0x00]
);
}
#[test]
fn generate_key_bytes_with_policies() {
assert_eq!(
generate_key(
Slot::Signature,
KeyAlg::Rsa2048,
PinPolicy::Once,
TouchPolicy::Always
),
vec![
0x00, 0x47, 0x00, 0x9C, 0x0B, 0xAC, 0x09, 0x80, 0x01, 0x07, 0xAA, 0x01, 0x02, 0xAB,
0x01, 0x02, 0x00
]
);
}
#[test]
fn change_pin_bytes() {
let apdu = change_reference(PIN_REF_APPLICATION, b"123456", b"654321");
assert_eq!(&apdu[..5], &[0x00, 0x24, 0x00, 0x80, 0x10]);
assert_eq!(
&apdu[5..],
&[
0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0xFF, 0xFF, 0x36, 0x35, 0x34, 0x33, 0x32, 0x31, 0xFF, 0xFF, ]
);
}
#[test]
fn unblock_pin_bytes() {
let apdu = unblock_pin(b"12345678", b"000000");
assert_eq!(&apdu[..5], &[0x00, 0x2C, 0x00, 0x80, 0x10]);
assert_eq!(&apdu[5..13], b"12345678");
assert_eq!(
&apdu[13..],
&[0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0xFF, 0xFF]
);
}
#[test]
fn set_management_key_bytes() {
let key = [0x42u8; 24];
let apdu = set_management_key(MgmtAlg::Aes192, &key, false);
assert_eq!(&apdu[..5], &[0x00, 0xFF, 0xFF, 0xFF, 0x1B]);
assert_eq!(&apdu[5..8], &[0x0A, 0x9B, 0x18]);
assert_eq!(&apdu[8..], &key);
assert_eq!(set_management_key(MgmtAlg::Aes192, &key, true)[3], 0xFE);
}
#[test]
fn set_pin_retries_and_reset_and_metadata_bytes() {
assert_eq!(set_pin_retries(5, 3), vec![0x00, 0xFA, 0x05, 0x03]);
assert_eq!(reset(), vec![0x00, 0xFB, 0x00, 0x00]);
assert_eq!(get_metadata(0x9B), vec![0x00, 0xF7, 0x00, 0x9B]);
}
#[test]
fn delete_key_kat_all_slots() {
assert_eq!(delete_key(Slot::Signature), vec![0x00, 0xF6, 0xFF, 0x9C]);
assert_eq!(
delete_key(Slot::Authentication),
vec![0x00, 0xF6, 0xFF, 0x9A]
);
assert_eq!(
delete_key(Slot::KeyManagement),
vec![0x00, 0xF6, 0xFF, 0x9D]
);
assert_eq!(
delete_key(Slot::CardAuthentication),
vec![0x00, 0xF6, 0xFF, 0x9E]
);
}
#[test]
fn clear_certificate_kat_all_slots() {
assert_eq!(
clear_certificate(Slot::Authentication),
vec![0x00, 0xDB, 0x3F, 0xFF, 0x07, 0x5C, 0x03, 0x5F, 0xC1, 0x05, 0x53, 0x00]
);
assert_eq!(
clear_certificate(Slot::Signature),
vec![0x00, 0xDB, 0x3F, 0xFF, 0x07, 0x5C, 0x03, 0x5F, 0xC1, 0x0A, 0x53, 0x00]
);
assert_eq!(
clear_certificate(Slot::KeyManagement),
vec![0x00, 0xDB, 0x3F, 0xFF, 0x07, 0x5C, 0x03, 0x5F, 0xC1, 0x0B, 0x53, 0x00]
);
assert_eq!(
clear_certificate(Slot::CardAuthentication),
vec![0x00, 0xDB, 0x3F, 0xFF, 0x07, 0x5C, 0x03, 0x5F, 0xC1, 0x01, 0x53, 0x00]
);
}
#[test]
fn put_data_short_object() {
let apdu = put_data(&OBJECT_CHUID, &[0xDE, 0xAD]);
assert_eq!(&apdu[..4], &[0x00, 0xDB, 0x3F, 0xFF]);
assert_eq!(apdu[4], 0x09); assert_eq!(
&apdu[5..],
&[0x5C, 0x03, 0x5F, 0xC1, 0x02, 0x53, 0x02, 0xDE, 0xAD]
);
}
#[test]
fn put_data_large_object_uses_extended_apdu() {
let der = vec![0x11u8; 1024];
let value = encode_certificate(&der);
let apdu = put_data(&Slot::Signature.cert_object_tag(), &value);
assert_eq!(&apdu[..5], &[0x00, 0xDB, 0x3F, 0xFF, 0x00]); let lc = ((apdu[5] as usize) << 8) | apdu[6] as usize;
assert_eq!(lc, apdu.len() - 7); }
#[test]
fn encode_certificate_wraps_der() {
let der = [0xAB, 0xCD, 0xEF];
assert_eq!(
encode_certificate(&der),
vec![0x70, 0x03, 0xAB, 0xCD, 0xEF, 0x71, 0x01, 0x00, 0xFE, 0x00]
);
}
#[test]
fn parse_general_auth_extracts_witness() {
let mut buf = vec![0x7C, 0x0A, 0x80, 0x08];
buf.extend_from_slice(&[1, 2, 3, 4, 5, 6, 7, 8]);
assert_eq!(
parse_general_auth(&buf, 0x80).unwrap(),
&[1, 2, 3, 4, 5, 6, 7, 8]
);
assert_eq!(
parse_general_auth(&[0x70, 0x02, 0x80, 0x00], 0x80),
Err(ParseError::NotAuthTemplate)
);
}
#[test]
fn parse_public_key_rsa_and_ecc() {
let mut rsa = vec![
0x7F, 0x49, 0x0B, 0x81, 0x04, 0xAA, 0xBB, 0xCC, 0xDD, 0x82, 0x03,
];
rsa.extend_from_slice(&[0x01, 0x00, 0x01]);
match parse_public_key(&rsa).unwrap() {
PublicKey::Rsa { modulus, exponent } => {
assert_eq!(modulus, vec![0xAA, 0xBB, 0xCC, 0xDD]);
assert_eq!(exponent, vec![0x01, 0x00, 0x01]);
}
_ => panic!("expected RSA"),
}
let ecc = vec![0x7F, 0x49, 0x06, 0x86, 0x04, 0x04, 0x11, 0x22, 0x33];
match parse_public_key(&ecc).unwrap() {
PublicKey::Ecc { point } => assert_eq!(point, vec![0x04, 0x11, 0x22, 0x33]),
_ => panic!("expected ECC"),
}
}
#[test]
fn parse_metadata_mgmt_and_pin() {
let md =
parse_metadata(&[0x01, 0x01, 0x0A, 0x02, 0x02, 0x00, 0x01, 0x05, 0x01, 0x01]).unwrap();
assert_eq!(md.algorithm, Some(0x0A));
assert_eq!(md.is_default, Some(true));
assert_eq!(md.policy, Some((0x00, 0x01)));
let pin = parse_metadata(&[0x06, 0x02, 0x03, 0x03, 0x05, 0x01, 0x00]).unwrap();
assert_eq!(pin.retries, Some((3, 3)));
assert_eq!(pin.is_default, Some(false));
}
#[test]
fn parse_metadata_origin_and_public_key() {
let md = parse_metadata(&[
0x01, 0x01, 0x11, 0x03, 0x01, 0x01, 0x04, 0x04, 0x86, 0x02, 0xAA, 0xBB,
])
.unwrap();
assert_eq!(md.origin, Some(1));
assert_eq!(md.public_key, Some(vec![0x86, 0x02, 0xAA, 0xBB]));
}
#[test]
fn parse_metadata_rejects_garbage() {
assert_eq!(parse_metadata(&[0x06]), Err(ParseError::Truncated));
assert_eq!(
parse_metadata(&[0x01, 0x05, 0xAA]),
Err(ParseError::Truncated)
);
assert!(matches!(
parse_metadata(&[0x01, 0x80, 0x00]),
Err(ParseError::BadResponse(_))
));
}
#[test]
fn general_auth_sign_short_and_extended() {
let apdu = general_auth_sign(KeyAlg::EccP256, 0x9A, &[0xAA, 0xBB, 0xCC, 0xDD]);
assert_eq!(
apdu,
vec![
0x00, 0x87, 0x11, 0x9A, 0x0A, 0x7C, 0x08, 0x82, 0x00, 0x81, 0x04, 0xAA, 0xBB, 0xCC,
0xDD, 0x00,
]
);
let apdu = general_auth_sign(KeyAlg::Rsa2048, 0x9A, &[0x55; 256]);
assert_eq!(&apdu[..5], &[0x00, 0x87, 0x07, 0x9A, 0x00]);
let lc = ((apdu[5] as usize) << 8) | apdu[6] as usize;
assert_eq!(lc, 4 + 2 + 4 + 256); assert_eq!(&apdu[7..11], &[0x7C, 0x82, 0x01, 0x06]);
assert_eq!(&apdu[apdu.len() - 2..], &[0x00, 0x00]);
assert_eq!(apdu.len(), 7 + lc + 2);
}
#[test]
fn get_response_bytes() {
assert_eq!(get_response(), vec![0x00, 0xC0, 0x00, 0x00, 0x00]);
}
#[test]
fn pad_pin_truncates_and_pads() {
let apdu = verify_pin(b"1234567890");
assert_eq!(&apdu[5..], b"12345678");
let apdu = verify_pin(b"123456");
assert_eq!(
&apdu[5..],
&[0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0xFF, 0xFF]
);
}
#[test]
fn read_ber_len_forms() {
assert_eq!(read_ber_len(&[0x82, 0x01, 0x30]).unwrap(), (0x130, 3));
assert_eq!(read_ber_len(&[0x81, 0xC8]).unwrap(), (0xC8, 2));
assert!(matches!(
read_ber_len(&[0x80]),
Err(ParseError::BadResponse(_))
));
assert!(matches!(
read_ber_len(&[0x83, 0x01, 0x00, 0x00]),
Err(ParseError::BadResponse(_))
));
assert_eq!(read_ber_len(&[0x82, 0x01]), Err(ParseError::Truncated));
assert_eq!(read_ber_len(&[]), Err(ParseError::Truncated));
}
}