ZZHLVXTPSMXE4F446Z2YEWUJWDODZA36FNJH3LRHKKPG2C3QGOPAC UHAEQPZUODJ5YVBZJPPJVLO7EBW6DC2JXHQBN26ARELAVULG3JUQC 7FRJYUI62VW257VVFQXND6OKSAILVTHGEJCXFE6CG6FIOIUTDVYAC 2WEO7OZLWJJPUYK4WXLT5FD46G2MAEIHEYMDW5GASCBUNKOPXCVAC MCS77Y4VJGB6TU2HOLASGSRW4B6MT74XABD4KYALIRS54GGN2DDQC ORSEEVB5ZBACWJH7J3BPQOLZVNNHBQU3A24VNQXI4TU4SUEVQ2JQC TAOFQAIISOBF3NJ5BG72NRG5JSTIJN4X2QZXFPB75US6WTFSK7DQC NHOSLQGG4CIWBE7VKL5MB7PSY3RZ5IVDFENMGZG6X755GGZ6B3VQC }Signature::SKEd25519 {ref signature,flags,counter,} => {let t = b"sk-ssh-ed25519@openssh.com";// sig blob: type string + sig string + flags byte + counter u32let total = t.len() + signature.0.len() + 8 + 1 + 4;bytes_.write_u32::<BigEndian>(total as u32).unwrap();bytes_.extend_ssh_string(t);bytes_.extend_ssh_string(&signature.0[..]);bytes_.push(*flags);bytes_.write_u32::<BigEndian>(*counter).unwrap();
}b"sk-ssh-ed25519@openssh.com" => {let mut p = pubkey.reader(0);let key_algo = p.read_string()?;let key_bytes = p.read_string()?;if key_algo != b"sk-ssh-ed25519@openssh.com"|| key_bytes.len() != sodium::ed25519::PUBLICKEY_BYTES{return Err(Error::CouldNotReadKey.into());}let application = std::str::from_utf8(p.read_string()?).map_err(|_| Error::CouldNotReadKey)?.to_string();let mut pk = sodium::ed25519::PublicKey {key: [0; sodium::ed25519::PUBLICKEY_BYTES],};pk.key.clone_from_slice(key_bytes);Ok(PublicKey::SKEd25519 {key: pk,application,})
}&PublicKey::SKEd25519 {ref key,ref application,} => {// ed25519_sig(64) + flags(1) + counter(4)if sig.len() != 64 + 1 + 4 {return false;}let ed25519_sig = &sig[..64];let flags = sig[64];let counter = u32::from_be_bytes([sig[65], sig[66], sig[67], sig[68]]);// Construct the verification message:// SHA256(application) || flags || counter || SHA256(original_message)let app_hash = Sha256::digest(application.as_bytes());let msg_hash = Sha256::digest(buffer);let mut verify_buf = Vec::with_capacity(32 + 1 + 4 + 32);verify_buf.extend_from_slice(&app_hash);verify_buf.push(flags);verify_buf.extend_from_slice(&counter.to_be_bytes());verify_buf.extend_from_slice(&msg_hash);sodium::ed25519::verify_detached(ed25519_sig, &verify_buf, key)
/// SK-Ed25519 key (FIDO2/U2F security key)./// The private key material is on the hardware token;/// `key_handle` is the credential ID used to identify the key on the token.SKEd25519 {public_key: sodium::ed25519::PublicKey,application: String,key_handle: Vec<u8>,},
}}fn write_sk_ed25519_signature(buffer: &mut CryptoVec, sig: &Signature) {if let Signature::SKEd25519 {ref signature,flags,counter,} = *sig{let name = SK_ED25519.0.as_bytes();let total = name.len() + signature.0.len() + 8 + 1 + 4;buffer.push_u32_be(total as u32);buffer.extend_ssh_string(name);buffer.extend_ssh_string(&signature.0);buffer.push(flags);buffer.push_u32_be(counter);}}/// Sign using a FIDO2 hardware security key.////// Performs synchronous USB HID I/O on a dedicated thread to avoid blocking/// the async executor.fn sk_ed25519_sign(to_sign: &[u8],application: &str,key_handle: &[u8],) -> Result<Signature, Error> {use ctap_hid_fido2::{FidoKeyHid, HidParam, LibCfg};// The FIDO2 authenticator signs: SHA256(application) || flags || counter || SHA256(message)// We pass SHA256(message) as the challenge; the authenticator handles the rest.let challenge: Vec<u8> = Sha256::digest(to_sign).to_vec();let application = application.to_string();let key_handle = key_handle.to_vec();// Spawn a dedicated thread so that synchronous USB HID I/O does not// block the tokio executor if called from an async context.let result = std::thread::spawn(move || -> Result<(Vec<u8>, u8, u32), Error> {let device = FidoKeyHid::new(&HidParam::get(), &LibCfg::init()).map_err(|e| {debug!("FIDO2 device error: {:?}", e);Error::Fido2(format!("failed to open FIDO2 device: {:?}", e))})?;let assertion = device.get_assertion(&application,&challenge,&[key_handle],None, // PIN).map_err(|e| {debug!("FIDO2 assertion error: {:?}", e);Error::Fido2(format!("FIDO2 assertion failed (is a PIN required?): {:?}",e))})?;Ok((assertion.signature,assertion.flags.as_u8(),assertion.sign_count,))}).join().map_err(|_| Error::Fido2("FIDO2 signing thread panicked".into()))??;let (sig_vec, flags, counter) = result;let mut sig_bytes = [0u8; 64];if sig_vec.len() != 64 {return Err(Error::Fido2(format!("unexpected FIDO2 signature length: {} (expected 64)",sig_vec.len())));
if t == b"sk-ssh-ed25519@openssh.com" {if let Ok(pubkey) = pos.read_string() {if pubkey.len() != sodium::ed25519::PUBLICKEY_BYTES {return Err(Error::CouldNotReadKey);}let application = std::str::from_utf8(pos.read_string()?).map_err(|_| Error::CouldNotReadKey)?.to_string();let mut p = sodium::ed25519::PublicKey {key: [0; sodium::ed25519::PUBLICKEY_BYTES],};p.key.clone_from_slice(pubkey);return Ok(PublicKey::SKEd25519 {key: p,application,});}}
/// Encode a Decode a PKCS#8-encoded private key.pub fn encode_pkcs8(key: &key::KeyPair) -> Vec<u8> {yasna::construct_der(|writer| {
/// Encode a PKCS#8-encoded private key.pub fn encode_pkcs8(key: &key::KeyPair) -> Result<Vec<u8>, Error> {if let key::KeyPair::SKEd25519 { .. } = *key {return Err(Error::Fido2("SK keys cannot be encoded as PKCS#8; they are hardware-bound".into(),));}Ok(yasna::construct_der(|writer| {
} else if key_type == KEYTYPE_SK_ED25519 {// OpenSSH SK-Ed25519 private key format:// string ed25519_public_key (32 bytes)// string application// uint8 flags// string key_handle// string reserved (empty)// string commentlet pubkey = position.read_string()?;let application = std::str::from_utf8(position.read_string()?).map_err(|_| Error::CouldNotReadKey)?.to_string();let _flags = position.read_byte()?;let key_handle = position.read_string()?.to_vec();let _reserved = position.read_string()?;let _comment = position.read_string()?;let mut public_key = key::ed25519::PublicKey::new_zeroed();public_key.key.clone_from_slice(pubkey);return Ok(key::KeyPair::SKEd25519 {public_key,application,key_handle,});
key::KeyPair::SKEd25519 {ref public_key,ref application,ref key_handle,} => {self.buf.extend_ssh_string(b"sk-ssh-ed25519@openssh.com");self.buf.extend_ssh_string(&public_key.key);self.buf.extend_ssh_string(application.as_bytes());// SK keys: flags + key_handle + reserved + commentself.buf.push(0x01); // flags: user presence requiredself.buf.extend_ssh_string(key_handle);self.buf.extend_ssh_string(b""); // reservedself.buf.extend_ssh_string(b""); // comment}
b"sk-ssh-ed25519@openssh.com" => {let mut p = key::ed25519::PublicKey::new_zeroed();p.key.clone_from_slice(r.read_string()?);let app_bytes = r.read_string()?;let application = std::str::from_utf8(app_bytes).map_err(|_| Error::CouldNotReadKey)?.to_string();keys.push(PublicKey::SKEd25519 {key: p,application,})}
if (hash == 2 && t == b"rsa-sha2-256") || (hash == 4 && t == b"rsa-sha2-512") || hash == 0 {
if t == b"sk-ssh-ed25519@openssh.com" {let sig = resp.read_string()?;let flags = resp.read_byte()?;let counter = resp.read_u32()?;// Write the full SK signature blob: type + sig + flags + counterlet total = t.len() + sig.len() + 8 + 1 + 4;data.push_u32_be(total as u32);data.extend_ssh_string(t);data.extend_ssh_string(sig);data.push(flags);data.push_u32_be(counter);} else if (hash == 2 && t == b"rsa-sha2-256")|| (hash == 4 && t == b"rsa-sha2-512")|| hash == 0{
let len1 = buf.len();BigEndian::write_u32(&mut buf[5..], (len1 - len0) as u32);}PublicKey::SKEd25519 {ref key,ref application,} => {buf.extend(&[0, 0, 0, 0]);let len0 = buf.len();buf.extend_ssh_string(b"sk-ssh-ed25519@openssh.com");buf.extend_ssh_string(&key.key[0..]);buf.extend_ssh_string(application.as_bytes());
let sig = s.read_string().map_err(crate::Error::from)?;
let inner_sig = s.read_string().map_err(crate::Error::from)?;// For SK keys, the signature blob also contains flags (1 byte) + counter (4 bytes)let sig: std::borrow::Cow<[u8]> =if algo_ == b"sk-ssh-ed25519@openssh.com" {let flags = s.read_byte().map_err(crate::Error::from)?;let counter = s.read_u32().map_err(crate::Error::from)?;// Pack as: ed25519_sig(64) + flags(1) + counter(4)let mut sk_sig = Vec::with_capacity(69);sk_sig.extend_from_slice(inner_sig);sk_sig.push(flags);sk_sig.extend_from_slice(&counter.to_be_bytes());std::borrow::Cow::Owned(sk_sig)} else {std::borrow::Cow::Borrowed(inner_sig)};