From f1dacd4bea85159c37b15ff60fb34da00b695d03 Mon Sep 17 00:00:00 2001 From: pjht Date: Mon, 28 Aug 2023 09:13:54 -0500 Subject: [PATCH] Switch to libfido2 bindings --- Cargo.lock | 327 +++++++++++++++++----------------------------- Cargo.toml | 4 +- src/error.rs | 3 - src/lib.rs | 44 +++---- src/main.rs | 6 +- src/ssh_attest.rs | 39 ------ 6 files changed, 142 insertions(+), 281 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 3ba70b0..61f9fc7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,17 +2,6 @@ # It is not intended for manual editing. version = 3 -[[package]] -name = "aes" -version = "0.8.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac1f845298e95f983ff1944b728ae08b8cebab80d684f0a832ed0fc74dfa27e2" -dependencies = [ - "cfg-if", - "cipher", - "cpufeatures", -] - [[package]] name = "anyhow" version = "1.0.72" @@ -70,12 +59,6 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf" -[[package]] -name = "base64" -version = "0.21.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "604178f6c5c21f02dc555784810edfb88d34ac2c73b2eae109655649ee73ce3d" - [[package]] name = "base64ct" version = "1.6.0" @@ -91,15 +74,6 @@ dependencies = [ "generic-array", ] -[[package]] -name = "block-padding" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8894febbff9f758034a5b8e12d87918f56dfc64a8e1fe757d65e29041538d93" -dependencies = [ - "generic-array", -] - [[package]] name = "bumpalo" version = "3.13.0" @@ -112,15 +86,6 @@ version = "1.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" -[[package]] -name = "cbc" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26b52a9543ae338f279b96b0b9fed9c8093744685043739079ce85cd58f289a6" -dependencies = [ - "cipher", -] - [[package]] name = "cc" version = "1.0.82" @@ -136,33 +101,6 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" -[[package]] -name = "ciborium" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "effd91f6c78e5a4ace8a5d3c0b6bfaec9e2baaef55f3efc00e45fb2e477ee926" -dependencies = [ - "ciborium-io", - "ciborium-ll", - "serde", -] - -[[package]] -name = "ciborium-io" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cdf919175532b369853f5d5e20b26b43112613fd6fe7aee757e35f7a44642656" - -[[package]] -name = "ciborium-ll" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "defaa24ecc093c77630e6c15e17c51f5e187bf35ee514f4e2d67baaa96dae22b" -dependencies = [ - "ciborium-io", - "half", -] - [[package]] name = "cipher" version = "0.4.4" @@ -210,29 +148,6 @@ dependencies = [ "typenum", ] -[[package]] -name = "ctap-hid-fido2" -version = "3.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3726fa3f7f978ce0a222ea73c13490115f95e7a31db3061d7c4c91bea58ea01a" -dependencies = [ - "aes", - "anyhow", - "base64", - "byteorder", - "cbc", - "hex", - "hidapi", - "num", - "pad", - "ring", - "serde", - "serde_cbor", - "strum", - "strum_macros", - "x509-parser", -] - [[package]] name = "data-encoding" version = "2.4.0" @@ -325,6 +240,12 @@ dependencies = [ "zeroize", ] +[[package]] +name = "equivalent" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" + [[package]] name = "ff" version = "0.13.0" @@ -369,34 +290,10 @@ dependencies = [ ] [[package]] -name = "half" -version = "1.8.2" +name = "hashbrown" +version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eabb4a44450da02c90444cf74558da904edde8fb4e9035a9a6a4e15445af0bd7" - -[[package]] -name = "heck" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" - -[[package]] -name = "hex" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" - -[[package]] -name = "hidapi" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "798154e4b6570af74899d71155fb0072d5b17e6aa12f39c8ef22c60fb8ec99e7" -dependencies = [ - "cc", - "libc", - "pkg-config", - "winapi", -] +checksum = "2c6201b9ff9fd90a5a3bac2e56a830d0caa509576f0e503818ee82c181b3437a" [[package]] name = "hmac" @@ -407,13 +304,22 @@ dependencies = [ "digest", ] +[[package]] +name = "indexmap" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d5477fe2230a79769d8dc68e0eabf5437907c0457a5614a9e8dddb67f65eb65d" +dependencies = [ + "equivalent", + "hashbrown", +] + [[package]] name = "inout" version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a0c10553d664a4d0bcff9f4215d0aac67a639cc68ef660840afe309b807bc9f5" dependencies = [ - "block-padding", "generic-array", ] @@ -447,6 +353,26 @@ version = "0.2.147" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b4668fb0ea861c1df094127ac5f1da3409a82116a4ba74fca2e58ef927159bb3" +[[package]] +name = "libfido2" +version = "0.1.0" +dependencies = [ + "libfido2-sys", + "num_enum", + "paste", + "thiserror", +] + +[[package]] +name = "libfido2-sys" +version = "0.2.0" +dependencies = [ + "anyhow", + "cfg-if", + "pkg-config", + "vcpkg", +] + [[package]] name = "libm" version = "0.2.7" @@ -481,20 +407,6 @@ dependencies = [ "minimal-lexical", ] -[[package]] -name = "num" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b05180d69e3da0e530ba2a1dae5110317e49e3b7f3d41be227dc5f92e49ee7af" -dependencies = [ - "num-bigint", - "num-complex", - "num-integer", - "num-iter", - "num-rational", - "num-traits", -] - [[package]] name = "num-bigint" version = "0.4.3" @@ -524,12 +436,14 @@ dependencies = [ ] [[package]] -name = "num-complex" -version = "0.4.3" +name = "num-derive" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02e0d21255c828d6f128a1e41534206671e8c3ea0c62f32291e808dc82cff17d" +checksum = "9e6a0fd4f737c707bd9086cc16c925f294943eb62eb71499e9fd4cf71f8b9f4e" dependencies = [ - "num-traits", + "proc-macro2", + "quote", + "syn 2.0.28", ] [[package]] @@ -553,18 +467,6 @@ dependencies = [ "num-traits", ] -[[package]] -name = "num-rational" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0638a1c9d0a3c0914158145bc76cff373a75a627e6ecbfb71cbe6f453a5a19b0" -dependencies = [ - "autocfg", - "num-bigint", - "num-integer", - "num-traits", -] - [[package]] name = "num-traits" version = "0.2.16" @@ -575,6 +477,27 @@ dependencies = [ "libm", ] +[[package]] +name = "num_enum" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70bf6736f74634d299d00086f02986875b3c2d924781a6a2cb6c201e73da0ceb" +dependencies = [ + "num_enum_derive", +] + +[[package]] +name = "num_enum_derive" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56ea360eafe1022f7cc56cd7b869ed57330fb2453d0c7831d99b74c65d2f5597" +dependencies = [ + "proc-macro-crate", + "proc-macro2", + "quote", + "syn 2.0.28", +] + [[package]] name = "oid-registry" version = "0.6.1" @@ -615,13 +538,10 @@ dependencies = [ ] [[package]] -name = "pad" -version = "0.1.6" +name = "paste" +version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2ad9b889f1b12e0b9ee24db044b5129150d5eada288edc800f789928dc8c0e3" -dependencies = [ - "unicode-width", -] +checksum = "de3145af08024dea9fa9914f381a17b8fc6034dfb00f3a84013f7ff43f29ed4c" [[package]] name = "pem-rfc7468" @@ -674,6 +594,16 @@ dependencies = [ "elliptic-curve", ] +[[package]] +name = "proc-macro-crate" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f4c021e1093a56626774e81216a4ce732a735e5bad4868a03f3ed65ca0c3919" +dependencies = [ + "once_cell", + "toml_edit", +] + [[package]] name = "proc-macro2" version = "1.0.66" @@ -778,12 +708,6 @@ dependencies = [ "nom", ] -[[package]] -name = "rustversion" -version = "1.0.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ffc183a10b4478d04cbbbfc96d0873219d962dd5accaff2ffbd4ceb7df837f4" - [[package]] name = "sec1" version = "0.7.3" @@ -803,30 +727,6 @@ name = "serde" version = "1.0.183" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32ac8da02677876d532745a130fc9d8e6edfa81a269b107c5b00829b91d8eb3c" -dependencies = [ - "serde_derive", -] - -[[package]] -name = "serde_cbor" -version = "0.11.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2bef2ebfde456fb76bbcf9f59315333decc4fda0b2b44b420243c11e0f5ec1f5" -dependencies = [ - "half", - "serde", -] - -[[package]] -name = "serde_derive" -version = "1.0.183" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aafe972d60b0b9bee71a91b92fee2d4fb3c9d7e8f6b179aa99f27203d99a4816" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.28", -] [[package]] name = "sha2" @@ -916,33 +816,14 @@ name = "ssh_attest_verifier" version = "0.1.0" dependencies = [ "anyhow", - "ciborium", - "ctap-hid-fido2", + "libfido2", + "num-derive", "ssh-encoding", "ssh-key", "thiserror", "x509-parser", ] -[[package]] -name = "strum" -version = "0.24.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "063e6045c0e62079840579a7e47a355ae92f60eb74daaf156fb1e84ba164e63f" - -[[package]] -name = "strum_macros" -version = "0.24.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e385be0d24f186b4ce2f9982191e7101bb737312ad61c1f2f984f34bcf85d59" -dependencies = [ - "heck", - "proc-macro2", - "quote", - "rustversion", - "syn 1.0.109", -] - [[package]] name = "subtle" version = "2.5.0" @@ -985,18 +866,18 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.44" +version = "1.0.47" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "611040a08a0439f8248d1990b111c95baa9c704c805fa1f62104b39655fd7f90" +checksum = "97a802ec30afc17eee47b2855fc72e0c4cd62be9b4efe6591edde0ec5bd68d8f" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.44" +version = "1.0.47" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "090198534930841fab3a5d1bb637cde49e339654e606195f8d9c76eeb081dc96" +checksum = "6bb623b56e39ab7dcd4b1b98bb6c8f8d907ed255b18de254088016b27a8ee19b" dependencies = [ "proc-macro2", "quote", @@ -1031,6 +912,23 @@ dependencies = [ "time-core", ] +[[package]] +name = "toml_datetime" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cda73e2f1397b1262d6dfdcef8aafae14d1de7748d66822d3bfeeb6d03e5e4b" + +[[package]] +name = "toml_edit" +version = "0.19.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8123f27e969974a3dfba720fdb560be359f57b44302d280ba72e76a74480e8a" +dependencies = [ + "indexmap", + "toml_datetime", + "winnow", +] + [[package]] name = "typenum" version = "1.16.0" @@ -1043,12 +941,6 @@ version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "301abaae475aa91687eb82514b328ab47a211a533026cb25fc3e519b86adfc3c" -[[package]] -name = "unicode-width" -version = "0.1.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b" - [[package]] name = "unicode-xid" version = "0.2.4" @@ -1061,6 +953,12 @@ version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" +[[package]] +name = "vcpkg" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" + [[package]] name = "version_check" version = "0.9.4" @@ -1159,6 +1057,15 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" +[[package]] +name = "winnow" +version = "0.5.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c2e3184b9c4e92ad5167ca73039d0c42476302ab603e2fec4487511f38ccefc" +dependencies = [ + "memchr", +] + [[package]] name = "x509-parser" version = "0.15.1" diff --git a/Cargo.toml b/Cargo.toml index 952ad07..d55eed5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,8 +7,8 @@ edition = "2021" [dependencies] anyhow = "1.0.72" -ciborium = "0.2.1" -ctap-hid-fido2 = "3.5.0" +libfido2 = { version = "0.1.0", path = "../libfido2" } +num-derive = "0.4.0" ssh-encoding = "0.2.0" ssh-key = { version = "0.6.0" } thiserror = "1.0.44" diff --git a/src/error.rs b/src/error.rs index 0fd12a2..e92068e 100644 --- a/src/error.rs +++ b/src/error.rs @@ -1,4 +1,3 @@ -use ssh_key::Algorithm; use thiserror::Error; use x509_parser::prelude::X509Error; @@ -20,8 +19,6 @@ pub enum Error { InvalidRootCa(String), #[error("FIDO public key type unsupported")] UnsupportedFidoKeyType, - #[error("SSH public key type {} does not match FIDO public key type {}", .ssh.as_str(), .fido.as_str())] - SshFidoTypeMismatch { ssh: Algorithm, fido: Algorithm }, #[error("SSH public key des not match FIDO public key")] SshFidoKeyMismatch, } diff --git a/src/lib.rs b/src/lib.rs index 373d6d4..b9646c4 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,14 +1,11 @@ mod error; mod ssh_attest; -use ctap_hid_fido2::{ - public_key::PublicKeyType, - verifier, -}; pub use error::Error; +use libfido2::{cred::{Credential, CredType}, fido_init}; use ssh_attest::SshAttestationInfo; use ssh_encoding::Decode; -use ssh_key::{public::KeyData, Algorithm, PublicKey}; +use ssh_key::{public::KeyData, PublicKey, Algorithm}; pub use x509_parser; use x509_parser::{prelude::*, x509::X509Name}; @@ -25,13 +22,26 @@ pub fn verify_attestation( challenge: Vec, ssh_pubkey: PublicKey, ) -> Result<(), Error> { + fido_init(true); // Decode the SSH attestation file let attestation = SshAttestationInfo::decode(&mut ssh_attest.as_slice()) .map_err(|e| Error::AttestationDecodeError(e))?; let attestation_cert = attestation .decode_cert() .map_err(|e| Error::AttestationCertDecodeError(e))?; - let attestation = attestation.to_fido_attestation().map_err(|_e| Error::AttestationInvalidAuthData)?; + + // Reconstruct the credential from the attestation + let mut credential = Credential::new().unwrap(); + match ssh_pubkey.algorithm() { + Algorithm::SkEcdsaSha2NistP256 => credential.set_type(CredType::Ecdsa256).unwrap(), + Algorithm::SkEd25519 => credential.set_type(CredType::Ed25519).unwrap(), + _ => return Err(Error::NonFidoSshKey.into()), + }; + credential.set_authdata_raw(attestation.auth_data.as_ref().unwrap().as_slice()).unwrap(); + credential.set_sig(attestation.enroll_sig.as_slice()).unwrap(); + credential.set_clientdata(challenge.as_slice()).unwrap(); + credential.set_x509(attestation.attestation_cert.as_slice()).unwrap(); + credential.set_fmt("packed").unwrap(); // Extract the RPID from the SSH public key let application = match ssh_pubkey.key_data() { @@ -40,9 +50,10 @@ pub fn verify_attestation( _ => return Err(Error::NonFidoSshKey.into()), }; + credential.set_rp(application, "").unwrap(); + // Verify the attestation RPID & signature - let verify_result = verifier::verify_attestation(&application, &challenge, &attestation); - if !verify_result.is_success { + if credential.verify().is_err() { return Err(Error::AttestationVerificationFailed); } @@ -61,21 +72,6 @@ pub fn verify_attestation( return Err(Error::InvalidAttestationCertSignature.into()); } - let fido_pubkey = &verify_result.credential_public_key; - - // Verify the FIDO public key is the same type as the SSH public key - let fido_pubkey_alg = match fido_pubkey.key_type { - PublicKeyType::Ecdsa256 => Algorithm::SkEcdsaSha2NistP256, - PublicKeyType::Ed25519 => Algorithm::SkEd25519, - PublicKeyType::Unknown => return Err(Error::UnsupportedFidoKeyType), - }; - - if fido_pubkey_alg != ssh_pubkey.algorithm() { - return Err(Error::SshFidoTypeMismatch { - ssh: ssh_pubkey.algorithm(), - fido: fido_pubkey_alg, - }); - } // Verify that the SSH public key data matches the FIDO public key data let key_data = match ssh_pubkey.key_data() { @@ -83,7 +79,7 @@ pub fn verify_attestation( KeyData::SkEd25519(key) => key.public_key().0.as_slice(), _ => unreachable!(), }; - if key_data != fido_pubkey.der { + if key_data != credential.pubkey() { return Err(Error::SshFidoKeyMismatch.into()); } Ok(()) diff --git a/src/main.rs b/src/main.rs index e9b909c..67a0701 100644 --- a/src/main.rs +++ b/src/main.rs @@ -27,9 +27,9 @@ fn main() -> anyhow::Result<()> { }) .collect::, _>>()?; - let ssh_attest = std::fs::read("id_ecdsa_rustgen_sk_attest.bin")?; - let challenge = std::fs::read("id_ecdsa_rustgen_sk_attest_chall.bin")?; - let ssh_pubkey = ssh_key::PublicKey::read_openssh_file(Path::new("id_ecdsa_rustgen_sk.pub"))?; + let ssh_attest = std::fs::read("id_ed25519-sk_attest.bin")?; + let challenge = std::fs::read("id_ed25519-sk_attest_chall.bin")?; + let ssh_pubkey = ssh_key::PublicKey::read_openssh_file(Path::new("id_ed25519-sk.pub"))?; verify_attestation(root_cas, ssh_attest, challenge, ssh_pubkey)?; println!("Attestation verified successfully"); diff --git a/src/ssh_attest.rs b/src/ssh_attest.rs index 006d6d0..fcc9779 100644 --- a/src/ssh_attest.rs +++ b/src/ssh_attest.rs @@ -1,7 +1,5 @@ use std::str::FromStr; -use ciborium::Value; -use ctap_hid_fido2::fidokey::make_credential::{Attestation, make_credential_response}; use ssh_encoding::{Decode, Label, LabelError}; use x509_parser::{nom::Finish, prelude::*}; @@ -49,43 +47,6 @@ impl SshAttestationInfo { .finish() .map(|(_, cert)| cert) } - - pub fn to_fido_attestation(&self) -> Result { - let mut attestation_map_bytes = Vec::new(); - ciborium::into_writer( - &Value::from( - [ - (0x01.into(), "packed".into()), - ( - 0x02.into(), - self.auth_data.as_deref().unwrap().into(), - ), - ( - 0x03.into(), - [ - // TODO: Don't hardcode algorithm - // -7: ECDSA P256 - ("alg".into(), (-7).into()), - ("sig".into(), self.enroll_sig.as_slice().into()), - ( - "x5c".into(), - [Value::from(self.attestation_cert.as_slice())] - .as_slice() - .into(), - ), - ] - .as_slice() - .into(), - ), - ] - .as_slice(), - ), - &mut attestation_map_bytes, - ) - .expect("Failed to serialize CBOR attestation info"); - make_credential_response::parse_cbor(&attestation_map_bytes) - - } } impl Decode for SshAttestationInfo {