@@ -20,6 +20,48 @@ pub const DERIVED_KEY_LENGTH: usize = 256 / 8;
2020#[ cfg( not( feature = "fernet-aes128" ) ) ]
2121pub const DERIVED_KEY_LENGTH : usize = 512 / 8 ;
2222
23+ pub const RATCHET_ID_LENGTH : usize = 10 ;
24+
25+ pub type RatchetId = [ u8 ; RATCHET_ID_LENGTH ] ;
26+
27+ pub fn ratchet_pub_from_priv ( priv_key_bytes : & [ u8 ] ) -> Result < [ u8 ; PUBLIC_KEY_LENGTH ] , RnsError > {
28+ if priv_key_bytes. len ( ) != PUBLIC_KEY_LENGTH {
29+ return Err ( RnsError :: InvalidArgument ) ;
30+ }
31+
32+ let mut secret_bytes = [ 0u8 ; PUBLIC_KEY_LENGTH ] ;
33+ secret_bytes. copy_from_slice ( & priv_key_bytes[ ..PUBLIC_KEY_LENGTH ] ) ;
34+ let secret = StaticSecret :: from ( secret_bytes) ;
35+
36+ Ok ( ratchet_pub_from_secret ( & secret) )
37+ }
38+
39+ pub fn ratchet_id_from_pub ( pub_key_bytes : & [ u8 ] ) -> Result < RatchetId , RnsError > {
40+ if pub_key_bytes. len ( ) != PUBLIC_KEY_LENGTH {
41+ return Err ( RnsError :: InvalidArgument ) ;
42+ }
43+
44+ let mut pub_bytes = [ 0u8 ; PUBLIC_KEY_LENGTH ] ;
45+ pub_bytes. copy_from_slice ( & pub_key_bytes[ ..PUBLIC_KEY_LENGTH ] ) ;
46+ Ok ( ratchet_id_from_pub_bytes ( & pub_bytes) )
47+ }
48+
49+ fn ratchet_pub_from_secret ( secret : & StaticSecret ) -> [ u8 ; PUBLIC_KEY_LENGTH ] {
50+ PublicKey :: from ( secret) . to_bytes ( )
51+ }
52+
53+ fn ratchet_id_from_pub_bytes ( pub_bytes : & [ u8 ; PUBLIC_KEY_LENGTH ] ) -> RatchetId {
54+ let digest = Sha256 :: new ( ) . chain_update ( pub_bytes) . finalize ( ) ;
55+ let mut id = [ 0u8 ; RATCHET_ID_LENGTH ] ;
56+ id. copy_from_slice ( & digest[ ..RATCHET_ID_LENGTH ] ) ;
57+ id
58+ }
59+
60+ fn ratchet_id_from_secret ( secret : & StaticSecret ) -> RatchetId {
61+ let pub_bytes = ratchet_pub_from_secret ( secret) ;
62+ ratchet_id_from_pub_bytes ( & pub_bytes)
63+ }
64+
2365pub trait EncryptIdentity {
2466 fn encrypt < ' a , R : CryptoRngCore + Copy > (
2567 & self ,
@@ -333,6 +375,61 @@ impl PrivateIdentity {
333375 pub fn derive_key ( & self , public_key : & PublicKey , salt : Option < & [ u8 ] > ) -> DerivedKey {
334376 DerivedKey :: new_from_private_key ( & self . private_key , public_key, salt)
335377 }
378+
379+ pub fn decrypt_token < ' a , R : CryptoRngCore + Copy > (
380+ & self ,
381+ rng : R ,
382+ ciphertext_token : & [ u8 ] ,
383+ salt : Option < & [ u8 ] > ,
384+ ratchets : & [ StaticSecret ] ,
385+ enforce_ratchets : bool ,
386+ ratchet_id : & mut Option < RatchetId > ,
387+ out_buf : & ' a mut [ u8 ] ,
388+ ) -> Result < & ' a [ u8 ] , RnsError > {
389+ * ratchet_id = None ;
390+
391+ if ciphertext_token. len ( ) <= PUBLIC_KEY_LENGTH {
392+ return Err ( RnsError :: InvalidArgument ) ;
393+ }
394+
395+ let ( peer_pub_bytes, ciphertext) = ciphertext_token. split_at ( PUBLIC_KEY_LENGTH ) ;
396+
397+ let mut peer_pub_arr = [ 0u8 ; PUBLIC_KEY_LENGTH ] ;
398+ peer_pub_arr. copy_from_slice ( peer_pub_bytes) ;
399+ let peer_pub = PublicKey :: from ( peer_pub_arr) ;
400+
401+ let default_salt = self . address_hash ( ) . as_slice ( ) ;
402+ let salt_slice = salt. unwrap_or ( default_salt) ;
403+
404+ for ratchet in ratchets {
405+ let shared = ratchet. diffie_hellman ( & peer_pub) ;
406+ let derived = DerivedKey :: new ( & shared, Some ( salt_slice) ) ;
407+ match self . decrypt ( rng, ciphertext, & derived, out_buf) {
408+ Ok ( plain_text) => {
409+ let len = plain_text. len ( ) ;
410+ * ratchet_id = Some ( ratchet_id_from_secret ( ratchet) ) ;
411+ return Ok ( & out_buf[ ..len] ) ;
412+ }
413+ Err ( RnsError :: IncorrectSignature ) | Err ( RnsError :: CryptoError ) => {
414+ continue ;
415+ }
416+ Err ( err) => return Err ( err) ,
417+ }
418+ }
419+
420+ if enforce_ratchets {
421+ return Err ( RnsError :: CryptoError ) ;
422+ }
423+
424+ let derived = self . derive_key ( & peer_pub, Some ( salt_slice) ) ;
425+ match self . decrypt ( rng, ciphertext, & derived, out_buf) {
426+ Ok ( plain_text) => {
427+ let len = plain_text. len ( ) ;
428+ Ok ( & out_buf[ ..len] )
429+ }
430+ Err ( err) => Err ( err) ,
431+ }
432+ }
336433}
337434
338435impl HashIdentity for PrivateIdentity {
@@ -455,8 +552,15 @@ impl DerivedKey {
455552#[ cfg( test) ]
456553mod tests {
457554 use rand_core:: { CryptoRng , OsRng , RngCore } ;
555+ use sha2:: { Digest , Sha256 } ;
556+ use x25519_dalek:: { PublicKey , StaticSecret } ;
557+
558+ use crate :: error:: RnsError ;
458559
459- use super :: { EncryptIdentity , PrivateIdentity , PUBLIC_KEY_LENGTH } ;
560+ use super :: {
561+ ratchet_id_from_pub, ratchet_pub_from_priv, DerivedKey , EncryptIdentity , PrivateIdentity ,
562+ PUBLIC_KEY_LENGTH , RATCHET_ID_LENGTH ,
563+ } ;
460564
461565 #[ test]
462566 fn private_identity_hex_string ( ) {
@@ -523,6 +627,136 @@ mod tests {
523627 86 , 44 , 75 , 95 , 158 , 43 , 180 , 113 , 78 , 129 , 131 , 76 , 103 ,
524628 ] ;
525629
630+ #[ test]
631+ fn ratchet_helper_roundtrip ( ) {
632+ let secret = StaticSecret :: random_from_rng ( TestRng :: new ( 0x0102030405060708 ) ) ;
633+ let secret_bytes = secret. to_bytes ( ) ;
634+ let pub_from_helper =
635+ ratchet_pub_from_priv ( & secret_bytes[ ..] ) . expect ( "ratchet public from private" ) ;
636+ let expected_pub = PublicKey :: from ( & secret) . to_bytes ( ) ;
637+ assert_eq ! ( pub_from_helper, expected_pub) ;
638+
639+ let ratchet_id = ratchet_id_from_pub ( & expected_pub[ ..] ) . expect ( "ratchet id" ) ;
640+ let digest = Sha256 :: new ( ) . chain_update ( expected_pub) . finalize ( ) ;
641+ assert_eq ! ( & ratchet_id[ ..] , & digest[ ..RATCHET_ID_LENGTH ] ) ;
642+ }
643+
644+ #[ test]
645+ fn decrypt_token_prefers_ratchet_keys ( ) {
646+ let recipient = PrivateIdentity :: new_from_name ( "ratchet-pref" ) ;
647+ let ratchet_secret = StaticSecret :: random_from_rng ( TestRng :: new ( 0x1122334455667788 ) ) ;
648+ let ratchet_public = PublicKey :: from ( & ratchet_secret) ;
649+ let ratchet_pub_bytes = ratchet_public. to_bytes ( ) ;
650+ let expected_id = ratchet_id_from_pub ( & ratchet_pub_bytes[ ..] ) . expect ( "ratchet id" ) ;
651+
652+ let derived = DerivedKey :: new_from_ephemeral_key (
653+ TestRng :: new ( 0x0f1e2d3c4b5a6978 ) ,
654+ & ratchet_public,
655+ Some ( recipient. address_hash ( ) . as_slice ( ) ) ,
656+ ) ;
657+
658+ let mut cipher_buf = [ 0u8 ; 512 ] ;
659+ let cipher = recipient
660+ . as_identity ( )
661+ . encrypt (
662+ TestRng :: new ( 0x8877665544332211 ) ,
663+ b"ratchet-msg" ,
664+ & derived,
665+ & mut cipher_buf,
666+ )
667+ . expect ( "cipher" ) ;
668+
669+ let mut plain_buf = [ 0u8 ; 512 ] ;
670+ let mut ratchet_id = None ;
671+ let ratchets = vec ! [ ratchet_secret] ;
672+ let plaintext = recipient
673+ . decrypt_token (
674+ TestRng :: new ( 0x13579bdf2468ace0 ) ,
675+ cipher,
676+ None ,
677+ ratchets. as_slice ( ) ,
678+ false ,
679+ & mut ratchet_id,
680+ & mut plain_buf,
681+ )
682+ . expect ( "ratchet decrypt" ) ;
683+
684+ assert_eq ! ( plaintext, b"ratchet-msg" ) ;
685+ assert_eq ! ( ratchet_id, Some ( expected_id) ) ;
686+ }
687+
688+ #[ test]
689+ fn decrypt_token_enforces_ratchets ( ) {
690+ let recipient = PrivateIdentity :: new_from_name ( "ratchet-enforce" ) ;
691+ let derived = recipient
692+ . as_identity ( )
693+ . derive_key ( TestRng :: new ( 0x1122aabbccddeeff ) ) ;
694+
695+ let mut cipher_buf = [ 0u8 ; 256 ] ;
696+ let cipher = recipient
697+ . as_identity ( )
698+ . encrypt (
699+ TestRng :: new ( 0xffeeddccbbaa2211 ) ,
700+ b"enforce" ,
701+ & derived,
702+ & mut cipher_buf,
703+ )
704+ . expect ( "cipher" ) ;
705+
706+ let mut plain_buf = [ 0u8 ; 256 ] ;
707+ let mut ratchet_id = None ;
708+ let result = recipient. decrypt_token (
709+ TestRng :: new ( 0x0101010101010101 ) ,
710+ cipher,
711+ None ,
712+ & [ ] ,
713+ true ,
714+ & mut ratchet_id,
715+ & mut plain_buf,
716+ ) ;
717+
718+ assert ! ( matches!( result, Err ( RnsError :: CryptoError ) ) ) ;
719+ assert ! ( ratchet_id. is_none( ) ) ;
720+ }
721+
722+ #[ test]
723+ fn decrypt_token_falls_back_without_enforcement ( ) {
724+ let recipient = PrivateIdentity :: new_from_name ( "ratchet-fallback" ) ;
725+ let derived = recipient
726+ . as_identity ( )
727+ . derive_key ( TestRng :: new ( 0x99aabbccddeeff00 ) ) ;
728+
729+ let mut cipher_buf = [ 0u8 ; 256 ] ;
730+ let cipher = recipient
731+ . as_identity ( )
732+ . encrypt (
733+ TestRng :: new ( 0x0011223344556677 ) ,
734+ b"fallback" ,
735+ & derived,
736+ & mut cipher_buf,
737+ )
738+ . expect ( "cipher" ) ;
739+
740+ let mut plain_buf = [ 0u8 ; 256 ] ;
741+ let mut ratchet_id = Some ( [ 0u8 ; RATCHET_ID_LENGTH ] ) ;
742+ let random_ratchet = StaticSecret :: random_from_rng ( TestRng :: new ( 0xabcdef0123456789 ) ) ;
743+ let ratchets = vec ! [ random_ratchet] ;
744+ let plaintext = recipient
745+ . decrypt_token (
746+ TestRng :: new ( 0x89abcdef01234567 ) ,
747+ cipher,
748+ None ,
749+ ratchets. as_slice ( ) ,
750+ false ,
751+ & mut ratchet_id,
752+ & mut plain_buf,
753+ )
754+ . expect ( "fallback decrypt" ) ;
755+
756+ assert_eq ! ( plaintext, b"fallback" ) ;
757+ assert ! ( ratchet_id. is_none( ) ) ;
758+ }
759+
526760 #[ derive( Clone , Copy ) ]
527761 struct TestRng {
528762 state : u128 ,
0 commit comments