@@ -529,6 +529,16 @@ impl VetKey {
529529 derive_symmetric_key ( self . serialize ( ) , domain_sep, output_len)
530530 }
531531
532+ /**
533+ * Return a DerivedKeyMaterial
534+ *
535+ * This class allows further key derivation and encryption but the underlying
536+ * secret key cannot be extracted.
537+ */
538+ pub fn as_derived_key_material ( & self ) -> DerivedKeyMaterial {
539+ DerivedKeyMaterial :: new ( self )
540+ }
541+
532542 /**
533543 * Deserialize a VetKey from the byte encoding
534544 *
@@ -547,6 +557,109 @@ impl VetKey {
547557 }
548558}
549559
560+ /// Key material derived from a VetKey
561+ ///
562+ /// This struct allows deriving further keys from the VetKey without
563+ /// allowing direct access to the VetKey secret key, preventing it
564+ /// from being reused inappropriately.
565+ ///
566+ /// As a convenience this struct also offers AES-GCM encryption/decryption
567+ #[ derive( Clone , Zeroize , ZeroizeOnDrop ) ]
568+ pub struct DerivedKeyMaterial {
569+ key : Vec < u8 > ,
570+ }
571+
572+ #[ derive( Copy , Clone , Eq , PartialEq , Debug ) ]
573+ /// An error while encrypting
574+ pub enum EncryptionError {
575+ /// The provided message was too long to be encrypted
576+ PlaintextTooLong ,
577+ }
578+
579+ #[ derive( Copy , Clone , Eq , PartialEq , Debug ) ]
580+ /// An error while decrypting
581+ pub enum DecryptionError {
582+ /// The ciphertext was too short to possibly be valid
583+ MessageTooShort ,
584+ /// The GCM tag did not validate
585+ InvalidCiphertext ,
586+ }
587+
588+ impl DerivedKeyMaterial {
589+ const GCM_KEY_SIZE : usize = 32 ;
590+ const GCM_TAG_SIZE : usize = 16 ;
591+ const GCM_NONCE_SIZE : usize = 12 ;
592+
593+ /// Create a new DerivedKeyMaterial
594+ fn new ( vetkey : & VetKey ) -> Self {
595+ Self {
596+ key : vetkey. serialize ( ) . to_vec ( ) ,
597+ }
598+ }
599+
600+ fn derive_aes_gcm_key ( & self , domain_sep : & str ) -> Vec < u8 > {
601+ derive_symmetric_key ( & self . key , domain_sep, Self :: GCM_KEY_SIZE )
602+ }
603+
604+ /// Encrypt a message
605+ ///
606+ /// The decryption used here is interoperable with the TypeScript
607+ /// library ic_vetkeys function `DerivedKeyMaterial.decryptMessage`
608+ pub fn encrypt_message < R : rand:: RngCore + rand:: CryptoRng > (
609+ & self ,
610+ message : & [ u8 ] ,
611+ domain_sep : & str ,
612+ rng : & mut R ,
613+ ) -> Result < Vec < u8 > , EncryptionError > {
614+ use aes_gcm:: { aead:: Aead , aead:: AeadCore , Aes256Gcm , Key , KeyInit } ;
615+ let key = self . derive_aes_gcm_key ( domain_sep) ;
616+ let key = Key :: < Aes256Gcm > :: from_slice ( & key) ;
617+ // aes_gcm::Aes256Gcm only supports/uses 12 byte nonces
618+ let nonce = Aes256Gcm :: generate_nonce ( rng) ;
619+ assert_eq ! ( nonce. len( ) , Self :: GCM_NONCE_SIZE ) ;
620+ let gcm = Aes256Gcm :: new ( key) ;
621+
622+ // The function returns an opaque `Error` with no details, but upon
623+ // examination, the only way it can fail is if the plaintext is larger
624+ // than GCM's maximum input length of 2^36 bytes.
625+ let ctext = gcm
626+ . encrypt ( & nonce, message)
627+ . map_err ( |_| EncryptionError :: PlaintextTooLong ) ?;
628+
629+ let mut res = vec ! [ ] ;
630+ res. extend_from_slice ( nonce. as_slice ( ) ) ;
631+ res. extend_from_slice ( ctext. as_slice ( ) ) ;
632+ Ok ( res)
633+ }
634+
635+ /// Decrypt a message
636+ ///
637+ /// The decryption used here is interoperable with the TypeScript
638+ /// library ic_vetkeys function `DerivedKeyMaterial.encryptMessage`
639+ pub fn decrypt_message (
640+ & self ,
641+ ctext : & [ u8 ] ,
642+ domain_sep : & str ,
643+ ) -> Result < Vec < u8 > , DecryptionError > {
644+ use aes_gcm:: { aead:: Aead , Aes256Gcm , Key , KeyInit } ;
645+ let key = self . derive_aes_gcm_key ( domain_sep) ;
646+ let key = Key :: < Aes256Gcm > :: from_slice ( & key) ;
647+
648+ // Minimum possible length 12 bytes nonce plus 16 bytes GCM tag
649+ if ctext. len ( ) < Self :: GCM_NONCE_SIZE + Self :: GCM_TAG_SIZE {
650+ return Err ( DecryptionError :: MessageTooShort ) ;
651+ }
652+ let nonce = aes_gcm:: Nonce :: from_slice ( & ctext[ 0 ..Self :: GCM_NONCE_SIZE ] ) ;
653+ let gcm = Aes256Gcm :: new ( key) ;
654+
655+ let ptext = gcm
656+ . decrypt ( nonce, & ctext[ Self :: GCM_NONCE_SIZE ..] )
657+ . map_err ( |_| DecryptionError :: InvalidCiphertext ) ?;
658+
659+ Ok ( ptext. as_slice ( ) . to_vec ( ) )
660+ }
661+ }
662+
550663#[ derive( Copy , Clone , Debug ) ]
551664/// Error indicating that deserializing an encrypted key failed
552665pub enum EncryptedVetKeyDeserializationError {
0 commit comments