diff --git a/Cargo.lock b/Cargo.lock index 8bfbc00cf..f2017f09d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1839,6 +1839,7 @@ dependencies = [ "const-oid 0.9.5", "der", "hex-literal 0.4.1", + "rand_core", "spki", "x509-cert", ] diff --git a/x509-ocsp/Cargo.toml b/x509-ocsp/Cargo.toml index 9421b52f6..e7291465c 100644 --- a/x509-ocsp/Cargo.toml +++ b/x509-ocsp/Cargo.toml @@ -15,12 +15,15 @@ edition = "2021" rust-version = "1.65" [dependencies] -der = { version = "0.7", features = ["alloc", "derive", "oid"] } -spki = { version = "0.7" } -x509-cert = { version = "0.2", default-features = false } +const-oid = { version = "0.9.5", default-features = false, features = ["db"] } +der = { version = "0.7.8", features = ["alloc", "derive", "oid"] } +spki = { version = "0.7.2", features = ["alloc"] } +x509-cert = { version = "0.2.4", default-features = false } + +# Optional +rand_core = { version = "0.6.4", optional = true, default-features = false } [dev-dependencies] -const-oid = { version = "0.9", features = ["db"] } # TODO: path = "../const-oid" hex-literal = "0.4.1" [package.metadata.docs.rs] diff --git a/x509-ocsp/src/basic.rs b/x509-ocsp/src/basic.rs new file mode 100644 index 000000000..1b51308fe --- /dev/null +++ b/x509-ocsp/src/basic.rs @@ -0,0 +1,244 @@ +//! Basic OCSP Response + +use alloc::vec::Vec; +use const_oid::AssociatedOid; +use core::{default::Default, option::Option}; +use der::{ + asn1::{BitString, GeneralizedTime, Null, OctetString}, + Choice, Decode, Enumerated, Sequence, +}; +use spki::AlgorithmIdentifierOwned; +use x509_cert::{ + certificate::Certificate, + crl::RevokedCert, + ext::{pkix::CrlReason, Extensions}, + name::Name, + serial_number::SerialNumber, + time::Time, +}; + +/// OCSP `Version` as defined in [RFC 6960 Section 4.1.1]. +/// +/// ```text +/// Version ::= INTEGER { v1(0) } +/// ``` +/// +/// [RFC 6960 Section 4.1.1]: https://datatracker.ietf.org/doc/html/rfc6960#section-4.1.1 +#[derive(Clone, Debug, Default, Copy, PartialEq, Eq, Enumerated)] +#[asn1(type = "INTEGER")] +#[repr(u8)] +pub enum Version { + /// Version 1 (default) + #[default] + V1 = 0, +} + +/// BasicOcspResponse structure as defined in [RFC 6960 Section 4.2.1]. +/// +/// ```text +/// BasicOCSPResponse ::= SEQUENCE { +/// tbsResponseData ResponseData, +/// signatureAlgorithm AlgorithmIdentifier, +/// signature BIT STRING, +/// certs [0] EXPLICIT SEQUENCE OF Certificate OPTIONAL } +/// ``` +/// +/// [RFC 6960 Section 4.2.1]: https://datatracker.ietf.org/doc/html/rfc6960#section-4.2.1 +#[derive(Clone, Debug, Eq, PartialEq, Sequence)] +#[allow(missing_docs)] +pub struct BasicOcspResponse { + pub tbs_response_data: ResponseData, + pub signature_algorithm: AlgorithmIdentifierOwned, + pub signature: BitString, + + #[asn1(context_specific = "0", optional = "true", tag_mode = "EXPLICIT")] + pub certs: Option>, +} + +/// ResponseData structure as defined in [RFC 6960 Section 4.2.1]. +/// +/// ```text +/// ResponseData ::= SEQUENCE { +/// version [0] EXPLICIT Version DEFAULT v1, +/// responderID ResponderID, +/// producedAt GeneralizedTime, +/// responses SEQUENCE OF SingleResponse, +/// responseExtensions [1] EXPLICIT Extensions OPTIONAL } +/// ``` +/// +/// [RFC 6960 Section 4.2.1]: https://datatracker.ietf.org/doc/html/rfc6960#section-4.2.1 +#[derive(Clone, Debug, Eq, PartialEq, Sequence)] +#[allow(missing_docs)] +pub struct ResponseData { + #[asn1( + context_specific = "0", + default = "Default::default", + tag_mode = "EXPLICIT" + )] + pub version: Version, + pub responder_id: ResponderId, + pub produced_at: GeneralizedTime, + pub responses: Vec, + + #[asn1(context_specific = "1", optional = "true", tag_mode = "EXPLICIT")] + pub response_extensions: Option, +} + +/// ResponderID structure as defined in [RFC 6960 Section 4.2.1]. +/// +/// ```text +/// ResponderID ::= CHOICE { +/// byName [1] Name, +/// byKey [2] KeyHash } +/// ``` +/// +/// [RFC 6960 Section 4.2.1]: https://datatracker.ietf.org/doc/html/rfc6960#section-4.2.1 +#[derive(Clone, Debug, Eq, PartialEq, Choice)] +#[allow(missing_docs)] +pub enum ResponderId { + #[asn1(context_specific = "1", tag_mode = "EXPLICIT", constructed = "true")] + ByName(Name), + + #[asn1(context_specific = "2", tag_mode = "EXPLICIT", constructed = "true")] + ByKey(KeyHash), +} + +/// KeyHash structure as defined in [RFC 6960 Section 4.2.1]. +/// +/// ```text +/// KeyHash ::= OCTET STRING -- SHA-1 hash of responder's public key +/// -- (i.e., the SHA-1 hash of the value of the +/// -- BIT STRING subjectPublicKey [excluding +/// -- the tag, length, and number of unused +/// -- bits] in the responder's certificate) +/// ``` +/// +/// [RFC 6960 Section 4.2.1]: https://datatracker.ietf.org/doc/html/rfc6960#section-4.2.1 +pub type KeyHash = OctetString; + +/// SingleResponse structure as defined in [RFC 6960 Section 4.2.1]. +/// +/// ```text +/// SingleResponse ::= SEQUENCE { +/// certID CertID, +/// certStatus CertStatus, +/// thisUpdate GeneralizedTime, +/// nextUpdate [0] EXPLICIT GeneralizedTime OPTIONAL, +/// singleExtensions [1] EXPLICIT Extensions OPTIONAL } +/// ``` +/// +/// [RFC 6960 Section 4.2.1]: https://datatracker.ietf.org/doc/html/rfc6960#section-4.2.1 +#[derive(Clone, Debug, Eq, PartialEq, Sequence)] +#[allow(missing_docs)] +pub struct SingleResponse { + pub cert_id: CertId, + pub cert_status: CertStatus, + pub this_update: GeneralizedTime, + + #[asn1(context_specific = "0", optional = "true", tag_mode = "EXPLICIT")] + pub next_update: Option, + + #[asn1(context_specific = "1", optional = "true", tag_mode = "EXPLICIT")] + pub single_extensions: Option, +} + +/// CertID structure as defined in [RFC 6960 Section 4.1.1]. +/// +/// ```text +/// CertID ::= SEQUENCE { +/// hashAlgorithm AlgorithmIdentifier, +/// issuerNameHash OCTET STRING, -- Hash of issuer's DN +/// issuerKeyHash OCTET STRING, -- Hash of issuer's public key +/// serialNumber CertificateSerialNumber } +/// ``` +/// +/// [RFC 6960 Section 4.1.1]: https://datatracker.ietf.org/doc/html/rfc6960#section-4.1.1 +#[derive(Clone, Debug, Eq, PartialEq, Sequence)] +#[allow(missing_docs)] +pub struct CertId { + pub hash_algorithm: AlgorithmIdentifierOwned, + pub issuer_name_hash: OctetString, + pub issuer_key_hash: OctetString, + pub serial_number: SerialNumber, +} + +/// CertStatus structure as defined in [RFC 6960 Section 4.2.1]. +/// +/// ```text +/// CertStatus ::= CHOICE { +/// good [0] IMPLICIT NULL, +/// revoked [1] IMPLICIT RevokedInfo, +/// unknown [2] IMPLICIT UnknownInfo } +/// ``` +/// +/// [RFC 6960 Section 4.2.1]: https://datatracker.ietf.org/doc/html/rfc6960#section-4.2.1 +#[derive(Clone, Debug, Eq, PartialEq, Choice)] +#[allow(missing_docs)] +pub enum CertStatus { + #[asn1(context_specific = "0", tag_mode = "IMPLICIT")] + Good(Null), + + #[asn1(context_specific = "1", tag_mode = "IMPLICIT", constructed = "true")] + Revoked(RevokedInfo), + + #[asn1(context_specific = "2", tag_mode = "IMPLICIT")] + Unknown(UnknownInfo), +} + +/// RevokedInfo structure as defined in [RFC 6960 Section 4.2.1]. +/// +/// ```text +/// RevokedInfo ::= SEQUENCE { +/// revocationTime GeneralizedTime, +/// revocationReason [0] EXPLICIT CRLReason OPTIONAL } +/// ``` +/// +/// [RFC 6960 Section 4.2.1]: https://datatracker.ietf.org/doc/html/rfc6960#section-4.2.1 +#[derive(Clone, Debug, Eq, PartialEq, Sequence)] +#[allow(missing_docs)] +pub struct RevokedInfo { + pub revocation_time: GeneralizedTime, + + #[asn1(context_specific = "0", optional = "true", tag_mode = "EXPLICIT")] + pub revocation_reason: Option, +} + +impl From<&RevokedCert> for RevokedInfo { + fn from(rc: &RevokedCert) -> Self { + Self { + revocation_time: match rc.revocation_date { + Time::UtcTime(t) => GeneralizedTime::from_date_time(t.to_date_time()), + Time::GeneralTime(t) => t, + }, + revocation_reason: if let Some(extensions) = &rc.crl_entry_extensions { + let mut filter = extensions + .iter() + .filter(|ext| ext.extn_id == CrlReason::OID); + match filter.next() { + None => None, + Some(ext) => match CrlReason::from_der(ext.extn_value.as_bytes()) { + Ok(reason) => Some(reason), + Err(_) => None, + }, + } + } else { + None + }, + } + } +} + +impl From for RevokedInfo { + fn from(rc: RevokedCert) -> Self { + Self::from(&rc) + } +} + +/// RevokedInfo structure as defined in [RFC 6960 Section 4.2.1]. +/// +/// ```text +/// UnknownInfo ::= NULL +/// ``` +/// +/// [RFC 6960 Section 4.2.1]: https://datatracker.ietf.org/doc/html/rfc6960#section-4.2.1 +pub type UnknownInfo = Null; diff --git a/x509-ocsp/src/ext.rs b/x509-ocsp/src/ext.rs new file mode 100644 index 000000000..7d8a97207 --- /dev/null +++ b/x509-ocsp/src/ext.rs @@ -0,0 +1,205 @@ +//! OCSP Extensions + +use alloc::vec::Vec; +use const_oid::{ + db::rfc6960::{ + ID_PKIX_OCSP_ARCHIVE_CUTOFF, ID_PKIX_OCSP_CRL, ID_PKIX_OCSP_NONCE, + ID_PKIX_OCSP_PREF_SIG_ALGS, ID_PKIX_OCSP_RESPONSE, ID_PKIX_OCSP_SERVICE_LOCATOR, + }, + AssociatedOid, +}; +use der::{ + asn1::{GeneralizedTime, Ia5String, ObjectIdentifier, OctetString, Uint}, + Sequence, ValueOrd, +}; +use spki::AlgorithmIdentifierOwned; +use x509_cert::{ + ext::{pkix::AuthorityInfoAccessSyntax, AsExtension, Extension}, + impl_newtype, + name::Name, +}; + +#[cfg(feature = "rand_core")] +use rand_core::CryptoRngCore; + +// x509-cert's is not exported +macro_rules! impl_extension { + ($newtype:ty, critical = $critical:expr) => { + impl AsExtension for $newtype { + fn critical(&self, _subject: &Name, _extensions: &[Extension]) -> bool { + $critical + } + } + }; +} + +/// Nonce extension as defined in [RFC 6960 Section 4.4.1]. +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct Nonce(pub OctetString); + +impl_newtype!(Nonce, OctetString); + +// Some responders do not honor nonces +impl_extension!(Nonce, critical = false); + +impl AssociatedOid for Nonce { + const OID: ObjectIdentifier = ID_PKIX_OCSP_NONCE; +} + +impl Nonce { + /// Creates a Nonce object given the bytes + pub fn new(bytes: impl Into>) -> Result { + Ok(Self(OctetString::new(bytes)?)) + } + + /// Creates a Nonce object given a random generator and a length. + /// + /// A proposed but not (yet) accepted RFC [RFC 8954] wants to limit nonces. RFC 6960 has no + /// mention of a minimum or maximum length. + /// + /// ```text + /// Nonce ::= OCTET STRING(SIZE(1..32)) + /// ``` + #[cfg(feature = "rand_core")] + pub fn generate(rng: &mut R, length: usize) -> Result { + let mut bytes = alloc::vec![0; length]; + rng.fill_bytes(&mut bytes); + Self::new(bytes) + } +} + +/// CrlReferences extension as defined in [RFC 6960 Section 4.4.2] +pub struct CrlReferences(pub Vec); + +impl_newtype!(CrlReferences, Vec); + +// It may be desirable for the OCSP responder to indicate the CRL on +// which a revoked or onHold certificate is found. This can be useful +// where OCSP is used between repositories, and also as an auditing +// mechanism. The CRL may be specified by a URL (the URL at which the +// CRL is available), a number (CRL number), or a time (the time at +// which the relevant CRL was created). These extensions will be +// specified as singleExtensions. The identifier for this extension +// will be id-pkix-ocsp-crl, while the value will be CrlID. +impl_extension!(CrlReferences, critical = false); + +impl AssociatedOid for CrlReferences { + const OID: ObjectIdentifier = ID_PKIX_OCSP_CRL; +} + +/// CrlID structure as defined in [RFC 6960 Section 4.4.2]. +/// +/// ```text +/// CrlID ::= SEQUENCE { +/// crlUrl [0] EXPLICIT IA5String OPTIONAL, +/// crlNum [1] EXPLICIT INTEGER OPTIONAL, +/// crlTime [2] EXPLICIT GeneralizedTime OPTIONAL } +/// ``` +/// +/// [RFC 6960 Section 4.4.2]: https://datatracker.ietf.org/doc/html/rfc6960#section-4.4.2 +#[derive(Clone, Debug, Eq, PartialEq, Sequence, ValueOrd)] +#[allow(missing_docs)] +pub struct CrlId { + #[asn1(context_specific = "0", optional = "true", tag_mode = "EXPLICIT")] + pub crl_url: Option, + + #[asn1(context_specific = "1", optional = "true", tag_mode = "EXPLICIT")] + pub crl_num: Option, + + #[asn1(context_specific = "2", optional = "true", tag_mode = "EXPLICIT")] + pub crl_time: Option, +} + +/// AcceptableResponses structure as defined in [RFC 6960 Section 4.4.3]. +/// +/// ```text +/// AcceptableResponses ::= SEQUENCE OF OBJECT IDENTIFIER +/// ``` +/// +/// [RFC 6960 Section 4.4.3]: https://datatracker.ietf.org/doc/html/rfc6960#section-4.4.3 +pub struct AcceptableResponses(pub Vec); + +impl_newtype!(AcceptableResponses, Vec); + +// As noted in Section 4.2.1, OCSP responders SHALL be capable of +// responding with responses of the id-pkix-ocsp-basic response type. +// Correspondingly, OCSP clients SHALL be capable of receiving and +// processing responses of the id-pkix-ocsp-basic response type. +impl_extension!(AcceptableResponses, critical = true); + +impl AssociatedOid for AcceptableResponses { + const OID: ObjectIdentifier = ID_PKIX_OCSP_RESPONSE; +} + +/// ArchiveCutoff structure as defined in [RFC 6960 Section 4.4.4]. +/// +/// ```text +/// ArchiveCutoff ::= GeneralizedTime +/// ``` +/// +/// [RFC 6960 Section 4.4.4]: https://datatracker.ietf.org/doc/html/rfc6960#section-4.4.4 +pub struct ArchiveCutoff(GeneralizedTime); + +impl_newtype!(ArchiveCutoff, GeneralizedTime); +impl_extension!(ArchiveCutoff, critical = false); + +impl AssociatedOid for ArchiveCutoff { + const OID: ObjectIdentifier = ID_PKIX_OCSP_ARCHIVE_CUTOFF; +} + +/// ServiceLocator structure as defined in [RFC 6960 Section 4.4.6]. +/// +/// ```text +/// ServiceLocator ::= SEQUENCE { +/// issuer Name, +/// locator AuthorityInfoAccessSyntax } +/// ``` +/// +/// [RFC 6960 Section 4.4.6]: https://datatracker.ietf.org/doc/html/rfc6960#section-4.4.6 +#[derive(Clone, Debug, Eq, PartialEq, Sequence)] +#[allow(missing_docs)] +pub struct ServiceLocator { + pub issuer: Name, + pub locator: Option, +} + +impl AssociatedOid for ServiceLocator { + const OID: ObjectIdentifier = ID_PKIX_OCSP_SERVICE_LOCATOR; +} + +impl_extension!(ServiceLocator, critical = false); + +/// PreferredSignatureAlgorithms structure as defined in [RFC 6960 Section 4.4.7.1]. +/// +/// ```text +/// PreferredSignatureAlgorithms ::= SEQUENCE OF PreferredSignatureAlgorithm +/// ``` +/// +/// [RFC 6960 Section 4.4.7.1]: https://datatracker.ietf.org/doc/html/rfc6960#section-4.4.7.1 +pub struct PreferredSignatureAlgorithms(pub Vec); + +impl_newtype!( + PreferredSignatureAlgorithms, + Vec +); +impl_extension!(PreferredSignatureAlgorithms, critical = false); + +impl AssociatedOid for PreferredSignatureAlgorithms { + const OID: ObjectIdentifier = ID_PKIX_OCSP_PREF_SIG_ALGS; +} + +/// PreferredSignatureAlgorithm structure as defined in [RFC 6960 Section 4.4.7.1]. +/// +/// ```text +/// PreferredSignatureAlgorithm ::= SEQUENCE { +/// sigIdentifier AlgorithmIdentifier, +/// certIdentifier AlgorithmIdentifier OPTIONAL } +/// ``` +/// +/// [RFC 6960 Section 4.4.7.1]: https://datatracker.ietf.org/doc/html/rfc6960#section-4.4.7.1 +#[derive(Clone, Debug, Eq, PartialEq, Sequence, ValueOrd)] +#[allow(missing_docs)] +pub struct PreferredSignatureAlgorithm { + pub sig_identifier: AlgorithmIdentifierOwned, + pub cert_identifier: Option, +} diff --git a/x509-ocsp/src/lib.rs b/x509-ocsp/src/lib.rs index cc60d4d81..bf0070f8f 100644 --- a/x509-ocsp/src/lib.rs +++ b/x509-ocsp/src/lib.rs @@ -1,4 +1,7 @@ #![no_std] +#![cfg_attr(docsrs, feature(doc_auto_cfg))] +#![doc = include_str!("../README.md")] +#![forbid(unsafe_code)] #![warn( clippy::mod_module_files, clippy::unwrap_used, @@ -8,462 +11,17 @@ unused_qualifications )] -//! The ocsp module features encoders and decoders for the structures defined in -//! [RFC 6960](https://datatracker.ietf.org/doc/html/rfc6960). - extern crate alloc; -use der::asn1::{BitStringRef, Ia5StringRef, ObjectIdentifier, OctetStringRef, UintRef}; -use der::asn1::{GeneralizedTime, Null}; -use der::{AnyRef, Choice, Enumerated, Sequence}; -use spki::{AlgorithmIdentifierOwned, AlgorithmIdentifierRef}; -use x509_cert::ext::pkix::name::GeneralName; -use x509_cert::ext::pkix::{AuthorityInfoAccessSyntax, CrlReason}; -use x509_cert::ext::Extensions; -use x509_cert::name::Name; -use x509_cert::serial_number::SerialNumber; -use x509_cert::Certificate; - -use alloc::vec::Vec; -use core::default::Default; -use core::option::Option; - -/// OcspNoCheck as defined in [RFC 6960 Section 4.2.2.2.1]. -/// -/// This extension is identified by the ID_PKIX_OCSP_NOCHECK OID. -/// -/// ```text -/// OcspNoCheck ::= NULL -/// ``` -/// -/// [RFC 6960 Section 4.2.2.2.1]: https://datatracker.ietf.org/doc/html/rfc6960#section-4.2.2.2.1 -pub type OcspNoCheck = Null; - -/// OCSPRequest structure as defined in [RFC 6960 Section 4.1.1]. -/// -/// ```text -/// OCSPRequest ::= SEQUENCE { -/// tbsRequest TBSRequest, -/// optionalSignature [0] EXPLICIT Signature OPTIONAL } -/// ``` -/// -/// [RFC 6960 Section 4.1.1]: https://datatracker.ietf.org/doc/html/rfc6960#section-4.1.1 -#[derive(Clone, Debug, Eq, PartialEq, Sequence)] -#[allow(missing_docs)] -pub struct OcspRequest<'a> { - pub tbs_request: TbsRequest<'a>, - - #[asn1(context_specific = "0", optional = "true", tag_mode = "EXPLICIT")] - pub optional_signature: Option>, -} - -/// TBSRequest structure as defined in [RFC 6960 Section 4.1.1]. -/// -/// ```text -/// TBSRequest ::= SEQUENCE { -/// version [0] EXPLICIT Version DEFAULT v1, -/// requestorName [1] EXPLICIT GeneralName OPTIONAL, -/// requestList SEQUENCE OF Request, -/// requestExtensions [2] EXPLICIT Extensions OPTIONAL } -/// ``` -/// -/// [RFC 6960 Section 4.1.1]: https://datatracker.ietf.org/doc/html/rfc6960#section-4.1.1 -#[derive(Clone, Debug, Eq, PartialEq, Sequence)] -#[allow(missing_docs)] -pub struct TbsRequest<'a> { - #[asn1( - context_specific = "0", - default = "Default::default", - tag_mode = "EXPLICIT" - )] - pub version: Version, - - #[asn1(context_specific = "1", optional = "true", tag_mode = "EXPLICIT")] - pub requestor_name: Option, - - pub request_list: Vec>, - - #[asn1(context_specific = "2", optional = "true", tag_mode = "EXPLICIT")] - pub request_extensions: Option, -} - -/// Signature structure as defined in [RFC 6960 Section 4.1.1]. -/// -/// ```text -/// Signature ::= SEQUENCE { -/// signatureAlgorithm AlgorithmIdentifier, -/// signature BIT STRING, -/// certs [0] EXPLICIT SEQUENCE OF Certificate OPTIONAL } -/// ``` -/// -/// [RFC 6960 Section 4.1.1]: https://datatracker.ietf.org/doc/html/rfc6960#section-4.1.1 -#[derive(Clone, Debug, Eq, PartialEq, Sequence)] -#[allow(missing_docs)] -pub struct Signature<'a> { - pub signature_algorithm: AlgorithmIdentifierRef<'a>, - pub signature: BitStringRef<'a>, - - #[asn1(context_specific = "0", optional = "true", tag_mode = "EXPLICIT")] - pub certs: Option>, -} - -/// OCSP `Version` as defined in [RFC 6960 Section 4.1.1]. -/// -/// ```text -/// Version ::= INTEGER { v1(0) } -/// ``` -/// -/// [RFC 6960 Section 4.1.1]: https://datatracker.ietf.org/doc/html/rfc6960#section-4.1.1 -#[derive(Clone, Debug, Copy, PartialEq, Eq, Enumerated)] -#[asn1(type = "INTEGER")] -#[repr(u8)] -#[allow(missing_docs)] -pub enum Version { - V1 = 0, -} - -impl Default for Version { - fn default() -> Self { - Self::V1 - } -} - -/// Request structure as defined in [RFC 6960 Section 4.1.1]. -/// -/// ```text -/// Request ::= SEQUENCE { -/// reqCert CertID, -/// singleRequestExtensions [0] EXPLICIT Extensions OPTIONAL } -/// ``` -/// -/// [RFC 6960 Section 4.1.1]: https://datatracker.ietf.org/doc/html/rfc6960#section-4.1.1 -#[derive(Clone, Debug, Eq, PartialEq, Sequence)] -#[allow(missing_docs)] -pub struct Request<'a> { - pub req_cert: CertId<'a>, - - #[asn1(context_specific = "0", optional = "true", tag_mode = "EXPLICIT")] - pub single_request_extensions: Option, -} - -/// CertID structure as defined in [RFC 6960 Section 4.1.1]. -/// -/// ```text -/// CertID ::= SEQUENCE { -/// hashAlgorithm AlgorithmIdentifier, -/// issuerNameHash OCTET STRING, -- Hash of issuer's DN -/// issuerKeyHash OCTET STRING, -- Hash of issuer's public key -/// serialNumber CertificateSerialNumber } -/// ``` -/// -/// [RFC 6960 Section 4.1.1]: https://datatracker.ietf.org/doc/html/rfc6960#section-4.1.1 -#[derive(Clone, Debug, Eq, PartialEq, Sequence)] -#[allow(missing_docs)] -pub struct CertId<'a> { - pub hash_algorithm: AlgorithmIdentifierRef<'a>, - pub issuer_name_hash: OctetStringRef<'a>, - pub issuer_key_hash: OctetStringRef<'a>, - pub serial_number: SerialNumber, -} - -/// OCSPResponse structure as defined in [RFC 6960 Section 4.2.1]. -/// -/// ```text -/// OCSPResponse ::= SEQUENCE { -/// responseStatus OCSPResponseStatus, -/// responseBytes [0] EXPLICIT ResponseBytes OPTIONAL } -/// ``` -/// -/// [RFC 6960 Section 4.2.1]: https://datatracker.ietf.org/doc/html/rfc6960#section-4.2.1 -#[derive(Clone, Debug, Eq, PartialEq, Sequence)] -#[allow(missing_docs)] -pub struct OcspResponse<'a> { - pub response_status: OcspResponseStatus, - - #[asn1(context_specific = "0", optional = "true", tag_mode = "EXPLICIT")] - pub response_bytes: Option>, -} - -/// OCSPResponseStatus structure as defined in [RFC 6960 Section 4.2.1]. -/// -/// ```text -/// OCSPResponseStatus ::= ENUMERATED { -/// successful (0), -- Response has valid confirmations -/// malformedRequest (1), -- Illegal confirmation request -/// internalError (2), -- Internal error in issuer -/// tryLater (3), -- Try again later -/// -- (4) is not used -/// sigRequired (5), -- Must sign the request -/// unauthorized (6) -- Request unauthorized -/// } -/// ``` -/// -/// [RFC 6960 Section 4.2.1]: https://datatracker.ietf.org/doc/html/rfc6960#section-4.2.1 -#[derive(Enumerated, Copy, Clone, Debug, Eq, PartialEq)] -#[repr(u32)] -#[allow(missing_docs)] -pub enum OcspResponseStatus { - Successful = 0, - MalformedRequest = 1, - InternalError = 2, - TryLater = 3, - SigRequired = 5, - Unauthorized = 6, -} - -/// ResponseBytes structure as defined in [RFC 6960 Section 4.2.1]. -/// -/// ```text -/// ResponseBytes ::= SEQUENCE { -/// responseType OBJECT IDENTIFIER, -/// response OCTET STRING } -/// ``` -/// -/// [RFC 6960 Section 4.2.1]: https://datatracker.ietf.org/doc/html/rfc6960#section-4.2.1 -#[derive(Clone, Debug, Eq, PartialEq, Sequence)] -#[allow(missing_docs)] -pub struct ResponseBytes<'a> { - pub response_type: ObjectIdentifier, - pub response: OctetStringRef<'a>, -} - -/// BasicOcspResponse structure as defined in [RFC 6960 Section 4.2.1]. -/// -/// ```text -/// BasicOCSPResponse ::= SEQUENCE { -/// tbsResponseData ResponseData, -/// signatureAlgorithm AlgorithmIdentifier, -/// signature BIT STRING, -/// certs [0] EXPLICIT SEQUENCE OF Certificate OPTIONAL } -/// ``` -/// -/// [RFC 6960 Section 4.2.1]: https://datatracker.ietf.org/doc/html/rfc6960#section-4.2.1 -#[derive(Clone, Debug, Eq, PartialEq, Sequence)] -#[allow(missing_docs)] -pub struct BasicOcspResponse<'a> { - pub tbs_response_data: ResponseData<'a>, - pub signature_algorithm: AlgorithmIdentifierOwned, - pub signature: BitStringRef<'a>, - - #[asn1(context_specific = "0", optional = "true", tag_mode = "EXPLICIT")] - pub certs: Option>>, -} - -/// ResponseData structure as defined in [RFC 6960 Section 4.2.1]. -/// -/// ```text -// ResponseData ::= SEQUENCE { -/// version [0] EXPLICIT Version DEFAULT v1, -/// responderID ResponderID, -/// producedAt GeneralizedTime, -/// responses SEQUENCE OF SingleResponse, -/// responseExtensions [1] EXPLICIT Extensions OPTIONAL } -/// ``` -/// -/// [RFC 6960 Section 4.2.1]: https://datatracker.ietf.org/doc/html/rfc6960#section-4.2.1 -#[derive(Clone, Debug, Eq, PartialEq, Sequence)] -#[allow(missing_docs)] -pub struct ResponseData<'a> { - #[asn1( - context_specific = "0", - default = "Default::default", - tag_mode = "EXPLICIT" - )] - pub version: Version, - pub responder_id: ResponderId<'a>, - pub produced_at: GeneralizedTime, - pub responses: Vec>, - - #[asn1(context_specific = "1", optional = "true", tag_mode = "EXPLICIT")] - pub response_extensions: Option, -} - -/// ResponderID structure as defined in [RFC 6960 Section 4.2.1]. -/// -/// ```text -// ResponderID ::= CHOICE { -/// byName [1] Name, -/// byKey [2] KeyHash } -/// ``` -/// -/// [RFC 6960 Section 4.2.1]: https://datatracker.ietf.org/doc/html/rfc6960#section-4.2.1 -#[derive(Clone, Debug, Eq, PartialEq, Choice)] -#[allow(missing_docs)] -pub enum ResponderId<'a> { - #[asn1(context_specific = "1", tag_mode = "EXPLICIT", constructed = "true")] - ByName(Name), - - #[asn1(context_specific = "2", tag_mode = "EXPLICIT", constructed = "true")] - ByKey(KeyHash<'a>), -} - -/// KeyHash structure as defined in [RFC 6960 Section 4.2.1]. -/// -/// ```text -/// KeyHash ::= OCTET STRING -- SHA-1 hash of responder's public key -/// -- (i.e., the SHA-1 hash of the value of the -/// -- BIT STRING subjectPublicKey [excluding -/// -- the tag, length, and number of unused -/// -- bits] in the responder's certificate) -/// ``` -/// -/// [RFC 6960 Section 4.2.1]: https://datatracker.ietf.org/doc/html/rfc6960#section-4.2.1 -pub type KeyHash<'a> = OctetStringRef<'a>; - -/// SingleResponse structure as defined in [RFC 6960 Section 4.2.1]. -/// -/// ```text -// SingleResponse ::= SEQUENCE { -/// certID CertID, -/// certStatus CertStatus, -/// thisUpdate GeneralizedTime, -/// nextUpdate [0] EXPLICIT GeneralizedTime OPTIONAL, -/// singleExtensions [1] EXPLICIT Extensions OPTIONAL } -/// ``` -/// -/// [RFC 6960 Section 4.2.1]: https://datatracker.ietf.org/doc/html/rfc6960#section-4.2.1 -#[derive(Clone, Debug, Eq, PartialEq, Sequence)] -#[allow(missing_docs)] -pub struct SingleResponse<'a> { - pub cert_id: CertId<'a>, - pub cert_status: CertStatus, - pub this_update: GeneralizedTime, - - #[asn1(context_specific = "0", optional = "true", tag_mode = "EXPLICIT")] - pub next_update: Option, - - #[asn1(context_specific = "1", optional = "true", tag_mode = "EXPLICIT")] - pub single_request_extensions: Option, -} - -/// CertStatus structure as defined in [RFC 6960 Section 4.2.1]. -/// -/// ```text -/// CertStatus ::= CHOICE { -/// good [0] IMPLICIT NULL, -/// revoked [1] IMPLICIT RevokedInfo, -/// unknown [2] IMPLICIT UnknownInfo } -/// ``` -/// -/// [RFC 6960 Section 4.2.1]: https://datatracker.ietf.org/doc/html/rfc6960#section-4.2.1 -#[derive(Clone, Debug, Eq, PartialEq, Choice)] -#[allow(missing_docs)] -pub enum CertStatus { - #[asn1(context_specific = "0", tag_mode = "IMPLICIT")] - Good(Null), - - #[asn1(context_specific = "1", tag_mode = "IMPLICIT", constructed = "true")] - Revoked(RevokedInfo), - - #[asn1(context_specific = "2", tag_mode = "IMPLICIT")] - Unknown(UnknownInfo), -} - -/// RevokedInfo structure as defined in [RFC 6960 Section 4.2.1]. -/// -/// ```text -// RevokedInfo ::= SEQUENCE { -/// revocationTime GeneralizedTime, -/// revocationReason [0] EXPLICIT CRLReason OPTIONAL } -/// ``` -/// -/// [RFC 6960 Section 4.2.1]: https://datatracker.ietf.org/doc/html/rfc6960#section-4.2.1 -#[derive(Clone, Debug, Eq, PartialEq, Sequence)] -#[allow(missing_docs)] -pub struct RevokedInfo { - pub revocation_time: GeneralizedTime, - - #[asn1(context_specific = "0", optional = "true", tag_mode = "EXPLICIT")] - pub revocation_reason: Option, -} - -/// RevokedInfo structure as defined in [RFC 6960 Section 4.2.1]. -/// -/// ```text -/// UnknownInfo ::= NULL -/// ``` -/// -/// [RFC 6960 Section 4.2.1]: https://datatracker.ietf.org/doc/html/rfc6960#section-4.2.1 -pub type UnknownInfo = Null; - -/// ArchiveCutoff structure as defined in [RFC 6960 Section 4.4.4]. -/// -/// ```text -// ArchiveCutoff ::= GeneralizedTime -/// ``` -/// -/// [RFC 6960 Section 4.4.4]: https://datatracker.ietf.org/doc/html/rfc6960#section-4.4.4 -pub type ArchiveCutoff = GeneralizedTime; - -/// AcceptableResponses structure as defined in [RFC 6960 Section 4.4.3]. -/// -/// ```text -// AcceptableResponses ::= SEQUENCE OF OBJECT IDENTIFIER -/// ``` -/// -/// [RFC 6960 Section 4.4.3]: https://datatracker.ietf.org/doc/html/rfc6960#section-4.4.3 -pub type AcceptableResponses = Vec; - -/// ServiceLocator structure as defined in [RFC 6960 Section 4.4.6]. -/// -/// ```text -/// ServiceLocator ::= SEQUENCE { -/// issuer Name, -/// locator AuthorityInfoAccessSyntax } -/// ``` -/// -/// [RFC 6960 Section 4.4.6]: https://datatracker.ietf.org/doc/html/rfc6960#section-4.4.6 -#[derive(Clone, Debug, Eq, PartialEq, Sequence)] -#[allow(missing_docs)] -pub struct ServiceLocator { - pub issuer: Name, - pub locator: AuthorityInfoAccessSyntax, -} - -/// CrlID structure as defined in [RFC 6960 Section 4.4.2]. -/// -/// ```text -/// CrlID ::= SEQUENCE { -/// crlUrl [0] EXPLICIT IA5String OPTIONAL, -/// crlNum [1] EXPLICIT INTEGER OPTIONAL, -/// crlTime [2] EXPLICIT GeneralizedTime OPTIONAL } -/// ``` -/// -/// [RFC 6960 Section 4.4.2]: https://datatracker.ietf.org/doc/html/rfc6960#section-4.4.2 -#[derive(Clone, Debug, Eq, PartialEq, Sequence)] -#[allow(missing_docs)] -pub struct CrlId<'a> { - #[asn1(context_specific = "0", optional = "true", tag_mode = "EXPLICIT")] - pub crl_url: Option>, - - #[asn1(context_specific = "1", optional = "true", tag_mode = "EXPLICIT")] - pub crl_num: Option>, - - #[asn1(context_specific = "2", optional = "true", tag_mode = "EXPLICIT")] - pub crl_time: Option, -} +mod basic; +mod request; +mod response; -/// PreferredSignatureAlgorithms structure as defined in [RFC 6960 Section 4.4.7.1]. -/// -/// ```text -/// PreferredSignatureAlgorithms ::= SEQUENCE OF PreferredSignatureAlgorithm -/// ``` -/// -/// [RFC 6960 Section 4.4.7.1]: https://datatracker.ietf.org/doc/html/rfc6960#section-4.4.7.1 -pub type PreferredSignatureAlgorithms<'a> = Vec>; +pub mod ext; -/// PreferredSignatureAlgorithm structure as defined in [RFC 6960 Section 4.4.7.1]. -/// -/// ```text -/// PreferredSignatureAlgorithm ::= SEQUENCE { -/// sigIdentifier AlgorithmIdentifier, -/// certIdentifier AlgorithmIdentifier OPTIONAL } -/// ``` -/// -/// [RFC 6960 Section 4.4.7.1]: https://datatracker.ietf.org/doc/html/rfc6960#section-4.4.7.1 -#[derive(Clone, Debug, Eq, PartialEq, Sequence)] -#[allow(missing_docs)] -pub struct PreferredSignatureAlgorithm<'a> { - pub sig_identifier: AlgorithmIdentifierRef<'a>, - pub cert_identifier: Option>, -} +pub use basic::{ + BasicOcspResponse, CertId, CertStatus, KeyHash, ResponderId, ResponseData, RevokedInfo, + SingleResponse, UnknownInfo, Version, +}; +pub use request::{OcspRequest, Request, Signature, TbsRequest}; +pub use response::{OcspNoCheck, OcspResponse, OcspResponseStatus, ResponseBytes}; diff --git a/x509-ocsp/src/request.rs b/x509-ocsp/src/request.rs new file mode 100644 index 000000000..adb4f87c1 --- /dev/null +++ b/x509-ocsp/src/request.rs @@ -0,0 +1,97 @@ +//! OCSP Request + +use crate::{CertId, Version}; +use alloc::vec::Vec; +use core::{default::Default, option::Option}; +use der::{asn1::BitString, Sequence}; +use spki::AlgorithmIdentifierOwned; +use x509_cert::{ + certificate::Certificate, + ext::{pkix::name::GeneralName, Extensions}, +}; + +/// OCSPRequest structure as defined in [RFC 6960 Section 4.1.1]. +/// +/// ```text +/// OCSPRequest ::= SEQUENCE { +/// tbsRequest TBSRequest, +/// optionalSignature [0] EXPLICIT Signature OPTIONAL } +/// ``` +/// +/// [RFC 6960 Section 4.1.1]: https://datatracker.ietf.org/doc/html/rfc6960#section-4.1.1 +#[derive(Clone, Debug, Eq, PartialEq, Sequence)] +#[allow(missing_docs)] +pub struct OcspRequest { + pub tbs_request: TbsRequest, + + #[asn1(context_specific = "0", optional = "true", tag_mode = "EXPLICIT")] + pub optional_signature: Option, +} + +/// TBSRequest structure as defined in [RFC 6960 Section 4.1.1]. +/// +/// ```text +/// TBSRequest ::= SEQUENCE { +/// version [0] EXPLICIT Version DEFAULT v1, +/// requestorName [1] EXPLICIT GeneralName OPTIONAL, +/// requestList SEQUENCE OF Request, +/// requestExtensions [2] EXPLICIT Extensions OPTIONAL } +/// ``` +/// +/// [RFC 6960 Section 4.1.1]: https://datatracker.ietf.org/doc/html/rfc6960#section-4.1.1 +#[derive(Clone, Debug, Eq, PartialEq, Sequence)] +#[allow(missing_docs)] +pub struct TbsRequest { + #[asn1( + context_specific = "0", + default = "Default::default", + tag_mode = "EXPLICIT" + )] + pub version: Version, + + #[asn1(context_specific = "1", optional = "true", tag_mode = "EXPLICIT")] + pub requestor_name: Option, + + pub request_list: Vec, + + #[asn1(context_specific = "2", optional = "true", tag_mode = "EXPLICIT")] + pub request_extensions: Option, +} + +/// Signature structure as defined in [RFC 6960 Section 4.1.1]. +/// +/// ```text +/// Signature ::= SEQUENCE { +/// signatureAlgorithm AlgorithmIdentifier, +/// signature BIT STRING, +/// certs [0] EXPLICIT SEQUENCE OF Certificate OPTIONAL } +/// ``` +/// +/// [RFC 6960 Section 4.1.1]: https://datatracker.ietf.org/doc/html/rfc6960#section-4.1.1 +#[derive(Clone, Debug, Eq, PartialEq, Sequence)] +#[allow(missing_docs)] +pub struct Signature { + pub signature_algorithm: AlgorithmIdentifierOwned, + pub signature: BitString, + + #[asn1(context_specific = "0", optional = "true", tag_mode = "EXPLICIT")] + pub certs: Option>, +} + +/// Request structure as defined in [RFC 6960 Section 4.1.1]. +/// +/// ```text +/// Request ::= SEQUENCE { +/// reqCert CertID, +/// singleRequestExtensions [0] EXPLICIT Extensions OPTIONAL } +/// ``` +/// +/// [RFC 6960 Section 4.1.1]: https://datatracker.ietf.org/doc/html/rfc6960#section-4.1.1 +#[derive(Clone, Debug, Eq, PartialEq, Sequence)] +#[allow(missing_docs)] +pub struct Request { + pub req_cert: CertId, + + #[asn1(context_specific = "0", optional = "true", tag_mode = "EXPLICIT")] + pub single_request_extensions: Option, +} diff --git a/x509-ocsp/src/response.rs b/x509-ocsp/src/response.rs new file mode 100644 index 000000000..da365b881 --- /dev/null +++ b/x509-ocsp/src/response.rs @@ -0,0 +1,134 @@ +//! OCSP Response + +use crate::BasicOcspResponse; +use const_oid::db::rfc6960::ID_PKIX_OCSP_BASIC; +use core::option::Option; +use der::{ + asn1::{Null, ObjectIdentifier, OctetString}, + Encode, Enumerated, Sequence, +}; + +/// OcspNoCheck as defined in [RFC 6960 Section 4.2.2.2.1]. +/// +/// This extension is identified by the ID_PKIX_OCSP_NOCHECK OID. +/// +/// ```text +/// OcspNoCheck ::= NULL +/// ``` +/// +/// [RFC 6960 Section 4.2.2.2.1]: https://datatracker.ietf.org/doc/html/rfc6960#section-4.2.2.2.1 +pub type OcspNoCheck = Null; + +/// OCSPResponse structure as defined in [RFC 6960 Section 4.2.1]. +/// +/// ```text +/// OCSPResponse ::= SEQUENCE { +/// responseStatus OCSPResponseStatus, +/// responseBytes [0] EXPLICIT ResponseBytes OPTIONAL } +/// ``` +/// +/// [RFC 6960 Section 4.2.1]: https://datatracker.ietf.org/doc/html/rfc6960#section-4.2.1 +#[derive(Clone, Debug, Eq, PartialEq, Sequence)] +#[allow(missing_docs)] +pub struct OcspResponse { + pub response_status: OcspResponseStatus, + + #[asn1(context_specific = "0", optional = "true", tag_mode = "EXPLICIT")] + pub response_bytes: Option, +} + +impl OcspResponse { + /// Encodes an `OcspResponse` with the status set to `Successful` + pub fn successful(basic: BasicOcspResponse) -> Result { + Ok(OcspResponse { + response_status: OcspResponseStatus::Successful, + response_bytes: Some(ResponseBytes { + response_type: ID_PKIX_OCSP_BASIC, + response: OctetString::new(basic.to_der()?)?, + }), + }) + } + + /// Encodes an `OcspResponse` with the status set to `MalformedRequest` + pub fn malformed_request() -> Self { + OcspResponse { + response_status: OcspResponseStatus::MalformedRequest, + response_bytes: None, + } + } + + /// Encodes an `OcspResponse` with the status set to `InternalError` + pub fn internal_error() -> Self { + OcspResponse { + response_status: OcspResponseStatus::InternalError, + response_bytes: None, + } + } + + /// Encodes an `OcspResponse` with the status set to `TryLater` + pub fn try_later() -> Self { + OcspResponse { + response_status: OcspResponseStatus::TryLater, + response_bytes: None, + } + } + + /// Encodes an `OcspResponse` with the status set to `SigRequired` + pub fn sig_required() -> Self { + OcspResponse { + response_status: OcspResponseStatus::SigRequired, + response_bytes: None, + } + } + + /// Encodes an `OcspResponse` with the status set to `Unauthorized` + pub fn unauthorized() -> Self { + OcspResponse { + response_status: OcspResponseStatus::Unauthorized, + response_bytes: None, + } + } +} + +/// OCSPResponseStatus structure as defined in [RFC 6960 Section 4.2.1]. +/// +/// ```text +/// OCSPResponseStatus ::= ENUMERATED { +/// successful (0), -- Response has valid confirmations +/// malformedRequest (1), -- Illegal confirmation request +/// internalError (2), -- Internal error in issuer +/// tryLater (3), -- Try again later +/// -- (4) is not used +/// sigRequired (5), -- Must sign the request +/// unauthorized (6) -- Request unauthorized +/// } +/// ``` +/// +/// [RFC 6960 Section 4.2.1]: https://datatracker.ietf.org/doc/html/rfc6960#section-4.2.1 +#[derive(Enumerated, Copy, Clone, Debug, Eq, PartialEq)] +#[repr(u32)] +#[allow(missing_docs)] +pub enum OcspResponseStatus { + Successful = 0, + MalformedRequest = 1, + InternalError = 2, + TryLater = 3, + SigRequired = 5, + Unauthorized = 6, +} + +/// ResponseBytes structure as defined in [RFC 6960 Section 4.2.1]. +/// +/// ```text +/// ResponseBytes ::= SEQUENCE { +/// responseType OBJECT IDENTIFIER, +/// response OCTET STRING } +/// ``` +/// +/// [RFC 6960 Section 4.2.1]: https://datatracker.ietf.org/doc/html/rfc6960#section-4.2.1 +#[derive(Clone, Debug, Eq, PartialEq, Sequence)] +#[allow(missing_docs)] +pub struct ResponseBytes { + pub response_type: ObjectIdentifier, + pub response: OctetString, +}