Make handling of Historical Bytes more robust.
Add unit tests.
This commit is contained in:
parent
9b321c5232
commit
73829a6b27
2 changed files with 260 additions and 61 deletions
|
@ -54,80 +54,279 @@ impl CardServiceData {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Split a compact-tlv "TL" (tag + length) into a 4-bit 'tag' and 4-bit
|
||||||
|
/// 'length'.
|
||||||
|
///
|
||||||
|
/// The COMPACT-TLV format has a Tag in the first nibble of a byte (bit
|
||||||
|
/// 5-8) and a length in the second nibble (bit 1-4).
|
||||||
|
fn split_tl(tl: u8) -> (u8, u8) {
|
||||||
|
let tag = (tl & 0xf0) >> 4;
|
||||||
|
let len = tl & 0x0f;
|
||||||
|
|
||||||
|
(tag, len)
|
||||||
|
}
|
||||||
|
|
||||||
impl Historical {
|
impl Historical {
|
||||||
pub fn get_card_capabilities(&self) -> Option<&CardCapabilities> {
|
pub fn get_card_capabilities(&self) -> Option<&CardCapabilities> {
|
||||||
self.cc.as_ref()
|
self.cc.as_ref()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn from(data: &[u8]) -> Result<Self, OpenpgpCardError> {
|
pub fn from(data: &[u8]) -> Result<Self, OpenpgpCardError> {
|
||||||
if data[0] == 0 {
|
let len = data.len();
|
||||||
|
|
||||||
|
if len < 4 {
|
||||||
|
// historical bytes cannot be this short
|
||||||
|
|
||||||
|
return Err(anyhow!(format!(
|
||||||
|
"Historical bytes too short ({} bytes), must be >= 4",
|
||||||
|
len
|
||||||
|
))
|
||||||
|
.into());
|
||||||
|
}
|
||||||
|
|
||||||
|
if data[0] != 0 {
|
||||||
// The OpenPGP application assumes a category indicator byte
|
// The OpenPGP application assumes a category indicator byte
|
||||||
// set to '00' (o-card 3.4.1, pg 44)
|
// set to '00' (o-card 3.4.1, pg 44)
|
||||||
|
|
||||||
let len = data.len();
|
return Err(anyhow!(
|
||||||
let cib = data[0];
|
"Unexpected category indicator in historical bytes"
|
||||||
let mut csd = None;
|
)
|
||||||
let mut cc = None;
|
.into());
|
||||||
|
}
|
||||||
|
|
||||||
// COMPACT - TLV data objects [ISO 12.1.1.2]
|
// category indicator byte
|
||||||
let mut ctlv = data[1..len - 3].to_vec();
|
let cib = data[0];
|
||||||
while !ctlv.is_empty() {
|
|
||||||
match ctlv[0] {
|
// Card service data byte
|
||||||
0x31 => {
|
let mut csd = None;
|
||||||
csd = Some(ctlv[1]);
|
|
||||||
ctlv.drain(0..2);
|
// Card capabilities
|
||||||
}
|
let mut cc = None;
|
||||||
0x73 => {
|
|
||||||
cc = Some([ctlv[1], ctlv[2], ctlv[3]]);
|
// get information from "COMPACT-TLV data objects" [ISO 12.1.1.2]
|
||||||
ctlv.drain(0..4);
|
let mut ctlv = data[1..len - 3].to_vec();
|
||||||
}
|
while !ctlv.is_empty() {
|
||||||
0 => {
|
let (t, l) = split_tl(ctlv[0]);
|
||||||
ctlv.drain(0..1);
|
|
||||||
}
|
// ctlv must still contain at least 1 + l bytes
|
||||||
_ => unimplemented!("unexpected tlv in historical bytes"),
|
// (1 byte for the tl, plus `l` bytes of data for this ctlv)
|
||||||
}
|
// (e.g. len = 4 -> tl + 3byte data)
|
||||||
|
if ctlv.len() < (1 + l as usize) {
|
||||||
|
return Err(anyhow!(
|
||||||
|
"Illegal length value in Historical Bytes TL {} len {} \
|
||||||
|
l {}",
|
||||||
|
ctlv[0],
|
||||||
|
ctlv.len(),
|
||||||
|
l
|
||||||
|
)
|
||||||
|
.into());
|
||||||
}
|
}
|
||||||
|
|
||||||
let sib = match data[len - 3] {
|
match (t, l) {
|
||||||
0 => {
|
(0x3, 0x1) => {
|
||||||
// Card does not offer life cycle management, commands
|
csd = Some(ctlv[1]);
|
||||||
// TERMINATE DF and ACTIVATE FILE are not supported
|
ctlv.drain(0..2);
|
||||||
0
|
|
||||||
}
|
}
|
||||||
3 => {
|
(0x7, 0x3) => {
|
||||||
// Initialisation state
|
cc = Some([ctlv[1], ctlv[2], ctlv[3]]);
|
||||||
// OpenPGP application can be reset to default values with
|
ctlv.drain(0..4);
|
||||||
// an ACTIVATE FILE command
|
|
||||||
3
|
|
||||||
}
|
}
|
||||||
5 => {
|
(_, _) => {
|
||||||
// Operational state (activated)
|
// Log other unexpected CTLV entries.
|
||||||
// Card supports life cycle management, commands TERMINATE
|
|
||||||
// DF and ACTIVATE FILE are available
|
|
||||||
5
|
|
||||||
}
|
|
||||||
_ => {
|
|
||||||
return Err(anyhow!(
|
|
||||||
"unexpected status indicator in \
|
|
||||||
historical bytes"
|
|
||||||
)
|
|
||||||
.into());
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Ignore final two bytes: according to the spec, they should
|
// (e.g. yubikey neo returns historical bytes as:
|
||||||
// show [0x90, 0x0] - but Yubikey Neo shows [0x0, 0x0].
|
// "[0, 73, 0, 0, 80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]")
|
||||||
// It's unclear if these status bytes are ever useful to process.
|
log::trace!(
|
||||||
|
"historical bytes: ignored (tag {}, len {})",
|
||||||
let cc = cc.map(CardCapabilities::from);
|
t,
|
||||||
let csd = csd.map(CardServiceData::from);
|
l
|
||||||
|
);
|
||||||
Ok(Self { cib, csd, cc, sib })
|
ctlv.drain(0..(l as usize + 1));
|
||||||
} else {
|
}
|
||||||
Err(anyhow!("Unexpected category indicator in historical bytes")
|
}
|
||||||
.into())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// status indicator byte
|
||||||
|
let sib = match data[len - 3] {
|
||||||
|
0 => {
|
||||||
|
// Card does not offer life cycle management, commands
|
||||||
|
// TERMINATE DF and ACTIVATE FILE are not supported
|
||||||
|
0
|
||||||
|
}
|
||||||
|
3 => {
|
||||||
|
// Initialisation state
|
||||||
|
// OpenPGP application can be reset to default values with
|
||||||
|
// an ACTIVATE FILE command
|
||||||
|
3
|
||||||
|
}
|
||||||
|
5 => {
|
||||||
|
// Operational state (activated)
|
||||||
|
// Card supports life cycle management, commands TERMINATE
|
||||||
|
// DF and ACTIVATE FILE are available
|
||||||
|
5
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
return Err(anyhow!(
|
||||||
|
"unexpected status indicator in historical bytes"
|
||||||
|
)
|
||||||
|
.into());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Ignore final two (status) bytes:
|
||||||
|
// according to the spec, they 'normally' show [0x90, 0x0] - but
|
||||||
|
// Yubikey Neo shows [0x0, 0x0].
|
||||||
|
// It's unclear if these status bytes are ever useful to process?
|
||||||
|
|
||||||
|
let cc = cc.map(CardCapabilities::from);
|
||||||
|
let csd = csd.map(CardServiceData::from);
|
||||||
|
|
||||||
|
Ok(Self { cib, csd, cc, sib })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// FIXME: add tests
|
#[cfg(test)]
|
||||||
|
mod test {
|
||||||
|
use super::*;
|
||||||
|
use anyhow::Result;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_split_tl() {
|
||||||
|
assert_eq!(split_tl(0x31), (3, 1));
|
||||||
|
assert_eq!(split_tl(0x73), (7, 3));
|
||||||
|
assert_eq!(split_tl(0x00), (0, 0));
|
||||||
|
assert_eq!(split_tl(0xff), (0xf, 0xf));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_gnuk() -> Result<()> {
|
||||||
|
// gnuk 1.2 stable
|
||||||
|
let data = [0x0, 0x31, 0x84, 0x73, 0x80, 0x1, 0x80, 0x5, 0x90, 0x0];
|
||||||
|
let hist = Historical::from(&data[..])?;
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
hist,
|
||||||
|
Historical {
|
||||||
|
cib: 0,
|
||||||
|
csd: Some(CardServiceData {
|
||||||
|
select_by_full_df_name: true,
|
||||||
|
select_by_partial_df_name: false,
|
||||||
|
dos_available_in_ef_dir: false,
|
||||||
|
dos_available_in_ef_atr_info: false,
|
||||||
|
access_services: [false, true, false,],
|
||||||
|
mf: false,
|
||||||
|
}),
|
||||||
|
cc: Some(CardCapabilities {
|
||||||
|
command_chaining: true,
|
||||||
|
extended_lc_le: false,
|
||||||
|
extended_length_information: false,
|
||||||
|
}),
|
||||||
|
sib: 5
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_floss34() -> Result<()> {
|
||||||
|
// floss shop openpgp smartcard 3.4
|
||||||
|
let data = [0x0, 0x31, 0xf5, 0x73, 0xc0, 0x1, 0x60, 0x5, 0x90, 0x0];
|
||||||
|
let hist = Historical::from(&data[..])?;
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
hist,
|
||||||
|
Historical {
|
||||||
|
cib: 0,
|
||||||
|
csd: Some(CardServiceData {
|
||||||
|
select_by_full_df_name: true,
|
||||||
|
select_by_partial_df_name: true,
|
||||||
|
dos_available_in_ef_dir: true,
|
||||||
|
dos_available_in_ef_atr_info: true,
|
||||||
|
access_services: [false, true, false,],
|
||||||
|
mf: true,
|
||||||
|
},),
|
||||||
|
cc: Some(CardCapabilities {
|
||||||
|
command_chaining: false,
|
||||||
|
extended_lc_le: true,
|
||||||
|
extended_length_information: true,
|
||||||
|
},),
|
||||||
|
sib: 5,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_yk5() -> Result<()> {
|
||||||
|
// yubikey 5
|
||||||
|
let data = [0x0, 0x73, 0x0, 0x0, 0xe0, 0x5, 0x90, 0x0];
|
||||||
|
let hist = Historical::from(&data[..])?;
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
hist,
|
||||||
|
Historical {
|
||||||
|
cib: 0,
|
||||||
|
csd: None,
|
||||||
|
cc: Some(CardCapabilities {
|
||||||
|
command_chaining: true,
|
||||||
|
extended_lc_le: true,
|
||||||
|
extended_length_information: true,
|
||||||
|
},),
|
||||||
|
sib: 5,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_yk4() -> Result<()> {
|
||||||
|
// yubikey 4
|
||||||
|
let data = [0x0, 0x73, 0x0, 0x0, 0x80, 0x5, 0x90, 0x0];
|
||||||
|
let hist = Historical::from(&data[..])?;
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
hist,
|
||||||
|
Historical {
|
||||||
|
cib: 0,
|
||||||
|
csd: None,
|
||||||
|
cc: Some(CardCapabilities {
|
||||||
|
command_chaining: true,
|
||||||
|
extended_lc_le: false,
|
||||||
|
extended_length_information: false,
|
||||||
|
},),
|
||||||
|
sib: 5,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_yk_neo() -> Result<()> {
|
||||||
|
// yubikey neo
|
||||||
|
let data = [
|
||||||
|
0x0, 0x73, 0x0, 0x0, 0x80, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||||
|
0x0, 0x0,
|
||||||
|
];
|
||||||
|
let hist = Historical::from(&data[..])?;
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
hist,
|
||||||
|
Historical {
|
||||||
|
cib: 0,
|
||||||
|
csd: None,
|
||||||
|
cc: Some(CardCapabilities {
|
||||||
|
command_chaining: true,
|
||||||
|
extended_lc_le: false,
|
||||||
|
extended_length_information: false
|
||||||
|
}),
|
||||||
|
sib: 0
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -201,7 +201,7 @@ pub struct ApplicationId {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Card Capabilities (73)
|
/// Card Capabilities (73)
|
||||||
#[derive(Debug)]
|
#[derive(Debug, PartialEq)]
|
||||||
pub struct CardCapabilities {
|
pub struct CardCapabilities {
|
||||||
command_chaining: bool,
|
command_chaining: bool,
|
||||||
extended_lc_le: bool,
|
extended_lc_le: bool,
|
||||||
|
@ -209,7 +209,7 @@ pub struct CardCapabilities {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Card service data (31)
|
/// Card service data (31)
|
||||||
#[derive(Debug)]
|
#[derive(Debug, PartialEq)]
|
||||||
pub struct CardServiceData {
|
pub struct CardServiceData {
|
||||||
select_by_full_df_name: bool,
|
select_by_full_df_name: bool,
|
||||||
select_by_partial_df_name: bool,
|
select_by_partial_df_name: bool,
|
||||||
|
@ -220,7 +220,7 @@ pub struct CardServiceData {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Historical Bytes
|
/// Historical Bytes
|
||||||
#[derive(Debug)]
|
#[derive(Debug, PartialEq)]
|
||||||
pub struct Historical {
|
pub struct Historical {
|
||||||
/// category indicator byte
|
/// category indicator byte
|
||||||
cib: u8,
|
cib: u8,
|
||||||
|
|
Loading…
Reference in a new issue