# CMC (Certificate Management over CMS)
The `synta-cmc` crate implements RFC 5272 / RFC 6402 Certificate Management
over CMS. CMC wraps certificate enrollment requests (PKCS#10 or CRMF) and
responses inside CMS SignedData envelopes with attribute-based controls for
transaction tracking, nonce exchange, and compliance checking.
```rust,ignore
use synta_cmc::builder::{PKIDataBuilder, PKIResponseBuilder};
use synta_cmc::parser::{parse_pki_data, parse_pki_response, unwrap_signed_cmc};
use synta_cmc::controls;
use synta_cmc::oids;
```
## Building a PKIData request
`PKIDataBuilder` constructs a CMC request envelope. Body part IDs are
auto-allocated; use `add_control_with_id` when an explicit ID is needed.
```rust,ignore
use synta_cmc::builder::PKIDataBuilder;
let der = PKIDataBuilder::new()
.transaction_id(42)?
.sender_nonce(&[0x01, 0x02, 0x03, 0x04])?
.identification("user@example.com")?
.add_pkcs10_request(&csr_der)?
.build()?;
```
### CRMF requests
CRMF requests use `add_crmf_request`. The builder validates the DER input
by decoding it as a `CertReqMsg` before encoding the `TaggedRequest::Crm`
CHOICE alternative:
```rust,ignore
let der = PKIDataBuilder::new()
.transaction_id(1)?
.add_crmf_request(&crmf_der)?
.build()?;
```
### Custom controls
```rust,ignore
use synta::{Integer, ToDer};
use synta_cmc::oids;
let value = Integer::from_i64(99).to_der()?;
// Auto-allocated body part ID:
let builder = PKIDataBuilder::new()
.add_control(oids::ID_CMC_TRANSACTION_ID, &[&value])?;
// Explicit body part ID:
let builder = PKIDataBuilder::new()
.add_control_with_id(5, oids::ID_CMC_TRANSACTION_ID, &[&value])?;
```
## Building a PKIResponse
`PKIResponseBuilder` constructs a CMC response envelope with
`CMCStatusInfoV2` controls:
```rust,ignore
use synta_cmc::builder::PKIResponseBuilder;
use synta_cmc::status::CMCFailInfo;
// Success response
let der = PKIResponseBuilder::new()
.add_status(&[1])?
.recipient_nonce(&[0xCC, 0xDD])?
.sender_nonce(&[0xAA, 0xBB])?
.build()?;
// Failure response
let der = PKIResponseBuilder::new()
.add_failed(&[1], CMCFailInfo::BadRequest)?
.build()?;
// Pending response
let der = PKIResponseBuilder::new()
.add_pending(&[1, 2, 3])?
.build()?;
```
## Parsing
### PKIData and PKIResponse
`parse_pki_data` and `parse_pki_response` return the generated ASN.1 types
with zero-copy borrowed fields:
```rust,ignore
use synta_cmc::parser::{parse_pki_data, parse_pki_response};
let parsed = parse_pki_data(&der)?;
println!("Controls: {}", parsed.control_sequence.len());
println!("Requests: {}", parsed.req_sequence.len());
let response = parse_pki_response(&response_der)?;
```
### CMS SignedData unwrapping
`unwrap_signed_cmc` extracts the inner eContent and signer certificates
from a CMS SignedData envelope. Both DER and BER input are accepted:
```rust,ignore
use synta_cmc::parser::unwrap_signed_cmc;
let (econtent, certs) = unwrap_signed_cmc(&signed_data_der)?;
let pki_data = parse_pki_data(&econtent)?;
println!("Signer certificates: {}", certs.len());
```
## Control extraction
The `controls` module provides typed extractors for common control
attributes. Each function searches a `&[TaggedAttribute]` slice by OID:
```rust,ignore
use synta_cmc::controls;
use synta_cmc::oids;
let parsed = parse_pki_data(&der)?;
// Typed extractors
let txn_id = controls::extract_transaction_id(&parsed.control_sequence);
let nonce = controls::extract_sender_nonce(&parsed.control_sequence);
let ident = controls::extract_identification(&parsed.control_sequence);
// Generic lookup by OID
let attr = controls::find_control(&parsed.control_sequence, oids::ID_CMC_DATA_RETURN);
```
## Status enums
`CMCStatus` and `CMCFailInfo` map RFC 5272 ยง6.1.1 status codes to Rust enums
with `Display`, integer conversion, and HTTP status code mapping:
```rust,ignore
use synta_cmc::status::{CMCStatus, CMCFailInfo};
let status = CMCStatus::from_i64(0).unwrap();
assert_eq!(format!("{status}"), "success");
let fail = CMCFailInfo::BadRequest;
assert_eq!(fail.to_i64(), 2);
assert_eq!(fail.http_status(), 400);
println!("{fail}"); // "badRequest"
```
| `Success` | 0 | `success` |
| `Failed` | 2 | `failed` |
| `Pending` | 3 | `pending` |
| `NoSupport` | 4 | `noSupport` |
| `ConfirmRequired` | 5 | `confirmRequired` |
| `PopRequired` | 6 | `popRequired` |
| `Partial` | 7 | `partial` |
| `BadAlg` | 0 | 400 | `badAlg` |
| `BadMessageCheck` | 1 | 400 | `badMessageCheck` |
| `BadRequest` | 2 | 400 | `badRequest` |
| `BadTime` | 3 | 400 | `badTime` |
| `BadCertId` | 4 | 404 | `badCertId` |
| `UnsupportedExt` | 5 | 400 | `unsupportedExt` |
| `MustArchiveKeys` | 6 | 400 | `mustArchiveKeys` |
| `BadIdentity` | 7 | 403 | `badIdentity` |
| `PopRequired` | 8 | 403 | `popRequired` |
| `PopFailed` | 9 | 403 | `popFailed` |
| `NoKeyReuse` | 10 | 400 | `noKeyReuse` |
| `InternalCaError` | 11 | 500 | `internalCAError` |
| `TryLater` | 12 | 503 | `tryLater` |
| `AuthDataFail` | 13 | 403 | `authDataFail` |
## Compliance checking
The `compliance` module validates CMC message structure against RFC 5274
and RFC 5272bis requirements:
```rust,ignore
use synta_cmc::compliance::{check_required_controls, check_5272bis_controls, AgentType};
let parsed = parse_pki_data(&der)?;
// RFC 5274: check controls required for a given agent type
let issues = check_required_controls(
AgentType::EndEntity,
&parsed.control_sequence,
);
for issue in &issues {
eprintln!("[{:?}] {}", issue.severity, issue.message);
}
// RFC 5272bis: check controls with transport authentication context
let issues = check_5272bis_controls(&parsed.control_sequence, false);
```
| `EndEntity` | `transactionId`, `senderNonce` |
| `RegistrationAuthority` | `transactionId`, `senderNonce` |
| `CertificationAuthority` | `transactionId`, `recipientNonce` |
## OID constants
All CMC and CCT OID constants are re-exported from the generated `cmc_types`
module through `synta_cmc::oids`:
| `ID_CMC_TRANSACTION_ID` | 1.3.6.1.5.5.7.7.5 | Transaction identifier |
| `ID_CMC_SENDER_NONCE` | 1.3.6.1.5.5.7.7.6 | Sender nonce |
| `ID_CMC_RECIPIENT_NONCE` | 1.3.6.1.5.5.7.7.7 | Recipient nonce |
| `ID_CMC_STATUS_INFO_V2` | 1.3.6.1.5.5.7.7.25 | Status info v2 |
| `ID_CMC_IDENTIFICATION` | 1.3.6.1.5.5.7.7.2 | Identification |
| `ID_CMC_IDENTITY_PROOF` | 1.3.6.1.5.5.7.7.3 | Identity proof |
| `ID_CMC_IDENTITY_PROOF_V2` | 1.3.6.1.5.5.7.7.34 | Identity proof v2 |
| `ID_CMC_REVOKE_REQUEST` | 1.3.6.1.5.5.7.7.17 | Revocation request |
| `ID_CCT_PKI_DATA` | 1.3.6.1.5.5.7.12.2 | CMC PKIData content type |
| `ID_CCT_PKI_RESPONSE` | 1.3.6.1.5.5.7.12.3 | CMC PKIResponse content type |
See `synta_cmc::oids` for the full list.
## Generated types
The `cmc_types` module is generated from `asn1/CMC.asn1` by `synta-codegen`
in the crate's `build.rs`. Key types:
| `PKIData` | Request envelope: controls + requests + CMS + other |
| `PKIResponse` | Response envelope: controls + CMS + other |
| `TaggedRequest` | CHOICE: `Tcr` (PKCS#10), `Crm` (CRMF), `Orm` (other) |
| `TaggedCertificationRequest` | BodyPartID + PKCS#10 CSR |
| `TaggedAttribute` | BodyPartID + OID + attribute values |
| `TaggedContentInfo` | BodyPartID + CMS ContentInfo |
| `CMCStatusInfoV2` | Enhanced status with body part paths |
| `OtherReqMsg` | Extension point for future request types |
## Error handling
All fallible operations return `Result<T, CmcError>`:
```rust,ignore
use synta_cmc::error::CmcError;
match result {
Err(CmcError::DecodeError(e)) => eprintln!("ASN.1 decode: {e}"),
Err(CmcError::EncodeError(e)) => eprintln!("ASN.1 encode: {e}"),
Err(CmcError::DuplicateBodyPartId(id)) => eprintln!("duplicate ID: {id}"),
Err(CmcError::BuilderError(msg)) => eprintln!("builder: {msg}"),
Err(CmcError::CmsError(msg)) => eprintln!("CMS: {msg}"),
Ok(value) => { /* ... */ }
}
```
## See also
- [CRMF Messages](../../python/src/protocols/crmf.md) -- certificate request format used inside CMC
- [CMP Messages](../../python/src/protocols/cmp.md) -- alternative certificate management protocol
- [CMS Overview](../../python/src/cms/overview.md) -- CMS SignedData transport layer