-
Notifications
You must be signed in to change notification settings - Fork 72
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[WIP] Add basic NFC support #114
base: main
Are you sure you want to change the base?
Changes from all commits
f9f1655
eb0371f
fcd16c3
1b3d8fa
e47a02a
db71f28
0de730f
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,200 @@ | ||
/* This Source Code Form is subject to the terms of the Mozilla Public | ||
* License, v. 2.0. If a copy of the MPL was not distributed with this | ||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */ | ||
|
||
use std::ffi::CString; | ||
use std::io; | ||
|
||
use crate::consts::*; | ||
use crate::util::io_err; | ||
|
||
pub trait APDUDevice { | ||
fn init_apdu(&mut self) -> io::Result<()>; | ||
fn send_apdu(&mut self, cmd: u8, p1: u8, send: &[u8]) -> io::Result<(Vec<u8>, [u8; 2])>; | ||
} | ||
|
||
//////////////////////////////////////////////////////////////////////// | ||
// Device Commands | ||
//////////////////////////////////////////////////////////////////////// | ||
|
||
pub fn apdu_register<T>(dev: &mut T, challenge: &[u8], application: &[u8]) -> io::Result<Vec<u8>> | ||
where | ||
T: APDUDevice, | ||
{ | ||
if challenge.len() != PARAMETER_SIZE || application.len() != PARAMETER_SIZE { | ||
return Err(io::Error::new( | ||
io::ErrorKind::InvalidInput, | ||
"Invalid parameter sizes", | ||
)); | ||
} | ||
|
||
let mut register_data = Vec::with_capacity(2 * PARAMETER_SIZE); | ||
register_data.extend(challenge); | ||
register_data.extend(application); | ||
|
||
let flags = U2F_REQUEST_USER_PRESENCE; | ||
let (resp, status) = dev.send_apdu(U2F_REGISTER, flags, ®ister_data)?; | ||
apdu_status_to_result(status, resp) | ||
} | ||
|
||
pub fn apdu_sign<T>( | ||
dev: &mut T, | ||
challenge: &[u8], | ||
application: &[u8], | ||
key_handle: &[u8], | ||
) -> io::Result<Vec<u8>> | ||
where | ||
T: APDUDevice, | ||
{ | ||
if challenge.len() != PARAMETER_SIZE || application.len() != PARAMETER_SIZE { | ||
return Err(io::Error::new( | ||
io::ErrorKind::InvalidInput, | ||
"Invalid parameter sizes", | ||
)); | ||
} | ||
|
||
if key_handle.len() > 256 { | ||
return Err(io::Error::new( | ||
io::ErrorKind::InvalidInput, | ||
"Key handle too large", | ||
)); | ||
} | ||
|
||
let mut sign_data = Vec::with_capacity(2 * PARAMETER_SIZE + 1 + key_handle.len()); | ||
sign_data.extend(challenge); | ||
sign_data.extend(application); | ||
sign_data.push(key_handle.len() as u8); | ||
sign_data.extend(key_handle); | ||
|
||
let flags = U2F_REQUEST_USER_PRESENCE; | ||
let (resp, status) = dev.send_apdu(U2F_AUTHENTICATE, flags, &sign_data)?; | ||
apdu_status_to_result(status, resp) | ||
} | ||
|
||
pub fn apdu_is_keyhandle_valid<T>( | ||
dev: &mut T, | ||
challenge: &[u8], | ||
application: &[u8], | ||
key_handle: &[u8], | ||
) -> io::Result<bool> | ||
where | ||
T: APDUDevice, | ||
{ | ||
if challenge.len() != PARAMETER_SIZE || application.len() != PARAMETER_SIZE { | ||
return Err(io::Error::new( | ||
io::ErrorKind::InvalidInput, | ||
"Invalid parameter sizes", | ||
)); | ||
} | ||
|
||
if key_handle.len() > 256 { | ||
return Err(io::Error::new( | ||
io::ErrorKind::InvalidInput, | ||
"Key handle too large", | ||
)); | ||
} | ||
|
||
let mut sign_data = Vec::with_capacity(2 * PARAMETER_SIZE + 1 + key_handle.len()); | ||
sign_data.extend(challenge); | ||
sign_data.extend(application); | ||
sign_data.push(key_handle.len() as u8); | ||
sign_data.extend(key_handle); | ||
|
||
let flags = U2F_CHECK_IS_REGISTERED; | ||
let (_, status) = dev.send_apdu(U2F_AUTHENTICATE, flags, &sign_data)?; | ||
Ok(status == SW_CONDITIONS_NOT_SATISFIED) | ||
} | ||
|
||
pub fn apdu_is_v2_device<T>(dev: &mut T) -> io::Result<bool> | ||
where | ||
T: APDUDevice, | ||
{ | ||
let (data, status) = dev.send_apdu(U2F_VERSION, 0x00, &[])?; | ||
let actual = CString::new(data)?; | ||
let expected = CString::new("U2F_V2")?; | ||
apdu_status_to_result(status, actual == expected) | ||
} | ||
|
||
//////////////////////////////////////////////////////////////////////// | ||
// Error Handling | ||
//////////////////////////////////////////////////////////////////////// | ||
|
||
pub fn apdu_status_to_result<T>(status: [u8; 2], val: T) -> io::Result<T> { | ||
use self::io::ErrorKind::{InvalidData, InvalidInput}; | ||
|
||
match status { | ||
SW_NO_ERROR => Ok(val), | ||
SW_WRONG_DATA => Err(io::Error::new(InvalidData, "wrong data")), | ||
SW_WRONG_LENGTH => Err(io::Error::new(InvalidInput, "wrong length")), | ||
SW_CONDITIONS_NOT_SATISFIED => Err(io_err("conditions not satisfied")), | ||
_ => Err(io_err(&format!("failed with status {:?}", status))), | ||
} | ||
} | ||
|
||
// https://en.wikipedia.org/wiki/Smart_card_application_protocol_data_unit | ||
// https://fidoalliance.org/specs/fido-u2f-v1. | ||
// 0-nfc-bt-amendment-20150514/fido-u2f-raw-message-formats.html#u2f-message-framing | ||
pub struct APDU {} | ||
|
||
impl APDU { | ||
pub fn serialize_long(ins: u8, p1: u8, data: &[u8]) -> io::Result<Vec<u8>> { | ||
let class: u8 = 0x00; | ||
if data.len() > 0xffff { | ||
return Err(io_err("payload length > 2^16")); | ||
} | ||
|
||
// Size of header + data + 2 zero bytes for maximum return size. | ||
let mut bytes = vec![0u8; U2FAPDUHEADER_SIZE + data.len() + 2]; | ||
bytes[0] = class; | ||
bytes[1] = ins; | ||
bytes[2] = p1; | ||
// p2 is always 0, at least, for our requirements. | ||
// lc[0] should always be 0. | ||
bytes[5] = (data.len() >> 8) as u8; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This should be skipped when Also, |
||
bytes[6] = data.len() as u8; | ||
bytes[7..7 + data.len()].copy_from_slice(data); | ||
|
||
// When sending zero data, the two data length bytes should be omitted. | ||
// Luckily, all later bytes are zero, so we can just truncate. | ||
if data.is_empty() { | ||
bytes.truncate(bytes.len() - 2); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is wrong - if In extended mode:
At the moment I think you're trying to go for maximum length (65536 bytes), which should have a message ending in (Edit: this previously incorrectly stated that |
||
} | ||
|
||
Ok(bytes) | ||
} | ||
|
||
// This will be used by future NFC code | ||
#[allow(dead_code)] | ||
pub fn serialize_short(ins: u8, p1: u8, data: &[u8]) -> io::Result<Vec<u8>> { | ||
let class: u8 = 0x00; | ||
if data.len() > 0xff { | ||
return Err(io_err("payload length > 2^8")); | ||
} | ||
|
||
let mut size = 5; // class, ins, p1, p2, response size field | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This should be 4: the response length bytes (Le) are omitted when the desired response length (Ne) is zero. |
||
if !data.is_empty() { | ||
size += 1 + data.len(); // data size field and data itself | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This needs a bit more description, because it appears that the data-size field is There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is already correct: when there is no command data payload (Ne=0), you omit the command length bytes (Lc). |
||
} | ||
let mut bytes = vec![0u8; size]; | ||
bytes[0] = class; | ||
bytes[1] = ins; | ||
bytes[2] = p1; | ||
// p2 is always 0, at least, for our requirements. | ||
bytes[4] = data.len() as u8; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This should be skipped when data.len() == 0 |
||
|
||
bytes[5..5 + data.len()].copy_from_slice(data); | ||
|
||
Ok(bytes) | ||
} | ||
|
||
pub fn deserialize(mut data: Vec<u8>) -> io::Result<(Vec<u8>, [u8; 2])> { | ||
if data.len() < 2 { | ||
return Err(io_err("unexpected response")); | ||
} | ||
|
||
let split_at = data.len() - 2; | ||
let status = data.split_off(split_at); | ||
|
||
Ok((data, [status[0], status[1]])) | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe construct this the same as you do
size
down inserialize_short
?