Skip to content

Commit b159c7c

Browse files
committed
Improve pkcs11 ECDSA signature formatting
- remove an unwrap - add documentation - add test checking if we're not misinterpreting `r` or `s` as negative values when MSB is set Signed-off-by: Marcel Guzik <marcel.guzik@cumulocity.com>
1 parent 80a3f39 commit b159c7c

File tree

1 file changed

+59
-14
lines changed
  • crates/extensions/tedge-p11-server/src/pkcs11

1 file changed

+59
-14
lines changed

crates/extensions/tedge-p11-server/src/pkcs11/mod.rs

Lines changed: 59 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,10 @@
55
//! - PKCS#11: https://docs.oasis-open.org/pkcs11/pkcs11-base/v2.40/os/pkcs11-base-v2.40-os.html
66
77
use anyhow::Context;
8+
use asn1_rs::BigInt;
89
use asn1_rs::FromDer as _;
10+
use asn1_rs::Integer;
11+
use asn1_rs::SequenceOf;
912
use asn1_rs::ToDer;
1013
use camino::Utf8PathBuf;
1114
use cryptoki::context::CInitializeArgs;
@@ -397,19 +400,32 @@ impl Signer for PkcsSigner {
397400
}
398401
}
399402

400-
fn format_asn1_ecdsa_signature(r_bytes: &[u8], s_bytes: &[u8]) -> Result<Vec<u8>, anyhow::Error> {
401-
let mut writer = Vec::new();
402-
403-
write_asn1_integer(&mut writer, r_bytes);
404-
405-
write_asn1_integer(&mut writer, s_bytes);
406-
407-
let seq = asn1_rs::Sequence::new(writer.into());
408-
let b = seq.to_der_vec().unwrap();
409-
Ok(b)
403+
/// Formats the output of PKCS11 EC signature as an ASN.1 Ecdsa-Sig-Value.
404+
///
405+
/// This function takes the raw `r` and `s` byte slices and encodes them as ASN.1 INTEGERs,
406+
/// then wraps them in an ASN.1 SEQUENCE, and finally serializes the structure to DER.
407+
///
408+
/// PKCS#11 EC signature operations typically return a raw concatenation of the `r` and `s` values,
409+
/// each representing a big-endian positive integer of fixed length (depending on the curve).
410+
/// However, most cryptographic protocols (including TLS and X.509) expect ECDSA signatures to be
411+
/// encoded as an ASN.1 DER SEQUENCE of two INTEGERs, as described in RFC 3279 section 2.2.3.
412+
///
413+
/// - https://docs.oasis-open.org/pkcs11/pkcs11-curr/v3.0/os/pkcs11-curr-v3.0-os.html#_Toc30061178
414+
/// - https://www.ietf.org/rfc/rfc3279#section-2.2.3
415+
fn format_asn1_ecdsa_signature(r_bytes: &[u8], s_bytes: &[u8]) -> anyhow::Result<Vec<u8>> {
416+
let r = format_asn1_integer(r_bytes).to_signed_bytes_be();
417+
let r = Integer::new(&r);
418+
let s = format_asn1_integer(s_bytes).to_signed_bytes_be();
419+
let s = Integer::new(&s);
420+
421+
let seq = SequenceOf::<Integer>::from_iter([r, s]);
422+
let seq_der = seq
423+
.to_der_vec()
424+
.context("Unexpected ASN.1 error when serializing Ecdsa-Sig-Value")?;
425+
Ok(seq_der)
410426
}
411427

412-
fn write_asn1_integer(writer: &mut dyn std::io::Write, b: &[u8]) {
428+
fn format_asn1_integer(b: &[u8]) -> BigInt {
413429
let mut i = asn1_rs::BigInt::from_signed_bytes_be(b);
414430
if i.sign() == asn1_rs::Sign::Minus {
415431
// Prepend a most significant zero byte if value < 0
@@ -418,9 +434,7 @@ fn write_asn1_integer(writer: &mut dyn std::io::Write, b: &[u8]) {
418434

419435
i = asn1_rs::BigInt::from_signed_bytes_be(&positive);
420436
}
421-
let i = i.to_signed_bytes_be();
422-
let i = asn1_rs::Integer::new(&i);
423-
let _ = i.write_der(writer);
437+
i
424438
}
425439

426440
fn get_ec_mechanism(session: &Session, key: ObjectHandle) -> anyhow::Result<SigScheme> {
@@ -450,3 +464,34 @@ fn get_ec_mechanism(session: &Session, key: ObjectHandle) -> anyhow::Result<SigS
450464
_ => anyhow::bail!("Parsed oID({oid}) doesn't match any supported EC curve"),
451465
}
452466
}
467+
468+
#[cfg(test)]
469+
mod tests {
470+
use super::*;
471+
use asn1_rs::Any;
472+
use asn1_rs::Integer;
473+
use asn1_rs::SequenceOf;
474+
475+
#[test]
476+
fn test_format_asn1_ecdsa_signature_invalid_asn1() {
477+
// Use 32-byte r and s (as for P-256)
478+
let r = [0x01u8; 32];
479+
let s = [0xffu8; 32];
480+
481+
let der = format_asn1_ecdsa_signature(&r, &s).expect("Should encode");
482+
483+
// Try to parse as ASN.1 SEQUENCE of two INTEGERs
484+
let parsed = Any::from_der(&der);
485+
assert!(parsed.is_ok(), "Should parse as ASN.1");
486+
487+
// Now check that the sequence contains exactly two INTEGERs
488+
let seq: SequenceOf<Integer> = SequenceOf::from_der(&der)
489+
.expect("Should parse as sequence")
490+
.1;
491+
assert_eq!(seq.len(), 2, "ASN.1 sequence should have two items");
492+
493+
// make sure input is not misinterpreted as negative numbers
494+
assert_eq!(seq[0].as_bigint().to_bytes_be().1, r);
495+
assert_eq!(seq[1].as_bigint().to_bytes_be().1, s);
496+
}
497+
}

0 commit comments

Comments
 (0)