synta 0.1.8

ASN.1 parser, decoder, and encoder library with DER/BER support and C FFI
Documentation
# Extensions

## Accessing extensions

Certificate extensions are stored as `Option<RawDer<'a>>` — a zero-copy span
of the SEQUENCE OF Extension DER bytes.  Access them via the certificate:

```rust,ignore
use synta_certificate::Certificate;

let extensions = &cert.tbs_certificate.extensions; // Option<RawDer<'a>>
if let Some(exts) = extensions {
    let raw_bytes: &[u8] = exts.as_bytes();
    // Walk manually or use get_extension_value_der()
}
```

The `Certificate` struct provides `get_extension_value_der` to find an extension
by OID and return its raw `extnValue` bytes:

```rust,ignore
use synta_certificate::{Certificate, oids};

// Returns Option<Vec<u8>> — the raw DER bytes of the extension value
let san_value = cert.get_extension_value_der(&oids::SUBJECT_ALT_NAME);
let bc_value  = cert.get_extension_value_der(&oids::BASIC_CONSTRAINTS);
```

## Encoding extension values

`synta-certificate` provides four helper functions that return the raw DER bytes
for the most common extension values.  These bytes go inside the OCTET STRING
wrapper in the `Extension` SEQUENCE.

### BasicConstraints

```rust,ignore
use synta_certificate::encode_basic_constraints;

// End-entity certificate (cA absent)
let ee_bc = encode_basic_constraints(false, None).unwrap();
assert_eq!(ee_bc, &[0x30, 0x00]);

// CA certificate with pathLen = 0
let ca_bc = encode_basic_constraints(true, Some(0)).unwrap();
assert_eq!(ca_bc, &[0x30, 0x06, 0x01, 0x01, 0xff, 0x02, 0x01, 0x00]);
```

### KeyUsage

```rust,ignore
use synta_certificate::{
    encode_key_usage,
    KEY_USAGE_DIGITAL_SIGNATURE,
    KEY_USAGE_KEY_CERT_SIGN,
    KEY_USAGE_C_RLSIGN,
};

// keyCertSign | cRLSign
let ku = encode_key_usage(
    (1 << KEY_USAGE_KEY_CERT_SIGN) | (1 << KEY_USAGE_C_RLSIGN)
).unwrap();
assert_eq!(ku, &[0x03, 0x02, 0x01, 0x06]);
```

Key usage bit positions:

| Constant | Bit |
|----------|-----|
| `KEY_USAGE_DIGITAL_SIGNATURE` | 0 |
| `KEY_USAGE_CONTENT_COMMITMENT` | 1 |
| `KEY_USAGE_KEY_ENCIPHERMENT` | 2 |
| `KEY_USAGE_DATA_ENCIPHERMENT` | 3 |
| `KEY_USAGE_KEY_AGREEMENT` | 4 |
| `KEY_USAGE_KEY_CERT_SIGN` | 5 |
| `KEY_USAGE_C_RLSIGN` | 6 |
| `KEY_USAGE_ENCIPHER_ONLY` | 7 |
| `KEY_USAGE_DECIPHER_ONLY` | 8 |

### SubjectKeyIdentifier

```rust,ignore
use synta_certificate::{
    encode_subject_key_identifier,
    KeyIdMethod, OpensslKeyIdHasher,
};

let ski = encode_subject_key_identifier(
    spki_der,
    KeyIdMethod::Rfc5280Sha1,
    &OpensslKeyIdHasher,
)?;
```

### AuthorityKeyIdentifier

```rust,ignore
use synta_certificate::{
    encode_authority_key_identifier,
    KeyIdMethod, OpensslKeyIdHasher,
};

// RFC 7093 method 1: SHA-256 of subjectPublicKey BIT STRING value, first 20 bytes
let aki = encode_authority_key_identifier(
    issuer_spki_der,
    KeyIdMethod::Rfc7093Method1Sha256,
    &OpensslKeyIdHasher,
)?;
```

### KeyIdMethod variants

| Variant | Hash | Input | Output | RFC |
|---------|------|-------|--------|-----|
| `Rfc5280Sha1` | SHA-1 | BIT STRING value of `subjectPublicKey` | 20 bytes | RFC 5280 §4.2.1.2 |
| `Rfc7093Method1Sha256` | SHA-256 | BIT STRING value | first 20 bytes | RFC 7093 m1 |
| `Rfc7093Method2Sha384` | SHA-384 | BIT STRING value | first 20 bytes | RFC 7093 m2 |
| `Rfc7093Method3Sha512` | SHA-512 | BIT STRING value | first 20 bytes | RFC 7093 m3 |
| `Rfc7093Method4 { algorithm_oid }` | configurable | full `SubjectPublicKeyInfo` DER | full hash | RFC 7093 m4 |

### Custom KeyIdHasher

```rust,ignore
use synta_certificate::{KeyIdHasher, KeyIdMethod};

struct MyHasher;

impl KeyIdHasher for MyHasher {
    type Error = std::convert::Infallible;

    fn hash(&self, algorithm_oid: &[u32], data: &[u8]) -> Result<Vec<u8>, Self::Error> {
        // dispatch on algorithm_oid and compute digest
        todo!()
    }
}
```

### NameConstraints

```rust,ignore
use synta_certificate::NameConstraintsBuilder;

// A leading dot constrains the entire subtree; no dot → exact host only.
// IPv4 IP constraint: 4-byte address || 4-byte mask (8 bytes total).
let nc_der = NameConstraintsBuilder::new()
    .permit_dns(".example.com")
    .permit_rfc822(".example.com")
    .permit_ip(&[192, 168, 0, 0, 255, 255, 0, 0])  // 192.168.0.0/16
    .exclude_dns(".legacy.example.com")
    .exclude_ip(&[10, 0, 0, 0, 255, 0, 0, 0])       // 10.0.0.0/8
    .build()
    .unwrap();

// The outer SEQUENCE content starts with [0] IMPLICIT (permitted)
// and optionally [1] IMPLICIT (excluded) GeneralSubtrees, per RFC 5280 §4.2.1.10.
assert_eq!(nc_der[0], 0x30); // outer SEQUENCE
```

Available methods — permit subtrees:

| Method | GeneralName type |
|--------|-----------------|
| `permit_dns(dns)` | `dNSName [2]` |
| `permit_rfc822(email)` | `rfc822Name [1]` |
| `permit_ip(addr_and_mask)` | `iPAddress [7]` |
| `permit_uri(uri)` | `uniformResourceIdentifier [6]` |
| `permit_directory_name(name_der)` | `directoryName [4]` |

Excluded subtrees: `exclude_dns`, `exclude_rfc822`, `exclude_ip`, `exclude_uri`,
`exclude_directory_name` — same signatures.

When embedded in a CA certificate the extension **must** be marked critical
(RFC 5280 §4.2.1.10).

### CRLDistributionPoints

Builds a `CRLDistributionPoints` extension value (OID 2.5.29.31, RFC 5280 §4.2.1.13).
Each `full_name_*` call appends one `DistributionPoint` entry to the SEQUENCE OF.

```rust,ignore
use synta_certificate::CRLDistributionPointsBuilder;

let cdp_der = CRLDistributionPointsBuilder::new()
    .full_name_uri("http://crl.example.com/ca.crl")
    .build()
    .unwrap();

// Multiple endpoints — HTTP primary, LDAP fallback.
let cdp_der = CRLDistributionPointsBuilder::new()
    .full_name_uri("http://crl.example.com/ca.crl")
    .full_name_uri("ldap://ldap.example.com/cn=CA,dc=example,dc=com?certificateRevocationList")
    .build()
    .unwrap();
```

Available methods:

| Method | GeneralName type |
|--------|-----------------|
| `full_name_uri(uri)` | `uniformResourceIdentifier [6]` |
| `full_name_dns(dns)` | `dNSName [2]` |
| `full_name_directory(name_der)` | `directoryName [4]` |

The `reasons` and `cRLIssuer` fields are not supported by this builder.

### IssuerAlternativeName

Builds an `IssuerAltName` extension value (OID 2.5.29.18, RFC 5280 §4.2.1.8).
The DER structure is a `GeneralNames` SEQUENCE — identical to `SubjectAltName`
but stored under the issuer OID.

```rust,ignore
use synta_certificate::IssuerAlternativeNameBuilder;

let ian_der = IssuerAlternativeNameBuilder::new()
    .rfc822_name("ca@example.com")
    .uri("http://www.example.com")
    .build()
    .unwrap();
```

Available methods: `dns_name`, `rfc822_name`, `uri`, `ip_address`,
`directory_name`, `registered_id` — same signatures as
`SubjectAlternativeNameBuilder`, minus `other_name`.

### IssuingDistributionPoint

Builds an `IssuingDistributionPoint` extension value (OID 2.5.29.28,
RFC 5280 §5.2.5). This extension belongs in CRL structures, not in certificates.
It must be marked critical when present.

```rust,ignore
use synta_certificate::IssuingDistributionPointBuilder;

let idp_der = IssuingDistributionPointBuilder::new()
    .full_name_uri("http://crl.example.com/ca.crl")
    .only_contains_user_certs(true)
    .build()
    .unwrap();
```

Available methods:

| Method | Description |
|--------|-------------|
| `full_name_uri(uri)` | Set the distribution point to a URI full name. |
| `full_name_dns(dns)` | Set the distribution point to a DNS name full name. |
| `only_contains_user_certs(bool)` | Limit CRL scope to end-entity certificate revocations. |
| `only_contains_cacerts(bool)` | Limit CRL scope to CA certificate revocations. |
| `indirect_crl(bool)` | Indicate that the CRL may cover certificates from other CAs. |
| `only_contains_attribute_certs(bool)` | Limit CRL scope to attribute certificate revocations. |

All boolean flags default to `false` (omitted in DER per DEFAULT FALSE semantics).

### CertificatePolicies

Builds a `CertificatePolicies` extension value (OID 2.5.29.32, RFC 5280 §4.2.1.4).
Each call appends one `PolicyInformation` entry to the SEQUENCE OF.  Only
`id-qt-cps` (CPS URI, `1.3.6.1.5.5.7.2.1`) qualifiers are supported.

```rust,ignore
use synta_certificate::CertificatePoliciesBuilder;

// Add two policies: one without qualifiers, one with a CPS URI.
let cp_der = CertificatePoliciesBuilder::new()
    .add_policy(&[2, 23, 140, 1, 2, 1])          // 2.23.140.1.2.1 — BR DV
    .add_policy_cps(
        &[2, 23, 140, 1, 2, 2],                   // 2.23.140.1.2.2 — BR OV
        "https://www.example.com/cps",
    )
    .build()
    .unwrap();
```

The `cps_uri` argument to `add_policy_cps` must be ASCII; non-ASCII characters
cause `.build()` to return an error.

## CMS EncryptedData (openssl feature)

```rust
use synta_certificate::{
    CmsEncryptor as _, CmsDecryptor as _,
    OpensslEncryptor, OpensslDecryptor,
    pkcs7_types, pkcs12_types,
};

let key: [u8; 16] = *b"0123456789abcdef";
let plaintext = b"secret payload";

// Encrypt → DER-encoded EncryptedData SEQUENCE
let der = OpensslEncryptor
    .create_encrypted_data(
        pkcs7_types::ID_DATA,
        pkcs12_types::ID_AES128_CBC,
        plaintext,
        &key,
    )
    .expect("encryption failed");
```

## New protocol and extension builders

`synta-certificate` includes fluent builders for several additional RFC-defined
structures.  Each builder follows the same pattern: set required and optional
fields, then call `.build()` which returns `Result<Vec<u8>, String>`.

### TimeStampReqBuilder (RFC 3161)

```rust,ignore
use synta_certificate::{TimeStampReqBuilder, oids};

let hash = sha256_of(data);
let req_der = TimeStampReqBuilder::new()
    .message_imprint(oids::ID_SHA256, &hash)
    .nonce(&nonce_bytes)
    .cert_req(true)
    .build()
    .unwrap();
```

Use `message_imprint_with_alg_der` when you have a pre-encoded
`AlgorithmIdentifier` DER blob instead of an OID component slice.

### ESS builders (RFC 2634)

```rust,ignore
use synta_certificate::{SigningCertificateBuilder, ReceiptRequestBuilder, ESSSecurityLabelBuilder};

// SigningCertificate — identifies signer cert by SHA-1 hash
let cert_sha1 = [0u8; 20]; // SHA-1 of the signer's DER certificate
let sc_der = SigningCertificateBuilder::new()
    .add_cert_id(&cert_sha1, None)
    .build()
    .unwrap();

// ReceiptRequest — requests signed receipts
let rr_der = ReceiptRequestBuilder::new()
    .signed_content_identifier(b"\x01\x02\x03\x04")
    .receipts_from_all()
    .add_receipt_to_email("receipts@example.com")
    .build()
    .unwrap();

// ESSSecurityLabel — information security label
let label_der = ESSSecurityLabelBuilder::new()
    .security_policy(&[1, 3, 6, 1, 5, 5, 7, 13, 1])
    .classification(4)  // SECRET
    .privacy_mark_utf8("SECRET")
    .build()
    .unwrap();
```

### PKCS#5 builders (RFC 8018)

```rust,ignore
use synta_certificate::{Pbkdf2ParamsBuilder, Pbes2ParamsBuilder, pkcs5_types};

let kdf_der = Pbkdf2ParamsBuilder::new()
    .salt(&salt)
    .iteration_count(600_000)
    .key_length(32)
    .prf(pkcs5_types::ID_HMAC_WITH_SHA256_P5)
    .build_as_algorithm_identifier(pkcs5_types::ID_PBKDF2)
    .unwrap();

let pbes2_der = Pbes2ParamsBuilder::new()
    .key_derivation_func(&kdf_der)
    .encryption_scheme_from_oid(pkcs5_types::AES256_CBC_PAD, &iv)
    .build()
    .unwrap();
```

### LogotypeExtnBuilder (RFC 9399)

```rust,ignore
use synta_certificate::{LogotypeExtnBuilder, LogotypeDetailsSpec, oids};

let extn_der = LogotypeExtnBuilder::new()
    .subject_logo_direct(LogotypeDetailsSpec {
        media_type: "image/png",
        hash_alg_oid: oids::ID_SHA256,
        hash_value: &img_hash,
        uri: "https://www.example.com/logo.png",
    })
    .build()
    .unwrap();
```

### AuthenticationContextsBuilder (RFC 7773)

```rust,ignore
use synta_certificate::AuthenticationContextsBuilder;

let extn_der = AuthenticationContextsBuilder::new()
    .add("urn:id:skatteverket:2:1.0", None)
    .add("urn:id:skatteverket:1:1.0", Some("https://www.skatteverket.se/ac/context.xml"))
    .build()
    .unwrap();
```