Skip to content

Commit b8f7953

Browse files
Tobias WaurickTobTheRock
Tobias Waurick
authored andcommitted
feat(crypto): update key derivation / tag computation to draft-03
With the newest draft now also the key id/auth tag length is used during the key derivation to create distinct salts. Also for AES CTR mode ciphers the tag length is also taken into account when computing the tag BREAKING CHANGE: The latest [changes in the draft](https://author-tools.ietf.org/diff?doc_1=draft-ietf-sframe-enc-01&doc_2=draft-ietf-sframe-enc-03) regarding the key derivation and tag computation, make theimplementation incompatible with previous versions
1 parent 892fec9 commit b8f7953

13 files changed

+154
-140
lines changed

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ authors = [
1010
"Richard Haehne <[email protected]>",
1111
]
1212

13-
description = "pure rust implementation of SFrame draft-ietf-sframe-enc-01"
13+
description = "pure rust implementation of SFrame draft-ietf-sframe-enc-03"
1414
repository = "https://github.com/goto-opensource/sframe-rs"
1515
documentation = "https://docs.rs/sframe/"
1616
readme = "README.md"

src/crypto/aead.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,8 @@ mod test {
6060
thread_rng().fill(data.as_mut_slice());
6161
let header = Header::default();
6262
let cipher_suite = CipherSuite::from(CipherSuiteVariant::AesGcm256Sha512);
63-
let secret = Secret::expand_from(&cipher_suite, KEY_MATERIAL.as_bytes()).unwrap();
63+
let secret =
64+
Secret::expand_from(&cipher_suite, KEY_MATERIAL.as_bytes(), KeyId::default()).unwrap();
6465

6566
let _tag = cipher_suite
6667
.encrypt(
@@ -138,7 +139,7 @@ mod test {
138139
fn prepare_secret(cipher_suite: &CipherSuite, test_vec: &SframeTest) -> Secret {
139140
if cipher_suite.is_ctr_mode() {
140141
// the test vectors do not provide the auth key, so we have to expand here
141-
Secret::expand_from(cipher_suite, &test_vec.key_material).unwrap()
142+
Secret::expand_from(cipher_suite, &test_vec.key_material, test_vec.key_id).unwrap()
142143
} else {
143144
Secret {
144145
key: test_vec.sframe_key.clone(),

src/crypto/cipher_suite.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33

44
/// Depicts which AEAD algorithm is used for encryption
55
/// and which hashing function is used for the key expansion,
6-
/// see [sframe draft 00 4.4](https://datatracker.ietf.org/doc/html/draft-ietf-sframe-enc-01#name-ciphersuites)
6+
/// see [sframe draft 03 4.4](https://datatracker.ietf.org/doc/html/draft-ietf-sframe-enc-03#name-cipher-suites)
77
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
88
#[cfg_attr(test, derive(strum_macros::Display))]
99
pub enum CipherSuiteVariant {

src/crypto/key_expansion.rs

Lines changed: 67 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -5,22 +5,58 @@ use super::{cipher_suite::CipherSuite, secret::Secret};
55
use crate::error::Result;
66

77
pub trait KeyExpansion {
8-
fn expand_from<T>(cipher_suite: &CipherSuite, key_material: T) -> Result<Secret>
8+
fn expand_from<M, K>(cipher_suite: &CipherSuite, key_material: M, key_id: K) -> Result<Secret>
99
where
10-
T: AsRef<[u8]>;
10+
M: AsRef<[u8]>,
11+
K: Into<u64>;
1112
}
1213

13-
pub const SFRAME_HKDF_SALT: &[u8] = b"SFrame10";
14-
pub const SFRAME_HKDF_KEY_EXPAND_INFO: &[u8] = b"key";
15-
pub const SFRAME_HDKF_SALT_EXPAND_INFO: &[u8] = b"salt";
14+
pub fn get_hkdf_key_expand_info(key_id: u64) -> Vec<u8> {
15+
[
16+
SFRAME_LABEL,
17+
SFRAME_HKDF_KEY_EXPAND_INFO,
18+
&key_id.to_be_bytes(),
19+
]
20+
.concat()
21+
}
1622

17-
#[cfg(feature = "openssl")]
18-
pub const SFRAME_HKDF_SUB_SALT: &[u8] = b"SFrame10 AES CTR AEAD";
19-
#[cfg(feature = "openssl")]
20-
pub const SFRAME_HKDF_SUB_ENC_EXPAND_INFO: &[u8] = b"enc";
21-
#[cfg(feature = "openssl")]
22-
pub const SFRAME_HDKF_SUB_AUTH_EXPAND_INFO: &[u8] = b"auth";
23+
pub fn get_hkdf_salt_expand_info(key_id: u64) -> Vec<u8> {
24+
[
25+
SFRAME_LABEL,
26+
SFRAME_HDKF_SALT_EXPAND_INFO,
27+
&key_id.to_be_bytes(),
28+
]
29+
.concat()
30+
}
31+
32+
const SFRAME_LABEL: &[u8] = b"SFrame 1.0 ";
33+
34+
// For the current test vectors different labels than specified were used
35+
// see https://github.com/sframe-wg/sframe/issues/137
36+
cfg_if::cfg_if! {
37+
if #[cfg(test)] {
38+
const SFRAME_HKDF_KEY_EXPAND_INFO: &[u8] = b"key ";
39+
const SFRAME_HDKF_SALT_EXPAND_INFO: &[u8] = b"salt ";
40+
} else {
41+
const SFRAME_HKDF_KEY_EXPAND_INFO: &[u8] = b"Secret key ";
42+
const SFRAME_HDKF_SALT_EXPAND_INFO: &[u8] = b"Secret salt ";
43+
}
44+
}
45+
46+
cfg_if::cfg_if! {
47+
if #[cfg(feature = "openssl")] {
48+
pub fn get_hkdf_aead_label(tag_len: usize) -> Vec<u8> {
49+
// for current platforms there is no issue casting from usize to u64
50+
return [SFRAME_HDKF_SUB_AEAD_LABEL, &(tag_len).to_be_bytes()].concat()
51+
}
2352

53+
pub const SFRAME_HDKF_SUB_AEAD_LABEL: &[u8] = b"SFrame 1.0 AES CTR AEAD ";
54+
pub const SFRAME_HKDF_SUB_ENC_EXPAND_INFO: &[u8] = b"enc";
55+
pub const SFRAME_HDKF_SUB_AUTH_EXPAND_INFO: &[u8] = b"auth";
56+
}
57+
}
58+
59+
#[cfg(feature = "openssl")]
2460
#[cfg(test)]
2561
mod test {
2662
use super::KeyExpansion;
@@ -30,16 +66,26 @@ mod test {
3066
use crate::{crypto::cipher_suite::CipherSuiteVariant, util::test::assert_bytes_eq};
3167

3268
mod aes_gcm {
69+
use crate::crypto::key_expansion::SFRAME_LABEL;
70+
3371
use super::*;
3472

3573
use test_case::test_case;
3674

3775
#[test_case(CipherSuiteVariant::AesGcm128Sha256; "AesGcm128Sha256")]
3876
#[test_case(CipherSuiteVariant::AesGcm256Sha512; "AesGcm256Sha512")]
77+
3978
fn derive_correct_base_keys(variant: CipherSuiteVariant) {
4079
let test_vec = get_sframe_test_vector(&variant.to_string());
41-
let secret =
42-
Secret::expand_from(&CipherSuite::from(variant), &test_vec.key_material).unwrap();
80+
81+
assert_bytes_eq(SFRAME_LABEL, &test_vec.sframe_label);
82+
83+
let secret = Secret::expand_from(
84+
&CipherSuite::from(variant),
85+
&test_vec.key_material,
86+
test_vec.key_id,
87+
)
88+
.unwrap();
4389

4490
assert_bytes_eq(&secret.key, &test_vec.sframe_key);
4591
assert_bytes_eq(&secret.salt, &test_vec.sframe_salt);
@@ -49,37 +95,23 @@ mod test {
4995
#[cfg(feature = "openssl")]
5096
mod aes_ctr {
5197
use super::*;
52-
use crate::test_vectors::get_aes_ctr_test_vector;
53-
5498
use test_case::test_case;
5599

56100
#[test_case(CipherSuiteVariant::AesCtr128HmacSha256_80; "AesCtr128HmacSha256_80")]
57101
#[test_case(CipherSuiteVariant::AesCtr128HmacSha256_64; "AesCtr128HmacSha256_64")]
58102
#[test_case(CipherSuiteVariant::AesCtr128HmacSha256_32; "AesCtr128HmacSha256_32")]
59103
fn derive_correct_sub_keys(variant: CipherSuiteVariant) {
60-
let test_vec = get_aes_ctr_test_vector(&variant.to_string());
104+
let test_vec = get_sframe_test_vector(&variant.to_string());
61105
let cipher_suite = CipherSuite::from(variant);
62-
let secret = Secret::expand_from(&cipher_suite, &test_vec.key_material).unwrap();
63106

64-
assert_bytes_eq(&secret.auth.unwrap(), &test_vec.auth_key);
65-
assert_bytes_eq(&secret.key, &test_vec.enc_key);
66-
}
67-
68-
#[test]
69-
fn derive_correct_keys_aes_ctr_128_hmac_sha256_64() {
70-
derive_correct_sub_keys(CipherSuiteVariant::AesCtr128HmacSha256_64);
71-
}
72-
73-
#[test]
74-
fn derive_correct_keys_aes_ctr_128_hmac_sha256_32() {
75-
derive_correct_sub_keys(CipherSuiteVariant::AesCtr128HmacSha256_32);
76-
}
107+
let secret =
108+
Secret::expand_from(&cipher_suite, &test_vec.key_material, test_vec.key_id)
109+
.unwrap();
77110

78-
#[test]
79-
// AesCtr128HmacSha256_80 is not available in the test vectors
80-
#[ignore]
81-
fn derive_correct_keys_aes_ctr_128_hmac_sha256_80() {
82-
derive_correct_sub_keys(CipherSuiteVariant::AesCtr128HmacSha256_80);
111+
assert_bytes_eq(&secret.salt, &test_vec.sframe_salt);
112+
// the subkeys stored in secret.key and secret.auth are not included in the test vectors
113+
assert_eq!(secret.auth.unwrap().len(), cipher_suite.hash_len);
114+
assert_eq!(secret.key.len(), cipher_suite.key_len);
83115
}
84116
}
85117
}

src/crypto/openssl/aead.rs

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -190,11 +190,14 @@ impl CipherSuite {
190190
let mut signer = openssl::sign::Signer::new(openssl::hash::MessageDigest::sha256(), &key)?;
191191

192192
// for current platforms there is no issue casting from usize to u64
193-
let aad_len = (aad.len() as u64).to_be_bytes();
194-
let ct_len = (encrypted.len() as u64).to_be_bytes();
195-
for buf in [&aad_len, &ct_len, nonce, aad, encrypted] {
193+
let aad_len = &(aad.len() as u64).to_be_bytes();
194+
let ct_len = &(encrypted.len() as u64).to_be_bytes();
195+
let tag_len = &(self.auth_tag_len as u64).to_be_bytes();
196+
197+
for buf in [aad_len, ct_len, tag_len, nonce, aad, encrypted] {
196198
signer.update(buf)?;
197199
}
200+
198201
let mut tag = signer.sign_to_vec()?;
199202
tag.resize(self.auth_tag_len, 0);
200203

src/crypto/openssl/key_expansion.rs

Lines changed: 41 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -5,22 +5,23 @@ use crate::{
55
crypto::{
66
cipher_suite::{CipherSuite, CipherSuiteVariant},
77
key_expansion::{
8-
KeyExpansion, SFRAME_HDKF_SALT_EXPAND_INFO, SFRAME_HDKF_SUB_AUTH_EXPAND_INFO,
9-
SFRAME_HKDF_KEY_EXPAND_INFO, SFRAME_HKDF_SALT, SFRAME_HKDF_SUB_ENC_EXPAND_INFO,
10-
SFRAME_HKDF_SUB_SALT,
8+
get_hkdf_aead_label, get_hkdf_key_expand_info, get_hkdf_salt_expand_info, KeyExpansion,
9+
SFRAME_HDKF_SUB_AUTH_EXPAND_INFO, SFRAME_HKDF_SUB_ENC_EXPAND_INFO,
1110
},
1211
secret::Secret,
1312
},
1413
error::{Result, SframeError},
1514
};
1615

1716
impl KeyExpansion for Secret {
18-
fn expand_from<T>(cipher_suite: &CipherSuite, key_material: T) -> Result<Secret>
17+
fn expand_from<M, K>(cipher_suite: &CipherSuite, key_material: M, key_id: K) -> Result<Secret>
1918
where
20-
T: AsRef<[u8]>,
19+
M: AsRef<[u8]>,
20+
K: Into<u64>,
2121
{
2222
let try_expand = || {
23-
let (base_key, salt) = expand_secret(cipher_suite, key_material.as_ref())?;
23+
let (base_key, salt) =
24+
expand_secret(cipher_suite, key_material.as_ref(), key_id.into())?;
2425
let (key, auth) = if cipher_suite.is_ctr_mode() {
2526
let (key, auth) = expand_subsecret(cipher_suite, &base_key)?;
2627
(key, Some(auth))
@@ -38,18 +39,20 @@ impl KeyExpansion for Secret {
3839
fn expand_secret(
3940
cipher_suite: &CipherSuite,
4041
key_material: &[u8],
42+
key_id: u64,
4143
) -> std::result::Result<(Vec<u8>, Vec<u8>), openssl::error::ErrorStack> {
42-
let prk = extract_prk(cipher_suite, key_material, SFRAME_HKDF_SALT)?;
44+
// No salt used for the extraction: https://www.ietf.org/archive/id/draft-ietf-sframe-enc-03.html#name-key-derivation
45+
let prk = extract_pseudo_random_key(cipher_suite, key_material, b"")?;
4346
let key = expand_key(
4447
cipher_suite,
4548
&prk,
46-
SFRAME_HKDF_KEY_EXPAND_INFO,
49+
&get_hkdf_key_expand_info(key_id),
4750
cipher_suite.key_len,
4851
)?;
4952
let salt = expand_key(
5053
cipher_suite,
5154
&prk,
52-
SFRAME_HDKF_SALT_EXPAND_INFO,
55+
&get_hkdf_salt_expand_info(key_id),
5356
cipher_suite.nonce_len,
5457
)?;
5558

@@ -60,7 +63,8 @@ fn expand_subsecret(
6063
cipher_suite: &CipherSuite,
6164
key: &[u8],
6265
) -> std::result::Result<(Vec<u8>, Vec<u8>), openssl::error::ErrorStack> {
63-
let prk = extract_prk(cipher_suite, key, SFRAME_HKDF_SUB_SALT)?;
66+
let salt = get_hkdf_aead_label(cipher_suite.auth_tag_len);
67+
let prk = extract_pseudo_random_key(cipher_suite, key, &salt)?;
6468
let key = expand_key(
6569
cipher_suite,
6670
&prk,
@@ -77,7 +81,7 @@ fn expand_subsecret(
7781
Ok((key, auth))
7882
}
7983

80-
fn extract_prk(
84+
fn extract_pseudo_random_key(
8185
cipher_suite: &CipherSuite,
8286
key_material: &[u8],
8387
salt: &[u8],
@@ -135,3 +139,29 @@ impl From<CipherSuiteVariant> for &'static openssl::md::MdRef {
135139
}
136140
}
137141
}
142+
#[cfg(test)]
143+
mod test {
144+
145+
use super::*;
146+
use crate::{test_vectors::get_aes_ctr_test_vector, util::test::assert_bytes_eq};
147+
148+
use test_case::test_case;
149+
150+
#[test_case(CipherSuiteVariant::AesCtr128HmacSha256_80; "AesCtr128HmacSha256_80")]
151+
#[test_case(CipherSuiteVariant::AesCtr128HmacSha256_64; "AesCtr128HmacSha256_64")]
152+
#[test_case(CipherSuiteVariant::AesCtr128HmacSha256_32; "AesCtr128HmacSha256_32")]
153+
fn derive_correct_sub_keys(variant: CipherSuiteVariant) {
154+
let test_vec = get_aes_ctr_test_vector(&variant.to_string());
155+
let cipher_suite = CipherSuite::from(variant);
156+
157+
let aead_salt = get_hkdf_aead_label(cipher_suite.auth_tag_len);
158+
assert_bytes_eq(&aead_salt, &test_vec.aead_label);
159+
160+
let prk = extract_pseudo_random_key(&cipher_suite, &test_vec.base_key, &aead_salt).unwrap();
161+
assert_bytes_eq(&prk, &test_vec.aead_secret);
162+
163+
let (key, auth) = expand_subsecret(&cipher_suite, &test_vec.base_key).unwrap();
164+
assert_bytes_eq(&key, &test_vec.enc_key);
165+
assert_bytes_eq(&auth, &test_vec.auth_key);
166+
}
167+
}

src/crypto/ring/key_expansion.rs

Lines changed: 18 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4,25 +4,34 @@
44
use crate::{
55
crypto::{
66
cipher_suite::{CipherSuite, CipherSuiteVariant},
7-
key_expansion::{
8-
KeyExpansion, SFRAME_HDKF_SALT_EXPAND_INFO, SFRAME_HKDF_KEY_EXPAND_INFO,
9-
SFRAME_HKDF_SALT,
10-
},
7+
key_expansion::{get_hkdf_key_expand_info, get_hkdf_salt_expand_info, KeyExpansion},
118
secret::Secret,
129
},
1310
error::{Result, SframeError},
1411
};
1512

1613
impl KeyExpansion for Secret {
17-
fn expand_from<T>(cipher_suite: &CipherSuite, key_material: T) -> Result<Secret>
14+
fn expand_from<M, K>(cipher_suite: &CipherSuite, key_material: M, key_id: K) -> Result<Secret>
1815
where
19-
T: AsRef<[u8]>,
16+
M: AsRef<[u8]>,
17+
K: Into<u64>,
2018
{
19+
let key_id = key_id.into();
2120
let algorithm = cipher_suite.variant.into();
22-
let prk = ring::hkdf::Salt::new(algorithm, SFRAME_HKDF_SALT).extract(key_material.as_ref());
21+
// No salt used for the extraction: https://www.ietf.org/archive/id/draft-ietf-sframe-enc-03.html#name-key-derivation
22+
let pseudo_random_key =
23+
ring::hkdf::Salt::new(algorithm, b"").extract(key_material.as_ref());
2324

24-
let key = expand_key(&prk, SFRAME_HKDF_KEY_EXPAND_INFO, cipher_suite.key_len)?;
25-
let salt = expand_key(&prk, SFRAME_HDKF_SALT_EXPAND_INFO, cipher_suite.nonce_len)?;
25+
let key = expand_key(
26+
&pseudo_random_key,
27+
&get_hkdf_key_expand_info(key_id),
28+
cipher_suite.key_len,
29+
)?;
30+
let salt = expand_key(
31+
&pseudo_random_key,
32+
&get_hkdf_salt_expand_info(key_id),
33+
cipher_suite.nonce_len,
34+
)?;
2635

2736
Ok(Secret {
2837
key,

src/header/basic_header.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ use super::{
1111
};
1212

1313
bitfield! {
14-
/// Modeled after [sframe draft 00 4.2](https://datatracker.ietf.org/doc/html/draft-ietf-sframe-enc-01#name-sframe-header)
14+
/// Modeled after [sframe draft 03 4.3](https://datatracker.ietf.org/doc/html/draft-ietf-sframe-enc-03#name-sframe-header)
1515
/// ```txt
1616
/// 0 1 2 3 4 5 6 7
1717
/// +-+-+-+-+-+-+-+-+---------------------------------+

src/header/extended_header.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ use super::{
1313
};
1414

1515
bitfield! {
16-
/// Modeled after [sframe draft 00 4.2](https://datatracker.ietf.org/doc/html/draft-ietf-sframe-enc-01#name-sframe-header)
16+
/// Modeled after [sframe draft 03 4.3](https://datatracker.ietf.org/doc/html/draft-ietf-sframe-enc-03#name-sframe-header)
1717
/// ```txt
1818
/// 0 1 2 3 4 5 6 7
1919
/// +-+-+-+-+-+-+-+-+---------------------------+---------------------------+

src/header/mod.rs

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ pub trait HeaderFields {
4444
}
4545

4646
/// Sframe header with a KID with a length of up to 3bits
47-
/// modeled after [sframe draft 00 4.2](https://datatracker.ietf.org/doc/html/draft-ietf-sframe-enc-01#name-sframe-header)
47+
/// modeled after [sframe draft 03 4.3](https://datatracker.ietf.org/doc/html/draft-ietf-sframe-enc-03#name-sframe-header)
4848
/// ```txt
4949
/// 0 1 2 3 4 5 6 7
5050
/// +-+-+-+-+-+-+-+-+---------------------------------+
@@ -73,7 +73,7 @@ impl BasicHeader {
7373
}
7474
}
7575
/// Extended sframe header with a KID with a length of up to 8 bytes
76-
/// modeled after [sframe draft 00 4.2](https://datatracker.ietf.org/doc/html/draft-ietf-sframe-enc-01#name-sframe-header)
76+
/// modeled after [sframe draft 03 4.3](https://datatracker.ietf.org/doc/html/draft-ietf-sframe-enc-03#name-sframe-header)
7777
/// ```txt
7878
/// 0 1 2 3 4 5 6 7
7979
/// +-+-+-+-+-+-+-+-+---------------------------+---------------------------+
@@ -102,7 +102,7 @@ impl ExtendedHeader {
102102
}
103103

104104
#[derive(Copy, Clone, Debug)]
105-
/// Represents an Sframe header modeled after [sframe draft 00 4.2](https://datatracker.ietf.org/doc/html/draft-ietf-sframe-enc-01#name-sframe-header)
105+
/// Represents an Sframe header modeled after [sframe draft 03 4.3](https://datatracker.ietf.org/doc/html/draft-ietf-sframe-enc-03#name-sframe-header)
106106
/// containing the key id of the sender (KID) and the current frame count (CTR).
107107
/// There are two variants, either with a KID represented by 3 bits (Basic) and an extended version with a KID of up to 8 bytes (Extended).
108108
/// The CTR field has a variable length of up to 8 bytes where the size is represented with LEN. Here LEN=0 represents a length of 1.
@@ -218,7 +218,6 @@ mod test {
218218
use super::{frame_count::FrameCount, keyid::KeyId, Header};
219219
use crate::header::{Deserialization, HeaderFields};
220220
use crate::util::test::assert_bytes_eq;
221-
use crate::CipherSuiteVariant::{AesGcm128Sha256, AesGcm256Sha512};
222221

223222
use pretty_assertions::assert_eq;
224223

0 commit comments

Comments
 (0)