Actually support AES GCM

This is an AEAD cipher, so we need some extra functionality. As another
bonus, we no longer panic if provided an IV with a different length than
the cipher's default.
This commit is contained in:
Steven Fackler 2016-11-08 20:25:57 +00:00
parent 1edb6f682e
commit 203a02c3e6
2 changed files with 100 additions and 3 deletions

View File

@ -114,6 +114,10 @@ pub const EVP_PKEY_RSA: c_int = NID_rsaEncryption;
pub const EVP_PKEY_HMAC: c_int = NID_hmac;
pub const EVP_PKEY_DSA: c_int = NID_dsa;
pub const EVP_CTRL_GCM_SET_IVLEN: c_int = 0x9;
pub const EVP_CTRL_GCM_GET_TAG: c_int = 0x10;
pub const EVP_CTRL_GCM_SET_TAG: c_int = 0x11;
pub const MBSTRING_ASC: c_int = MBSTRING_FLAG | 1;
pub const MBSTRING_BMP: c_int = MBSTRING_FLAG | 2;
pub const MBSTRING_FLAG: c_int = 0x1000;
@ -1400,6 +1404,7 @@ extern {
pub fn EVP_CIPHER_CTX_new() -> *mut EVP_CIPHER_CTX;
pub fn EVP_CIPHER_CTX_set_padding(ctx: *mut EVP_CIPHER_CTX, padding: c_int) -> c_int;
pub fn EVP_CIPHER_CTX_set_key_length(ctx: *mut EVP_CIPHER_CTX, keylen: c_int) -> c_int;
pub fn EVP_CIPHER_CTX_ctrl(ctx: *mut EVP_CIPHER_CTX, type_: c_int, arg: c_int, ptr: *mut c_void) -> c_int;
pub fn EVP_CIPHER_CTX_free(ctx: *mut EVP_CIPHER_CTX);
pub fn EVP_CipherInit(ctx: *mut EVP_CIPHER_CTX, evp: *const EVP_CIPHER,

View File

@ -135,8 +135,7 @@ impl Crypter {
///
/// # Panics
///
/// Panics if an IV is required by the cipher but not provided, or if the
/// IV's length does not match the expected length (see `Cipher::iv_len`).
/// Panics if an IV is required by the cipher but not provided.
pub fn new(t: Cipher,
mode: Mode,
key: &[u8],
@ -169,7 +168,13 @@ impl Crypter {
let key = key.as_ptr() as *mut _;
let iv = match (iv, t.iv_len()) {
(Some(iv), Some(len)) => {
assert!(iv.len() == len);
if iv.len() != len {
assert!(iv.len() <= c_int::max_value() as usize);
try!(cvt(ffi::EVP_CIPHER_CTX_ctrl(crypter.ctx,
ffi::EVP_CTRL_GCM_SET_IVLEN,
iv.len() as c_int,
ptr::null_mut())));
}
iv.as_ptr() as *mut _
}
(Some(_), None) | (None, None) => ptr::null_mut(),
@ -196,6 +201,39 @@ impl Crypter {
}
}
/// Sets the tag used to authenticate ciphertext in AEAD ciphers such as AES GCM.
///
/// When decrypting cipher text using an AEAD cipher, this must be called before `finalize`.
pub fn set_tag(&mut self, tag: &[u8]) -> Result<(), ErrorStack> {
unsafe {
assert!(tag.len() <= c_int::max_value() as usize);
// NB: this constant is actually more general than just GCM.
cvt(ffi::EVP_CIPHER_CTX_ctrl(self.ctx,
ffi::EVP_CTRL_GCM_SET_TAG,
tag.len() as c_int,
tag.as_ptr() as *mut _))
.map(|_| ())
}
}
/// Feeds Additional Authenticated Data (AAD) through the cipher.
///
/// This can only be used with AEAD ciphers such as AES GCM. Data fed in is not encrypted, but
/// is factored into the authentication tag. It must be called before the first call to
/// `update`.
pub fn aad_update(&mut self, input: &[u8]) -> Result<(), ErrorStack> {
unsafe {
assert!(input.len() <= c_int::max_value() as usize);
let mut len = 0;
cvt(ffi::EVP_CipherUpdate(self.ctx,
ptr::null_mut(),
&mut len,
input.as_ptr(),
input.len() as c_int))
.map(|_| ())
}
}
/// Feeds data from `input` through the cipher, writing encrypted/decrypted
/// bytes into `output`.
///
@ -244,6 +282,21 @@ impl Crypter {
Ok(outl as usize)
}
}
/// Retrieves the authentication tag used to authenticate ciphertext in AEAD ciphers such
/// as AES GCM.
///
/// When encrypting data with an AEAD cipher, this must be called after `finalize`.
pub fn get_tag(&self, tag: &mut [u8]) -> Result<(), ErrorStack> {
unsafe {
assert!(tag.len() <= c_int::max_value() as usize);
cvt(ffi::EVP_CIPHER_CTX_ctrl(self.ctx,
ffi::EVP_CTRL_GCM_GET_TAG,
tag.len() as c_int,
tag.as_mut_ptr() as *mut _))
.map(|_| ())
}
}
}
impl Drop for Crypter {
@ -319,6 +372,7 @@ use self::compat::*;
#[cfg(test)]
mod tests {
use serialize::hex::{FromHex, ToHex};
use super::*;
// Test vectors from FIPS-197:
// http://csrc.nist.gov/publications/fips/fips197/fips-197.pdf
@ -534,4 +588,42 @@ mod tests {
cipher_test(super::Cipher::des_ecb(), pt, ct, key, iv);
}
#[test]
fn test_aes128_gcm() {
let key = "0e00c76561d2bd9b40c3c15427e2b08f";
let iv =
"492cadaccd3ca3fbc9cf9f06eb3325c4e159850b0dbe98199b89b7af528806610b6f63998e1eae80c348e7\
4cbb921d8326631631fc6a5d304f39166daf7ea15fa1977f101819adb510b50fe9932e12c5a85aa3fd1e73\
d8d760af218be829903a77c63359d75edd91b4f6ed5465a72662f5055999e059e7654a8edc921aa0d496";
let pt =
"fef03c2d7fb15bf0d2df18007d99f967c878ad59359034f7bb2c19af120685d78e32f6b8b83b032019956c\
a9c0195721476b85";
let aad =
"d8f1163d8c840292a2b2dacf4ac7c36aff8733f18fabb4fa5594544125e03d1e6e5d6d0fd61656c8d8f327\
c92839ae5539bb469c9257f109ebff85aad7bd220fdaa95c022dbd0c7bb2d878ad504122c943045d3c5eba\
8f1f56c0";
let ct =
"4f6cf471be7cbd2575cd5a1747aea8fe9dea83e51936beac3e68f66206922060c697ffa7af80ad6bb68f2c\
f4fc97416ee52abe";
let tag = "e20b6655";
let mut crypter = Crypter::new(Cipher::aes_128_gcm(), Mode::Encrypt, &key.from_hex().unwrap(), Some(&iv.from_hex().unwrap())).unwrap();
let mut out = [0; 1024];
crypter.aad_update(&aad.from_hex().unwrap()).unwrap();
let mut nwritten = crypter.update(&pt.from_hex().unwrap(), &mut out).unwrap();
nwritten += crypter.finalize(&mut out[nwritten..]).unwrap();
assert_eq!(ct, out[..nwritten].to_hex());
let mut actual_tag = [0; 4];
crypter.get_tag(&mut actual_tag).unwrap();
assert_eq!(tag, actual_tag.to_hex());
let mut crypter = Crypter::new(Cipher::aes_128_gcm(), Mode::Decrypt, &key.from_hex().unwrap(), Some(&iv.from_hex().unwrap())).unwrap();
let mut out = [0; 1024];
crypter.aad_update(&aad.from_hex().unwrap()).unwrap();
let mut nwritten = crypter.update(&ct.from_hex().unwrap(), &mut out).unwrap();
crypter.set_tag(&tag.from_hex().unwrap()).unwrap();
nwritten += crypter.finalize(&mut out[nwritten..]).unwrap();
assert_eq!(pt, out[..nwritten].to_hex());
}
}