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 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_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 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()
}
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 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,
}
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);
}
}