Skip to content

Commit b510d88

Browse files
authored
Merge pull request #3691 from Bravo555/improve/pkcs11
PKCS11 Extract uri module to another file and improve PKCS11 ECDSA signature formatting
2 parents 5def2ee + b159c7c commit b510d88

File tree

2 files changed

+252
-208
lines changed

2 files changed

+252
-208
lines changed

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

Lines changed: 61 additions & 208 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,11 @@
44
//! - thin-edge: docs/src/references/hsm-support.md
55
//! - PKCS#11: https://docs.oasis-open.org/pkcs11/pkcs11-base/v2.40/os/pkcs11-base-v2.40-os.html
66
7-
use asn1_rs::FromDer as _;
8-
pub use cryptoki::types::AuthPin;
9-
107
use anyhow::Context;
8+
use asn1_rs::BigInt;
9+
use asn1_rs::FromDer as _;
10+
use asn1_rs::Integer;
11+
use asn1_rs::SequenceOf;
1112
use asn1_rs::ToDer;
1213
use camino::Utf8PathBuf;
1314
use cryptoki::context::CInitializeArgs;
@@ -35,6 +36,10 @@ use std::fmt::Debug;
3536
use std::sync::Arc;
3637
use std::sync::Mutex;
3738

39+
pub use cryptoki::types::AuthPin;
40+
41+
mod uri;
42+
3843
// oIDs for curves defined here: https://datatracker.ietf.org/doc/html/rfc5480#section-2.1.1.1
3944
// other can be browsed here: https://oid-base.com/get/1.3.132.0.34
4045
const SECP256R1_OID: &str = "1.2.840.10045.3.1.7";
@@ -395,19 +400,32 @@ impl Signer for PkcsSigner {
395400
}
396401
}
397402

398-
fn format_asn1_ecdsa_signature(r_bytes: &[u8], s_bytes: &[u8]) -> Result<Vec<u8>, anyhow::Error> {
399-
let mut writer = Vec::new();
400-
401-
write_asn1_integer(&mut writer, r_bytes);
402-
403-
write_asn1_integer(&mut writer, s_bytes);
404-
405-
let seq = asn1_rs::Sequence::new(writer.into());
406-
let b = seq.to_der_vec().unwrap();
407-
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)
408426
}
409427

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

417435
i = asn1_rs::BigInt::from_signed_bytes_be(&positive);
418436
}
419-
let i = i.to_signed_bytes_be();
420-
let i = asn1_rs::Integer::new(&i);
421-
let _ = i.write_der(writer);
437+
i
422438
}
423439

424440
fn get_ec_mechanism(session: &Session, key: ObjectHandle) -> anyhow::Result<SigScheme> {
@@ -449,196 +465,33 @@ fn get_ec_mechanism(session: &Session, key: ObjectHandle) -> anyhow::Result<SigS
449465
}
450466
}
451467

452-
pub mod uri {
453-
use std::borrow::Cow;
454-
use std::collections::HashMap;
455-
456-
/// Attributes decoded from a PKCS #11 URL.
457-
///
458-
/// Attributes only relevant to us shall be put into fields and the rest is in `other` hashmap.
459-
///
460-
/// https://www.rfc-editor.org/rfc/rfc7512.html
461-
#[derive(Debug, Clone, Default)]
462-
pub struct Pkcs11Uri<'a> {
463-
pub token: Option<Cow<'a, str>>,
464-
pub serial: Option<Cow<'a, str>>,
465-
pub id: Option<Vec<u8>>,
466-
pub object: Option<Cow<'a, str>>,
467-
pub other: HashMap<&'a str, Cow<'a, str>>,
468-
}
469-
470-
impl<'a> Pkcs11Uri<'a> {
471-
pub fn parse(uri: &'a str) -> anyhow::Result<Self> {
472-
let path = uri
473-
.strip_prefix("pkcs11:")
474-
.ok_or_else(|| anyhow::anyhow!("missing PKCS #11 URI scheme"))?;
475-
476-
// split of the query component
477-
let path = path.split_once('?').map(|(l, _)| l).unwrap_or(path);
478-
479-
// parse attributes, duplicate attributes are an error (RFC section 2.3)
480-
let pairs_iter = path.split(';').filter_map(|pair| pair.split_once('='));
481-
let mut pairs: HashMap<&str, &str> = HashMap::new();
482-
for (k, v) in pairs_iter {
483-
let prev_value = pairs.insert(k, v);
484-
if prev_value.is_some() {
485-
anyhow::bail!("PKCS#11 URI contains duplicate attribute ({k})");
486-
}
487-
}
488-
489-
let token = pairs
490-
.remove("token")
491-
.map(|v| percent_encoding::percent_decode_str(v).decode_utf8_lossy());
492-
let serial = pairs
493-
.remove("serial")
494-
.map(|v| percent_encoding::percent_decode_str(v).decode_utf8_lossy());
495-
let object = pairs
496-
.remove("object")
497-
.map(|v| percent_encoding::percent_decode_str(v).decode_utf8_lossy());
498-
499-
let id: Option<Vec<u8>> = pairs
500-
.remove("id")
501-
.map(|id| percent_encoding::percent_decode_str(id).collect());
502-
503-
let other = pairs
504-
.into_iter()
505-
.map(|(k, v)| {
506-
(
507-
k,
508-
percent_encoding::percent_decode_str(v).decode_utf8_lossy(),
509-
)
510-
})
511-
.collect();
512-
513-
Ok(Self {
514-
token,
515-
serial,
516-
id,
517-
object,
518-
other,
519-
})
520-
}
521-
522-
/// Add new attributes from `other` to `self`.
523-
///
524-
/// If other contains new attributes not present in self, add them to self. If these
525-
/// attributes are already present in self, preserve value currently in self.
526-
pub fn append_attributes(&mut self, other: Self) {
527-
self.token = self.token.take().or(other.token);
528-
self.serial = self.serial.take().or(other.serial);
529-
self.id = self.id.take().or(other.id);
530-
self.object = self.object.take().or(other.object);
531-
532-
for (attribute, value) in other.other {
533-
if !self.other.contains_key(attribute) {
534-
self.other.insert(attribute, value);
535-
}
536-
}
537-
}
538-
}
539-
540-
#[cfg(test)]
541-
mod tests {
542-
use super::*;
543-
544-
#[test]
545-
fn decodes_valid_pkcs11_uri() {
546-
// test input URIs taken from RFC examples section and combined with properties we actually use
547-
// https://www.rfc-editor.org/rfc/rfc7512.html#section-3
548-
let input = "pkcs11:token=The%20Software%20PKCS%2311%20Softtoken;\
549-
manufacturer=Snake%20Oil,%20Inc.;\
550-
model=1.0;\
551-
object=my-certificate;\
552-
type=cert;\
553-
id=%69%95%3E%5C%F4%BD%EC%91;\
554-
serial=\
555-
?pin-source=file:/etc/token_pin";
556-
557-
let attributes = Pkcs11Uri::parse(input).unwrap();
558-
559-
assert_eq!(attributes.token.unwrap(), "The Software PKCS#11 Softtoken");
560-
assert_eq!(
561-
attributes.other.get("manufacturer").unwrap(),
562-
"Snake Oil, Inc."
563-
);
564-
assert_eq!(attributes.other.get("model").unwrap(), "1.0");
565-
assert_eq!(attributes.serial.unwrap(), "");
566-
assert_eq!(attributes.object.unwrap(), "my-certificate");
567-
assert_eq!(
568-
attributes.id,
569-
Some(vec![0x69, 0x95, 0x3e, 0x5c, 0xf4, 0xbd, 0xec, 0x91])
570-
);
571-
}
572-
573-
#[test]
574-
fn fails_on_uris_with_duplicate_attributes() {
575-
let input = "pkcs11:token=my-token;token=my-token";
576-
let err = Pkcs11Uri::parse(input).unwrap_err();
577-
assert!(err
578-
.to_string()
579-
.contains("PKCS#11 URI contains duplicate attribute (token)"));
580-
}
581-
582-
#[test]
583-
fn fails_on_uris_with_invalid_scheme() {
584-
let input = "not a pkcs#11 uri";
585-
let err = Pkcs11Uri::parse(input).unwrap_err();
586-
assert!(err.to_string().contains("missing PKCS #11 URI scheme"));
587-
}
588-
589-
#[test]
590-
fn appends_attributes_correctly() {
591-
let mut uri1 = Pkcs11Uri::parse("pkcs11:token=token1").unwrap();
592-
let uri2 = Pkcs11Uri::parse(
593-
"pkcs11:token=token2;serial=serial2;id=%01%02;object=object2;key1=value1",
594-
)
595-
.unwrap();
596-
597-
uri1.append_attributes(uri2);
598-
599-
assert_eq!(uri1.token.unwrap(), "token1");
600-
assert_eq!(uri1.serial.unwrap(), "serial2");
601-
assert_eq!(uri1.id, Some(vec![0x01, 0x02]));
602-
assert_eq!(uri1.object.unwrap(), "object2");
603-
assert_eq!(uri1.other.get("key1").unwrap(), "value1");
604-
}
605-
606-
#[test]
607-
fn appends_attributes_with_no_conflicts() {
608-
let mut uri1 = Pkcs11Uri::parse("pkcs11:").unwrap();
609-
let uri2 = Pkcs11Uri::parse(
610-
"pkcs11:token=token2;serial=serial2;id=%01%02;object=object2;key1=value1",
611-
)
612-
.unwrap();
613-
614-
uri1.append_attributes(uri2);
615-
616-
assert_eq!(uri1.token.unwrap(), "token2");
617-
assert_eq!(uri1.serial.unwrap(), "serial2");
618-
assert_eq!(uri1.id, Some(vec![0x01, 0x02]));
619-
assert_eq!(uri1.object.unwrap(), "object2");
620-
assert_eq!(uri1.other.get("key1").unwrap(), "value1");
621-
}
622-
623-
#[test]
624-
fn does_not_override_existing_attributes() {
625-
let mut uri1 = Pkcs11Uri::parse(
626-
"pkcs11:token=token1;serial=serial1;id=%01;object=object1;key1=value1",
627-
)
628-
.unwrap();
629-
let uri2 = Pkcs11Uri::parse(
630-
"pkcs11:token=token2;serial=serial2;id=%02;object=object2;key2=value2",
631-
)
632-
.unwrap();
633-
634-
uri1.append_attributes(uri2);
635-
636-
assert_eq!(uri1.token.unwrap(), "token1");
637-
assert_eq!(uri1.serial.unwrap(), "serial1");
638-
assert_eq!(uri1.id, Some(vec![0x01]));
639-
assert_eq!(uri1.object.unwrap(), "object1");
640-
assert_eq!(uri1.other.get("key1").unwrap(), "value1");
641-
assert_eq!(uri1.other.get("key2").unwrap(), "value2");
642-
}
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);
643496
}
644497
}

0 commit comments

Comments
 (0)