| This parts of this patch are split between dev-rust/openssl and |
| dev-rust/openssl-sys. |
| |
| It is a rebase of a pull request on top of the openssl-v0.10.32 tag: |
| https://github.com/sfackler/rust-openssl/pull/1234 |
| |
| Author: Daiki Ueno <dueno@redhat.com> |
| Date: Fri Feb 14 17:29:06 2020 +0100 |
| |
| Expose HKDF functions |
| |
| This adds functions for HKDF (HMAC-based Extract-and-Expand Key |
| Derivation Function), provided by OpenSSL 1.1.1. |
| |
| --- a/src/pkcs5.rs |
| +++ b/src/pkcs5.rs |
| @@ -2,10 +2,12 @@ use ffi; |
| use libc::c_int; |
| use std::ptr; |
| |
| -use cvt; |
| -use error::ErrorStack; |
| -use hash::MessageDigest; |
| -use symm::Cipher; |
| +use crate::cvt; |
| +use crate::cvt_p; |
| +use crate::error::ErrorStack; |
| +use crate::hash::DigestBytes; |
| +use crate::hash::MessageDigest; |
| +use crate::symm::Cipher; |
| |
| #[derive(Clone, Eq, PartialEq, Hash, Debug)] |
| pub struct KeyIvPair { |
| @@ -139,6 +141,216 @@ pub fn scrypt( |
| } |
| } |
| |
| +/// Derive a key using the HKDF algorithm, by applying `hkdf_extract` |
| +/// followed by `hkdf_expand`. |
| +/// |
| +/// Requires OpenSSL 1.1.1 or newer. |
| +#[cfg(any(ossl111))] |
| +pub fn hkdf( |
| + input: &[u8], |
| + salt: &[u8], |
| + info: &[u8], |
| + hash: MessageDigest, |
| + key: &mut [u8], |
| +) -> Result<(), ErrorStack> { |
| + unsafe { |
| + assert!(input.len() <= c_int::max_value() as usize); |
| + assert!(salt.len() <= c_int::max_value() as usize); |
| + assert!(info.len() <= c_int::max_value() as usize); |
| + assert!(key.len() <= c_int::max_value() as usize); |
| + |
| + ffi::init(); |
| + let kctx = cvt_p(ffi::EVP_PKEY_CTX_new_id( |
| + ffi::EVP_PKEY_HKDF, |
| + ptr::null_mut(), |
| + ))?; |
| + |
| + let ret = (|| { |
| + cvt(ffi::EVP_PKEY_derive_init(kctx))?; |
| + |
| + cvt(ffi::EVP_PKEY_CTX_hkdf_mode( |
| + kctx, |
| + ffi::EVP_PKEY_HKDEF_MODE_EXTRACT_AND_EXPAND, |
| + ))?; |
| + |
| + cvt(ffi::EVP_PKEY_CTX_set_hkdf_md(kctx, hash.as_ptr()))?; |
| + |
| + cvt(ffi::EVP_PKEY_CTX_set1_hkdf_key( |
| + kctx, |
| + input.as_ptr() as *const _, |
| + input.len() as c_int, |
| + ))?; |
| + |
| + cvt(ffi::EVP_PKEY_CTX_set1_hkdf_salt( |
| + kctx, |
| + salt.as_ptr() as *const _, |
| + salt.len() as c_int, |
| + ))?; |
| + |
| + cvt(ffi::EVP_PKEY_CTX_add1_hkdf_info( |
| + kctx, |
| + info.as_ptr() as *const _, |
| + info.len() as c_int, |
| + ))?; |
| + Ok(()) |
| + })(); |
| + |
| + if let Err(e) = ret { |
| + // Free memory |
| + ffi::EVP_PKEY_CTX_free(kctx); |
| + return Err(e); |
| + } |
| + |
| + let mut len = key.len(); |
| + |
| + let ret = cvt(ffi::EVP_PKEY_derive(kctx, key.as_mut_ptr(), &mut len)); |
| + |
| + // Free memory |
| + ffi::EVP_PKEY_CTX_free(kctx); |
| + |
| + if let Err(e) = ret { |
| + return Err(e); |
| + } |
| + |
| + Ok(()) |
| + } |
| +} |
| + |
| +/// Extract a pseudorandom key from an input keying material using the |
| +/// HKDF algorithm. |
| +/// |
| +/// Requires OpenSSL 1.1.1 or newer. |
| +#[cfg(any(ossl111))] |
| +pub fn hkdf_extract( |
| + input: &[u8], |
| + salt: &[u8], |
| + hash: MessageDigest, |
| +) -> Result<DigestBytes, ErrorStack> { |
| + unsafe { |
| + assert!(input.len() <= c_int::max_value() as usize); |
| + assert!(salt.len() <= c_int::max_value() as usize); |
| + |
| + ffi::init(); |
| + let kctx = cvt_p(ffi::EVP_PKEY_CTX_new_id( |
| + ffi::EVP_PKEY_HKDF, |
| + ptr::null_mut(), |
| + ))?; |
| + |
| + let ret = (|| { |
| + cvt(ffi::EVP_PKEY_derive_init(kctx))?; |
| + |
| + cvt(ffi::EVP_PKEY_CTX_hkdf_mode( |
| + kctx, |
| + ffi::EVP_PKEY_HKDEF_MODE_EXTRACT_ONLY, |
| + ))?; |
| + |
| + cvt(ffi::EVP_PKEY_CTX_set_hkdf_md(kctx, hash.as_ptr()))?; |
| + |
| + cvt(ffi::EVP_PKEY_CTX_set1_hkdf_key( |
| + kctx, |
| + input.as_ptr() as *const _, |
| + input.len() as c_int, |
| + ))?; |
| + |
| + cvt(ffi::EVP_PKEY_CTX_set1_hkdf_salt( |
| + kctx, |
| + salt.as_ptr() as *const _, |
| + salt.len() as c_int, |
| + ))?; |
| + Ok(()) |
| + })(); |
| + |
| + if let Err(e) = ret { |
| + // Free memory |
| + ffi::EVP_PKEY_CTX_free(kctx); |
| + return Err(e); |
| + } |
| + |
| + let mut len = ffi::EVP_MAX_MD_SIZE as usize; |
| + let mut buf = [0; ffi::EVP_MAX_MD_SIZE as usize]; |
| + let ret = cvt(ffi::EVP_PKEY_derive(kctx, buf.as_mut_ptr(), &mut len)); |
| + |
| + // Free memory |
| + ffi::EVP_PKEY_CTX_free(kctx); |
| + |
| + if let Err(e) = ret { |
| + return Err(e); |
| + } |
| + |
| + Ok(DigestBytes { |
| + buf, |
| + len: len as usize, |
| + }) |
| + } |
| +} |
| + |
| +/// Expand a pseudorandom key to an output keying material using the |
| +/// HKDF algorithm. |
| +/// |
| +/// Requires OpenSSL 1.1.1 or newer. |
| +#[cfg(any(ossl111))] |
| +pub fn hkdf_expand( |
| + prk: &[u8], |
| + info: &[u8], |
| + hash: MessageDigest, |
| + key: &mut [u8], |
| +) -> Result<(), ErrorStack> { |
| + unsafe { |
| + assert!(prk.len() <= c_int::max_value() as usize); |
| + assert!(info.len() <= c_int::max_value() as usize); |
| + assert!(key.len() <= c_int::max_value() as usize); |
| + |
| + ffi::init(); |
| + let kctx = cvt_p(ffi::EVP_PKEY_CTX_new_id( |
| + ffi::EVP_PKEY_HKDF, |
| + ptr::null_mut(), |
| + ))?; |
| + |
| + let ret = (|| { |
| + cvt(ffi::EVP_PKEY_derive_init(kctx))?; |
| + |
| + cvt(ffi::EVP_PKEY_CTX_hkdf_mode( |
| + kctx, |
| + ffi::EVP_PKEY_HKDEF_MODE_EXPAND_ONLY, |
| + ))?; |
| + |
| + cvt(ffi::EVP_PKEY_CTX_set_hkdf_md(kctx, hash.as_ptr()))?; |
| + |
| + cvt(ffi::EVP_PKEY_CTX_set1_hkdf_key( |
| + kctx, |
| + prk.as_ptr() as *const _, |
| + prk.len() as c_int, |
| + ))?; |
| + |
| + cvt(ffi::EVP_PKEY_CTX_add1_hkdf_info( |
| + kctx, |
| + info.as_ptr() as *const _, |
| + info.len() as c_int, |
| + ))?; |
| + Ok(()) |
| + })(); |
| + |
| + if let Err(e) = ret { |
| + // Free memory |
| + ffi::EVP_PKEY_CTX_free(kctx); |
| + return Err(e); |
| + } |
| + |
| + let mut len = key.len(); |
| + |
| + let ret = cvt(ffi::EVP_PKEY_derive(kctx, key.as_mut_ptr(), &mut len)); |
| + |
| + // Free memory |
| + ffi::EVP_PKEY_CTX_free(kctx); |
| + |
| + if let Err(e) = ret { |
| + return Err(e); |
| + } |
| + |
| + Ok(()) |
| + } |
| +} |
| + |
| #[cfg(test)] |
| mod tests { |
| use hash::MessageDigest; |
| @@ -302,4 +514,63 @@ mod tests { |
| .unwrap(); |
| assert_eq!(hex::encode(&actual[..]), expected); |
| } |
| + |
| + #[test] |
| + #[cfg(ossl111)] |
| + fn hkdf() { |
| + // Test vectors from RFC 5689 |
| + let input = "0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b"; |
| + let salt = "000102030405060708090a0b0c"; |
| + let info = "f0f1f2f3f4f5f6f7f8f9"; |
| + let mut key = vec![0; 42]; |
| + let expected = |
| + "3cb25f25faacd57a90434f64d0362f2a2d2d0a90cf1a5a4c5db02d56ecc4c5bf34007208d5b887185865"; |
| + |
| + super::hkdf( |
| + &hex::decode(input).unwrap(), |
| + &hex::decode(salt).unwrap(), |
| + &hex::decode(info).unwrap(), |
| + MessageDigest::sha256(), |
| + &mut key, |
| + ) |
| + .unwrap(); |
| + assert_eq!(hex::encode(&key[..]), expected); |
| + } |
| + |
| + #[test] |
| + #[cfg(ossl111)] |
| + fn hkdf_extract() { |
| + // Test vectors from RFC 5689 |
| + let input = "0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b"; |
| + let salt = "000102030405060708090a0b0c"; |
| + let expected = "077709362c2e32df0ddc3f0dc47bba6390b6c73bb50f9c3122ec844ad7c2b3e5"; |
| + |
| + let prk = super::hkdf_extract( |
| + &hex::decode(input).unwrap(), |
| + &hex::decode(salt).unwrap(), |
| + MessageDigest::sha256(), |
| + ) |
| + .unwrap(); |
| + assert_eq!(hex::encode(&prk[..]), expected); |
| + } |
| + |
| + #[test] |
| + #[cfg(ossl111)] |
| + fn hkdf_expand() { |
| + // Test vectors from RFC 5689 |
| + let prk = "077709362c2e32df0ddc3f0dc47bba6390b6c73bb50f9c3122ec844ad7c2b3e5"; |
| + let info = "f0f1f2f3f4f5f6f7f8f9"; |
| + let mut key = vec![0; 42]; |
| + let expected = |
| + "3cb25f25faacd57a90434f64d0362f2a2d2d0a90cf1a5a4c5db02d56ecc4c5bf34007208d5b887185865"; |
| + |
| + super::hkdf_expand( |
| + &hex::decode(prk).unwrap(), |
| + &hex::decode(info).unwrap(), |
| + MessageDigest::sha256(), |
| + &mut key, |
| + ) |
| + .unwrap(); |
| + assert_eq!(hex::encode(&key[..]), expected); |
| + } |
| } |