Skip to content

Commit 01c26b7

Browse files
authored
Merge pull request #603 from Dstack-TEE/issue-553-stable-der
fix(ra-tls): stabilize derive_dh_secret encoding
2 parents ea85992 + 55c728f commit 01c26b7

File tree

1 file changed

+51
-1
lines changed

1 file changed

+51
-1
lines changed

ra-tls/src/kdf.rs

Lines changed: 51 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,10 +65,29 @@ fn sha256(data: &[u8]) -> [u8; 32] {
6565
hasher.finalize().into()
6666
}
6767

68+
/// Returns the canonical PKCS#8 DER encoding of a P-256 key pair.
69+
///
70+
/// This uses `p256::SecretKey::to_pkcs8_der()` directly instead of
71+
/// `rcgen::KeyPair::serialized_der()` to decouple the encoding from the
72+
/// rcgen library version. The p256 crate produces canonical ASN.1 DER which
73+
/// is deterministic and identical to rcgen's current output.
74+
///
75+
/// This matters because `derive_dh_secret` hashes the PKCS#8 DER bytes
76+
/// (including the public key) to produce a secret — a historical design
77+
/// choice that we must preserve for backward compatibility.
78+
fn p256_keypair_to_pkcs8_der(key_pair: &KeyPair) -> Result<Vec<u8>> {
79+
let sk = p256::SecretKey::from_pkcs8_der(key_pair.serialized_der())
80+
.context("failed to decode secret key")?;
81+
let pkcs8_der = sk
82+
.to_pkcs8_der()
83+
.context("failed to encode secret key to PKCS#8 DER")?;
84+
Ok(pkcs8_der.as_bytes().to_vec())
85+
}
86+
6887
/// Derives a X25519 secret from a given key pair.
6988
pub fn derive_dh_secret(from: &KeyPair, context_data: &[&[u8]]) -> Result<[u8; 32]> {
7089
let key_pair = derive_p256_key_pair(from, context_data)?;
71-
let derived_secret = sha256(key_pair.serialized_der());
90+
let derived_secret = sha256(&p256_keypair_to_pkcs8_der(&key_pair)?);
7291
Ok(derived_secret)
7392
}
7493

@@ -95,4 +114,35 @@ mod tests {
95114
let key = KeyPair::generate_for(&PKCS_ECDSA_P256_SHA256).unwrap();
96115
let _derived_key = derive_p256_key_pair(&key, &[b"context one"]).unwrap();
97116
}
117+
118+
#[test]
119+
fn test_derive_dh_secret_stable_output() {
120+
// Fixed test vector generated from the original rcgen-based implementation.
121+
// If this test fails after a dependency upgrade, the PKCS#8 encoding has
122+
// changed and deployed secrets would be silently broken.
123+
// Do NOT update the expected value — fix the encoding instead.
124+
let root_der = hex::decode(
125+
"308187020100301306072a8648ce3d020106082a8648ce3d030107046d306b02\
126+
01010420f57527cea4ab7ffb49af99b158cdc0e3ec06398f528349ea236b7d2a\
127+
fe19cec1a1440342000491f50522407ce29dce3ed7d31a15d80c1c42f13a2355\
128+
2d2b33a0ce09ee11e47bce95936f3e7f80d195f879e28e1b144ef37ac9ab8e36\
129+
a690cbf930b775897b27",
130+
)
131+
.unwrap();
132+
let expected_secret = "663afd58820be8ad645f9c035e93199d114ab16f738db62393bc1d7d623e8813";
133+
134+
let root_key = KeyPair::from_der_and_sign_algo(
135+
&PrivateKeyDer::try_from(root_der.as_slice()).unwrap(),
136+
&PKCS_ECDSA_P256_SHA256,
137+
)
138+
.unwrap();
139+
let context = [b"context one".as_ref(), b"context two".as_ref()];
140+
let secret = derive_dh_secret(&root_key, &context).unwrap();
141+
142+
assert_eq!(
143+
hex::encode(secret),
144+
expected_secret,
145+
"derive_dh_secret output changed — this would break existing deployments"
146+
);
147+
}
98148
}

0 commit comments

Comments
 (0)