use serde_derive::*;
use serde_repr::*;
#[derive(Serialize, Deserialize, Debug, PartialOrd, Eq, PartialEq)]
pub enum Transport {
#[serde(rename = "bt")]
Bluetooth,
#[serde(rename = "ble")]
BluetoothLE,
#[serde(rename = "nfc")]
Nfc,
#[serde(rename = "usb")]
Usb,
#[serde(rename = "usb-internal")]
UsbInternal,
}
#[derive(Serialize, Deserialize, Clone, Debug)]
#[serde(rename_all = "camelCase")]
pub struct Registration {
pub version: String,
pub app_id: String,
pub key_handle: String,
#[serde(with = "serde_bytes")]
pub pub_key: Vec<u8>,
#[serde(with = "serde_bytes")]
pub attestation_cert: Vec<u8>,
}
#[derive(Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct RegisterRequest {
pub version: String,
pub challenge: String,
}
#[derive(Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct RegisteredKey {
pub version: String,
pub key_handle: String,
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(default)]
pub transports: Option<Vec<Transport>>,
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(default)]
pub app_id: Option<String>,
}
#[derive(Serialize, Deserialize)]
pub enum U2fRequestType {
#[serde(rename = "u2f_register_request")]
Register,
#[serde(rename = "u2f_sign_request")]
Sign,
}
#[derive(Serialize, Deserialize)]
pub enum U2fResponseType {
#[serde(rename = "u2f_register_response")]
Register,
#[serde(rename = "u2f_sign_response")]
Sign,
}
impl From<U2fRequestType> for U2fResponseType {
fn from(t: U2fRequestType) -> Self {
if let U2fRequestType::Register = t {
U2fResponseType::Register
} else {
U2fResponseType::Sign
}
}
}
impl<'a> From<&'a U2fRequestType> for U2fResponseType {
fn from(t: &'a U2fRequestType) -> Self {
if let U2fRequestType::Register = t {
U2fResponseType::Register
} else {
U2fResponseType::Sign
}
}
}
#[derive(Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct U2fRequest {
#[serde(rename = "type")]
pub req_type: U2fRequestType,
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(default)]
pub app_id: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(default)]
pub timeout_seconds: Option<u64>,
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(default)]
pub request_id: Option<u64>,
#[serde(flatten)]
pub data: Request,
}
#[derive(Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct U2fRegisterRequest {
pub register_requests: Vec<RegisterRequest>,
pub registered_keys: Vec<RegisteredKey>,
}
#[derive(Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct U2fSignRequest {
pub challenge: String,
pub registered_keys: Vec<RegisteredKey>,
}
#[derive(Serialize, Deserialize)]
#[serde(untagged)]
pub enum Request {
Register(U2fRegisterRequest),
Sign(U2fSignRequest),
}
#[derive(Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct U2fResponse {
#[serde(rename = "type")]
pub rsp_type: U2fResponseType,
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(default)]
pub request_id: Option<u64>,
pub response_data: Response,
}
#[derive(Serialize_repr, Deserialize_repr, PartialEq, Eq, Debug)]
#[repr(u8)]
pub enum ErrorCode {
Ok = 0,
OtherError = 1,
BadRequest = 2,
ConfigurationUnsupported = 3,
DeviceIneligible = 4,
Timeout = 5,
}
#[derive(Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ClientError {
pub error_code: ErrorCode,
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(default)]
pub error_message: Option<String>,
}
impl ClientError {
pub fn bad_request(msg: Option<String>) -> ClientError {
ClientError {
error_code: ErrorCode::BadRequest,
error_message: msg,
}
}
pub fn other_error(msg: Option<String>) -> ClientError {
ClientError {
error_code: ErrorCode::OtherError,
error_message: msg,
}
}
pub fn configuration_unsupported(msg: Option<String>) -> ClientError {
ClientError {
error_code: ErrorCode::ConfigurationUnsupported,
error_message: msg,
}
}
pub fn device_ineligible(msg: Option<String>) -> ClientError {
ClientError {
error_code: ErrorCode::DeviceIneligible,
error_message: msg,
}
}
pub fn timeout(msg: Option<String>) -> ClientError {
ClientError {
error_code: ErrorCode::Timeout,
error_message: msg,
}
}
}
#[derive(Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct U2fRegisterResponse {
pub version: String,
pub registration_data: String,
pub client_data: String,
}
#[derive(Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct U2fSignResponse {
pub key_handle: String,
pub signature_data: String,
pub client_data: String,
}
#[derive(Serialize, Deserialize)]
#[serde(untagged)]
pub enum Response {
Register(U2fRegisterResponse),
Sign(U2fSignResponse),
Error(ClientError),
}
#[derive(Serialize, Deserialize)]
pub enum ClientDataType {
#[serde(rename = "navigator.id.getAssertion")]
Authentication,
#[serde(rename = "navigator.id.finishEnrollment")]
Registration,
}
#[derive(Serialize, Deserialize)]
pub struct ClientData {
pub typ: ClientDataType,
pub challenge: String,
pub origin: String,
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(default)]
pub cid_pubkey: Option<String>,
}
#[test]
fn request_json_format() {
let sign_req_str = "{\"type\": \"u2f_sign_request\",\"appId\": \"https://example.com\",\"challenge\": \"YWM3OGQ5YWJhODljNzlhMDU0NTZjZDhiNmU3NWY3NGE\",\"registeredKeys\": [{\"version\": \"U2F_V2\", \"keyHandle\": \"test\", \"transports\": [\"usb\", \"nfc\"]}],\"timeoutSeconds\": 30}";
let sign_req = serde_json::from_str::<U2fRequest>(sign_req_str).unwrap();
if let U2fRequestType::Sign = sign_req.req_type {
assert_eq!(sign_req.app_id.unwrap(), "https://example.com");
assert!(sign_req.request_id.is_none());
assert_eq!(sign_req.timeout_seconds, Some(30));
if let Request::Sign(sign) = &sign_req.data {
assert_eq!(sign.challenge, "YWM3OGQ5YWJhODljNzlhMDU0NTZjZDhiNmU3NWY3NGE");
assert_eq!(sign.registered_keys.len(), 1);
assert!(sign.registered_keys[0].app_id.is_none());
assert_eq!(sign.registered_keys[0].version, "U2F_V2");
assert_eq!(sign.registered_keys[0].key_handle, "test");
assert_eq!(sign.registered_keys[0].transports, Some(vec![Transport::Usb, Transport::Nfc]));
}
} else {
panic!()
}
}