use crate::apdu::Command;
use crate::card::{Card, Retries, ShortEfId, ins};
#[cfg(feature = "verify")]
use crate::certificate::Certificate;
use crate::error::{Error, Result};
use crate::pin::Pin;
use crate::transport::Transmit;
pub const DF: [u8; 10] = [0xD3, 0x92, 0xF0, 0x00, 0x26, 0x01, 0x00, 0x00, 0x00, 0x01];
pub mod ef {
pub const SIGN_CERTIFICATE: u16 = 0x0001;
pub const SIGN_CA_CERTIFICATE: u16 = 0x0002;
pub const TOKEN_INFO: u16 = 0x0006;
pub const CERTIFICATE_AVAILABILITY: u16 = 0x0008;
pub const UNKNOWN_0009: u16 = 0x0009;
pub const TERMINAL_CA: u16 = 0x0016;
pub const AUTH_CERTIFICATE: u16 = 0x000A;
pub const AUTH_CA_CERTIFICATE: u16 = 0x000B;
pub const AUTH_KEY: u16 = 0x0017;
pub const AUTH_PIN: u16 = 0x0018;
pub const SIGN_KEY: u16 = 0x001A;
pub const SIGN_PIN: u16 = 0x001B;
}
#[derive(Debug)]
pub struct JpkiAp<'a, T> {
card: &'a mut Card<T>,
}
impl<'a, T: Transmit> JpkiAp<'a, T> {
pub fn select(card: &'a mut Card<T>) -> Result<Self> {
card.select_df(&DF)?;
Ok(JpkiAp { card })
}
pub fn card(&mut self) -> &mut Card<T> {
self.card
}
pub fn read_ef(&mut self, id: u16) -> Result<Vec<u8>> {
self.card.select_ef(id)?;
self.card.read_binary_all()
}
pub fn read_token_type(&mut self) -> Result<TokenType> {
self.card.select_ef(ef::TOKEN_INFO)?;
let raw = self.card.read_binary_physical()?;
Ok(TokenType::from_bytes(&raw))
}
pub fn read_token_info(&mut self) -> Result<TokenInfo> {
self.card.select_ef(ef::TOKEN_INFO)?;
let raw = self.card.read_binary_physical()?;
TokenInfo::parse(&raw)
}
pub fn read_certificate_availability(&mut self) -> Result<CertificateAvailability> {
self.card.select_ef(ef::CERTIFICATE_AVAILABILITY)?;
let raw = self.card.read_binary_physical()?;
CertificateAvailability::parse(&raw)
}
pub fn read_auth_certificate_der(&mut self) -> Result<Vec<u8>> {
self.read_ef(ef::AUTH_CERTIFICATE)
}
pub fn read_auth_ca_certificate_der(&mut self) -> Result<Vec<u8>> {
self.read_ef(ef::AUTH_CA_CERTIFICATE)
}
pub fn read_sign_certificate_der(&mut self) -> Result<Vec<u8>> {
self.read_ef(ef::SIGN_CERTIFICATE)
}
pub fn read_sign_ca_certificate_der(&mut self) -> Result<Vec<u8>> {
self.read_ef(ef::SIGN_CA_CERTIFICATE)
}
#[cfg(feature = "verify")]
pub fn read_auth_certificate(&mut self) -> Result<Certificate> {
Certificate::parse(&self.read_auth_certificate_der()?)
}
#[cfg(feature = "verify")]
pub fn read_auth_ca_certificate(&mut self) -> Result<Certificate> {
Certificate::parse(&self.read_auth_ca_certificate_der()?)
}
#[cfg(feature = "verify")]
pub fn read_sign_certificate(&mut self) -> Result<Certificate> {
Certificate::parse(&self.read_sign_certificate_der()?)
}
#[cfg(feature = "verify")]
pub fn read_sign_ca_certificate(&mut self) -> Result<Certificate> {
Certificate::parse(&self.read_sign_ca_certificate_der()?)
}
pub fn verify_auth_pin(&mut self, pin: &Pin) -> Result<()> {
self.card.select_ef(ef::AUTH_PIN)?;
self.card.verify(pin)
}
pub fn verify_sign_pin(&mut self, pin: &Pin) -> Result<()> {
self.card.select_ef(ef::SIGN_PIN)?;
self.card.verify(pin)
}
pub fn change_auth_pin(&mut self, current_pin: &Pin, new_pin: &Pin) -> Result<()> {
self.change_pin(ef::AUTH_PIN, current_pin, new_pin)
}
pub fn change_sign_pin(&mut self, current_pin: &Pin, new_pin: &Pin) -> Result<()> {
self.change_pin(ef::SIGN_PIN, current_pin, new_pin)
}
pub fn auth_pin_retries(&mut self) -> Result<Retries> {
self.card.select_ef(ef::AUTH_PIN)?;
self.card.pin_retries()
}
pub fn sign_pin_retries(&mut self) -> Result<Retries> {
self.card.select_ef(ef::SIGN_PIN)?;
self.card.pin_retries()
}
fn change_pin(&mut self, key: u16, current_pin: &Pin, new_pin: &Pin) -> Result<()> {
self.card.select_ef(key)?;
self.card.verify(current_pin)?;
self.card.change_reference_data(new_pin)
}
pub fn sign_with_auth_key(&mut self, scheme: SignatureScheme, data: &[u8]) -> Result<Vec<u8>> {
self.sign(ef::AUTH_KEY, scheme, data)
}
pub fn sign_with_sign_key(&mut self, scheme: SignatureScheme, data: &[u8]) -> Result<Vec<u8>> {
self.sign(ef::SIGN_KEY, scheme, data)
}
#[cfg(feature = "verify")]
pub fn sign_with_auth_key_checked(
&mut self,
scheme: SignatureScheme,
data: &[u8],
) -> Result<Vec<u8>> {
let key = self.read_auth_certificate()?.public_key()?;
let signature = self.sign_with_auth_key(scheme, data)?;
scheme.verify(&key, data, &signature)?;
Ok(signature)
}
#[cfg(feature = "verify")]
pub fn sign_with_sign_key_checked(
&mut self,
scheme: SignatureScheme,
data: &[u8],
) -> Result<Vec<u8>> {
let key = self.read_sign_certificate()?.public_key()?;
let signature = self.sign_with_sign_key(scheme, data)?;
scheme.verify(&key, data, &signature)?;
Ok(signature)
}
fn sign(&mut self, key: u16, scheme: SignatureScheme, data: &[u8]) -> Result<Vec<u8>> {
scheme.check_input(data)?;
let p2 = 0x80 | ShortEfId::from_ef_id(key)?.value();
let command =
Command::with_data_le(0x80, ins::COMPUTE_SIGNATURE, scheme.p1(), p2, data, 256);
self.card.call_ok(&command)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SignatureScheme {
Verbatim,
Sha256Bare,
PreHashedDigestInfo,
Sha256DigestInfo,
PreHashedPss,
Sha256Pss,
}
#[cfg(feature = "verify")]
impl SignatureScheme {
pub fn verify(
&self,
key: &crate::data::RsaPublicKey,
data: &[u8],
signature: &[u8],
) -> Result<()> {
use crate::data::{sha256, sha256_digest_info};
match self {
SignatureScheme::Verbatim => key.verify_pkcs1(data, signature),
SignatureScheme::Sha256Bare => key.verify_pkcs1(&sha256(data), signature),
SignatureScheme::PreHashedDigestInfo => {
key.verify_pkcs1(&sha256_digest_info(data), signature)
}
SignatureScheme::Sha256DigestInfo => key.verify_pkcs1_sha256(data, signature),
SignatureScheme::PreHashedPss => key.verify_pss_prehashed(data, signature),
SignatureScheme::Sha256Pss => key.verify_pss_sha256(data, signature),
}
}
}
impl SignatureScheme {
pub const fn p1(self) -> u8 {
match self {
SignatureScheme::Verbatim => 0x00,
SignatureScheme::Sha256Bare => 0x01,
SignatureScheme::PreHashedDigestInfo => 0x02,
SignatureScheme::Sha256DigestInfo => 0x03,
SignatureScheme::PreHashedPss => 0x04,
SignatureScheme::Sha256Pss => 0x05,
}
}
pub const fn hashes_on_card(self) -> bool {
matches!(
self,
SignatureScheme::Sha256Bare
| SignatureScheme::Sha256DigestInfo
| SignatureScheme::Sha256Pss
)
}
fn check_input(self, data: &[u8]) -> Result<()> {
let allowed = match self {
SignatureScheme::PreHashedDigestInfo | SignatureScheme::PreHashedPss => 32..=32,
SignatureScheme::Verbatim => 1..=245,
_ => 1..=255,
};
if allowed.contains(&data.len()) {
Ok(())
} else {
Err(Error::BadSigningInput {
scheme: self,
len: data.len(),
})
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::transport::mock::MockTransport;
#[test]
fn select_targets_the_jpki_aid() {
let mut card = Card::new(MockTransport::new([vec![0x90, 0x00]]));
JpkiAp::select(&mut card).unwrap();
assert_eq!(
card.transport().sent[0],
[
0x00, 0xA4, 0x04, 0x0C, 0x0A, 0xD3, 0x92, 0xF0, 0x00, 0x26, 0x01, 0x00, 0x00, 0x00,
0x01
]
);
}
#[test]
fn verifying_the_sign_password_selects_its_key_reference() {
let mut card = Card::new(MockTransport::new([
vec![0x90, 0x00], vec![0x90, 0x00], vec![0x90, 0x00], ]));
let mut jpki = JpkiAp::select(&mut card).unwrap();
jpki.verify_sign_pin(&Pin::new("PASSWORD1234").unwrap())
.unwrap();
assert_eq!(
card.transport().sent[1],
[0x00, 0xA4, 0x02, 0x0C, 0x02, 0x00, 0x1B]
);
assert_eq!(
&card.transport().sent[2][..5],
[0x00, 0x20, 0x00, 0x80, 0x0C]
);
}
#[test]
fn changing_the_auth_pin_verifies_the_old_value_before_replacing_it() {
let mut card = Card::new(MockTransport::new([
vec![0x90, 0x00], vec![0x90, 0x00], vec![0x90, 0x00], vec![0x90, 0x00], ]));
let mut jpki = JpkiAp::select(&mut card).unwrap();
jpki.change_auth_pin(
&Pin::numeric("1234").unwrap(),
&Pin::numeric("5678").unwrap(),
)
.unwrap();
assert_eq!(
card.transport().sent[1],
[0x00, 0xA4, 0x02, 0x0C, 0x02, 0x00, 0x18]
);
assert_eq!(
card.transport().sent[2],
[0x00, 0x20, 0x00, 0x80, 0x04, b'1', b'2', b'3', b'4']
);
assert_eq!(
card.transport().sent[3],
[0x00, 0x24, 0x01, 0x80, 0x04, b'5', b'6', b'7', b'8']
);
}
#[test]
fn changing_the_sign_pin_stops_when_the_old_value_is_wrong() {
let mut card = Card::new(MockTransport::new([
vec![0x90, 0x00], vec![0x90, 0x00], vec![0x63, 0xC2], ]));
let mut jpki = JpkiAp::select(&mut card).unwrap();
let err = jpki
.change_sign_pin(
&Pin::new("CURRENT1").unwrap(),
&Pin::new("REPLACEMENT2").unwrap(),
)
.unwrap_err();
assert!(matches!(err, Error::PinIncorrect { retries: Some(2) }));
assert_eq!(card.transport().sent.len(), 3);
assert_eq!(
card.transport().sent[1],
[0x00, 0xA4, 0x02, 0x0C, 0x02, 0x00, 0x1B]
);
}
#[test]
fn signing_names_the_key_in_p2_instead_of_selecting_it() {
let mut card = Card::new(MockTransport::new([
vec![0x90, 0x00], vec![0xAB, 0xCD, 0x90, 0x00], ]));
let mut jpki = JpkiAp::select(&mut card).unwrap();
let signature = jpki
.sign_with_auth_key(SignatureScheme::Verbatim, &[0x01, 0x02])
.unwrap();
assert_eq!(signature, [0xAB, 0xCD]);
assert_eq!(card.transport().sent.len(), 2, "no SELECT of the key EF");
assert_eq!(
card.transport().sent[1],
[0x80, 0x2A, 0x00, 0x97, 0x02, 0x01, 0x02, 0x00]
);
}
#[test]
fn each_scheme_has_its_own_p1_and_key() {
for (scheme, p1) in [
(SignatureScheme::Verbatim, 0x00),
(SignatureScheme::Sha256Bare, 0x01),
(SignatureScheme::PreHashedDigestInfo, 0x02),
(SignatureScheme::Sha256DigestInfo, 0x03),
(SignatureScheme::PreHashedPss, 0x04),
(SignatureScheme::Sha256Pss, 0x05),
] {
assert_eq!(scheme.p1(), p1);
let mut card = Card::new(MockTransport::new([vec![0x90, 0x00], vec![0x90, 0x00]]));
let mut jpki = JpkiAp::select(&mut card).unwrap();
jpki.sign_with_sign_key(scheme, &[0u8; 32]).unwrap();
assert_eq!(card.transport().sent[1][..4], [0x80, 0x2A, p1, 0x9A]);
}
}
#[test]
fn rejects_inputs_the_card_would_refuse() {
let mut card = Card::new(MockTransport::new([vec![0x90, 0x00]]));
let mut jpki = JpkiAp::select(&mut card).unwrap();
for scheme in [
SignatureScheme::PreHashedDigestInfo,
SignatureScheme::PreHashedPss,
] {
assert!(matches!(
jpki.sign_with_auth_key(scheme, &[0u8; 31]),
Err(Error::BadSigningInput { len: 31, .. })
));
assert!(matches!(
jpki.sign_with_auth_key(scheme, &[0u8; 33]),
Err(Error::BadSigningInput { len: 33, .. })
));
}
assert!(matches!(
jpki.sign_with_auth_key(SignatureScheme::Verbatim, &[0u8; 246]),
Err(Error::BadSigningInput { len: 246, .. })
));
assert!(matches!(
jpki.sign_with_auth_key(SignatureScheme::Verbatim, &[]),
Err(Error::BadSigningInput { len: 0, .. })
));
assert_eq!(card.transport().sent.len(), 1);
}
#[test]
fn reads_the_certificate_availability() {
let mut card = Card::new(MockTransport::new([
vec![0x90, 0x00],
vec![0x8F, 0x8F, 0x00, 0x90, 0x00],
]));
let mut ap = JpkiAp { card: &mut card };
let a = ap.read_certificate_availability().unwrap();
assert_eq!(a.raw, [0x8F, 0x8F, 0x00]);
assert!(a.has_sign_certificate() && a.has_auth_certificate());
let missing = CertificateAvailability::parse(&[0x00, 0x8F, 0x00]).unwrap();
assert!(!missing.has_sign_certificate());
assert!(missing.has_auth_certificate());
assert!(CertificateAvailability::parse(&[0x8F, 0x8F]).is_err());
assert!(CertificateAvailability::parse(&[0x8F; 16]).is_err());
}
#[test]
fn knows_which_schemes_hash_on_the_card() {
assert!(!SignatureScheme::Verbatim.hashes_on_card());
assert!(!SignatureScheme::PreHashedDigestInfo.hashes_on_card());
assert!(!SignatureScheme::PreHashedPss.hashes_on_card());
assert!(SignatureScheme::Sha256Bare.hashes_on_card());
assert!(SignatureScheme::Sha256DigestInfo.hashes_on_card());
assert!(SignatureScheme::Sha256Pss.hashes_on_card());
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct CertificateAvailability {
pub sign: u8,
pub auth: u8,
pub raw: [u8; 3],
}
impl CertificateAvailability {
pub const PRESENT: u8 = 0x8F;
pub fn parse(raw: &[u8]) -> Result<Self> {
let len = raw.len();
let raw: [u8; 3] = raw
.try_into()
.map_err(|_| Error::Malformed(format!("EF 0008 is three bytes, not {len}")))?;
Ok(CertificateAvailability {
sign: raw[0],
auth: raw[1],
raw,
})
}
pub fn has_sign_certificate(&self) -> bool {
self.sign == Self::PRESENT
}
pub fn has_auth_certificate(&self) -> bool {
self.auth == Self::PRESENT
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum TokenType {
Card,
Android,
IPhone,
Other([u8; 32]),
Absent,
}
pub mod token_flag {
pub const RNG: u32 = 0x0000_0001;
pub const LOGIN_REQUIRED: u32 = 0x0000_0004;
pub const USER_PIN_INITIALIZED: u32 = 0x0000_0008;
pub const CLOCK_ON_TOKEN: u32 = 0x0000_0040;
pub const TOKEN_INITIALIZED: u32 = 0x0000_0400;
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct TokenInfo {
pub token_type: TokenType,
pub manufacturer_id: String,
pub model: String,
pub serial_number: String,
pub flags: u32,
pub max_session_count: u32,
pub session_count: u32,
pub max_rw_session_count: u32,
pub rw_session_count: u32,
pub max_pin_len: u32,
pub min_pin_len: u32,
pub total_public_memory: u32,
pub free_public_memory: u32,
pub total_private_memory: u32,
pub free_private_memory: u32,
pub hardware_version: [u8; 2],
pub firmware_version: [u8; 2],
pub utc_time: [u8; 16],
}
impl TokenInfo {
pub const LEN: usize = 160;
pub const UNAVAILABLE_INFORMATION: u32 = u32::MAX;
pub const EFFECTIVELY_INFINITE: u32 = 0;
pub fn parse(raw: &[u8]) -> Result<Self> {
if raw.len() != Self::LEN {
return Err(Error::Malformed(format!(
"EF 0006 is {} bytes, not {}",
raw.len(),
Self::LEN
)));
}
fn text(raw: &[u8], field: &str) -> Result<String> {
std::str::from_utf8(raw)
.map(|value| value.trim_end_matches(' ').to_owned())
.map_err(|_| Error::Malformed(format!("EF 0006 {field} is not valid UTF-8")))
}
fn word(raw: &[u8], offset: usize) -> u32 {
u32::from_be_bytes(
raw[offset..offset + 4]
.try_into()
.expect("TokenInfo length was checked"),
)
}
Ok(TokenInfo {
token_type: TokenType::from_bytes(raw),
manufacturer_id: text(&raw[32..64], "manufacturer ID")?,
model: text(&raw[64..80], "model")?,
serial_number: text(&raw[80..96], "serial number")?,
flags: word(raw, 96),
max_session_count: word(raw, 100),
session_count: word(raw, 104),
max_rw_session_count: word(raw, 108),
rw_session_count: word(raw, 112),
max_pin_len: word(raw, 116),
min_pin_len: word(raw, 120),
total_public_memory: word(raw, 124),
free_public_memory: word(raw, 128),
total_private_memory: word(raw, 132),
free_private_memory: word(raw, 136),
hardware_version: raw[140..142]
.try_into()
.expect("TokenInfo length was checked"),
firmware_version: raw[142..144]
.try_into()
.expect("TokenInfo length was checked"),
utc_time: raw[144..160]
.try_into()
.expect("TokenInfo length was checked"),
})
}
pub fn has_flag(&self, flag: u32) -> bool {
self.flags & flag == flag
}
}
impl TokenType {
pub const CARD: &'static [u8; 32] = b"JPKIAPICCTOKEN2 ";
pub const ANDROID: &'static [u8; 32] = b"JPKIAPGPSETOKEN ";
pub const IPHONE: &'static [u8; 32] = b"JPKIAPIOSTOKEN ";
pub fn from_bytes(token_info: &[u8]) -> Self {
let Some(name) = token_info.get(..32) else {
return TokenType::Absent;
};
match name {
n if n == Self::CARD.as_slice() => TokenType::Card,
n if n == Self::ANDROID.as_slice() => TokenType::Android,
n if n == Self::IPHONE.as_slice() => TokenType::IPhone,
n => TokenType::Other(n.try_into().expect("32 bytes")),
}
}
pub fn name(&self) -> &str {
match self {
TokenType::Card => "JPKIAPICCTOKEN2",
TokenType::Android => "JPKIAPGPSETOKEN",
TokenType::IPhone => "JPKIAPIOSTOKEN",
TokenType::Other(n) => std::str::from_utf8(n).unwrap_or("?").trim_end(),
TokenType::Absent => "",
}
}
pub fn is_physical_card(&self) -> bool {
matches!(self, TokenType::Card)
}
}
#[cfg(test)]
mod token_tests {
use super::*;
#[test]
fn classifies_the_three_known_tokens() {
assert_eq!(TokenType::from_bytes(TokenType::CARD), TokenType::Card);
assert_eq!(
TokenType::from_bytes(TokenType::ANDROID),
TokenType::Android
);
assert_eq!(TokenType::from_bytes(TokenType::IPHONE), TokenType::IPhone);
assert!(TokenType::Card.is_physical_card());
assert!(!TokenType::Android.is_physical_card());
assert_eq!(TokenType::Card.name(), "JPKIAPICCTOKEN2");
}
#[test]
fn only_the_first_32_bytes_decide() {
let mut file = TokenType::CARD.to_vec();
file.extend_from_slice(&[0xAA; 128]);
assert_eq!(TokenType::from_bytes(&file), TokenType::Card);
}
#[test]
fn anything_else_is_kept_verbatim() {
let mut other = *TokenType::CARD;
other[14] = b'3';
match TokenType::from_bytes(&other) {
TokenType::Other(n) => assert_eq!(n, other),
t => panic!("expected Other, got {t:?}"),
}
assert_eq!(TokenType::from_bytes(b"short"), TokenType::Absent);
}
#[test]
fn parses_the_physical_card_token_info_layout() {
let mut raw = [b' '; TokenInfo::LEN];
raw[..32].copy_from_slice(TokenType::CARD);
raw[32..64].copy_from_slice(b"00000000000000000000000000000001");
raw[64..72].copy_from_slice(b"E16R01NJ");
raw[80..96].copy_from_slice(b"0000000020500003");
for (offset, value) in [
(96, 0x0000_040D),
(100, 1),
(104, 0),
(108, 1),
(112, 0),
(116, 16),
(120, 6),
(124, u32::MAX),
(128, u32::MAX),
(132, u32::MAX),
(136, u32::MAX),
] {
raw[offset..offset + 4].copy_from_slice(&value.to_be_bytes());
}
raw[140..142].copy_from_slice(b"03");
raw[142..144].copy_from_slice(b"01");
raw[144..160].fill(b'9');
let info = TokenInfo::parse(&raw).unwrap();
assert_eq!(info.token_type, TokenType::Card);
assert_eq!(info.manufacturer_id, "00000000000000000000000000000001");
assert_eq!(info.model, "E16R01NJ");
assert_eq!(info.serial_number, "0000000020500003");
assert!(info.has_flag(token_flag::RNG));
assert!(info.has_flag(token_flag::LOGIN_REQUIRED));
assert!(info.has_flag(token_flag::USER_PIN_INITIALIZED));
assert!(info.has_flag(token_flag::TOKEN_INITIALIZED));
assert!(!info.has_flag(token_flag::CLOCK_ON_TOKEN));
assert_eq!((info.max_session_count, info.session_count), (1, 0));
assert_eq!((info.max_rw_session_count, info.rw_session_count), (1, 0));
assert_eq!((info.max_pin_len, info.min_pin_len), (16, 6));
assert_eq!(info.total_public_memory, TokenInfo::UNAVAILABLE_INFORMATION);
assert_eq!(info.free_public_memory, TokenInfo::UNAVAILABLE_INFORMATION);
assert_eq!(
info.total_private_memory,
TokenInfo::UNAVAILABLE_INFORMATION
);
assert_eq!(info.free_private_memory, TokenInfo::UNAVAILABLE_INFORMATION);
assert_eq!(info.hardware_version, *b"03");
assert_eq!(info.firmware_version, *b"01");
assert_eq!(info.utc_time, [b'9'; 16]);
}
#[test]
fn token_info_requires_the_exact_structure_size() {
let error = TokenInfo::parse(&[0; TokenInfo::LEN - 1]).unwrap_err();
assert!(error.to_string().contains("159 bytes, not 160"));
}
}