Skip to content

Commit e8cbda9

Browse files
authored
Determine ECDSA curve from certificate, not hash algorithm
1 parent 3a6e9bd commit e8cbda9

5 files changed

Lines changed: 157 additions & 54 deletions

File tree

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
# Unreleased
22

3+
* DTLS 1.2 ECDSA determine curve from certificate, not hash algorithm #57
4+
35
# 0.3.0
46

57
* DTLS 1.3 (breaking) #53

src/crypto/aws_lc_rs/sign.rs

Lines changed: 37 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,19 @@
22
33
use std::str;
44

5-
use aws_lc_rs::signature::{EcdsaKeyPair, EcdsaSigningAlgorithm, UnparsedPublicKey};
6-
use aws_lc_rs::signature::{ECDSA_P256_SHA256_ASN1, ECDSA_P256_SHA256_ASN1_SIGNING};
7-
use aws_lc_rs::signature::{ECDSA_P384_SHA384_ASN1, ECDSA_P384_SHA384_ASN1_SIGNING};
5+
use aws_lc_rs::signature::ECDSA_P384_SHA384_ASN1_SIGNING;
6+
use aws_lc_rs::signature::{EcdsaKeyPair, EcdsaSigningAlgorithm, EcdsaVerificationAlgorithm};
7+
use aws_lc_rs::signature::{UnparsedPublicKey, ECDSA_P256_SHA256_ASN1_SIGNING};
8+
use aws_lc_rs::signature::{ECDSA_P256_SHA256_ASN1, ECDSA_P256_SHA384_ASN1};
9+
use aws_lc_rs::signature::{ECDSA_P384_SHA256_ASN1, ECDSA_P384_SHA384_ASN1};
810
use der::{Decode, Encode};
911
use spki::ObjectIdentifier;
1012
use x509_cert::Certificate as X509Certificate;
1113

12-
use super::super::{KeyProvider, SignatureVerifier, SigningKey};
14+
use super::super::{check_verify_scheme, KeyProvider, SignatureVerifier, SigningKey};
15+
use super::super::{OID_P256, OID_P384};
1316
use crate::buffer::Buf;
14-
use crate::types::{HashAlgorithm, SignatureAlgorithm};
17+
use crate::types::{HashAlgorithm, NamedGroup, SignatureAlgorithm};
1518

1619
/// ECDSA signing key implementation.
1720
struct EcdsaSigningKey {
@@ -188,21 +191,38 @@ impl SignatureVerifier for AwsLcSignatureVerifier {
188191
.as_bytes()
189192
.ok_or_else(|| "Invalid EC subject_public_key bitstring".to_string())?;
190193

191-
let algorithm = match hash_alg {
192-
HashAlgorithm::SHA256 => &ECDSA_P256_SHA256_ASN1,
193-
HashAlgorithm::SHA384 => &ECDSA_P384_SHA384_ASN1,
194-
_ => {
195-
return Err(format!(
196-
"Unsupported hash algorithm for ECDSA: {:?}",
197-
hash_alg
198-
));
199-
}
194+
let curve_oid: ObjectIdentifier = spki
195+
.algorithm
196+
.parameters
197+
.as_ref()
198+
.ok_or("Missing EC curve parameter in certificate")?
199+
.decode_as()
200+
.map_err(|_| "Invalid EC curve parameter in certificate".to_string())?;
201+
202+
let group = match curve_oid {
203+
OID_P256 => NamedGroup::Secp256r1,
204+
OID_P384 => NamedGroup::Secp384r1,
205+
_ => return Err(format!("Unsupported EC curve: {}", curve_oid)),
206+
};
207+
208+
check_verify_scheme(sig_alg, hash_alg, group)?;
209+
210+
let algorithm: &EcdsaVerificationAlgorithm = match (group, hash_alg) {
211+
(NamedGroup::Secp256r1, HashAlgorithm::SHA256) => &ECDSA_P256_SHA256_ASN1,
212+
(NamedGroup::Secp256r1, HashAlgorithm::SHA384) => &ECDSA_P256_SHA384_ASN1,
213+
(NamedGroup::Secp384r1, HashAlgorithm::SHA256) => &ECDSA_P384_SHA256_ASN1,
214+
(NamedGroup::Secp384r1, HashAlgorithm::SHA384) => &ECDSA_P384_SHA384_ASN1,
215+
// unreachable: check_verify_scheme already validated
216+
_ => unreachable!(),
200217
};
201218

202219
let public_key = UnparsedPublicKey::new(algorithm, pubkey_bytes);
203-
public_key
204-
.verify(data, signature)
205-
.map_err(|_| format!("ECDSA signature verification failed for {:?}", hash_alg))
220+
public_key.verify(data, signature).map_err(|_| {
221+
format!(
222+
"ECDSA signature verification failed for {:?} {:?}",
223+
hash_alg, group
224+
)
225+
})
206226
}
207227
}
208228

src/crypto/mod.rs

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -30,10 +30,13 @@ pub use crate::buffer::{Buf, TmpBuf};
3030
// Re-export all provider traits and types (similar to rustls structure)
3131
// This allows users to do: use dimpl::crypto::{CryptoProvider, SupportedDtls12CipherSuite, ...};
3232
pub use provider::{
33-
ActiveKeyExchange, Cipher, CryptoProvider, CryptoSafe, HashContext, HashProvider, HkdfProvider,
34-
HmacProvider, KeyProvider, PrfProvider, SecureRandom, SignatureVerifier, SigningKey,
35-
SupportedDtls12CipherSuite, SupportedDtls13CipherSuite, SupportedKxGroup,
33+
check_verify_scheme, ActiveKeyExchange, Cipher, CryptoProvider, CryptoSafe, HashContext,
34+
HashProvider, HkdfProvider, HmacProvider, KeyProvider, PrfProvider, SecureRandom,
35+
SignatureVerifier, SigningKey, SupportedDtls12CipherSuite, SupportedDtls13CipherSuite,
36+
SupportedKxGroup,
3637
};
38+
#[cfg(feature = "_crypto-common")]
39+
pub use provider::{OID_P256, OID_P384};
3740

3841
// Re-export shared types for provider trait implementations
3942
pub use crate::dtls12::message::Dtls12CipherSuite;

src/crypto/provider.rs

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,15 @@ use crate::crypto::{Aad, Nonce};
143143
use crate::dtls12::message::Dtls12CipherSuite;
144144
use crate::types::{Dtls13CipherSuite, HashAlgorithm, NamedGroup, SignatureAlgorithm};
145145

146+
/// OID for the P-256 elliptic curve (secp256r1 / prime256v1).
147+
#[cfg(feature = "_crypto-common")]
148+
pub const OID_P256: spki::ObjectIdentifier =
149+
spki::ObjectIdentifier::new_unwrap("1.2.840.10045.3.1.7");
150+
151+
/// OID for the P-384 elliptic curve (secp384r1).
152+
#[cfg(feature = "_crypto-common")]
153+
pub const OID_P384: spki::ObjectIdentifier = spki::ObjectIdentifier::new_unwrap("1.3.132.0.34");
154+
146155
// ============================================================================
147156
// Marker Trait
148157
// ============================================================================
@@ -249,6 +258,60 @@ pub trait SignatureVerifier: CryptoSafe {
249258
) -> Result<(), String>;
250259
}
251260

261+
/// Allow-list of supported (signature, hash, curve) combinations for
262+
/// DTLS 1.2 signature verification.
263+
///
264+
/// In DTLS 1.2 the hash algorithm and the certificate's curve are
265+
/// independent choices, so all cross-combinations are valid.
266+
///
267+
/// Signature | Hash | Curve
268+
/// -----------+---------+-----------
269+
/// ECDSA | SHA-256 | P-256
270+
/// ECDSA | SHA-256 | P-384
271+
/// ECDSA | SHA-384 | P-256
272+
/// ECDSA | SHA-384 | P-384
273+
const SUPPORTED_VERIFY_SCHEMES: &[(SignatureAlgorithm, HashAlgorithm, NamedGroup)] = &[
274+
(
275+
SignatureAlgorithm::ECDSA,
276+
HashAlgorithm::SHA256,
277+
NamedGroup::Secp256r1,
278+
),
279+
(
280+
SignatureAlgorithm::ECDSA,
281+
HashAlgorithm::SHA256,
282+
NamedGroup::Secp384r1,
283+
),
284+
(
285+
SignatureAlgorithm::ECDSA,
286+
HashAlgorithm::SHA384,
287+
NamedGroup::Secp256r1,
288+
),
289+
(
290+
SignatureAlgorithm::ECDSA,
291+
HashAlgorithm::SHA384,
292+
NamedGroup::Secp384r1,
293+
),
294+
];
295+
296+
/// Check that a (signature, hash, curve) combination is in the allow-list.
297+
pub fn check_verify_scheme(
298+
sig_alg: SignatureAlgorithm,
299+
hash_alg: HashAlgorithm,
300+
group: NamedGroup,
301+
) -> Result<(), String> {
302+
if SUPPORTED_VERIFY_SCHEMES
303+
.iter()
304+
.any(|(s, h, g)| *s == sig_alg && *h == hash_alg && *g == group)
305+
{
306+
Ok(())
307+
} else {
308+
Err(format!(
309+
"Unsupported signature verification: {:?} + {:?} + {:?}",
310+
sig_alg, hash_alg, group
311+
))
312+
}
313+
}
314+
252315
/// Private key parser (factory for SigningKey).
253316
pub trait KeyProvider: CryptoSafe {
254317
/// Parse and load a private key from DER/PEM bytes.

src/crypto/rust_crypto/sign.rs

Lines changed: 49 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,11 @@ use pkcs8::DecodePrivateKey;
1010
use spki::ObjectIdentifier;
1111
use x509_cert::Certificate as X509Certificate;
1212

13+
use super::super::check_verify_scheme;
1314
use super::super::{KeyProvider, SignatureVerifier, SigningKey as SigningKeyTrait};
15+
use super::super::{OID_P256, OID_P384};
1416
use crate::buffer::Buf;
15-
use crate::types::{HashAlgorithm, SignatureAlgorithm};
17+
use crate::types::{HashAlgorithm, NamedGroup, SignatureAlgorithm};
1618

1719
/// ECDSA signing key implementation.
1820
enum EcdsaSigningKey {
@@ -201,47 +203,60 @@ impl SignatureVerifier for RustCryptoSignatureVerifier {
201203
.as_bytes()
202204
.ok_or_else(|| "Invalid EC subject_public_key bitstring".to_string())?;
203205

204-
match hash_alg {
205-
HashAlgorithm::SHA256 => {
206-
use ecdsa::signature::hazmat::PrehashVerifier;
207-
use sha2::{Digest, Sha256};
208-
206+
let curve_oid: ObjectIdentifier = spki
207+
.algorithm
208+
.parameters
209+
.as_ref()
210+
.ok_or("Missing EC curve parameter in certificate")?
211+
.decode_as()
212+
.map_err(|_| "Invalid EC curve parameter in certificate".to_string())?;
213+
214+
let group = match curve_oid {
215+
OID_P256 => NamedGroup::Secp256r1,
216+
OID_P384 => NamedGroup::Secp384r1,
217+
_ => return Err(format!("Unsupported EC curve: {}", curve_oid)),
218+
};
219+
220+
check_verify_scheme(sig_alg, hash_alg, group)?;
221+
222+
use ecdsa::signature::hazmat::PrehashVerifier;
223+
use sha2::Digest;
224+
225+
// Hash the data before verification (PrehashVerifier expects a hash digest)
226+
let hash: Box<[u8]> = match hash_alg {
227+
HashAlgorithm::SHA256 => Box::from(sha2::Sha256::digest(data).as_slice()),
228+
HashAlgorithm::SHA384 => Box::from(sha2::Sha384::digest(data).as_slice()),
229+
// unreachable: check_verify_scheme already validated
230+
_ => unreachable!(),
231+
};
232+
233+
match group {
234+
NamedGroup::Secp256r1 => {
209235
let verifying_key = VerifyingKey::<NistP256>::from_sec1_bytes(pubkey_bytes)
210236
.map_err(|_| "Invalid P-256 public key".to_string())?;
211-
let signature = Signature::<NistP256>::from_der(signature)
237+
let sig = Signature::<NistP256>::from_der(signature)
212238
.map_err(|_| "Invalid signature format".to_string())?;
213-
214-
// Hash the data before verification (PrehashVerifier expects a hash digest)
215-
let mut hasher = Sha256::new();
216-
hasher.update(data);
217-
let hash = hasher.finalize();
218-
219-
verifying_key
220-
.verify_prehash(&hash, &signature)
221-
.map_err(|_| format!("ECDSA signature verification failed for {:?}", hash_alg))
239+
verifying_key.verify_prehash(&hash, &sig).map_err(|_| {
240+
format!(
241+
"ECDSA signature verification failed for {:?} {:?}",
242+
hash_alg, group
243+
)
244+
})
222245
}
223-
HashAlgorithm::SHA384 => {
224-
use ecdsa::signature::hazmat::PrehashVerifier;
225-
use sha2::{Digest, Sha384};
226-
246+
NamedGroup::Secp384r1 => {
227247
let verifying_key = VerifyingKey::<NistP384>::from_sec1_bytes(pubkey_bytes)
228248
.map_err(|_| "Invalid P-384 public key".to_string())?;
229-
let signature = Signature::<NistP384>::from_der(signature)
249+
let sig = Signature::<NistP384>::from_der(signature)
230250
.map_err(|_| "Invalid signature format".to_string())?;
231-
232-
// Hash the data before verification (PrehashVerifier expects a hash digest)
233-
let mut hasher = Sha384::new();
234-
hasher.update(data);
235-
let hash = hasher.finalize();
236-
237-
verifying_key
238-
.verify_prehash(&hash, &signature)
239-
.map_err(|_| format!("ECDSA signature verification failed for {:?}", hash_alg))
251+
verifying_key.verify_prehash(&hash, &sig).map_err(|_| {
252+
format!(
253+
"ECDSA signature verification failed for {:?} {:?}",
254+
hash_alg, group
255+
)
256+
})
240257
}
241-
_ => Err(format!(
242-
"Unsupported hash algorithm for ECDSA: {:?}",
243-
hash_alg
244-
)),
258+
// unreachable: OID match above only produces Secp256r1/Secp384r1
259+
_ => unreachable!(),
245260
}
246261
}
247262
}

0 commit comments

Comments
 (0)