use serde_repr::*;
use serde_derive::*;
#[derive(Serialize, Deserialize, Debug, PartialOrd, 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)]
#[serde(rename_all = "camelCase")]
pub struct Registration {
pub version: String,
pub app_id: String,
pub key_handle: String,
pub pub_key: Vec<u8>,
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,
}
#[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 req_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, 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>,
}
#[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 {
assert!(false);
}
}