Skip to content

Commit 32d8811

Browse files
JstatiaCopilot
andcommitted
World-class zero-allocation optimization pass (#190)
Complete Cow<'static, str> migration across all error types (SigningError, FactoryError, HeaderError, ReceiptVerifyError, ValidationFailure, CoseSign1ValidationError). Arc<str> for hot-path string facts, &'static str for coverage/key_usage fields, [u8;32] stack digests, ArcSlice proofs, GenericArray hashes, Arc JWKS cache, SigningPayload::Borrowed. FFI fixes: removed #[repr(C)] from 6 validation structs embedding Rust types, added #[must_use] to 6 builder/options types. Created READMEs for certificates, MST, AKV, and DID:x509 extension packs. Structured error types with named Cow fields for zero-allocation static error messages. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent 347d8ee commit 32d8811

File tree

169 files changed

+9059
-7686
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

169 files changed

+9059
-7686
lines changed

native/rust/cose_openssl/Cargo.toml

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
[package]
22
name = "cose-openssl"
33
version = "0.1.0"
4-
edition = "2024"
4+
edition = { workspace = true }
5+
license = { workspace = true }
6+
description = "Low-level OpenSSL bindings for COSE signing and verification"
57

68
[lib]
79
crate-type = ["lib"]
@@ -11,6 +13,7 @@ pqc = []
1113

1214
[lints.rust]
1315
warnings = "deny"
16+
unexpected_cfgs = { level = "warn", check-cfg = ['cfg(coverage_nightly)'] }
1417

1518
[dependencies]
1619
openssl-sys = "0.9"

native/rust/cose_openssl/src/cose.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1-
use crate::cbor::{CborSlice, CborValue, serialize_array};
1+
use crate::cbor::{serialize_array, CborSlice, CborValue};
22
use crate::ossl_wrappers::{
3-
EvpKey, KeyType, WhichEC, WhichRSA, ecdsa_der_to_fixed, ecdsa_fixed_to_der,
4-
rsa_pss_md_for_cose_alg,
3+
ecdsa_der_to_fixed, ecdsa_fixed_to_der, rsa_pss_md_for_cose_alg, EvpKey, KeyType, WhichEC,
4+
WhichRSA,
55
};
66

77
#[cfg(feature = "pqc")]

native/rust/cose_openssl/src/ossl_wrappers.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ use std::ptr;
66
// Not exposed by openssl-sys 0.9, but available at link time (OpenSSL 3.0+).
77
unsafe extern "C" {
88
fn EVP_PKEY_is_a(pkey: *const ossl::EVP_PKEY, name: *const std::ffi::c_char)
9-
-> std::ffi::c_int;
9+
-> std::ffi::c_int;
1010

1111
fn EVP_PKEY_get_group_name(
1212
pkey: *const ossl::EVP_PKEY,

native/rust/did/x509/Cargo.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
[package]
22
name = "did_x509"
3-
edition.workspace = true
4-
license.workspace = true
3+
edition = { workspace = true }
4+
license = { workspace = true }
55
version = "0.1.0"
66
description = "DID:x509 identifier parsing, building, validation and resolution"
77

native/rust/did/x509/README.md

Lines changed: 319 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,319 @@
1+
<!-- Copyright (c) Microsoft Corporation. Licensed under the MIT License. -->
2+
3+
# did_x509
4+
5+
DID:x509 identifier parsing, building, validation, and resolution.
6+
7+
## Overview
8+
9+
This crate implements the [DID:x509 method specification](https://github.com/nicosResworworking-group/did-x509),
10+
which creates Decentralized Identifiers (DIDs) from X.509 certificate chains.
11+
A DID:x509 identifier binds a trust anchor (CA certificate fingerprint) to one
12+
or more policy constraints (EKU, subject, SAN, Fulcio issuer) that must be
13+
satisfied by the leaf certificate in a presented chain.
14+
15+
Key capabilities:
16+
17+
- **Parsing** — zero-copy-friendly DID:x509 string parsing with full validation
18+
- **Building** — fluent construction of DID:x509 identifiers from certificate chains
19+
- **Validation** — validate DID:x509 identifiers against certificate chains
20+
- **Resolution** — resolve DID:x509 identifiers to W3C DID Documents with JWK public keys
21+
- **Policy validators** — EKU, Subject DN, SAN (email/dns/uri/dn), and Fulcio issuer
22+
- **FFI** — complete C/C++ projection via the companion `did_x509_ffi` crate
23+
24+
## DID:x509 Format
25+
26+
```
27+
did:x509:0:sha256:<base64url_CA_fingerprint>::eku:<oid1>:<oid2>::subject:CN:<value>
28+
│ │ │ │ │ │
29+
│ │ │ │ │ └─ Subject policy
30+
│ │ │ │ └─ EKU policy
31+
│ │ │ └─ Base64url-encoded CA certificate fingerprint
32+
│ │ └─ Hash algorithm (sha256, sha384, sha512)
33+
│ └─ Version (always 0)
34+
└─ DID method prefix
35+
```
36+
37+
Multiple policies are separated by `::` (double colon). Within a policy, values
38+
are separated by `:` (single colon). Special characters are percent-encoded.
39+
40+
## Architecture
41+
42+
```
43+
┌─────────────────────────────────────────────────┐
44+
│ did_x509 │
45+
├─────────────┬───────────────┬───────────────────┤
46+
│ parsing/ │ builder │ validator │
47+
│ ├ Parser │ ├ build() │ ├ validate() │
48+
│ ├ Percent │ ├ build_ │ └ policy match │
49+
│ │ encode │ │ sha256() │ │
50+
│ └ Percent │ ├ build_ ├───────────────────┤
51+
│ decode │ │ from_ │ resolver │
52+
│ │ │ chain() │ ├ resolve() │
53+
│ │ └ build_ │ ├ RSA→JWK │
54+
│ │ from_ │ └ EC→JWK │
55+
│ │ chain_ │ │
56+
│ │ with_eku()│ │
57+
├─────────────┴───────────────┴───────────────────┤
58+
│ models/ │
59+
│ ├ DidX509ParsedIdentifier │
60+
│ ├ DidX509Policy (Eku, Subject, San, Fulcio) │
61+
│ ├ DidX509ValidationResult │
62+
│ ├ SanType (Email, Dns, Uri, Dn) │
63+
│ ├ CertificateInfo, X509Name │
64+
│ └ SubjectAlternativeName │
65+
├─────────────────────────────────────────────────┤
66+
│ policy_validators │ x509_extensions │
67+
│ ├ validate_eku() │ ├ extract_eku_oids() │
68+
│ ├ validate_subject()│ ├ extract_extended_ │
69+
│ ├ validate_san() │ │ key_usage() │
70+
│ └ validate_fulcio() │ ├ extract_fulcio_issuer()│
71+
│ │ └ extract_san() │
72+
├──────────────────────┴──────────────────────────┤
73+
│ did_document │ constants │
74+
│ ├ DidDocument │ ├ OID constants │
75+
│ ├ Verification │ ├ Attribute labels │
76+
│ │ Method │ └ oid_to_attribute_ │
77+
│ └ to_json() │ label() │
78+
└─────────────────────────────────────────────────┘
79+
80+
81+
x509-parser (DER parsing)
82+
sha2 (fingerprint hashing)
83+
serde/serde_json (DID Document serialization)
84+
```
85+
86+
## Modules
87+
88+
| Module | Description |
89+
|--------|-------------|
90+
| `parsing` | `DidX509Parser::parse()` — parses DID:x509 strings into structured identifiers |
91+
| `builder` | `DidX509Builder` — constructs DID:x509 strings from certificates and policies |
92+
| `validator` | `DidX509Validator::validate()` — validates DIDs against certificate chains |
93+
| `resolver` | `DidX509Resolver::resolve()` — resolves DIDs to W3C DID Documents |
94+
| `models` | Core types: `DidX509ParsedIdentifier`, `DidX509Policy`, `DidX509ValidationResult` |
95+
| `policy_validators` | Per-policy validation: EKU, Subject DN, SAN, Fulcio issuer |
96+
| `x509_extensions` | X.509 extension extraction utilities (EKU, SAN, Fulcio) |
97+
| `san_parser` | Subject Alternative Name parsing from certificates |
98+
| `did_document` | W3C DID Document model with JWK-based verification methods |
99+
| `constants` | DID:x509 format constants, well-known OIDs, attribute labels |
100+
| `error` | `DidX509Error` with 24 descriptive variants |
101+
102+
## Key Types
103+
104+
### `DidX509Parser`
105+
106+
Parses a DID:x509 string into its structured components with full validation
107+
of version, hash algorithm, fingerprint length, and policy syntax.
108+
109+
```rust
110+
use did_x509::DidX509Parser;
111+
112+
let did = "did:x509:0:sha256:WE4P5dd8DnLHSkyHaIjhp4udlkExample::eku:1.3.6.1.5.5.7.3.3";
113+
let parsed = DidX509Parser::parse(did).unwrap();
114+
115+
assert_eq!(parsed.hash_algorithm, "sha256");
116+
assert!(parsed.has_eku_policy());
117+
assert_eq!(parsed.policies.len(), 1);
118+
```
119+
120+
### `DidX509Builder`
121+
122+
Constructs DID:x509 identifier strings from CA certificates and policy constraints.
123+
124+
```rust
125+
use did_x509::{DidX509Builder, DidX509Policy};
126+
127+
// Build from a CA certificate with EKU policy
128+
let did = DidX509Builder::build_sha256(
129+
ca_cert_der,
130+
&[DidX509Policy::Eku(vec!["1.3.6.1.5.5.7.3.3".into()])],
131+
).unwrap();
132+
133+
// Build from a certificate chain (automatically uses root as CA)
134+
let did = DidX509Builder::build_from_chain(
135+
&[leaf_der, intermediate_der, root_der],
136+
&[DidX509Policy::Eku(vec!["1.3.6.1.5.5.7.3.3".into()])],
137+
).unwrap();
138+
139+
// Build with EKU extracted from the leaf certificate
140+
let did = DidX509Builder::build_from_chain_with_eku(
141+
&[leaf_der, intermediate_der, root_der],
142+
).unwrap();
143+
```
144+
145+
### `DidX509Validator`
146+
147+
Validates a DID:x509 identifier against a certificate chain by verifying the
148+
CA fingerprint matches a certificate in the chain and all policy constraints
149+
are satisfied by the leaf certificate.
150+
151+
```rust
152+
use did_x509::DidX509Validator;
153+
154+
let result = DidX509Validator::validate(did_string, &[leaf_der, root_der]).unwrap();
155+
156+
if result.is_valid {
157+
println!("CA matched at chain index: {}", result.matched_ca_index.unwrap());
158+
} else {
159+
for error in &result.errors {
160+
eprintln!("Validation error: {}", error);
161+
}
162+
}
163+
```
164+
165+
### `DidX509Resolver`
166+
167+
Resolves a DID:x509 identifier to a W3C DID Document containing the leaf
168+
certificate's public key in JWK format. Performs full validation first.
169+
170+
```rust
171+
use did_x509::DidX509Resolver;
172+
173+
let doc = DidX509Resolver::resolve(did_string, &[leaf_der, root_der]).unwrap();
174+
175+
// DID Document contains the public key as a JsonWebKey2020 verification method
176+
assert_eq!(doc.id, did_string);
177+
assert_eq!(doc.verification_method[0].type_, "JsonWebKey2020");
178+
179+
// Serialize to JSON
180+
let json = doc.to_json(true).unwrap();
181+
```
182+
183+
### `DidX509Policy`
184+
185+
Policy constraints that can be included in a DID:x509 identifier:
186+
187+
```rust
188+
use did_x509::{DidX509Policy, SanType};
189+
190+
// Extended Key Usage — OID list
191+
let eku = DidX509Policy::Eku(vec!["1.3.6.1.5.5.7.3.3".into()]);
192+
193+
// Subject Distinguished Name — key-value pairs
194+
let subject = DidX509Policy::Subject(vec![
195+
("CN".to_string(), "example.com".to_string()),
196+
("O".to_string(), "Example Corp".to_string()),
197+
]);
198+
199+
// Subject Alternative Name — typed value
200+
let san = DidX509Policy::San(SanType::Email, "user@example.com".to_string());
201+
202+
// Fulcio issuer — OIDC issuer URL
203+
let fulcio = DidX509Policy::FulcioIssuer("https://accounts.google.com".to_string());
204+
```
205+
206+
### `DidX509Error`
207+
208+
Comprehensive error type with 24 variants covering every failure mode:
209+
210+
| Category | Variants |
211+
|----------|----------|
212+
| Format | `EmptyDid`, `InvalidPrefix`, `InvalidFormat`, `MissingPolicies` |
213+
| Version | `UnsupportedVersion` |
214+
| Hash | `UnsupportedHashAlgorithm`, `EmptyFingerprint`, `FingerprintLengthMismatch`, `InvalidFingerprintChars` |
215+
| Policy syntax | `EmptyPolicy`, `InvalidPolicyFormat`, `EmptyPolicyName`, `EmptyPolicyValue` |
216+
| EKU | `InvalidEkuOid` |
217+
| Subject | `InvalidSubjectPolicyComponents`, `EmptySubjectPolicyKey`, `DuplicateSubjectPolicyKey` |
218+
| SAN | `InvalidSanPolicyFormat`, `InvalidSanType` |
219+
| Fulcio | `EmptyFulcioIssuer` |
220+
| Chain | `InvalidChain`, `CertificateParseError`, `NoCaMatch` |
221+
| Validation | `PolicyValidationFailed`, `ValidationFailed` |
222+
| Encoding | `PercentDecodingError`, `InvalidHexCharacter` |
223+
224+
## Supported Hash Algorithms
225+
226+
| Algorithm | Fingerprint Length | Constant |
227+
|-----------|--------------------|----------|
228+
| SHA-256 | 32 bytes (43 base64url chars) | `HASH_ALGORITHM_SHA256` |
229+
| SHA-384 | 48 bytes (64 base64url chars) | `HASH_ALGORITHM_SHA384` |
230+
| SHA-512 | 64 bytes (86 base64url chars) | `HASH_ALGORITHM_SHA512` |
231+
232+
## Supported Policies
233+
234+
| Policy | DID Syntax | Description |
235+
|--------|-----------|-------------|
236+
| EKU | `eku:<oid1>:<oid2>` | Extended Key Usage OIDs must all be present on the leaf cert |
237+
| Subject | `subject:<attr>:<val>` | Subject DN attributes must match (CN, O, OU, L, ST, C, STREET) |
238+
| SAN | `san:<type>:<value>` | Subject Alternative Name must match (email, dns, uri, dn) |
239+
| Fulcio Issuer | `fulcio-issuer:<url>` | Fulcio OIDC issuer extension must match |
240+
241+
## FFI Support
242+
243+
The companion `did_x509_ffi` crate exposes the full API through C-compatible functions:
244+
245+
| FFI Function | Purpose |
246+
|-------------|---------|
247+
| `did_x509_parse` | Parse a DID:x509 string into a handle |
248+
| `did_x509_parsed_get_fingerprint` | Get the CA fingerprint bytes |
249+
| `did_x509_parsed_get_hash_algorithm` | Get the hash algorithm string |
250+
| `did_x509_parsed_get_policy_count` | Get the number of policies |
251+
| `did_x509_parsed_free` | Free a parsed handle |
252+
| `did_x509_build_with_eku` | Build a DID:x509 string with EKU policy |
253+
| `did_x509_build_from_chain` | Build from a certificate chain |
254+
| `did_x509_validate` | Validate a DID against a certificate chain |
255+
| `did_x509_resolve` | Resolve a DID to a JSON DID Document |
256+
| `did_x509_error_message` | Get last error message |
257+
| `did_x509_error_code` | Get last error code |
258+
| `did_x509_error_free` | Free an error handle |
259+
| `did_x509_string_free` | Free a Rust-allocated string |
260+
261+
C and C++ headers are available at:
262+
- **C**: `native/c/include/cose/did/x509.h`
263+
- **C++**: `native/c_pp/include/cose/did/x509.hpp`
264+
265+
## Usage Example: SCITT Compliance
266+
267+
A common pattern for SCITT (Supply Chain Integrity, Transparency, and Trust)
268+
compliance is to build a DID:x509 identifier from a signing certificate chain
269+
and embed it as the `iss` (issuer) claim in CWT protected headers:
270+
271+
```rust
272+
use did_x509::{DidX509Builder, DidX509Policy, DidX509Validator};
273+
274+
// 1. Build the DID from the signing chain (leaf-first order)
275+
let did = DidX509Builder::build_from_chain_with_eku(
276+
&[leaf_der, intermediate_der, root_der],
277+
).expect("Failed to build DID:x509");
278+
279+
// 2. The DID string can be used as the CWT `iss` claim
280+
// e.g., "did:x509:0:sha256:<fingerprint>::eku:1.3.6.1.5.5.7.3.3"
281+
282+
// 3. During validation, verify the DID against the presented chain
283+
let result = DidX509Validator::validate(&did, &[leaf_der, intermediate_der, root_der])
284+
.expect("Validation error");
285+
assert!(result.is_valid);
286+
```
287+
288+
## Dependencies
289+
290+
| Crate | Purpose |
291+
|-------|---------|
292+
| `x509-parser` | DER certificate parsing, extension extraction |
293+
| `sha2` | SHA-256/384/512 fingerprint computation |
294+
| `serde` / `serde_json` | DID Document JSON serialization |
295+
296+
## Memory Design
297+
298+
- **Parsing**: `DidX509Parser::parse()` returns owned `DidX509ParsedIdentifier` (allocation required for fingerprint bytes and policy data extracted from the DID string)
299+
- **Policies**: `DidX509Policy::Eku` uses `Vec<Cow<'static, str>>` — static OID strings use `Cow::Borrowed` (zero allocation), dynamic OIDs use `Cow::Owned`
300+
- **DID Documents**: `VerificationMethod` JWK maps use `HashMap<Cow<'static, str>, String>` — all JWK field names (`kty`, `crv`, `x`, `y`, `n`, `e`) are `Cow::Borrowed`
301+
- **Validation**: `DidX509ValidationResult` collects errors as `Vec<String>` — only allocated on validation failure
302+
- **Fingerprinting**: SHA digests use `to_vec()` for cross-algorithm uniform handling (structurally required)
303+
- **Policy validators**: Borrow certificate data (zero-copy) — only allocate on error paths
304+
305+
## Test Coverage
306+
307+
The crate has 23 test files covering:
308+
309+
- Parser tests: format validation, edge cases, percent encoding/decoding
310+
- Builder tests: SHA-256/384/512, chain construction, EKU extraction
311+
- Validator tests: fingerprint matching, policy validation, error cases
312+
- Resolver tests: RSA and EC key conversion, DID Document generation
313+
- Policy validator tests: EKU, Subject DN, SAN, Fulcio issuer
314+
- X.509 extension tests: extraction utilities
315+
- Comprehensive edge case and coverage-targeted tests
316+
317+
## License
318+
319+
Licensed under the MIT License. See [LICENSE](../../../../LICENSE) for details.

native/rust/did/x509/ffi/Cargo.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
[package]
22
name = "did_x509_ffi"
3-
edition.workspace = true
4-
license.workspace = true
3+
edition = { workspace = true }
4+
license = { workspace = true }
55
version = "0.1.0"
66
description = "C/C++ FFI for DID:x509 parsing, building, validation and resolution"
77

native/rust/did/x509/ffi/src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -381,7 +381,7 @@ pub fn impl_build_with_eku_inner(
381381
}
382382
let c_str = unsafe { std::ffi::CStr::from_ptr(oid_ptr) };
383383
match c_str.to_str() {
384-
Ok(s) => oids.push(s.to_string()),
384+
Ok(s) => oids.push(std::borrow::Cow::Owned(s.to_string())),
385385
Err(_) => {
386386
set_error(
387387
out_error,

0 commit comments

Comments
 (0)