Skip to content

Commit 2e23569

Browse files
committed
Simplify derive_dh_secret: use p256::to_pkcs8_der() instead of manual DER template
The p256 crate's PKCS#8 encoding is deterministic (canonical ASN.1 DER), so there's no need to manually reconstruct the DER bytes. This produces identical output as verified by the compatibility test.
1 parent 38a20df commit 2e23569

1 file changed

Lines changed: 9 additions & 90 deletions

File tree

ra-tls/src/kdf.rs

Lines changed: 9 additions & 90 deletions
Original file line numberDiff line numberDiff line change
@@ -12,49 +12,6 @@ use ring::{
1212
};
1313
use rustls_pki_types::PrivateKeyDer;
1414

15-
// PKCS#8 PrivateKeyInfo template for a P-256 key generated by p256/pkcs8 0.10,
16-
// without the EC public key. This prefix is stable because all lengths and
17-
// algorithm identifiers are constant for prime256v1.
18-
//
19-
// Structure:
20-
// PrivateKeyInfo ::= SEQUENCE {
21-
// version INTEGER (0),
22-
// privateKeyAlgorithm AlgorithmIdentifier {
23-
// id-ecPublicKey, prime256v1
24-
// },
25-
// privateKey OCTET STRING (ECPrivateKey)
26-
// }
27-
//
28-
// ECPrivateKey ::= SEQUENCE {
29-
// version INTEGER (1),
30-
// privateKey OCTET STRING (32 bytes),
31-
// publicKey [1] BIT STRING OPTIONAL
32-
// }
33-
//
34-
// The remaining suffix encodes the [1] publicKey BIT STRING header; the
35-
// actual SEC1-encoded uncompressed point (65 bytes) is appended after it.
36-
const P256_PKCS8_PREFIX: [u8; 36] = [
37-
0x30, 0x81, 0x87, // SEQUENCE, len 0x87
38-
0x02, 0x01, 0x00, // version = 0
39-
0x30, 0x13, // SEQUENCE (AlgorithmIdentifier)
40-
0x06, 0x07, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x02, 0x01, // id-ecPublicKey
41-
0x06, 0x08, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x03, 0x01, 0x07, // prime256v1
42-
0x04, 0x6d, // OCTET STRING, len 0x6d (ECPrivateKey)
43-
0x30, 0x6b, // SEQUENCE (ECPrivateKey), len 0x6b
44-
0x02, 0x01, 0x01, // version = 1
45-
0x04, 0x20, // OCTET STRING, len 32 (private key)
46-
];
47-
48-
// Context-specific [1] publicKey BIT STRING wrapper; the 65-byte SEC1
49-
// uncompressed point (0x04 || X || Y) is appended after this header.
50-
const P256_PKCS8_PUBLIC_KEY_PREFIX: [u8; 5] = [
51-
0xa1, 0x44, // [1] constructed, len 0x44
52-
0x03, 0x42, // BIT STRING, len 0x42
53-
0x00, // number of unused bits
54-
];
55-
56-
const P256_PKCS8_TOTAL_LEN: usize = 138;
57-
5815
struct AnySizeKey(usize);
5916
impl KeyType for AnySizeKey {
6017
fn len(&self) -> usize {
@@ -110,65 +67,27 @@ fn sha256(data: &[u8]) -> [u8; 32] {
11067

11168
/// Derives a X25519 secret from a given key pair.
11269
///
113-
/// Historically this was implemented as:
114-
/// 1. derive a P-256 key pair from `from` using HKDF
115-
/// 2. hash `rcgen::KeyPair::serialized_der()` with SHA-256
70+
/// This derives a P-256 scalar from `from` via HKDF, encodes it as PKCS#8
71+
/// DER using the p256 crate, and hashes the result with SHA-256.
11672
///
117-
/// That made the result sensitive to library-level changes in PKCS#8
118-
/// encoding. To avoid this, we now:
119-
/// - derive the same P-256 scalar as before
120-
/// - encode it using a fixed, Dstack-defined PKCS#8 layout
121-
/// - hash that encoding with SHA-256
73+
/// The p256 crate's PKCS#8 encoding is deterministic (canonical ASN.1 DER)
74+
/// and produces output identical to the previous rcgen-based implementation.
12275
pub fn derive_dh_secret(from: &KeyPair, context_data: &[&[u8]]) -> Result<[u8; 32]> {
123-
use p256::elliptic_curve::sec1::ToEncodedPoint;
124-
125-
// 1. Decode the root CA key from rcgen::KeyPair into a P-256 scalar.
12676
let der_bytes = from.serialized_der();
12777
let sk =
12878
p256::SecretKey::from_pkcs8_der(der_bytes).context("failed to decode root secret key")?;
12979
let sk_bytes = sk.as_scalar_primitive().to_bytes();
13080

131-
// 2. Derive the same 32-byte scalar as before using HKDF.
13281
let derived_sk_bytes = derive_key(sk_bytes.as_slice(), context_data, 32)
13382
.or(Err(anyhow!("failed to derive key")))?;
134-
let derived_sk_bytes: [u8; 32] = derived_sk_bytes
135-
.as_slice()
136-
.try_into()
137-
.map_err(|_| anyhow!("unexpected length for derived key"))?;
13883

139-
// 3. Compute the corresponding P-256 public key (uncompressed SEC1).
14084
let derived_sk = p256::SecretKey::from_slice(&derived_sk_bytes)
14185
.context("failed to decode derived secret key")?;
142-
let public_key = derived_sk.public_key();
143-
let encoded_point = public_key.to_encoded_point(false);
144-
let public_key_bytes = encoded_point.as_bytes(); // 0x04 || X || Y (65 bytes)
145-
146-
// 4. Build a fixed PKCS#8 PrivateKeyInfo encoding matching the previous
147-
// rcgen/pkcs8 output for prime256v1 keys.
148-
anyhow::ensure!(
149-
public_key_bytes.len() == 65,
150-
"unexpected P-256 public key length"
151-
);
152-
153-
let mut pkcs8 = [0u8; P256_PKCS8_TOTAL_LEN];
154-
// Prefix up to the private key OCTET STRING contents.
155-
pkcs8[..P256_PKCS8_PREFIX.len()].copy_from_slice(&P256_PKCS8_PREFIX);
156-
157-
// 32-byte private key.
158-
let mut offset = P256_PKCS8_PREFIX.len();
159-
pkcs8[offset..offset + 32].copy_from_slice(&derived_sk_bytes);
160-
offset += 32;
161-
162-
// [1] BIT STRING public key header.
163-
pkcs8[offset..offset + P256_PKCS8_PUBLIC_KEY_PREFIX.len()]
164-
.copy_from_slice(&P256_PKCS8_PUBLIC_KEY_PREFIX);
165-
offset += P256_PKCS8_PUBLIC_KEY_PREFIX.len();
166-
167-
// SEC1-encoded uncompressed public key bytes.
168-
pkcs8[offset..offset + public_key_bytes.len()].copy_from_slice(public_key_bytes);
169-
170-
let derived_secret = sha256(&pkcs8);
171-
Ok(derived_secret)
86+
let pkcs8_der = derived_sk
87+
.to_pkcs8_der()
88+
.context("failed to encode derived secret key")?;
89+
90+
Ok(sha256(pkcs8_der.as_bytes()))
17291
}
17392

17493
#[cfg(test)]

0 commit comments

Comments
 (0)