Git · dadoum.zone
Repositories
Help
Report an Issue
apple-crates
Code
Commits
Branches
Tags
Search
Tree:
a74924e
Branches
Tags
main
apple-crates
airdrop
src
tls.rs
Refactor and unify a lot of things for another project and implement airdrop support
Dadoum
commited
a74924e
at 2026-10-25 22:15:58
tls.rs
Blame
History
Raw
//! TLS for AirDrop, built from the selected [`Identity`]. //! //! Both directions present our certificate and decide nothing at the TLS layer //! about who the peer is. An Everyone-mode peer's certificate is self-signed; //! who signed a sender's certificate is decided by the receiver's Contacts //! policy, which verifies the chain against Apple's CA itself. So the //! verifiers below accept any certificate, but they still check every //! handshake signature: the peer must hold the key for the certificate it //! presents, or the certificate means nothing to the policy that reads it. //! //! TLS 1.2 and 1.3 only. The Python implementations allowed 1.1; no Apple //! device that speaks AirDrop over AWDL needs it. use std::fmt; use std::io; use std::sync::Arc; use crate::identity::Identity; use rustls::client::danger::{HandshakeSignatureValid, ServerCertVerified, ServerCertVerifier}; use rustls::crypto::{ CryptoProvider, WebPkiSupportedAlgorithms, verify_tls12_signature, verify_tls13_signature, }; use rustls::pki_types::pem::PemObject; use rustls::pki_types::{CertificateDer, PrivateKeyDer, ServerName, UnixTime}; use rustls::server::danger::{ClientCertVerified, ClientCertVerifier}; use rustls::{ ClientConfig, DigitallySignedStruct, DistinguishedName, ServerConfig, SignatureScheme, }; /// Why a TLS configuration could not be built. #[derive(Debug)] pub enum TlsError { /// The certificate PEM held no certificate, or did not parse. Certificate, /// The key PEM held no usable private key. Key, /// rustls refused the pair (for instance, the key does not match). Rustls(rustls::Error), } impl fmt::Display for TlsError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { TlsError::Certificate => f.write_str("the identity certificate could not be read"), TlsError::Key => f.write_str("the identity key could not be read"), TlsError::Rustls(e) => write!(f, "the identity was refused: {e}"), } } } impl std::error::Error for TlsError {} /// The one crypto provider every Omdrop connection uses. pub fn provider() -> Arc<CryptoProvider> { Arc::new(rustls::crypto::ring::default_provider()) } /// A certificate chain (leaf first) and its private key. pub type ChainAndKey = (Vec<CertificateDer<'static>>, PrivateKeyDer<'static>); /// The certificate chain (leaf first) and key of `identity`. pub fn certified_key(identity: &Identity) -> Result<ChainAndKey, TlsError> { Ok((identity.chain().to_vec(), identity.key())) } /// Whether the receiver asks the sender for a certificate. #[derive(Clone, Debug)] pub enum ClientAuth { /// No CertificateRequest at all: Everyone mode. None, /// Request a certificate, optionally, naming these CAs in the request /// (Apple's root and intermediates, as the Python receiver did), and accept /// whatever arrives. The Contacts policy reads it after the handshake from /// `ServerConnection::peer_certificates()`. Request { hint_subjects: Vec<DistinguishedName>, }, } /// The receiver's configuration. pub fn server_config( identity: &Identity, client_auth: ClientAuth, ) -> Result<Arc<ServerConfig>, TlsError> { let provider = provider(); let (chain, key) = certified_key(identity)?; let builder = ServerConfig::builder_with_provider(provider.clone()) .with_safe_default_protocol_versions() .map_err(TlsError::Rustls)?; let builder = match client_auth { ClientAuth::None => builder.with_no_client_auth(), ClientAuth::Request { hint_subjects } => { builder.with_client_cert_verifier(Arc::new(AnyClientCert { algorithms: provider.signature_verification_algorithms, hint_subjects, })) } }; builder .with_single_cert(chain, key) .map(Arc::new) .map_err(TlsError::Rustls) } /// The sender's configuration: present our certificate, accept any peer's. /// /// An Apple-issued leaf goes out with the Apple intermediate that issued it, /// as a Mac sender's does (our receiver logs a two-certificate chain from /// one). R7, 2026-10-14: an iPhone in Contacts Only closed every connection /// from a sender presenting the bare leaf. pub fn client_config(identity: &Identity) -> Result<Arc<ClientConfig>, TlsError> { client_config_with(identity, crate::apple::INTERMEDIATES_PEM) } /// The chain [`client_config`] presents: our certificate, then its Apple /// issuer when it has one. pub fn client_chain(identity: &Identity) -> Result<ChainAndKey, TlsError> { client_chain_with(identity, crate::apple::INTERMEDIATES_PEM) } fn client_chain_with( identity: &Identity, intermediates: &[&[u8]], ) -> Result<ChainAndKey, TlsError> { let (mut chain, key) = certified_key(identity)?; if let Some(issuer) = issuing_intermediate(&chain, intermediates) { chain.push(issuer); } Ok((chain, key)) } fn client_config_with( identity: &Identity, intermediates: &[&[u8]], ) -> Result<Arc<ClientConfig>, TlsError> { let provider = provider(); let (chain, key) = client_chain_with(identity, intermediates)?; ClientConfig::builder_with_provider(provider.clone()) .with_safe_default_protocol_versions() .map_err(TlsError::Rustls)? .dangerous() .with_custom_certificate_verifier(Arc::new(AnyServerCert { algorithms: provider.signature_verification_algorithms, })) .with_client_auth_cert(chain, key) .map(Arc::new) .map_err(TlsError::Rustls) } /// The certificate among `pems` that issued the last certificate of `chain`, /// when the chain does not already end in an issuer of its own: the one whose /// subject is, byte for byte, that certificate's issuer. A self-signed leaf /// names itself as issuer and gets nothing. Matching names only; no signature /// is checked, because the receiver verifies the chain anyway. pub fn issuing_intermediate( chain: &[CertificateDer<'static>], pems: &[&[u8]], ) -> Option<CertificateDer<'static>> { let last = chain.last()?; let (issuer, subject) = issuer_and_subject(last)?; if issuer == subject { return None; } pems.iter() .flat_map(|pem| CertificateDer::pem_slice_iter(pem).filter_map(Result::ok)) .find(|cand| issuer_and_subject(cand).is_some_and(|(_, s)| s == issuer)) } /// One DER TLV: (tag, the whole TLV, its content, what follows it). type Tlv<'a> = (u8, &'a [u8], &'a [u8], &'a [u8]); fn tlv(b: &[u8]) -> Option<Tlv<'_>> { let (&tag, rest) = b.split_first()?; let (&first, rest) = rest.split_first()?; let (len, rest) = if first < 0x80 { (usize::from(first), rest) } else { let n = usize::from(first & 0x7f); if n == 0 || n > 4 || rest.len() < n { return None; } let len = rest[..n] .iter() .fold(0usize, |a, &d| (a << 8) | usize::from(d)); (len, &rest[n..]) }; if rest.len() < len { return None; } let header = b.len() - rest.len(); Some((tag, &b[..header + len], &rest[..len], &rest[len..])) } /// A certificate's issuer and subject Names, as encoded DER. fn issuer_and_subject(cert: &[u8]) -> Option<(&[u8], &[u8])> { let (0x30, _, cert, _) = tlv(cert)? else { return None; }; let (0x30, _, tbs, _) = tlv(cert)? else { return None; }; let mut rest = tbs; // [0] EXPLICIT version, when present. if rest.first() == Some(&0xa0) { rest = tlv(rest)?.3; } let (_, _, _, rest) = tlv(rest)?; // serialNumber let (_, _, _, rest) = tlv(rest)?; // signature AlgorithmIdentifier let (0x30, issuer, _, rest) = tlv(rest)? else { return None; }; let (_, _, _, rest) = tlv(rest)?; // validity let (0x30, subject, _, _) = tlv(rest)? else { return None; }; Some((issuer, subject)) } /// The subject names of the certificates in these PEM blobs (Apple's root and /// intermediates, `crate::apple`), for [`ClientAuth::Request`]. pub fn hint_subjects(pems: &[&[u8]]) -> io::Result<Vec<DistinguishedName>> { let mut out = Vec::new(); for pem in pems { for cert in CertificateDer::pem_slice_iter(pem) { let cert = cert.map_err(|e| io::Error::new(io::ErrorKind::InvalidData, format!("{e:?}")))?; let anchor = webpki::anchor_from_trusted_cert(&cert) .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e.to_string()))?; out.push(DistinguishedName::in_sequence(anchor.subject.as_ref())); } } Ok(out) } /// The DER the client presented, leaf first, if any. pub fn peer_chain(conn: &rustls::ServerConnection) -> Vec<CertificateDer<'static>> { conn.peer_certificates() .map(|c| c.iter().map(|c| c.clone().into_owned()).collect()) .unwrap_or_default() } #[derive(Debug)] struct AnyServerCert { algorithms: WebPkiSupportedAlgorithms, } impl ServerCertVerifier for AnyServerCert { fn verify_server_cert( &self, _end_entity: &CertificateDer<'_>, _intermediates: &[CertificateDer<'_>], _server_name: &ServerName<'_>, _ocsp_response: &[u8], _now: UnixTime, ) -> Result<ServerCertVerified, rustls::Error> { Ok(ServerCertVerified::assertion()) } fn verify_tls12_signature( &self, message: &[u8], cert: &CertificateDer<'_>, dss: &DigitallySignedStruct, ) -> Result<HandshakeSignatureValid, rustls::Error> { verify_tls12_signature(message, cert, dss, &self.algorithms) } fn verify_tls13_signature( &self, message: &[u8], cert: &CertificateDer<'_>, dss: &DigitallySignedStruct, ) -> Result<HandshakeSignatureValid, rustls::Error> { verify_tls13_signature(message, cert, dss, &self.algorithms) } fn supported_verify_schemes(&self) -> Vec<SignatureScheme> { self.algorithms.supported_schemes() } } #[derive(Debug)] struct AnyClientCert { algorithms: WebPkiSupportedAlgorithms, hint_subjects: Vec<DistinguishedName>, } impl ClientCertVerifier for AnyClientCert { fn offer_client_auth(&self) -> bool { true } fn client_auth_mandatory(&self) -> bool { false } fn root_hint_subjects(&self) -> &[DistinguishedName] { &self.hint_subjects } fn verify_client_cert( &self, _end_entity: &CertificateDer<'_>, _intermediates: &[CertificateDer<'_>], _now: UnixTime, ) -> Result<ClientCertVerified, rustls::Error> { Ok(ClientCertVerified::assertion()) } fn verify_tls12_signature( &self, message: &[u8], cert: &CertificateDer<'_>, dss: &DigitallySignedStruct, ) -> Result<HandshakeSignatureValid, rustls::Error> { verify_tls12_signature(message, cert, dss, &self.algorithms) } fn verify_tls13_signature( &self, message: &[u8], cert: &CertificateDer<'_>, dss: &DigitallySignedStruct, ) -> Result<HandshakeSignatureValid, rustls::Error> { verify_tls13_signature(message, cert, dss, &self.algorithms) } fn supported_verify_schemes(&self) -> Vec<SignatureScheme> { self.algorithms.supported_schemes() } } #[cfg(test)] mod tests { use super::*; use rustls::{ClientConnection, ServerConnection}; fn handshake( server: &mut ServerConnection, client: &mut ClientConnection, ) -> Result<(), rustls::Error> { for _ in 0..20 { let mut buf = Vec::new(); while client.wants_write() { client.write_tls(&mut buf).unwrap(); } let mut rd = &buf[..]; while !rd.is_empty() { server.read_tls(&mut rd).unwrap(); server.process_new_packets()?; } let mut buf = Vec::new(); while server.wants_write() { server.write_tls(&mut buf).unwrap(); } let mut rd = &buf[..]; while !rd.is_empty() { client.read_tls(&mut rd).unwrap(); client.process_new_packets()?; } if !server.is_handshaking() && !client.is_handshaking() { return Ok(()); } } panic!("handshake did not finish"); } fn connect( server_id: &Identity, client_id: &Identity, auth: ClientAuth, ) -> (Result<(), rustls::Error>, ServerConnection) { let mut server = ServerConnection::new(server_config(server_id, auth).unwrap()).unwrap(); let name = ServerName::IpAddress(std::net::Ipv6Addr::LOCALHOST.into()); let mut client = ClientConnection::new(client_config(client_id).unwrap(), name).unwrap(); (handshake(&mut server, &mut client), server) } #[test] fn receiver_sees_the_senders_certificate_only_when_it_asks() { let receiver = Identity::self_signed("receiver").unwrap(); let sender = Identity::self_signed("sender").unwrap(); let (done, server) = connect(&receiver, &sender, ClientAuth::None); done.unwrap(); assert!(peer_chain(&server).is_empty()); let (done, server) = connect( &receiver, &sender, ClientAuth::Request { hint_subjects: vec![], }, ); done.unwrap(); assert_eq!(peer_chain(&server), vec![sender.leaf().clone()]); } #[test] fn the_hints_name_apples_roots() { let hints = hint_subjects(&[crate::apple::ROOT_CA_PEM]).unwrap(); assert_eq!(hints.len(), 1); } #[test] fn an_apple_leaf_would_travel_with_its_issuer() { let aai2 = CertificateDer::pem_slice_iter(crate::apple::INTERMEDIATES_PEM[1]) .next() .unwrap() .unwrap(); let root = issuing_intermediate(std::slice::from_ref(&aai2), &[crate::apple::ROOT_CA_PEM]).unwrap(); assert_ne!(root, aai2); // A self-signed certificate names itself as issuer and gets nothing. assert_eq!( issuing_intermediate(&[root], &[crate::apple::ROOT_CA_PEM]), None ); } }