summaryrefslogtreecommitdiffstats
path: root/third_party/rust/neqo-crypto/src/selfencrypt.rs
diff options
context:
space:
mode:
Diffstat (limited to 'third_party/rust/neqo-crypto/src/selfencrypt.rs')
-rw-r--r--third_party/rust/neqo-crypto/src/selfencrypt.rs155
1 files changed, 155 insertions, 0 deletions
diff --git a/third_party/rust/neqo-crypto/src/selfencrypt.rs b/third_party/rust/neqo-crypto/src/selfencrypt.rs
new file mode 100644
index 0000000000..bcf219a4e9
--- /dev/null
+++ b/third_party/rust/neqo-crypto/src/selfencrypt.rs
@@ -0,0 +1,155 @@
+// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
+// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
+// option. This file may not be copied, modified, or distributed
+// except according to those terms.
+
+use crate::constants::{Cipher, Version};
+use crate::err::{Error, Res};
+use crate::p11::{random, SymKey};
+use crate::{hkdf, Aead};
+
+use neqo_common::{hex, qinfo, qtrace, Encoder};
+
+use std::mem;
+
+#[derive(Debug)]
+pub struct SelfEncrypt {
+ version: Version,
+ cipher: Cipher,
+ key_id: u8,
+ key: SymKey,
+ old_key: Option<SymKey>,
+}
+
+impl SelfEncrypt {
+ const VERSION: u8 = 1;
+ const SALT_LENGTH: usize = 16;
+
+ /// # Errors
+ /// Failure to generate a new HKDF key using NSS results in an error.
+ pub fn new(version: Version, cipher: Cipher) -> Res<Self> {
+ let key = hkdf::generate_key(version, cipher)?;
+ Ok(Self {
+ version,
+ cipher,
+ key_id: 0,
+ key,
+ old_key: None,
+ })
+ }
+
+ fn make_aead(&self, k: &SymKey, salt: &[u8]) -> Res<Aead> {
+ debug_assert_eq!(salt.len(), Self::SALT_LENGTH);
+ let salt = hkdf::import_key(self.version, salt)?;
+ let secret = hkdf::extract(self.version, self.cipher, Some(&salt), k)?;
+ Aead::new(self.version, self.cipher, &secret, "neqo self")
+ }
+
+ /// Rotate keys. This causes any previous key that is being held to be replaced by the current key.
+ ///
+ /// # Errors
+ /// Failure to generate a new HKDF key using NSS results in an error.
+ pub fn rotate(&mut self) -> Res<()> {
+ let new_key = hkdf::generate_key(self.version, self.cipher)?;
+ self.old_key = Some(mem::replace(&mut self.key, new_key));
+ let (kid, _) = self.key_id.overflowing_add(1);
+ self.key_id = kid;
+ qinfo!(["SelfEncrypt"], "Rotated keys to {}", self.key_id);
+ Ok(())
+ }
+
+ /// Seal an item using the underlying key. This produces a single buffer that contains
+ /// the encrypted `plaintext`, plus a version number and salt.
+ /// `aad` is only used as input to the AEAD, it is not included in the output; the
+ /// caller is responsible for carrying the AAD as appropriate.
+ ///
+ /// # Errors
+ /// Failure to protect using NSS AEAD APIs produces an error.
+ pub fn seal(&self, aad: &[u8], plaintext: &[u8]) -> Res<Vec<u8>> {
+ // Format is:
+ // struct {
+ // uint8 version;
+ // uint8 key_id;
+ // uint8 salt[16];
+ // opaque aead_encrypted(plaintext)[length as expanded];
+ // };
+ // AAD covers the entire header, plus the value of the AAD parameter that is provided.
+ let salt = random(Self::SALT_LENGTH);
+ let cipher = self.make_aead(&self.key, &salt)?;
+ let encoded_len = 2 + salt.len() + plaintext.len() + cipher.expansion();
+
+ let mut enc = Encoder::with_capacity(encoded_len);
+ enc.encode_byte(Self::VERSION);
+ enc.encode_byte(self.key_id);
+ enc.encode(&salt);
+
+ let mut extended_aad = enc.clone();
+ extended_aad.encode(aad);
+
+ let offset = enc.len();
+ let mut output: Vec<u8> = enc.into();
+ output.resize(encoded_len, 0);
+ cipher.encrypt(0, extended_aad.as_ref(), plaintext, &mut output[offset..])?;
+ qtrace!(
+ ["SelfEncrypt"],
+ "seal {} {} -> {}",
+ hex(aad),
+ hex(plaintext),
+ hex(&output)
+ );
+ Ok(output)
+ }
+
+ fn select_key(&self, kid: u8) -> Option<&SymKey> {
+ if kid == self.key_id {
+ Some(&self.key)
+ } else {
+ let (prev_key_id, _) = self.key_id.overflowing_sub(1);
+ if kid == prev_key_id {
+ self.old_key.as_ref()
+ } else {
+ None
+ }
+ }
+ }
+
+ /// Open the protected `ciphertext`.
+ ///
+ /// # Errors
+ /// Returns an error when the self-encrypted object is invalid;
+ /// when the keys have been rotated; or when NSS fails.
+ #[allow(clippy::similar_names)] // aad is similar to aead
+ pub fn open(&self, aad: &[u8], ciphertext: &[u8]) -> Res<Vec<u8>> {
+ if ciphertext[0] != Self::VERSION {
+ return Err(Error::SelfEncryptFailure);
+ }
+ let key = if let Some(k) = self.select_key(ciphertext[1]) {
+ k
+ } else {
+ return Err(Error::SelfEncryptFailure);
+ };
+ let offset = 2 + Self::SALT_LENGTH;
+
+ let mut extended_aad = Encoder::with_capacity(offset + aad.len());
+ extended_aad.encode(&ciphertext[0..offset]);
+ extended_aad.encode(aad);
+
+ let aead = self.make_aead(key, &ciphertext[2..offset])?;
+ // NSS insists on having extra space available for decryption.
+ let padded_len = ciphertext.len() - offset;
+ let mut output = vec![0; padded_len];
+ let decrypted =
+ aead.decrypt(0, extended_aad.as_ref(), &ciphertext[offset..], &mut output)?;
+ let final_len = decrypted.len();
+ output.truncate(final_len);
+ qtrace!(
+ ["SelfEncrypt"],
+ "open {} {} -> {}",
+ hex(aad),
+ hex(ciphertext),
+ hex(&output)
+ );
+ Ok(output)
+ }
+}