commit 9165a5cf61cc8a2db74ff3634155560fe1401a50
parent 22b48502a5726985237faea0df5e2e5d2541ce4c
Author: Zack Newman <zack@philomathiclife.com>
Date: Mon, 13 Jul 2026 15:08:04 -0600
bump msrv. update deps. new lints. move unit tests
Diffstat:
6 files changed, 909 insertions(+), 918 deletions(-)
diff --git a/Cargo.toml b/Cargo.toml
@@ -9,7 +9,7 @@ license = "MIT OR Apache-2.0"
name = "webauthn_rp"
readme = "README.md"
repository = "https://git.philomathiclife.com/repos/webauthn_rp/"
-rust-version = "1.95.0"
+rust-version = "1.97.0"
version = "0.4.0+spec-3"
[lints.rust]
@@ -28,12 +28,14 @@ unused = { level = "deny", priority = -1 }
warnings = { level = "deny", priority = -1 }
ambiguous-negative-literals = { level = "deny", priority = -1 }
closure-returning-async-block = { level = "deny", priority = -1 }
+dead-code-pub-in-binary = { level = "deny", priority = -1 }
deprecated-in-future = { level = "deny", priority = -1 }
+#deprecated-llvm-intrinsic = { level = "deny", priority = -1 }
deref-into-dyn-supertrait = { level = "deny", priority = -1 }
ffi-unwind-calls = { level = "deny", priority = -1 }
#fuzzy-provenance-casts = { level = "deny", priority = -1 }
impl-trait-redundant-captures = { level = "deny", priority = -1 }
-linker-messages = { level = "deny", priority = -1 }
+linker-info = { level = "deny", priority = -1 }
#lossy-provenance-casts = { level = "deny", priority = -1 }
macro-use-extern-crate = { level = "deny", priority = -1 }
meta-variable-misuse = { level = "deny", priority = -1 }
@@ -81,6 +83,7 @@ decimal_bitwise_operands = "allow"
exhaustive_enums = "allow"
exhaustive_structs = "allow"
implicit_return = "allow"
+inline_trait_bounds = "allow"
min_ident_chars = "allow"
missing_trait_methods = "allow"
module_name_repetitions = "allow"
@@ -115,25 +118,25 @@ targets = [
]
[dependencies]
-base64url_nopad = { version = "0.1.5", default-features = false }
-ed25519-dalek = { version = "3.0.0-pre.7", default-features = false }
-hashbrown = { version = "0.17.0", default-features = false }
-ml-dsa = { version = "0.1.0-rc.9", default-features = false }
-p256 = { version = "0.14.0-rc.9", default-features = false, features = ["ecdsa"] }
-p384 = { version = "0.14.0-rc.9", default-features = false, features = ["ecdsa"] }
-rand = { version = "0.10.1", default-features = false, features = ["thread_rng"] }
+base64url_nopad = { version = "0.2.0", default-features = false }
+ed25519-dalek = { version = "3.0.0", default-features = false }
+hashbrown = { version = "0.17.1", default-features = false }
+ml-dsa = { version = "0.1.1", default-features = false }
+p256 = { version = "0.14.0", default-features = false, features = ["ecdsa"] }
+p384 = { version = "0.14.0", default-features = false, features = ["ecdsa"] }
+rand = { version = "0.10.2", default-features = false, features = ["thread_rng"] }
rsa = { version = "0.10.0-rc.18", default-features = false, features = ["encoding", "sha2"] }
serde = { version = "1.0.228", default-features = false, features = ["alloc"], optional = true }
-serde_json = { version = "1.0.149", default-features = false, features = ["alloc"], optional = true }
+serde_json = { version = "1.0.150", default-features = false, features = ["alloc"], optional = true }
url = { version = "2.5.8", default-features = false }
[dev-dependencies]
-base64url_nopad = { version = "0.1.5", default-features = false, features = ["alloc"] }
-ed25519-dalek = { version = "3.0.0-pre.7", default-features = false, features = ["alloc", "pkcs8"] }
-ml-dsa = { version = "0.1.0-rc.9", default-features = false, features = ["alloc", "pkcs8"] }
-p256 = { version = "0.14.0-rc.9", default-features = false, features = ["pem"] }
-p384 = { version = "0.14.0-rc.9", default-features = false, features = ["pkcs8"] }
-serde_json = { version = "1.0.149", default-features = false, features = ["preserve_order"] }
+base64url_nopad = { version = "0.2.0", default-features = false, features = ["alloc"] }
+ed25519-dalek = { version = "3.0.0", default-features = false, features = ["alloc", "pkcs8"] }
+ml-dsa = { version = "0.1.1", default-features = false, features = ["alloc", "pkcs8"] }
+p256 = { version = "0.14.0", default-features = false, features = ["pem"] }
+p384 = { version = "0.14.0", default-features = false, features = ["pkcs8"] }
+serde_json = { version = "1.0.150", default-features = false, features = ["preserve_order"] }
### FEATURES #################################################################
diff --git a/src/lib.rs b/src/lib.rs
@@ -506,6 +506,7 @@
clippy::doc_paragraphs_missing_punctuation,
reason = "false positive for crate documentation having image links"
)]
+#![cfg_attr(test, expect(dead_code_pub_in_binary, reason = "ignore for tests"))]
#![cfg_attr(docsrs, feature(doc_cfg))]
//#[cfg(not(any(feature = "custom", all(feature = "bin", feature = "serde"))))]
//compile_error!("'custom' must be enabled or both 'bin' and 'serde' must be enabled");
diff --git a/src/response/register/ser.rs b/src/response/register/ser.rs
@@ -24,902 +24,7 @@ use serde::de::{Deserialize, Deserializer, Error, IgnoredAny, MapAccess, Unexpec
/// Functionality for deserializing DER-encoded `SubjectPublicKeyInfo` _without_ making copies of data or
/// verifying the key is valid. This exists purely to ensure that the public key we receive in JSON is the same as
/// the public key in the attestation object.
-mod spki {
- use super::super::{
- Ed25519PubKey, MlDsa44PubKey, MlDsa65PubKey, MlDsa87PubKey, RsaPubKey, RsaPubKeyErr,
- UncompressedP256PubKey, UncompressedP384PubKey, UncompressedPubKey,
- };
- use core::fmt::{self, Display, Formatter};
- use p256::{
- NistP256,
- elliptic_curve::{Curve, common::typenum::type_operators::ToInt as _},
- };
- use p384::NistP384;
- /// Value assigned to the integer type under the universal tag class per
- /// [ITU-T X.680](https://www.itu.int/rec/T-REC-X.680-202102-I/en).
- const INTEGER: u8 = 2;
- /// Value assigned to the bitstring type under the universal tag class per
- /// [ITU-T X.680](https://www.itu.int/rec/T-REC-X.680-202102-I/en).
- const BITSTRING: u8 = 3;
- /// Value assigned to the null type under the universal tag class per
- /// [ITU-T X.680](https://www.itu.int/rec/T-REC-X.680-202102-I/en).
- const NULL: u8 = 5;
- /// Value assigned to the object identifier type under the universal tag class per
- /// [ITU-T X.680](https://www.itu.int/rec/T-REC-X.680-202102-I/en).
- const OID: u8 = 6;
- /// Value assigned to the sequence type under the universal tag class per
- /// [ITU-T X.680](https://www.itu.int/rec/T-REC-X.680-202102-I/en).
- const SEQUENCE: u8 = 16;
- /// Value assigned to a constructed [`SEQUENCE`] per
- /// [ITU-T X.690](https://www.itu.int/rec/T-REC-X.690-202102-I/en).
- ///
- /// All sequences are constructed once encoded, so this will likely always be used instead of
- /// `SEQUENCE`.
- const CONSTRUCTED_SEQUENCE: u8 = SEQUENCE | 0b0010_0000;
- /// Length of the header before the encoded key in a DER-encoded ASN.1 `SubjectPublicKeyInfo`
- /// for an ML-DSA-87 public key.
- const MLDSA87_HEADER_LEN: usize = 22;
- /// Length of a DER-encoded ASN.1 `SubjectPublicKeyInfo` for ML-DSA-87 public key.
- const MLDSA87_LEN: usize = MLDSA87_HEADER_LEN + 2592;
- /// Length of the header before the encoded key in a DER-encoded ASN.1 `SubjectPublicKeyInfo`
- /// for an ML-DSA-65 public key.
- const MLDSA65_HEADER_LEN: usize = 22;
- /// Length of a DER-encoded ASN.1 `SubjectPublicKeyInfo` for ML-DSA-65 public key.
- const MLDSA65_LEN: usize = MLDSA65_HEADER_LEN + 1952;
- /// Length of the header before the encoded key in a DER-encoded ASN.1 `SubjectPublicKeyInfo`
- /// for an ML-DSA-44 public key.
- const MLDSA44_HEADER_LEN: usize = 22;
- /// Length of a DER-encoded ASN.1 `SubjectPublicKeyInfo` for ML-DSA-44 public key.
- const MLDSA44_LEN: usize = MLDSA44_HEADER_LEN + 1312;
- /// Length of the header before the compressed y-coordinate in a DER-encoded ASN.1 `SubjectPublicKeyInfo`
- /// for an Ed25519 public key.
- const ED25519_HEADER_LEN: usize = 12;
- /// Length of a DER-encoded ASN.1 `SubjectPublicKeyInfo` for Ed25519 public key.
- const ED25519_LEN: usize = ED25519_HEADER_LEN + ed25519_dalek::PUBLIC_KEY_LENGTH;
- /// `ED25519_LEN` as a `u8`.
- // `44 as u8` is clearly OK.
- #[expect(
- clippy::as_conversions,
- clippy::cast_possible_truncation,
- reason = "comments above justify their correctness"
- )]
- const ED25519_LEN_U8: u8 = ED25519_LEN as u8;
- /// Length of the header before the uncompressed SEC- 1 pubic key in a DER-encoded ASN.1 `SubjectPublicKeyInfo`
- /// for an uncompressed ECDSA public key based on secp256r1/P-256.
- const P256_HEADER_LEN: usize = 27;
- /// Number of bytes the x-coordinate takes in an uncompressed P-256 public key.
- const P256_X_LEN: usize = <NistP256 as Curve>::FieldBytesSize::INT;
- /// Number of bytes the y-coordinate takes in an uncompressed P-256 public key.
- const P256_Y_LEN: usize = <NistP256 as Curve>::FieldBytesSize::INT;
- /// Number of bytes the x and y coordinates take in an uncompressed P-256 public key when concatenated together.
- const P256_COORD_LEN: usize = P256_X_LEN + P256_Y_LEN;
- /// Length of a DER-encoded ASN.1 `SubjectPublicKeyInfo` for an uncompressed SEC-1 ECDSA public key
- /// based on secp256r1/P-256.
- const P256_LEN: usize = P256_HEADER_LEN + P256_COORD_LEN;
- /// `P256_LEN` as a `u8`.
- // `91 as u8` is clearly OK.
- #[expect(
- clippy::as_conversions,
- clippy::cast_possible_truncation,
- reason = "comments above justify their correctness"
- )]
- const P256_LEN_U8: u8 = P256_LEN as u8;
- /// Length of the header before the uncompressed SEC- 1 pubic key in a DER-encoded ASN.1 `SubjectPublicKeyInfo`
- /// for an uncompressed ECDSA public key based on secp384r1/P-384.
- const P384_HEADER_LEN: usize = 24;
- /// Number of bytes the x-coordinate takes in an uncompressed P-384 public key.
- const P384_X_LEN: usize = <NistP384 as Curve>::FieldBytesSize::INT;
- /// Number of bytes the y-coordinate takes in an uncompressed P-384 public key.
- const P384_Y_LEN: usize = <NistP384 as Curve>::FieldBytesSize::INT;
- /// Number of bytes the x and y coordinates take in an uncompressed P-384 public key when concatenated together.
- const P384_COORD_LEN: usize = P384_X_LEN + P384_Y_LEN;
- /// Length of a DER-encoded ASN.1 `SubjectPublicKeyInfo` for an uncompressed SEC-1 ECDSA public key
- /// based on secp384r1/P-384.
- const P384_LEN: usize = P384_HEADER_LEN + P384_COORD_LEN;
- /// `P384_LEN` as a `u8`.
- // `120 as u8` is clearly OK.
- #[expect(
- clippy::as_conversions,
- clippy::cast_possible_truncation,
- reason = "comments above justify their correctness"
- )]
- const P384_LEN_U8: u8 = P384_LEN as u8;
- /// Error returned from [`SubjectPublicKeyInfo::from_der`].
- pub(super) enum SubjectPublicKeyInfoErr {
- /// The length of the DER-encoded ML-DSA-87 key was invalid.
- MlDsa87Len,
- /// The header of the DER-encoded ML-DSA-87 key was invalid.
- MlDsa87Header,
- /// The length of the DER-encoded ML-DSA-65 key was invalid.
- MlDsa65Len,
- /// The header of the DER-encoded ML-DSA-65 key was invalid.
- MlDsa65Header,
- /// The length of the DER-encoded ML-DSA-44 key was invalid.
- MlDsa44Len,
- /// The header of the DER-encoded ML-DSA-44 key was invalid.
- MlDsa44Header,
- /// The length of the DER-encoded Ed25519 key was invalid.
- Ed25519Len,
- /// The header of the DER-encoded Ed25519 key was invalid.
- Ed25519Header,
- /// The length of the DER-encoded P-256 key was invalid.
- P256Len,
- /// The header of the DER-encoded P-256 key was invalid.
- P256Header,
- /// The length of the DER-encoded P-384 key was invalid.
- P384Len,
- /// The header of the DER-encoded P-384 key was invalid.
- P384Header,
- /// The length of the DER-encoded RSA key was invalid.
- RsaLen,
- /// The DER-encoding of the RSA key was invalid.
- RsaEncoding,
- /// The exponent of the DER-encoded RSA key was too large.
- RsaExponentTooLarge,
- /// The DER-encoded RSA key was not a valid [`RsaPubKey`].
- RsaPubKey(RsaPubKeyErr),
- }
- impl Display for SubjectPublicKeyInfoErr {
- fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
- match *self {
- Self::MlDsa87Len => {
- f.write_str("length of the DER-encoded ML-DSA-87 key was invalid")
- }
- Self::MlDsa87Header => {
- f.write_str("header of the DER-encoded ML-DSA-87 key was invalid")
- }
- Self::MlDsa65Len => {
- f.write_str("length of the DER-encoded ML-DSA-65 key was invalid")
- }
- Self::MlDsa65Header => {
- f.write_str("header of the DER-encoded ML-DSA-65 key was invalid")
- }
- Self::MlDsa44Len => {
- f.write_str("length of the DER-encoded ML-DSA-44 key was invalid")
- }
- Self::MlDsa44Header => {
- f.write_str("header of the DER-encoded ML-DSA-44 key was invalid")
- }
- Self::Ed25519Len => {
- f.write_str("length of the DER-encoded Ed25519 key was invalid")
- }
- Self::Ed25519Header => {
- f.write_str("header of the DER-encoded Ed25519 key was invalid")
- }
- Self::P256Len => f.write_str("length of the DER-encoded P-256 key was invalid"),
- Self::P256Header => f.write_str("header of the DER-encoded P-256 key was invalid"),
- Self::P384Len => f.write_str("length of the DER-encoded P-384 key was invalid"),
- Self::P384Header => f.write_str("header of the DER-encoded P-384 key was invalid"),
- Self::RsaLen => f.write_str("length of the DER-encoded RSA key was invalid"),
- Self::RsaEncoding => {
- f.write_str("the DER-encoding of the RSA public key was invalid")
- }
- Self::RsaExponentTooLarge => {
- f.write_str("the DER-encoded RSA public key had an exponent that was too large")
- }
- Self::RsaPubKey(err) => {
- write!(f, "the DER-encoded RSA public was not valid: {err}")
- }
- }
- }
- }
- /// Types that can be deserialized from the DER-encoded ASN.1 `SubjectPublicKeyInfo` as defined in
- /// [RFC 5280](https://datatracker.ietf.org/doc/html/rfc5280#appendix-A.1) and other applicable RFCs
- /// and documents (e.g., [ITU-T X.690](https://www.itu.int/rec/T-REC-X.690-202102-I/en)).
- pub(super) trait SubjectPublicKeyInfo<'a>: Sized {
- /// Transforms the DER-encoded ASN.1 `SubjectPublicKeyInfo` into `Self`.
- ///
- /// # Errors
- ///
- /// Errors iff `der` does not conform.
- #[expect(single_use_lifetimes, reason = "false positive")]
- fn from_der<'b: 'a>(der: &'b [u8]) -> Result<Self, SubjectPublicKeyInfoErr>;
- }
- impl<'a> SubjectPublicKeyInfo<'a> for MlDsa87PubKey<&'a [u8]> {
- #[expect(single_use_lifetimes, reason = "false positive")]
- fn from_der<'b: 'a>(der: &'b [u8]) -> Result<Self, SubjectPublicKeyInfoErr> {
- /// ```asn
- /// SubjectPublicKeyInfo ::= SEQUENCE {
- /// algorithm AlgorithmIdentifier,
- /// subjectPublicKey BIT STRING
- /// }
- ///
- /// AlgorithmIdentifier ::= SEQUENCE {
- /// algorithm OBJECT IDENTIFIER,
- /// parameters ANY DEFINED BY algorithm OPTIONAL
- /// }
- /// ```
- ///
- /// [RFC 9882](https://www.rfc-editor.org/rfc/rfc9882.html) requires parameters to not exist
- /// in `AlgorithmIdentifier`.
- ///
- /// RFC 9882 defines the OID as 2.16.840.1.101.3.4.3.19 which is encoded as 96.134.72.1.101.3.4.3.19
- /// per [X.690](https://www.itu.int/rec/T-REC-X.690-202102-I/en).
- ///
- /// [FIPS 204](https://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.204.pdf) defines the bitstring as a reinterpretation of the byte string.
- const HEADER: [u8; MLDSA87_HEADER_LEN] = [
- CONSTRUCTED_SEQUENCE,
- 130,
- 10,
- 50,
- CONSTRUCTED_SEQUENCE,
- 9 + 2,
- OID,
- 9,
- 96,
- 134,
- 72,
- 1,
- 101,
- 3,
- 4,
- 3,
- 19,
- BITSTRING,
- 130,
- 10,
- 33,
- // The number of unused bits.
- 0,
- ];
- der.split_at_checked(HEADER.len())
- .ok_or(SubjectPublicKeyInfoErr::MlDsa87Len)
- .and_then(|(header, rem)| {
- if header == HEADER {
- if rem.len() == 2592 {
- Ok(Self(rem))
- } else {
- Err(SubjectPublicKeyInfoErr::MlDsa87Len)
- }
- } else {
- Err(SubjectPublicKeyInfoErr::MlDsa87Header)
- }
- })
- }
- }
- impl<'a> SubjectPublicKeyInfo<'a> for MlDsa65PubKey<&'a [u8]> {
- #[expect(single_use_lifetimes, reason = "false positive")]
- fn from_der<'b: 'a>(der: &'b [u8]) -> Result<Self, SubjectPublicKeyInfoErr> {
- /// ```asn
- /// SubjectPublicKeyInfo ::= SEQUENCE {
- /// algorithm AlgorithmIdentifier,
- /// subjectPublicKey BIT STRING
- /// }
- ///
- /// AlgorithmIdentifier ::= SEQUENCE {
- /// algorithm OBJECT IDENTIFIER,
- /// parameters ANY DEFINED BY algorithm OPTIONAL
- /// }
- /// ```
- ///
- /// [RFC 9882](https://www.rfc-editor.org/rfc/rfc9882.html) requires parameters to not exist
- /// in `AlgorithmIdentifier`.
- ///
- /// RFC 9882 defines the OID as 2.16.840.1.101.3.4.3.18 which is encoded as 96.134.72.1.101.3.4.3.18
- /// per [X.690](https://www.itu.int/rec/T-REC-X.690-202102-I/en).
- ///
- /// [FIPS 204](https://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.204.pdf) defines the bitstring as a reinterpretation of the byte string.
- const HEADER: [u8; MLDSA65_HEADER_LEN] = [
- CONSTRUCTED_SEQUENCE,
- 130,
- 7,
- 178,
- CONSTRUCTED_SEQUENCE,
- 9 + 2,
- OID,
- 9,
- 96,
- 134,
- 72,
- 1,
- 101,
- 3,
- 4,
- 3,
- 18,
- BITSTRING,
- 130,
- 7,
- 161,
- // The number of unused bits.
- 0,
- ];
- der.split_at_checked(HEADER.len())
- .ok_or(SubjectPublicKeyInfoErr::MlDsa65Len)
- .and_then(|(header, rem)| {
- if header == HEADER {
- if rem.len() == 1952 {
- Ok(Self(rem))
- } else {
- Err(SubjectPublicKeyInfoErr::MlDsa65Len)
- }
- } else {
- Err(SubjectPublicKeyInfoErr::MlDsa65Header)
- }
- })
- }
- }
- impl<'a> SubjectPublicKeyInfo<'a> for MlDsa44PubKey<&'a [u8]> {
- #[expect(single_use_lifetimes, reason = "false positive")]
- fn from_der<'b: 'a>(der: &'b [u8]) -> Result<Self, SubjectPublicKeyInfoErr> {
- /// ```asn
- /// SubjectPublicKeyInfo ::= SEQUENCE {
- /// algorithm AlgorithmIdentifier,
- /// subjectPublicKey BIT STRING
- /// }
- ///
- /// AlgorithmIdentifier ::= SEQUENCE {
- /// algorithm OBJECT IDENTIFIER,
- /// parameters ANY DEFINED BY algorithm OPTIONAL
- /// }
- /// ```
- ///
- /// [RFC 9882](https://www.rfc-editor.org/rfc/rfc9882.html) requires parameters to not exist
- /// in `AlgorithmIdentifier`.
- ///
- /// RFC 9882 defines the OID as 2.16.840.1.101.3.4.3.17 which is encoded as 96.134.72.1.101.3.4.3.17
- /// per [X.690](https://www.itu.int/rec/T-REC-X.690-202102-I/en).
- ///
- /// [FIPS 204](https://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.204.pdf) defines the bitstring as a reinterpretation of the byte string.
- const HEADER: [u8; MLDSA44_HEADER_LEN] = [
- CONSTRUCTED_SEQUENCE,
- 130,
- 5,
- 50,
- CONSTRUCTED_SEQUENCE,
- 9 + 2,
- OID,
- 9,
- 96,
- 134,
- 72,
- 1,
- 101,
- 3,
- 4,
- 3,
- 17,
- BITSTRING,
- 130,
- 5,
- 33,
- // The number of unused bits.
- 0,
- ];
- der.split_at_checked(HEADER.len())
- .ok_or(SubjectPublicKeyInfoErr::MlDsa44Len)
- .and_then(|(header, rem)| {
- if header == HEADER {
- if rem.len() == 1312 {
- Ok(Self(rem))
- } else {
- Err(SubjectPublicKeyInfoErr::MlDsa44Len)
- }
- } else {
- Err(SubjectPublicKeyInfoErr::MlDsa44Header)
- }
- })
- }
- }
- impl<'a> SubjectPublicKeyInfo<'a> for Ed25519PubKey<&'a [u8]> {
- #[expect(single_use_lifetimes, reason = "false positive")]
- fn from_der<'b: 'a>(der: &'b [u8]) -> Result<Self, SubjectPublicKeyInfoErr> {
- /// ```asn
- /// SubjectPublicKeyInfo ::= SEQUENCE {
- /// algorithm AlgorithmIdentifier,
- /// subjectPublicKey BIT STRING
- /// }
- ///
- /// AlgorithmIdentifier ::= SEQUENCE {
- /// algorithm OBJECT IDENTIFIER,
- /// parameters ANY DEFINED BY algorithm OPTIONAL
- /// }
- /// ```
- ///
- /// [RFC 8410](https://www.rfc-editor.org/rfc/rfc8410#section-3) requires parameters to not exist
- /// in `AlgorithmIdentifier`.
- ///
- /// RFC 8410 defines the OID as 1.3.101.112 which is encoded as 43.101.112
- /// per [X.690](https://www.itu.int/rec/T-REC-X.690-202102-I/en).
- ///
- /// RFC 8410 defines the bitstring as a reinterpretation of the byte string.
- const HEADER: [u8; ED25519_HEADER_LEN] = [
- CONSTRUCTED_SEQUENCE,
- // `ED25519_LEN_U8` is the length of the entire payload; thus we subtract
- // the "consumed" length.
- ED25519_LEN_U8 - 2,
- CONSTRUCTED_SEQUENCE,
- // AlgorithmIdentifier only contains the OID; thus it's length is 5.
- 3 + 2,
- OID,
- 3,
- 43,
- 101,
- 112,
- BITSTRING,
- // `ED25519_LEN_U8` is the length of the entire payload; thus we subtract
- // the "consumed" length.
- ED25519_LEN_U8 - 11,
- // The number of unused bits.
- 0,
- ];
- der.split_at_checked(HEADER.len())
- .ok_or(SubjectPublicKeyInfoErr::Ed25519Len)
- .and_then(|(header, rem)| {
- if header == HEADER {
- if rem.len() == ed25519_dalek::PUBLIC_KEY_LENGTH {
- Ok(Self(rem))
- } else {
- Err(SubjectPublicKeyInfoErr::Ed25519Len)
- }
- } else {
- Err(SubjectPublicKeyInfoErr::Ed25519Header)
- }
- })
- }
- }
- impl<'a> SubjectPublicKeyInfo<'a> for UncompressedP256PubKey<'a> {
- #[expect(single_use_lifetimes, reason = "false positive")]
- fn from_der<'b: 'a>(der: &'b [u8]) -> Result<Self, SubjectPublicKeyInfoErr> {
- // ```asn
- // SubjectPublicKeyInfo ::= SEQUENCE {
- // algorithm AlgorithmIdentifier,
- // subjectPublicKey BIT STRING
- // }
- //
- // AlgorithmIdentifier ::= SEQUENCE {
- // algorithm OBJECT IDENTIFIER,
- // parameters ANY DEFINED BY algorithm OPTIONAL
- // }
- //
- // ECParameters ::= CHOICE {
- // namedCurve OBJECT IDENTIFIER
- // -- implicitCurve NULL
- // -- specifiedCurve SpecifiedECDomain
- // }
- // ```
- // [RFC 5480](https://www.rfc-editor.org/rfc/rfc5480#section-2.1.1) requires parameters to exist and
- // be of the `ECParameters` form with the requirement that `namedCurve` is chosen.
- //
- // RFC 5480 defines the OID for id-ecPublicKey as 1.2.840.10045.2.1 and the OID for the namedCurve
- // secp256r1 as 1.2.840.10045.3.1.7. The former OID is encoded as 42.134.72.206.61.2.1 and the latter
- // is encoded as 42.134.72.206.61.3.1.7 per [X.690](https://www.itu.int/rec/T-REC-X.690-202102-I/en).
- //
- // [RFC 5480](https://www.rfc-editor.org/rfc/rfc5480#section-5) only requires support for the
- // uncompressed form and only states that the compressed form MAY be supported. In practice this means
- // DER-encoded payloads almost always are of the uncompressed form for compatibility reasons. This in
- // conjunction with the fact the COSE key is required to be in the uncompressed form means we only support
- // DER-encoded payloads containing uncompressed keys.
- //
- // [SEC 1](https://secg.org/sec1-v2.pdf) defines the point as an octet string, and the conversion
- // to a bitstring simply requires reinterpreting the octet string as a bitstring.
-
- /// Header of the DER-encoded payload before the public key.
- const HEADER: [u8; P256_HEADER_LEN] = [
- CONSTRUCTED_SEQUENCE,
- // `P256_LEN_U8` is the length of the entire payload; thus we subtract
- // the "consumed" length.
- P256_LEN_U8 - 2,
- CONSTRUCTED_SEQUENCE,
- 7 + 2 + 8 + 2,
- OID,
- 7,
- 42,
- 134,
- 72,
- 206,
- 61,
- 2,
- 1,
- OID,
- 8,
- 42,
- 134,
- 72,
- 206,
- 61,
- 3,
- 1,
- 7,
- BITSTRING,
- // `P256_LEN_U8` is the length of the entire payload; thus we subtract
- // the "consumed" length.
- P256_LEN_U8 - 25,
- // The number of unused bits.
- 0,
- // SEC-1 tag for an uncompressed key.
- 4,
- ];
- der.split_at_checked(HEADER.len())
- .ok_or(SubjectPublicKeyInfoErr::P256Len)
- .and_then(|(header, header_rem)| {
- if header == HEADER {
- header_rem
- .split_at_checked(P256_X_LEN)
- .ok_or(SubjectPublicKeyInfoErr::P256Len)
- .and_then(|(x, y)| {
- if y.len() == P256_Y_LEN {
- Ok(Self(x, y))
- } else {
- Err(SubjectPublicKeyInfoErr::P256Len)
- }
- })
- } else {
- Err(SubjectPublicKeyInfoErr::P256Header)
- }
- })
- }
- }
- impl<'a> SubjectPublicKeyInfo<'a> for UncompressedP384PubKey<'a> {
- #[expect(single_use_lifetimes, reason = "false positive")]
- fn from_der<'b: 'a>(der: &'b [u8]) -> Result<Self, SubjectPublicKeyInfoErr> {
- // ```asn
- // SubjectPublicKeyInfo ::= SEQUENCE {
- // algorithm AlgorithmIdentifier,
- // subjectPublicKey BIT STRING
- // }
- //
- // AlgorithmIdentifier ::= SEQUENCE {
- // algorithm OBJECT IDENTIFIER,
- // parameters ANY DEFINED BY algorithm OPTIONAL
- // }
- //
- // ECParameters ::= CHOICE {
- // namedCurve OBJECT IDENTIFIER
- // -- implicitCurve NULL
- // -- specifiedCurve SpecifiedECDomain
- // }
- // ```
- // [RFC 5480](https://www.rfc-editor.org/rfc/rfc5480#section-2.1.1) requires parameters to exist and
- // be of the `ECParameters` form with the requirement that `namedCurve` is chosen.
- //
- // RFC 5480 defines the OID for id-ecPublicKey as 1.2.840.10045.2.1 and the OID for the namedCurve
- // secp384r1 as 1.3.132.0.34. The former OID is encoded as 42.134.72.206.61.2.1 and the latter
- // is encoded as 43.129.4.0.34 per [X.690](https://www.itu.int/rec/T-REC-X.690-202102-I/en).
- //
- // [RFC 5480](https://www.rfc-editor.org/rfc/rfc5480#section-5) only requires support for the
- // uncompressed form and only states that the compressed form MAY be supported. In practice this means
- // DER-encoded payloads almost always are of the uncompressed form for compatibility reasons. This in
- // conjunction with the fact the COSE key is required to be in the uncompressed form means we only support
- // DER-encoded payloads containing uncompressed keys.
- //
- // [SEC 1](https://secg.org/sec1-v2.pdf) defines the point as an octet string, and the conversion
- // to a bitstring simply requires reinterpreting the octet string as a bitstring.
-
- /// Header of the DER-encoded payload before the public key.
- const HEADER: [u8; P384_HEADER_LEN] = [
- CONSTRUCTED_SEQUENCE,
- // `P384_LEN_U8` is the length of the entire payload; thus we subtract
- // the "consumed" length.
- P384_LEN_U8 - 2,
- CONSTRUCTED_SEQUENCE,
- 7 + 2 + 5 + 2,
- OID,
- 7,
- 42,
- 134,
- 72,
- 206,
- 61,
- 2,
- 1,
- OID,
- 5,
- 43,
- 129,
- 4,
- 0,
- 34,
- BITSTRING,
- // `P384_LEN_U8` is the length of the entire payload; thus we subtract
- // the "consumed" length.
- P384_LEN_U8 - 22,
- // The number of unused bits.
- 0,
- // SEC-1 tag for an uncompressed key.
- 4,
- ];
- der.split_at_checked(HEADER.len())
- .ok_or(SubjectPublicKeyInfoErr::P384Len)
- .and_then(|(header, header_rem)| {
- if header == HEADER {
- header_rem
- .split_at_checked(P384_X_LEN)
- .ok_or(SubjectPublicKeyInfoErr::P384Len)
- .and_then(|(x, y)| {
- if y.len() == P384_Y_LEN {
- Ok(Self(x, y))
- } else {
- Err(SubjectPublicKeyInfoErr::P384Len)
- }
- })
- } else {
- Err(SubjectPublicKeyInfoErr::P384Header)
- }
- })
- }
- }
- impl<'a> SubjectPublicKeyInfo<'a> for RsaPubKey<&'a [u8]> {
- #[expect(single_use_lifetimes, reason = "false positive")]
- #[expect(
- clippy::arithmetic_side_effects,
- clippy::big_endian_bytes,
- clippy::indexing_slicing,
- clippy::missing_asserts_for_indexing,
- reason = "comments justify their correctness"
- )]
- #[expect(
- clippy::too_many_lines,
- reason = "rsa keys are the only type that would benefit from a modular SubjectPublicKeyInfo (similar to FromCbor). if more types benefit in the future, then this will be done."
- )]
- fn from_der<'b: 'a>(der: &'b [u8]) -> Result<Self, SubjectPublicKeyInfoErr> {
- // ```asn
- // SubjectPublicKeyInfo ::= SEQUENCE {
- // algorithm AlgorithmIdentifier,
- // subjectPublicKey BIT STRING
- // }
- //
- // AlgorithmIdentifier ::= SEQUENCE {
- // algorithm OBJECT IDENTIFIER,
- // parameters ANY DEFINED BY algorithm OPTIONAL
- // }
- //
- // RSAPublicKey ::= SEQUENCE {
- // modulus INTEGER, -- n
- // publicExponent INTEGER --e
- // }
- //
- // pkcs-1 OBJECT IDENTIFIER ::= { iso(1) member-body(2) us(840)
- // rsadsi(113549) pkcs(1) 1
- // }
- //
- // rsaEncryption OBJECT IDENTIFIER ::= { pkcs-1 1}
- // ```
- // [RFC 3279](https://www.rfc-editor.org/rfc/rfc3279#section-2.3.1) requires parameters to exist and
- // be null.
- //
- // RFC 3279 defines the OID for rsaEncryption as 1.2.840.113549.1.1.1 which is encoded as
- // 42.134.72.134.247.13.1.1.1 per [X.690](https://www.itu.int/rec/T-REC-X.690-202102-I/en).
- //
- // Note we only allow moduli that are 256 to 2048 bytes in length inclusively. Additionally
- // we only allow `u32` exponents; consequently all lengths that include the modulus will always be
- // encoded with two bytes.
-
- /// `AlgorithmIdentifier` header including the `BITSTRING` and number of bytes the length
- /// is encoded in of the `BITSTRING` type of `subjectPublicKey`
- /// (130-128 = 2 bytes to encode the length).
- const ALG_OID_HEADER: [u8; 17] = [
- CONSTRUCTED_SEQUENCE,
- 9 + 2 + 2,
- OID,
- 9,
- 42,
- 134,
- 72,
- 134,
- 247,
- 13,
- 1,
- 1,
- 1,
- NULL,
- 0,
- BITSTRING,
- 130,
- ];
- /// `CONSTRUCTED_SEQUENCE` whose length is encoded in two bytes.
- const SEQ_LONG: [u8; 2] = [CONSTRUCTED_SEQUENCE, 130];
- /// `INTEGER` whose length is encoded in two bytes.
- const INT_LONG: [u8; 2] = [INTEGER, 130];
- der.split_at_checked(SEQ_LONG.len())
- .ok_or(SubjectPublicKeyInfoErr::RsaLen)
- .and_then(|(seq, seq_rem)| {
- if seq == SEQ_LONG {
- seq_rem
- .split_at_checked(2)
- .ok_or(SubjectPublicKeyInfoErr::RsaLen)
- .and_then(|(seq_len, seq_len_rem)| {
- let mut len = [0; 2];
- len.copy_from_slice(seq_len);
- let rem_len = usize::from(u16::from_be_bytes(len));
- if rem_len == seq_len_rem.len() {
- if rem_len > 255 {
- // We can safely split here since we know `seq_len_rem` is at least
- // 256 which is greater than `ALG_OID_HEADER.len()`.
- let (a_oid, a_oid_rem) = seq_len_rem.split_at(ALG_OID_HEADER.len());
- if a_oid == ALG_OID_HEADER {
- // `a_oid_rem.len()` is at least 239, so splitting is fine.
- let (bit_str_len_enc, bit_str_val) = a_oid_rem.split_at(2);
- let mut bit_string_len = [0; 2];
- bit_string_len.copy_from_slice(bit_str_len_enc);
- let bit_str_val_len = usize::from(u16::from_be_bytes(bit_string_len));
- if bit_str_val_len == bit_str_val.len() {
- if bit_str_val_len > 255 {
- // `bit_str_val.len() > 255`, so splitting is fine.
- let (unused_bits, bits_rem) = bit_str_val.split_at(1);
- if unused_bits == [0] {
- // We can safely split here since we know `bits_rem.len()` is at least
- // 255.
- let (rsa_seq, rsa_seq_rem) = bits_rem.split_at(SEQ_LONG.len());
- if rsa_seq == SEQ_LONG {
- // `rsa_seq_rem.len()` is at least 253, so splitting is fine.
- let (rsa_seq_len_enc, rsa_seq_len_enc_rem) = rsa_seq_rem.split_at(2);
- let mut rsa_seq_len = [0; 2];
- rsa_seq_len.copy_from_slice(rsa_seq_len_enc);
- let rsa_key_info_len = usize::from(u16::from_be_bytes(rsa_seq_len));
- if rsa_key_info_len == rsa_seq_len_enc_rem.len() {
- if rsa_key_info_len > 255 {
- // We can safely split here since we know `rsa_seq_len_enc_rem.len()`
- // is at least 256.
- let (n_meta, n_meta_rem) = rsa_seq_len_enc_rem.split_at(INT_LONG.len());
- if n_meta == INT_LONG {
- // `n_meta_rem.len()` is at least 254, so splitting is fine.
- let (n_len_enc, n_len_enc_rem) = n_meta_rem.split_at(2);
- let mut n_len = [0; 2];
- n_len.copy_from_slice(n_len_enc);
- let mod_len = usize::from(u16::from_be_bytes(n_len));
- if mod_len > 255 {
- n_len_enc_rem.split_at_checked(mod_len).ok_or(SubjectPublicKeyInfoErr::RsaLen).and_then(|(mut n, n_rem)| {
- // `n.len() > 255`, so indexing is fine.
- let n_first = n[0];
- // DER integers are signed; thus the most significant bit must be 0.
- // DER integers are minimally encoded; thus when a leading 0 exists,
- // the second byte must be at least 128.
- // `n.len() > 255`, so indexing is fine.
- if n_first < 128 && (n_first != 0 || n[1] > 127) {
- if n_first == 0 {
- // `n.len() > 255`, so indexing is fine.
- // We must remove the leading 0.
- n = &n[1..];
- }
- n_rem.split_first().ok_or(SubjectPublicKeyInfoErr::RsaLen).and_then(|(e_type, e_type_rem)| {
- if *e_type == INTEGER {
- e_type_rem.split_first().ok_or(SubjectPublicKeyInfoErr::RsaLen).and_then(|(e_len, e_len_rem)| {
- let e_len_usize = usize::from(*e_len);
- if e_len_usize == e_len_rem.len() {
- e_len_rem.first().ok_or(SubjectPublicKeyInfoErr::RsaLen).and_then(|&e_first| {
- // DER integers are signed; thus the most significant bit must be 0.
- if e_first < 128 {
- // `RsaPubKey` only allows `u32` exponents, which means we only care
- // about lengths up to 5.
- match e_len_usize {
- 1 => Ok(u32::from(e_first)),
- 2..=5 => if e_first == 0 {
- // DER integers are minimally encoded; thus when a leading
- // 0 exists, the second byte must be at least 128.
- // We know the length is at least 2; thus this won't `panic`.
- if e_len_rem[1] > 127 {
- let mut e = [0; 4];
- if e_len_usize == 5 {
- // We know the length is at least 2; thus this won't `panic`.
- e.copy_from_slice(&e_len_rem[1..]);
- } else {
- // `e.len() == 4` and `e_len_usize` is at most 4; thus underflow
- // won't occur nor will indexing `panic`. `e` is big-endian,
- // so we start from the right.
- e[4 - e_len_usize..].copy_from_slice(e_len_rem);
- }
- Ok(u32::from_be_bytes(e))
- } else {
- Err(SubjectPublicKeyInfoErr::RsaEncoding)
- }
- } else if e_len_usize == 5 {
- // 5 bytes are only possible for `INTEGER`s that
- // are greater than `i32::MAX`, which will be encoded
- // with a leading 0.
- Err(SubjectPublicKeyInfoErr::RsaEncoding)
- } else {
- let mut e = [0; 4];
- // `e.len() == 4` and `e_len_usize` is at most 4; thus underflow
- // won't occur nor will indexing `panic`. `e` is big-endian,
- // so we start from the right.
- e[4 - e_len_usize..].copy_from_slice(e_len_rem);
- Ok(u32::from_be_bytes(e))
- },
- _ => Err(SubjectPublicKeyInfoErr::RsaExponentTooLarge),
- }.and_then(|e| Self::try_from((n, e)).map_err(SubjectPublicKeyInfoErr::RsaPubKey))
- } else {
- Err(SubjectPublicKeyInfoErr::RsaEncoding)
- }
- })
- } else {
- Err(SubjectPublicKeyInfoErr::RsaLen)
- }
- })
- } else {
- Err(SubjectPublicKeyInfoErr::RsaEncoding)
- }
- })
- } else {
- Err(SubjectPublicKeyInfoErr::RsaEncoding)
- }
- })
- } else {
- Err(SubjectPublicKeyInfoErr::RsaEncoding)
- }
- } else {
- Err(SubjectPublicKeyInfoErr::RsaEncoding)
- }
- } else {
- Err(SubjectPublicKeyInfoErr::RsaEncoding)
- }
- } else {
- Err(SubjectPublicKeyInfoErr::RsaLen)
- }
- } else {
- Err(SubjectPublicKeyInfoErr::RsaEncoding)
- }
- } else {
- Err(SubjectPublicKeyInfoErr::RsaEncoding)
- }
- } else {
- Err(SubjectPublicKeyInfoErr::RsaEncoding)
- }
- } else {
- Err(SubjectPublicKeyInfoErr::RsaLen)
- }
- } else {
- Err(SubjectPublicKeyInfoErr::RsaEncoding)
- }
- } else {
- Err(SubjectPublicKeyInfoErr::RsaEncoding)
- }
- } else {
- Err(SubjectPublicKeyInfoErr::RsaLen)
- }
- })
- } else {
- Err(SubjectPublicKeyInfoErr::RsaEncoding)
- }
- })
- }
- }
- impl<'a> SubjectPublicKeyInfo<'a> for UncompressedPubKey<'a> {
- #[expect(clippy::indexing_slicing, reason = "comments justify correctness")]
- #[expect(single_use_lifetimes, reason = "false positive")]
- fn from_der<'b: 'a>(der: &'b [u8]) -> Result<Self, SubjectPublicKeyInfoErr> {
- /// Index in a DER-encoded payload of the ML-DSA-* public key that corresponds
- /// to the OID length.
- const ML_DSA_OID_LEN_IDX: usize = 5;
- /// Length of the OID for the DER-encoded ML-DSA-* public keys.
- ///
- /// Note this is _not_ the same as the OID length for an RSA public key (i.e., 13).
- const ML_DSA_OID_LEN: u8 = 11;
- // The only lengths that overlap is RSA with ML-DSA-44 and ML-DSA-65.
- match der.len() {
- // The minimum modulus we support for RSA is 2048 bits which is 256 bytes;
- // thus clearly its encoding will be at least 256 which is greater than
- // all of the non-ML-DSA-* values and less than the ML-DSA-* values.
- // The maximum modulus we support for RSA is 16K bits which is 2048 bytes and the maximum
- // exponent we support for RSA is 4 bytes which is hundreds of bytes less than 2614
- // (i.e., the length of a DER-encoded ML-DSAS-87 public key).
- ED25519_LEN => Ed25519PubKey::from_der(der).map(Self::Ed25519),
- P256_LEN => UncompressedP256PubKey::from_der(der).map(Self::P256),
- P384_LEN => UncompressedP384PubKey::from_der(der).map(Self::P384),
- MLDSA44_LEN => {
- // `ML_DSA_OID_LEN_IDX < MLDSA44_LEN = der.len()` so indexing
- // won't `panic`.
- if der[ML_DSA_OID_LEN_IDX] == ML_DSA_OID_LEN {
- MlDsa44PubKey::from_der(der).map(Self::MlDsa44)
- } else {
- RsaPubKey::from_der(der).map(Self::Rsa)
- }
- }
- MLDSA65_LEN => {
- // `ML_DSA_OID_LEN_IDX < MLDSA65_LEN = der.len()` so indexing
- // won't `panic`.
- if der[ML_DSA_OID_LEN_IDX] == ML_DSA_OID_LEN {
- MlDsa65PubKey::from_der(der).map(Self::MlDsa65)
- } else {
- RsaPubKey::from_der(der).map(Self::Rsa)
- }
- }
- MLDSA87_LEN => MlDsa87PubKey::from_der(der).map(Self::MlDsa87),
- _ => RsaPubKey::from_der(der).map(Self::Rsa),
- }
- }
- }
-}
+mod spki;
/// Helper type returned from [`AuthenticatorAttestationVisitor::visit_map`].
///
/// The purpose of this type is to hopefully avoid re-parsing the raw attestation object multiple times. In
diff --git a/src/response/register/ser/spki.rs b/src/response/register/ser/spki.rs
@@ -0,0 +1,882 @@
+use super::super::{
+ Ed25519PubKey, MlDsa44PubKey, MlDsa65PubKey, MlDsa87PubKey, RsaPubKey, RsaPubKeyErr,
+ UncompressedP256PubKey, UncompressedP384PubKey, UncompressedPubKey,
+};
+use core::fmt::{self, Display, Formatter};
+use p256::{
+ NistP256,
+ elliptic_curve::{Curve, common::typenum::type_operators::ToInt as _},
+};
+use p384::NistP384;
+/// Value assigned to the integer type under the universal tag class per
+/// [ITU-T X.680](https://www.itu.int/rec/T-REC-X.680-202102-I/en).
+const INTEGER: u8 = 2;
+/// Value assigned to the bitstring type under the universal tag class per
+/// [ITU-T X.680](https://www.itu.int/rec/T-REC-X.680-202102-I/en).
+const BITSTRING: u8 = 3;
+/// Value assigned to the null type under the universal tag class per
+/// [ITU-T X.680](https://www.itu.int/rec/T-REC-X.680-202102-I/en).
+const NULL: u8 = 5;
+/// Value assigned to the object identifier type under the universal tag class per
+/// [ITU-T X.680](https://www.itu.int/rec/T-REC-X.680-202102-I/en).
+const OID: u8 = 6;
+/// Value assigned to the sequence type under the universal tag class per
+/// [ITU-T X.680](https://www.itu.int/rec/T-REC-X.680-202102-I/en).
+const SEQUENCE: u8 = 16;
+/// Value assigned to a constructed [`SEQUENCE`] per
+/// [ITU-T X.690](https://www.itu.int/rec/T-REC-X.690-202102-I/en).
+///
+/// All sequences are constructed once encoded, so this will likely always be used instead of
+/// `SEQUENCE`.
+const CONSTRUCTED_SEQUENCE: u8 = SEQUENCE | 0b0010_0000;
+/// Length of the header before the encoded key in a DER-encoded ASN.1 `SubjectPublicKeyInfo`
+/// for an ML-DSA-87 public key.
+const MLDSA87_HEADER_LEN: usize = 22;
+/// Length of a DER-encoded ASN.1 `SubjectPublicKeyInfo` for ML-DSA-87 public key.
+const MLDSA87_LEN: usize = MLDSA87_HEADER_LEN + 2592;
+/// Length of the header before the encoded key in a DER-encoded ASN.1 `SubjectPublicKeyInfo`
+/// for an ML-DSA-65 public key.
+const MLDSA65_HEADER_LEN: usize = 22;
+/// Length of a DER-encoded ASN.1 `SubjectPublicKeyInfo` for ML-DSA-65 public key.
+const MLDSA65_LEN: usize = MLDSA65_HEADER_LEN + 1952;
+/// Length of the header before the encoded key in a DER-encoded ASN.1 `SubjectPublicKeyInfo`
+/// for an ML-DSA-44 public key.
+const MLDSA44_HEADER_LEN: usize = 22;
+/// Length of a DER-encoded ASN.1 `SubjectPublicKeyInfo` for ML-DSA-44 public key.
+const MLDSA44_LEN: usize = MLDSA44_HEADER_LEN + 1312;
+/// Length of the header before the compressed y-coordinate in a DER-encoded ASN.1 `SubjectPublicKeyInfo`
+/// for an Ed25519 public key.
+const ED25519_HEADER_LEN: usize = 12;
+/// Length of a DER-encoded ASN.1 `SubjectPublicKeyInfo` for Ed25519 public key.
+const ED25519_LEN: usize = ED25519_HEADER_LEN + ed25519_dalek::PUBLIC_KEY_LENGTH;
+/// `ED25519_LEN` as a `u8`.
+// `44 as u8` is clearly OK.
+#[expect(
+ clippy::as_conversions,
+ clippy::cast_possible_truncation,
+ reason = "comments above justify their correctness"
+)]
+const ED25519_LEN_U8: u8 = ED25519_LEN as u8;
+/// Length of the header before the uncompressed SEC- 1 pubic key in a DER-encoded ASN.1 `SubjectPublicKeyInfo`
+/// for an uncompressed ECDSA public key based on secp256r1/P-256.
+const P256_HEADER_LEN: usize = 27;
+/// Number of bytes the x-coordinate takes in an uncompressed P-256 public key.
+const P256_X_LEN: usize = <NistP256 as Curve>::FieldBytesSize::INT;
+/// Number of bytes the y-coordinate takes in an uncompressed P-256 public key.
+const P256_Y_LEN: usize = <NistP256 as Curve>::FieldBytesSize::INT;
+/// Number of bytes the x and y coordinates take in an uncompressed P-256 public key when concatenated together.
+const P256_COORD_LEN: usize = P256_X_LEN + P256_Y_LEN;
+/// Length of a DER-encoded ASN.1 `SubjectPublicKeyInfo` for an uncompressed SEC-1 ECDSA public key
+/// based on secp256r1/P-256.
+const P256_LEN: usize = P256_HEADER_LEN + P256_COORD_LEN;
+/// `P256_LEN` as a `u8`.
+// `91 as u8` is clearly OK.
+#[expect(
+ clippy::as_conversions,
+ clippy::cast_possible_truncation,
+ reason = "comments above justify their correctness"
+)]
+const P256_LEN_U8: u8 = P256_LEN as u8;
+/// Length of the header before the uncompressed SEC- 1 pubic key in a DER-encoded ASN.1 `SubjectPublicKeyInfo`
+/// for an uncompressed ECDSA public key based on secp384r1/P-384.
+const P384_HEADER_LEN: usize = 24;
+/// Number of bytes the x-coordinate takes in an uncompressed P-384 public key.
+const P384_X_LEN: usize = <NistP384 as Curve>::FieldBytesSize::INT;
+/// Number of bytes the y-coordinate takes in an uncompressed P-384 public key.
+const P384_Y_LEN: usize = <NistP384 as Curve>::FieldBytesSize::INT;
+/// Number of bytes the x and y coordinates take in an uncompressed P-384 public key when concatenated together.
+const P384_COORD_LEN: usize = P384_X_LEN + P384_Y_LEN;
+/// Length of a DER-encoded ASN.1 `SubjectPublicKeyInfo` for an uncompressed SEC-1 ECDSA public key
+/// based on secp384r1/P-384.
+const P384_LEN: usize = P384_HEADER_LEN + P384_COORD_LEN;
+/// `P384_LEN` as a `u8`.
+// `120 as u8` is clearly OK.
+#[expect(
+ clippy::as_conversions,
+ clippy::cast_possible_truncation,
+ reason = "comments above justify their correctness"
+)]
+const P384_LEN_U8: u8 = P384_LEN as u8;
+/// Error returned from [`SubjectPublicKeyInfo::from_der`].
+pub(super) enum SubjectPublicKeyInfoErr {
+ /// The length of the DER-encoded ML-DSA-87 key was invalid.
+ MlDsa87Len,
+ /// The header of the DER-encoded ML-DSA-87 key was invalid.
+ MlDsa87Header,
+ /// The length of the DER-encoded ML-DSA-65 key was invalid.
+ MlDsa65Len,
+ /// The header of the DER-encoded ML-DSA-65 key was invalid.
+ MlDsa65Header,
+ /// The length of the DER-encoded ML-DSA-44 key was invalid.
+ MlDsa44Len,
+ /// The header of the DER-encoded ML-DSA-44 key was invalid.
+ MlDsa44Header,
+ /// The length of the DER-encoded Ed25519 key was invalid.
+ Ed25519Len,
+ /// The header of the DER-encoded Ed25519 key was invalid.
+ Ed25519Header,
+ /// The length of the DER-encoded P-256 key was invalid.
+ P256Len,
+ /// The header of the DER-encoded P-256 key was invalid.
+ P256Header,
+ /// The length of the DER-encoded P-384 key was invalid.
+ P384Len,
+ /// The header of the DER-encoded P-384 key was invalid.
+ P384Header,
+ /// The length of the DER-encoded RSA key was invalid.
+ RsaLen,
+ /// The DER-encoding of the RSA key was invalid.
+ RsaEncoding,
+ /// The exponent of the DER-encoded RSA key was too large.
+ RsaExponentTooLarge,
+ /// The DER-encoded RSA key was not a valid [`RsaPubKey`].
+ RsaPubKey(RsaPubKeyErr),
+}
+impl Display for SubjectPublicKeyInfoErr {
+ fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
+ match *self {
+ Self::MlDsa87Len => f.write_str("length of the DER-encoded ML-DSA-87 key was invalid"),
+ Self::MlDsa87Header => {
+ f.write_str("header of the DER-encoded ML-DSA-87 key was invalid")
+ }
+ Self::MlDsa65Len => f.write_str("length of the DER-encoded ML-DSA-65 key was invalid"),
+ Self::MlDsa65Header => {
+ f.write_str("header of the DER-encoded ML-DSA-65 key was invalid")
+ }
+ Self::MlDsa44Len => f.write_str("length of the DER-encoded ML-DSA-44 key was invalid"),
+ Self::MlDsa44Header => {
+ f.write_str("header of the DER-encoded ML-DSA-44 key was invalid")
+ }
+ Self::Ed25519Len => f.write_str("length of the DER-encoded Ed25519 key was invalid"),
+ Self::Ed25519Header => f.write_str("header of the DER-encoded Ed25519 key was invalid"),
+ Self::P256Len => f.write_str("length of the DER-encoded P-256 key was invalid"),
+ Self::P256Header => f.write_str("header of the DER-encoded P-256 key was invalid"),
+ Self::P384Len => f.write_str("length of the DER-encoded P-384 key was invalid"),
+ Self::P384Header => f.write_str("header of the DER-encoded P-384 key was invalid"),
+ Self::RsaLen => f.write_str("length of the DER-encoded RSA key was invalid"),
+ Self::RsaEncoding => f.write_str("the DER-encoding of the RSA public key was invalid"),
+ Self::RsaExponentTooLarge => {
+ f.write_str("the DER-encoded RSA public key had an exponent that was too large")
+ }
+ Self::RsaPubKey(err) => {
+ write!(f, "the DER-encoded RSA public was not valid: {err}")
+ }
+ }
+ }
+}
+/// Types that can be deserialized from the DER-encoded ASN.1 `SubjectPublicKeyInfo` as defined in
+/// [RFC 5280](https://datatracker.ietf.org/doc/html/rfc5280#appendix-A.1) and other applicable RFCs
+/// and documents (e.g., [ITU-T X.690](https://www.itu.int/rec/T-REC-X.690-202102-I/en)).
+pub(super) trait SubjectPublicKeyInfo<'a>: Sized {
+ /// Transforms the DER-encoded ASN.1 `SubjectPublicKeyInfo` into `Self`.
+ ///
+ /// # Errors
+ ///
+ /// Errors iff `der` does not conform.
+ #[expect(single_use_lifetimes, reason = "false positive")]
+ fn from_der<'b: 'a>(der: &'b [u8]) -> Result<Self, SubjectPublicKeyInfoErr>;
+}
+impl<'a> SubjectPublicKeyInfo<'a> for MlDsa87PubKey<&'a [u8]> {
+ #[expect(single_use_lifetimes, reason = "false positive")]
+ fn from_der<'b: 'a>(der: &'b [u8]) -> Result<Self, SubjectPublicKeyInfoErr> {
+ /// ```asn
+ /// SubjectPublicKeyInfo ::= SEQUENCE {
+ /// algorithm AlgorithmIdentifier,
+ /// subjectPublicKey BIT STRING
+ /// }
+ ///
+ /// AlgorithmIdentifier ::= SEQUENCE {
+ /// algorithm OBJECT IDENTIFIER,
+ /// parameters ANY DEFINED BY algorithm OPTIONAL
+ /// }
+ /// ```
+ ///
+ /// [RFC 9882](https://www.rfc-editor.org/rfc/rfc9882.html) requires parameters to not exist
+ /// in `AlgorithmIdentifier`.
+ ///
+ /// RFC 9882 defines the OID as 2.16.840.1.101.3.4.3.19 which is encoded as 96.134.72.1.101.3.4.3.19
+ /// per [X.690](https://www.itu.int/rec/T-REC-X.690-202102-I/en).
+ ///
+ /// [FIPS 204](https://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.204.pdf) defines the bitstring as a reinterpretation of the byte string.
+ const HEADER: [u8; MLDSA87_HEADER_LEN] = [
+ CONSTRUCTED_SEQUENCE,
+ 130,
+ 10,
+ 50,
+ CONSTRUCTED_SEQUENCE,
+ 9 + 2,
+ OID,
+ 9,
+ 96,
+ 134,
+ 72,
+ 1,
+ 101,
+ 3,
+ 4,
+ 3,
+ 19,
+ BITSTRING,
+ 130,
+ 10,
+ 33,
+ // The number of unused bits.
+ 0,
+ ];
+ der.split_at_checked(HEADER.len())
+ .ok_or(SubjectPublicKeyInfoErr::MlDsa87Len)
+ .and_then(|(header, rem)| {
+ if header == HEADER {
+ if rem.len() == 2592 {
+ Ok(Self(rem))
+ } else {
+ Err(SubjectPublicKeyInfoErr::MlDsa87Len)
+ }
+ } else {
+ Err(SubjectPublicKeyInfoErr::MlDsa87Header)
+ }
+ })
+ }
+}
+impl<'a> SubjectPublicKeyInfo<'a> for MlDsa65PubKey<&'a [u8]> {
+ #[expect(single_use_lifetimes, reason = "false positive")]
+ fn from_der<'b: 'a>(der: &'b [u8]) -> Result<Self, SubjectPublicKeyInfoErr> {
+ /// ```asn
+ /// SubjectPublicKeyInfo ::= SEQUENCE {
+ /// algorithm AlgorithmIdentifier,
+ /// subjectPublicKey BIT STRING
+ /// }
+ ///
+ /// AlgorithmIdentifier ::= SEQUENCE {
+ /// algorithm OBJECT IDENTIFIER,
+ /// parameters ANY DEFINED BY algorithm OPTIONAL
+ /// }
+ /// ```
+ ///
+ /// [RFC 9882](https://www.rfc-editor.org/rfc/rfc9882.html) requires parameters to not exist
+ /// in `AlgorithmIdentifier`.
+ ///
+ /// RFC 9882 defines the OID as 2.16.840.1.101.3.4.3.18 which is encoded as 96.134.72.1.101.3.4.3.18
+ /// per [X.690](https://www.itu.int/rec/T-REC-X.690-202102-I/en).
+ ///
+ /// [FIPS 204](https://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.204.pdf) defines the bitstring as a reinterpretation of the byte string.
+ const HEADER: [u8; MLDSA65_HEADER_LEN] = [
+ CONSTRUCTED_SEQUENCE,
+ 130,
+ 7,
+ 178,
+ CONSTRUCTED_SEQUENCE,
+ 9 + 2,
+ OID,
+ 9,
+ 96,
+ 134,
+ 72,
+ 1,
+ 101,
+ 3,
+ 4,
+ 3,
+ 18,
+ BITSTRING,
+ 130,
+ 7,
+ 161,
+ // The number of unused bits.
+ 0,
+ ];
+ der.split_at_checked(HEADER.len())
+ .ok_or(SubjectPublicKeyInfoErr::MlDsa65Len)
+ .and_then(|(header, rem)| {
+ if header == HEADER {
+ if rem.len() == 1952 {
+ Ok(Self(rem))
+ } else {
+ Err(SubjectPublicKeyInfoErr::MlDsa65Len)
+ }
+ } else {
+ Err(SubjectPublicKeyInfoErr::MlDsa65Header)
+ }
+ })
+ }
+}
+impl<'a> SubjectPublicKeyInfo<'a> for MlDsa44PubKey<&'a [u8]> {
+ #[expect(single_use_lifetimes, reason = "false positive")]
+ fn from_der<'b: 'a>(der: &'b [u8]) -> Result<Self, SubjectPublicKeyInfoErr> {
+ /// ```asn
+ /// SubjectPublicKeyInfo ::= SEQUENCE {
+ /// algorithm AlgorithmIdentifier,
+ /// subjectPublicKey BIT STRING
+ /// }
+ ///
+ /// AlgorithmIdentifier ::= SEQUENCE {
+ /// algorithm OBJECT IDENTIFIER,
+ /// parameters ANY DEFINED BY algorithm OPTIONAL
+ /// }
+ /// ```
+ ///
+ /// [RFC 9882](https://www.rfc-editor.org/rfc/rfc9882.html) requires parameters to not exist
+ /// in `AlgorithmIdentifier`.
+ ///
+ /// RFC 9882 defines the OID as 2.16.840.1.101.3.4.3.17 which is encoded as 96.134.72.1.101.3.4.3.17
+ /// per [X.690](https://www.itu.int/rec/T-REC-X.690-202102-I/en).
+ ///
+ /// [FIPS 204](https://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.204.pdf) defines the bitstring as a reinterpretation of the byte string.
+ const HEADER: [u8; MLDSA44_HEADER_LEN] = [
+ CONSTRUCTED_SEQUENCE,
+ 130,
+ 5,
+ 50,
+ CONSTRUCTED_SEQUENCE,
+ 9 + 2,
+ OID,
+ 9,
+ 96,
+ 134,
+ 72,
+ 1,
+ 101,
+ 3,
+ 4,
+ 3,
+ 17,
+ BITSTRING,
+ 130,
+ 5,
+ 33,
+ // The number of unused bits.
+ 0,
+ ];
+ der.split_at_checked(HEADER.len())
+ .ok_or(SubjectPublicKeyInfoErr::MlDsa44Len)
+ .and_then(|(header, rem)| {
+ if header == HEADER {
+ if rem.len() == 1312 {
+ Ok(Self(rem))
+ } else {
+ Err(SubjectPublicKeyInfoErr::MlDsa44Len)
+ }
+ } else {
+ Err(SubjectPublicKeyInfoErr::MlDsa44Header)
+ }
+ })
+ }
+}
+impl<'a> SubjectPublicKeyInfo<'a> for Ed25519PubKey<&'a [u8]> {
+ #[expect(single_use_lifetimes, reason = "false positive")]
+ fn from_der<'b: 'a>(der: &'b [u8]) -> Result<Self, SubjectPublicKeyInfoErr> {
+ /// ```asn
+ /// SubjectPublicKeyInfo ::= SEQUENCE {
+ /// algorithm AlgorithmIdentifier,
+ /// subjectPublicKey BIT STRING
+ /// }
+ ///
+ /// AlgorithmIdentifier ::= SEQUENCE {
+ /// algorithm OBJECT IDENTIFIER,
+ /// parameters ANY DEFINED BY algorithm OPTIONAL
+ /// }
+ /// ```
+ ///
+ /// [RFC 8410](https://www.rfc-editor.org/rfc/rfc8410#section-3) requires parameters to not exist
+ /// in `AlgorithmIdentifier`.
+ ///
+ /// RFC 8410 defines the OID as 1.3.101.112 which is encoded as 43.101.112
+ /// per [X.690](https://www.itu.int/rec/T-REC-X.690-202102-I/en).
+ ///
+ /// RFC 8410 defines the bitstring as a reinterpretation of the byte string.
+ const HEADER: [u8; ED25519_HEADER_LEN] = [
+ CONSTRUCTED_SEQUENCE,
+ // `ED25519_LEN_U8` is the length of the entire payload; thus we subtract
+ // the "consumed" length.
+ ED25519_LEN_U8 - 2,
+ CONSTRUCTED_SEQUENCE,
+ // AlgorithmIdentifier only contains the OID; thus it's length is 5.
+ 3 + 2,
+ OID,
+ 3,
+ 43,
+ 101,
+ 112,
+ BITSTRING,
+ // `ED25519_LEN_U8` is the length of the entire payload; thus we subtract
+ // the "consumed" length.
+ ED25519_LEN_U8 - 11,
+ // The number of unused bits.
+ 0,
+ ];
+ der.split_at_checked(HEADER.len())
+ .ok_or(SubjectPublicKeyInfoErr::Ed25519Len)
+ .and_then(|(header, rem)| {
+ if header == HEADER {
+ if rem.len() == ed25519_dalek::PUBLIC_KEY_LENGTH {
+ Ok(Self(rem))
+ } else {
+ Err(SubjectPublicKeyInfoErr::Ed25519Len)
+ }
+ } else {
+ Err(SubjectPublicKeyInfoErr::Ed25519Header)
+ }
+ })
+ }
+}
+impl<'a> SubjectPublicKeyInfo<'a> for UncompressedP256PubKey<'a> {
+ #[expect(single_use_lifetimes, reason = "false positive")]
+ fn from_der<'b: 'a>(der: &'b [u8]) -> Result<Self, SubjectPublicKeyInfoErr> {
+ // ```asn
+ // SubjectPublicKeyInfo ::= SEQUENCE {
+ // algorithm AlgorithmIdentifier,
+ // subjectPublicKey BIT STRING
+ // }
+ //
+ // AlgorithmIdentifier ::= SEQUENCE {
+ // algorithm OBJECT IDENTIFIER,
+ // parameters ANY DEFINED BY algorithm OPTIONAL
+ // }
+ //
+ // ECParameters ::= CHOICE {
+ // namedCurve OBJECT IDENTIFIER
+ // -- implicitCurve NULL
+ // -- specifiedCurve SpecifiedECDomain
+ // }
+ // ```
+ // [RFC 5480](https://www.rfc-editor.org/rfc/rfc5480#section-2.1.1) requires parameters to exist and
+ // be of the `ECParameters` form with the requirement that `namedCurve` is chosen.
+ //
+ // RFC 5480 defines the OID for id-ecPublicKey as 1.2.840.10045.2.1 and the OID for the namedCurve
+ // secp256r1 as 1.2.840.10045.3.1.7. The former OID is encoded as 42.134.72.206.61.2.1 and the latter
+ // is encoded as 42.134.72.206.61.3.1.7 per [X.690](https://www.itu.int/rec/T-REC-X.690-202102-I/en).
+ //
+ // [RFC 5480](https://www.rfc-editor.org/rfc/rfc5480#section-5) only requires support for the
+ // uncompressed form and only states that the compressed form MAY be supported. In practice this means
+ // DER-encoded payloads almost always are of the uncompressed form for compatibility reasons. This in
+ // conjunction with the fact the COSE key is required to be in the uncompressed form means we only support
+ // DER-encoded payloads containing uncompressed keys.
+ //
+ // [SEC 1](https://secg.org/sec1-v2.pdf) defines the point as an octet string, and the conversion
+ // to a bitstring simply requires reinterpreting the octet string as a bitstring.
+
+ /// Header of the DER-encoded payload before the public key.
+ const HEADER: [u8; P256_HEADER_LEN] = [
+ CONSTRUCTED_SEQUENCE,
+ // `P256_LEN_U8` is the length of the entire payload; thus we subtract
+ // the "consumed" length.
+ P256_LEN_U8 - 2,
+ CONSTRUCTED_SEQUENCE,
+ 7 + 2 + 8 + 2,
+ OID,
+ 7,
+ 42,
+ 134,
+ 72,
+ 206,
+ 61,
+ 2,
+ 1,
+ OID,
+ 8,
+ 42,
+ 134,
+ 72,
+ 206,
+ 61,
+ 3,
+ 1,
+ 7,
+ BITSTRING,
+ // `P256_LEN_U8` is the length of the entire payload; thus we subtract
+ // the "consumed" length.
+ P256_LEN_U8 - 25,
+ // The number of unused bits.
+ 0,
+ // SEC-1 tag for an uncompressed key.
+ 4,
+ ];
+ der.split_at_checked(HEADER.len())
+ .ok_or(SubjectPublicKeyInfoErr::P256Len)
+ .and_then(|(header, header_rem)| {
+ if header == HEADER {
+ header_rem
+ .split_at_checked(P256_X_LEN)
+ .ok_or(SubjectPublicKeyInfoErr::P256Len)
+ .and_then(|(x, y)| {
+ if y.len() == P256_Y_LEN {
+ Ok(Self(x, y))
+ } else {
+ Err(SubjectPublicKeyInfoErr::P256Len)
+ }
+ })
+ } else {
+ Err(SubjectPublicKeyInfoErr::P256Header)
+ }
+ })
+ }
+}
+impl<'a> SubjectPublicKeyInfo<'a> for UncompressedP384PubKey<'a> {
+ #[expect(single_use_lifetimes, reason = "false positive")]
+ fn from_der<'b: 'a>(der: &'b [u8]) -> Result<Self, SubjectPublicKeyInfoErr> {
+ // ```asn
+ // SubjectPublicKeyInfo ::= SEQUENCE {
+ // algorithm AlgorithmIdentifier,
+ // subjectPublicKey BIT STRING
+ // }
+ //
+ // AlgorithmIdentifier ::= SEQUENCE {
+ // algorithm OBJECT IDENTIFIER,
+ // parameters ANY DEFINED BY algorithm OPTIONAL
+ // }
+ //
+ // ECParameters ::= CHOICE {
+ // namedCurve OBJECT IDENTIFIER
+ // -- implicitCurve NULL
+ // -- specifiedCurve SpecifiedECDomain
+ // }
+ // ```
+ // [RFC 5480](https://www.rfc-editor.org/rfc/rfc5480#section-2.1.1) requires parameters to exist and
+ // be of the `ECParameters` form with the requirement that `namedCurve` is chosen.
+ //
+ // RFC 5480 defines the OID for id-ecPublicKey as 1.2.840.10045.2.1 and the OID for the namedCurve
+ // secp384r1 as 1.3.132.0.34. The former OID is encoded as 42.134.72.206.61.2.1 and the latter
+ // is encoded as 43.129.4.0.34 per [X.690](https://www.itu.int/rec/T-REC-X.690-202102-I/en).
+ //
+ // [RFC 5480](https://www.rfc-editor.org/rfc/rfc5480#section-5) only requires support for the
+ // uncompressed form and only states that the compressed form MAY be supported. In practice this means
+ // DER-encoded payloads almost always are of the uncompressed form for compatibility reasons. This in
+ // conjunction with the fact the COSE key is required to be in the uncompressed form means we only support
+ // DER-encoded payloads containing uncompressed keys.
+ //
+ // [SEC 1](https://secg.org/sec1-v2.pdf) defines the point as an octet string, and the conversion
+ // to a bitstring simply requires reinterpreting the octet string as a bitstring.
+
+ /// Header of the DER-encoded payload before the public key.
+ const HEADER: [u8; P384_HEADER_LEN] = [
+ CONSTRUCTED_SEQUENCE,
+ // `P384_LEN_U8` is the length of the entire payload; thus we subtract
+ // the "consumed" length.
+ P384_LEN_U8 - 2,
+ CONSTRUCTED_SEQUENCE,
+ 7 + 2 + 5 + 2,
+ OID,
+ 7,
+ 42,
+ 134,
+ 72,
+ 206,
+ 61,
+ 2,
+ 1,
+ OID,
+ 5,
+ 43,
+ 129,
+ 4,
+ 0,
+ 34,
+ BITSTRING,
+ // `P384_LEN_U8` is the length of the entire payload; thus we subtract
+ // the "consumed" length.
+ P384_LEN_U8 - 22,
+ // The number of unused bits.
+ 0,
+ // SEC-1 tag for an uncompressed key.
+ 4,
+ ];
+ der.split_at_checked(HEADER.len())
+ .ok_or(SubjectPublicKeyInfoErr::P384Len)
+ .and_then(|(header, header_rem)| {
+ if header == HEADER {
+ header_rem
+ .split_at_checked(P384_X_LEN)
+ .ok_or(SubjectPublicKeyInfoErr::P384Len)
+ .and_then(|(x, y)| {
+ if y.len() == P384_Y_LEN {
+ Ok(Self(x, y))
+ } else {
+ Err(SubjectPublicKeyInfoErr::P384Len)
+ }
+ })
+ } else {
+ Err(SubjectPublicKeyInfoErr::P384Header)
+ }
+ })
+ }
+}
+impl<'a> SubjectPublicKeyInfo<'a> for RsaPubKey<&'a [u8]> {
+ #[expect(single_use_lifetimes, reason = "false positive")]
+ #[expect(
+ clippy::arithmetic_side_effects,
+ clippy::big_endian_bytes,
+ clippy::indexing_slicing,
+ clippy::missing_asserts_for_indexing,
+ reason = "comments justify their correctness"
+ )]
+ #[expect(
+ clippy::too_many_lines,
+ reason = "rsa keys are the only type that would benefit from a modular SubjectPublicKeyInfo (similar to FromCbor). if more types benefit in the future, then this will be done."
+ )]
+ fn from_der<'b: 'a>(der: &'b [u8]) -> Result<Self, SubjectPublicKeyInfoErr> {
+ // ```asn
+ // SubjectPublicKeyInfo ::= SEQUENCE {
+ // algorithm AlgorithmIdentifier,
+ // subjectPublicKey BIT STRING
+ // }
+ //
+ // AlgorithmIdentifier ::= SEQUENCE {
+ // algorithm OBJECT IDENTIFIER,
+ // parameters ANY DEFINED BY algorithm OPTIONAL
+ // }
+ //
+ // RSAPublicKey ::= SEQUENCE {
+ // modulus INTEGER, -- n
+ // publicExponent INTEGER --e
+ // }
+ //
+ // pkcs-1 OBJECT IDENTIFIER ::= { iso(1) member-body(2) us(840)
+ // rsadsi(113549) pkcs(1) 1
+ // }
+ //
+ // rsaEncryption OBJECT IDENTIFIER ::= { pkcs-1 1}
+ // ```
+ // [RFC 3279](https://www.rfc-editor.org/rfc/rfc3279#section-2.3.1) requires parameters to exist and
+ // be null.
+ //
+ // RFC 3279 defines the OID for rsaEncryption as 1.2.840.113549.1.1.1 which is encoded as
+ // 42.134.72.134.247.13.1.1.1 per [X.690](https://www.itu.int/rec/T-REC-X.690-202102-I/en).
+ //
+ // Note we only allow moduli that are 256 to 2048 bytes in length inclusively. Additionally
+ // we only allow `u32` exponents; consequently all lengths that include the modulus will always be
+ // encoded with two bytes.
+
+ /// `AlgorithmIdentifier` header including the `BITSTRING` and number of bytes the length
+ /// is encoded in of the `BITSTRING` type of `subjectPublicKey`
+ /// (130-128 = 2 bytes to encode the length).
+ const ALG_OID_HEADER: [u8; 17] = [
+ CONSTRUCTED_SEQUENCE,
+ 9 + 2 + 2,
+ OID,
+ 9,
+ 42,
+ 134,
+ 72,
+ 134,
+ 247,
+ 13,
+ 1,
+ 1,
+ 1,
+ NULL,
+ 0,
+ BITSTRING,
+ 130,
+ ];
+ /// `CONSTRUCTED_SEQUENCE` whose length is encoded in two bytes.
+ const SEQ_LONG: [u8; 2] = [CONSTRUCTED_SEQUENCE, 130];
+ /// `INTEGER` whose length is encoded in two bytes.
+ const INT_LONG: [u8; 2] = [INTEGER, 130];
+ der.split_at_checked(SEQ_LONG.len())
+ .ok_or(SubjectPublicKeyInfoErr::RsaLen)
+ .and_then(|(seq, seq_rem)| {
+ if seq == SEQ_LONG {
+ seq_rem
+ .split_at_checked(2)
+ .ok_or(SubjectPublicKeyInfoErr::RsaLen)
+ .and_then(|(seq_len, seq_len_rem)| {
+ let mut len = [0; 2];
+ len.copy_from_slice(seq_len);
+ let rem_len = usize::from(u16::from_be_bytes(len));
+ if rem_len == seq_len_rem.len() {
+ if rem_len > 255 {
+ // We can safely split here since we know `seq_len_rem` is at least
+ // 256 which is greater than `ALG_OID_HEADER.len()`.
+ let (a_oid, a_oid_rem) = seq_len_rem.split_at(ALG_OID_HEADER.len());
+ if a_oid == ALG_OID_HEADER {
+ // `a_oid_rem.len()` is at least 239, so splitting is fine.
+ let (bit_str_len_enc, bit_str_val) = a_oid_rem.split_at(2);
+ let mut bit_string_len = [0; 2];
+ bit_string_len.copy_from_slice(bit_str_len_enc);
+ let bit_str_val_len = usize::from(u16::from_be_bytes(bit_string_len));
+ if bit_str_val_len == bit_str_val.len() {
+ if bit_str_val_len > 255 {
+ // `bit_str_val.len() > 255`, so splitting is fine.
+ let (unused_bits, bits_rem) = bit_str_val.split_at(1);
+ if unused_bits == [0] {
+ // We can safely split here since we know `bits_rem.len()` is at least
+ // 255.
+ let (rsa_seq, rsa_seq_rem) = bits_rem.split_at(SEQ_LONG.len());
+ if rsa_seq == SEQ_LONG {
+ // `rsa_seq_rem.len()` is at least 253, so splitting is fine.
+ let (rsa_seq_len_enc, rsa_seq_len_enc_rem) = rsa_seq_rem.split_at(2);
+ let mut rsa_seq_len = [0; 2];
+ rsa_seq_len.copy_from_slice(rsa_seq_len_enc);
+ let rsa_key_info_len = usize::from(u16::from_be_bytes(rsa_seq_len));
+ if rsa_key_info_len == rsa_seq_len_enc_rem.len() {
+ if rsa_key_info_len > 255 {
+ // We can safely split here since we know `rsa_seq_len_enc_rem.len()`
+ // is at least 256.
+ let (n_meta, n_meta_rem) = rsa_seq_len_enc_rem.split_at(INT_LONG.len());
+ if n_meta == INT_LONG {
+ // `n_meta_rem.len()` is at least 254, so splitting is fine.
+ let (n_len_enc, n_len_enc_rem) = n_meta_rem.split_at(2);
+ let mut n_len = [0; 2];
+ n_len.copy_from_slice(n_len_enc);
+ let mod_len = usize::from(u16::from_be_bytes(n_len));
+ if mod_len > 255 {
+ n_len_enc_rem.split_at_checked(mod_len).ok_or(SubjectPublicKeyInfoErr::RsaLen).and_then(|(mut n, n_rem)| {
+ // `n.len() > 255`, so indexing is fine.
+ let n_first = n[0];
+ // DER integers are signed; thus the most significant bit must be 0.
+ // DER integers are minimally encoded; thus when a leading 0 exists,
+ // the second byte must be at least 128.
+ // `n.len() > 255`, so indexing is fine.
+ if n_first < 128 && (n_first != 0 || n[1] > 127) {
+ if n_first == 0 {
+ // `n.len() > 255`, so indexing is fine.
+ // We must remove the leading 0.
+ n = &n[1..];
+ }
+ n_rem.split_first().ok_or(SubjectPublicKeyInfoErr::RsaLen).and_then(|(e_type, e_type_rem)| {
+ if *e_type == INTEGER {
+ e_type_rem.split_first().ok_or(SubjectPublicKeyInfoErr::RsaLen).and_then(|(e_len, e_len_rem)| {
+ let e_len_usize = usize::from(*e_len);
+ if e_len_usize == e_len_rem.len() {
+ e_len_rem.first().ok_or(SubjectPublicKeyInfoErr::RsaLen).and_then(|&e_first| {
+ // DER integers are signed; thus the most significant bit must be 0.
+ if e_first < 128 {
+ // `RsaPubKey` only allows `u32` exponents, which means we only care
+ // about lengths up to 5.
+ match e_len_usize {
+ 1 => Ok(u32::from(e_first)),
+ 2..=5 => if e_first == 0 {
+ // DER integers are minimally encoded; thus when a leading
+ // 0 exists, the second byte must be at least 128.
+ // We know the length is at least 2; thus this won't `panic`.
+ if e_len_rem[1] > 127 {
+ let mut e = [0; 4];
+ if e_len_usize == 5 {
+ // We know the length is at least 2; thus this won't `panic`.
+ e.copy_from_slice(&e_len_rem[1..]);
+ } else {
+ // `e.len() == 4` and `e_len_usize` is at most 4; thus underflow
+ // won't occur nor will indexing `panic`. `e` is big-endian,
+ // so we start from the right.
+ e[4 - e_len_usize..].copy_from_slice(e_len_rem);
+ }
+ Ok(u32::from_be_bytes(e))
+ } else {
+ Err(SubjectPublicKeyInfoErr::RsaEncoding)
+ }
+ } else if e_len_usize == 5 {
+ // 5 bytes are only possible for `INTEGER`s that
+ // are greater than `i32::MAX`, which will be encoded
+ // with a leading 0.
+ Err(SubjectPublicKeyInfoErr::RsaEncoding)
+ } else {
+ let mut e = [0; 4];
+ // `e.len() == 4` and `e_len_usize` is at most 4; thus underflow
+ // won't occur nor will indexing `panic`. `e` is big-endian,
+ // so we start from the right.
+ e[4 - e_len_usize..].copy_from_slice(e_len_rem);
+ Ok(u32::from_be_bytes(e))
+ },
+ _ => Err(SubjectPublicKeyInfoErr::RsaExponentTooLarge),
+ }.and_then(|e| Self::try_from((n, e)).map_err(SubjectPublicKeyInfoErr::RsaPubKey))
+ } else {
+ Err(SubjectPublicKeyInfoErr::RsaEncoding)
+ }
+ })
+ } else {
+ Err(SubjectPublicKeyInfoErr::RsaLen)
+ }
+ })
+ } else {
+ Err(SubjectPublicKeyInfoErr::RsaEncoding)
+ }
+ })
+ } else {
+ Err(SubjectPublicKeyInfoErr::RsaEncoding)
+ }
+ })
+ } else {
+ Err(SubjectPublicKeyInfoErr::RsaEncoding)
+ }
+ } else {
+ Err(SubjectPublicKeyInfoErr::RsaEncoding)
+ }
+ } else {
+ Err(SubjectPublicKeyInfoErr::RsaEncoding)
+ }
+ } else {
+ Err(SubjectPublicKeyInfoErr::RsaLen)
+ }
+ } else {
+ Err(SubjectPublicKeyInfoErr::RsaEncoding)
+ }
+ } else {
+ Err(SubjectPublicKeyInfoErr::RsaEncoding)
+ }
+ } else {
+ Err(SubjectPublicKeyInfoErr::RsaEncoding)
+ }
+ } else {
+ Err(SubjectPublicKeyInfoErr::RsaLen)
+ }
+ } else {
+ Err(SubjectPublicKeyInfoErr::RsaEncoding)
+ }
+ } else {
+ Err(SubjectPublicKeyInfoErr::RsaEncoding)
+ }
+ } else {
+ Err(SubjectPublicKeyInfoErr::RsaLen)
+ }
+ })
+ } else {
+ Err(SubjectPublicKeyInfoErr::RsaEncoding)
+ }
+ })
+ }
+}
+impl<'a> SubjectPublicKeyInfo<'a> for UncompressedPubKey<'a> {
+ #[expect(clippy::indexing_slicing, reason = "comments justify correctness")]
+ #[expect(single_use_lifetimes, reason = "false positive")]
+ fn from_der<'b: 'a>(der: &'b [u8]) -> Result<Self, SubjectPublicKeyInfoErr> {
+ /// Index in a DER-encoded payload of the ML-DSA-* public key that corresponds
+ /// to the OID length.
+ const ML_DSA_OID_LEN_IDX: usize = 5;
+ /// Length of the OID for the DER-encoded ML-DSA-* public keys.
+ ///
+ /// Note this is _not_ the same as the OID length for an RSA public key (i.e., 13).
+ const ML_DSA_OID_LEN: u8 = 11;
+ // The only lengths that overlap is RSA with ML-DSA-44 and ML-DSA-65.
+ match der.len() {
+ // The minimum modulus we support for RSA is 2048 bits which is 256 bytes;
+ // thus clearly its encoding will be at least 256 which is greater than
+ // all of the non-ML-DSA-* values and less than the ML-DSA-* values.
+ // The maximum modulus we support for RSA is 16K bits which is 2048 bytes and the maximum
+ // exponent we support for RSA is 4 bytes which is hundreds of bytes less than 2614
+ // (i.e., the length of a DER-encoded ML-DSAS-87 public key).
+ ED25519_LEN => Ed25519PubKey::from_der(der).map(Self::Ed25519),
+ P256_LEN => UncompressedP256PubKey::from_der(der).map(Self::P256),
+ P384_LEN => UncompressedP384PubKey::from_der(der).map(Self::P384),
+ MLDSA44_LEN => {
+ // `ML_DSA_OID_LEN_IDX < MLDSA44_LEN = der.len()` so indexing
+ // won't `panic`.
+ if der[ML_DSA_OID_LEN_IDX] == ML_DSA_OID_LEN {
+ MlDsa44PubKey::from_der(der).map(Self::MlDsa44)
+ } else {
+ RsaPubKey::from_der(der).map(Self::Rsa)
+ }
+ }
+ MLDSA65_LEN => {
+ // `ML_DSA_OID_LEN_IDX < MLDSA65_LEN = der.len()` so indexing
+ // won't `panic`.
+ if der[ML_DSA_OID_LEN_IDX] == ML_DSA_OID_LEN {
+ MlDsa65PubKey::from_der(der).map(Self::MlDsa65)
+ } else {
+ RsaPubKey::from_der(der).map(Self::Rsa)
+ }
+ }
+ MLDSA87_LEN => MlDsa87PubKey::from_der(der).map(Self::MlDsa87),
+ _ => RsaPubKey::from_der(der).map(Self::Rsa),
+ }
+ }
+}
diff --git a/src/response/register/ser/tests.rs b/src/response/register/ser/tests.rs
@@ -5429,7 +5429,7 @@ fn mldsa87_registration_deserialize_data_mismatch() {
Unexpected::Bytes([0; 32].as_slice()),
&format!(
"DER-encoded public key to match the public key within the attestation object: MlDsa87(MlDsa87PubKey({:?}))",
- &[1u8; 2592]
+ [1u8; 2592]
)
.as_str(),
)
@@ -7778,7 +7778,7 @@ fn mldsa65_registration_deserialize_data_mismatch() {
Unexpected::Bytes([0; 32].as_slice()),
&format!(
"DER-encoded public key to match the public key within the attestation object: MlDsa65(MlDsa65PubKey({:?}))",
- &[1u8; 1952]
+ [1u8; 1952]
)
.as_str(),
)
@@ -9487,7 +9487,7 @@ fn mldsa44_registration_deserialize_data_mismatch() {
Unexpected::Bytes([0; 32].as_slice()),
&format!(
"DER-encoded public key to match the public key within the attestation object: MlDsa44(MlDsa44PubKey({:?}))",
- &[1u8; 1312]
+ [1u8; 1312]
)
.as_str(),
)
diff --git a/src/response/register/ser_relaxed/tests.rs b/src/response/register/ser_relaxed/tests.rs
@@ -5542,7 +5542,7 @@ fn mldsa87_registration_deserialize_data_mismatch() {
Unexpected::Bytes([0; 32].as_slice()),
&format!(
"DER-encoded public key to match the public key within the attestation object: MlDsa87(MlDsa87PubKey({:?}))",
- &[1u8; 2592]
+ [1u8; 2592]
)
.as_str(),
)
@@ -7880,7 +7880,7 @@ fn mldsa65_registration_deserialize_data_mismatch() {
Unexpected::Bytes([0; 32].as_slice()),
&format!(
"DER-encoded public key to match the public key within the attestation object: MlDsa65(MlDsa65PubKey({:?}))",
- &[1u8; 1952]
+ [1u8; 1952]
)
.as_str(),
)
@@ -9578,7 +9578,7 @@ fn mldsa44_registration_deserialize_data_mismatch() {
Unexpected::Bytes([0; 32].as_slice()),
&format!(
"DER-encoded public key to match the public key within the attestation object: MlDsa44(MlDsa44PubKey({:?}))",
- &[1u8; 1312]
+ [1u8; 1312]
)
.as_str(),
)