From 09befeaeb35aed693f9e425d35dad055adb4f751 Mon Sep 17 00:00:00 2001 From: Joe Heck Date: Mon, 27 Apr 2026 12:47:40 -0700 Subject: [PATCH 1/8] Fix stale DocC disambiguation hash for PrivateKey init The P256 PrivateKey initializer hash changed from 6xkmz to 2we15. --- Sources/X509/Docs.docc/Creating Certificates.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/X509/Docs.docc/Creating Certificates.md b/Sources/X509/Docs.docc/Creating Certificates.md index 6e7d5369..fba85f52 100644 --- a/Sources/X509/Docs.docc/Creating Certificates.md +++ b/Sources/X509/Docs.docc/Creating Certificates.md @@ -114,7 +114,7 @@ and the signature algorithm would be constrained to what that key is capable of. match the private key that the subject entity has attested to possessing. We can use the keys from `swift-crypto` for this operation. We'll select `P256.Signing.PrivateKey` as our private key, which -we can wrap up in ``Certificate/PrivateKey/init(_:)-6xkmz`` to get `issuerPrivateKey`. We can then derive `publicKey` via +we can wrap up in ``Certificate/PrivateKey/init(_:)-2we15`` to get `issuerPrivateKey`. We can then derive `publicKey` via ``Certificate/PrivateKey/publicKey``. Finally, we'll pick the only signature algorithm compatible with that key, which is ``Certificate/SignatureAlgorithm-swift.struct/ecdsaWithSHA256``. From b2f9f4fa9ca5fc13a18e203f4d3255b3ebf1f23a Mon Sep 17 00:00:00 2001 From: Joe Heck Date: Mon, 27 Apr 2026 13:27:02 -0700 Subject: [PATCH 2/8] Fix documentation errors, typos, and a bug in CertificateStore.appending - Fix copy-paste errors in doc comments (NameConstraints, Error.swift, CSRAttribute, ExtendedKeyUsage) - Fix incorrect type names in code examples (SubjectAlternativeNames) - Add missing `try` keyword in code example - Make KeyUsage arguments consistent between snippet and full example - Fix bug where CertificateStore.appending(_:) returned self instead of copy - Fix typos: satifies, currency subnet, the the, PCKS#9 - Fix British spellings: realise, behaviour, fulfil, canonicalisation, organised, modelled - Fix grammar: subject-verb agreement, missing articles, broken sentences, triple backticks, run-on sentences - Fix "the subject is being issued" -> "the certificate is being issued" --- Sources/X509/CSR/CSRAttribute.swift | 2 +- Sources/X509/Certificate.swift | 6 +++--- Sources/X509/CertificateSerialNumber.swift | 2 +- Sources/X509/Docs.docc/Creating Certificates.md | 6 +++--- Sources/X509/Docs.docc/Examining Certificates.md | 16 ++++++++-------- Sources/X509/Error.swift | 6 +++--- .../X509/Extension Types/ExtendedKeyUsage.swift | 4 ++-- .../X509/Extension Types/NameConstraints.swift | 8 ++++---- Sources/X509/Extensions.swift | 8 ++++---- Sources/X509/OCSP/OCSPPolicy.swift | 4 ++-- Sources/X509/RDNAttribute.swift | 6 +++--- Sources/X509/RelativeDistinguishedName.swift | 2 +- Sources/X509/Signature.swift | 2 +- Sources/X509/Verifier/CertificateStore.swift | 4 ++-- .../X509/Verifier/CustomCertificateStore.swift | 2 +- 15 files changed, 39 insertions(+), 39 deletions(-) diff --git a/Sources/X509/CSR/CSRAttribute.swift b/Sources/X509/CSR/CSRAttribute.swift index aeeec4a8..5d567046 100644 --- a/Sources/X509/CSR/CSRAttribute.swift +++ b/Sources/X509/CSR/CSRAttribute.swift @@ -27,7 +27,7 @@ extension CertificateSigningRequest { public struct Attribute { /// The identifier for this attribute type. /// - /// Common values are stored in `ASN1ObjectIdentifier.X509ExtensionID`. + /// Common values are stored in `ASN1ObjectIdentifier.CSRAttributes`. public var oid: ASN1ObjectIdentifier /// The encoded bytes of the values of this attribute. diff --git a/Sources/X509/Certificate.swift b/Sources/X509/Certificate.swift index b5dad20f..976a727f 100644 --- a/Sources/X509/Certificate.swift +++ b/Sources/X509/Certificate.swift @@ -60,7 +60,7 @@ import SwiftASN1 /// Both of these goals encourage this type to be immutable. A ``Certificate`` represents /// a specific assertion of identity. Its ``Certificate/signature-swift.property`` is signed /// across the rest of the data. Allowing users to change this data makes it easy to accidentally modify -/// a ``Certificate`` in one part of your code and not realise that the signature has inevitably +/// a ``Certificate`` in one part of your code and not realize that the signature has inevitably /// been invalidated. #if canImport(Security) /// @@ -146,14 +146,14 @@ public struct Certificate { /// The bytes of the ``Signature``. /// /// These are preserved to ensure that we reserialize exactly what we deserialized, regardless - /// of any canonicalisation we might do. + /// of any canonicalization we might do. @usableFromInline internal let signatureBytes: ArraySlice /// The bytes of the ``signatureAlgorithm-swift.property``. /// /// These are preserved to ensure that we reserialize exactly what we deserialized, regardless of - /// any canonicalisation we might do. + /// any canonicalization we might do. @usableFromInline internal let signatureAlgorithmBytes: ArraySlice diff --git a/Sources/X509/CertificateSerialNumber.swift b/Sources/X509/CertificateSerialNumber.swift index dbd405f7..5f455478 100644 --- a/Sources/X509/CertificateSerialNumber.swift +++ b/Sources/X509/CertificateSerialNumber.swift @@ -52,7 +52,7 @@ extension Certificate { /// /// In general this API should only be used for testing, as fixed width integers /// are not sufficiently large for use in certificates. Using this API for production - /// use-cases may expose users to hash collision attacks on generated certificates. + /// use cases may expose users to hash collision attacks on generated certificates. /// /// Prefer using ``Certificate/SerialNumber-swift.struct/init(integerLiteral:)`` /// with a `StaticBigInt` which enables arbitrary-precision. diff --git a/Sources/X509/Docs.docc/Creating Certificates.md b/Sources/X509/Docs.docc/Creating Certificates.md index fba85f52..2c54de05 100644 --- a/Sources/X509/Docs.docc/Creating Certificates.md +++ b/Sources/X509/Docs.docc/Creating Certificates.md @@ -65,14 +65,14 @@ but for our use-case it's sufficient to know that we don't need to set anything all modern use-cases the common name is nothing more than an identifier, so we can set it to whatever we like. ```swift -let subjectName = DistinguishedName { +let subjectName = try DistinguishedName { CommonName("My awesome subject") } ``` ### Issuer Name -Just as the subject name identifies the entity to whom the subject is being issued, the issuer name identifies the entity that +Just as the subject name identifies the entity to whom the certificate is being issued, the issuer name identifies the entity that issued the certificate. In particular, the issuer name should be exactly equivalent to the subject name in the issuing entity's certificate. @@ -97,7 +97,7 @@ let extensions = try Certificate.Extensions { BasicConstraints.isCertificateAuthority(maxPathLength: nil) ) Critical( - KeyUsage(digitalSignature: true, keyCertSign: true) + KeyUsage(keyCertSign: true) ) SubjectAlternativeNames([.dnsName("localhost")]) } diff --git a/Sources/X509/Docs.docc/Examining Certificates.md b/Sources/X509/Docs.docc/Examining Certificates.md index 16fe74e1..98220917 100644 --- a/Sources/X509/Docs.docc/Examining Certificates.md +++ b/Sources/X509/Docs.docc/Examining Certificates.md @@ -30,7 +30,7 @@ FGgfAiEA1PMpcTTeK8Hi17N0oaNNIWERLqDketPZG5E3OdEDw9E= ``` This format is the DER representation of a certificate, base64-encoded, line wrapped at 64 -bytes, and bracketed by the `BEGIN X`/`END X` delimiter. As PEMs can easily be translated +bytes, and bracketed by the `BEGIN X`/`END X` delimiters. As PEMs can easily be translated to DER bytes and vice-versa, the rest of this document will focus on the DER format. To parse a certificate from DER, we can construct it with a helper initializer from `SwiftASN1`: @@ -65,14 +65,14 @@ distinguished name containing a ``CommonName`` ``RelativeDistinguishedName/Attri A string representation of a ``DistinguishedName`` can be obtained by using `String(describing:)`. This uses the common [RFC4514 format](https://www.rfc-editor.org/rfc/rfc4514) for textual ``DistinguishedName``s. -Generally speaking the specific content of the names in these fields are unimportant to end users, though they do +Generally speaking the specific content of the names in these fields is unimportant to end users, though they do play a vital role in validating certificates. ### Keys Certificates are bound to a specific private key, which can be used to authenticate that a specific entity is the one to whom the certificate was issued. This binding is achieved by embedding the public key corresponding -to the underlying private key into the certificate object itself. This is modelled in the ``Certificate/publicKey-swift.property`` +to the underlying private key into the certificate object itself. This is modeled in the ``Certificate/publicKey-swift.property`` field. In ``X509`` the ``Certificate/PublicKey-swift.struct`` type is opaque, but they can be compared for equality. @@ -88,8 +88,8 @@ is a full-fidelity representation of all extensions found in the certificate. Th as ``Certificate/Extension`` objects, an opaque raw-bytes representation of the extension. Implementations are generally not required to understand all extensions in a certificate, as many are -strictly informational. However, some extensions must be understood in order for the certificate to safely -fulfil their function. As a result, users of a certificate should always search for extensions that have +strictly informational. However, some extensions must be understood for the certificate to safely +fulfill their function. As a result, users of a certificate should always search for extensions that have the ``Certificate/Extension/critical`` bit set to `true` and, if they do not understand those extensions, should refuse to trust the certificate. @@ -114,7 +114,7 @@ for the ``SubjectAlternativeNames``, the typical code would be: ```swift let opaqueSanExtension = certificate.extensions.first(where: { $0.oid == .X509ExtensionID.subjectAlternativeName }) if let opaqueSanExtension { - let unwrappedSanExtension = try SubjectAlternativeName(opaqueSanExtension) + let unwrappedSanExtension = try SubjectAlternativeNames(opaqueSanExtension) } ``` @@ -123,7 +123,7 @@ to search for a specific extension. The above code could be replaced by: ```swift if let opaqueSanExtension = certificate.extensions[oid: .X509ExtensionID.subjectAlternativeName] { - let unwrappedSanExtension = try SubjectAlternativeName(opaqueSanExtension) + let unwrappedSanExtension = try SubjectAlternativeNames(opaqueSanExtension) } ``` @@ -142,7 +142,7 @@ that can be used to get a specific typed extension: This lets us reduce the above code to a single line: ```swift -let unwrappedSanExtension = try certificate.extensions.subjectAlternativeName +let unwrappedSanExtension = try certificate.extensions.subjectAlternativeNames ``` This shorthand is most useful when searching for a specific extension. When attempting to work with an entire certificate, diff --git a/Sources/X509/Error.swift b/Sources/X509/Error.swift index f4c09806..5d096d03 100644 --- a/Sources/X509/Error.swift +++ b/Sources/X509/Error.swift @@ -79,8 +79,8 @@ public struct CertificateError: Error, Hashable, CustomStringConvertible { ) } - /// The private key algorithm used in a ``Certificate`` is not supported by this library. - /// - Parameter reason: A detailed reason explaining what private key algorithm was not supported. + /// The public key algorithm used in a ``Certificate`` is not supported by this library. + /// - Parameter reason: A detailed reason explaining what public key algorithm was not supported. /// - Parameter file: The file where the error occurs. /// - Parameter line: The line where the error occurs. /// - Returns: A ``CertificateError`` with ``code`` set to ``ErrorCode/unsupportedPublicKeyAlgorithm``. @@ -163,7 +163,7 @@ public struct CertificateError: Error, Hashable, CustomStringConvertible { ) } - /// A digest private key isn't supported + /// The private key isn't supported by this library. /// - Parameter reason: A detailed reason indicating the unsupported private key. /// - Parameter file: The file where the error occurs. /// - Parameter line: The line where the error occurs. diff --git a/Sources/X509/Extension Types/ExtendedKeyUsage.swift b/Sources/X509/Extension Types/ExtendedKeyUsage.swift index cc36fcdd..a156b6c9 100644 --- a/Sources/X509/Extension Types/ExtendedKeyUsage.swift +++ b/Sources/X509/Extension Types/ExtendedKeyUsage.swift @@ -15,7 +15,7 @@ import SwiftASN1 /// Indicates one or more purposes for which the certified public key -/// may be used, in addition to or instead of the the purposes indicated +/// may be used, in addition to or instead of the purposes indicated /// in the ``KeyUsage`` extension. public struct ExtendedKeyUsage { @usableFromInline @@ -155,7 +155,7 @@ extension ExtendedKeyUsage { /// /// - Returns: A pair `(inserted, index)`, where `inserted` is a Boolean value /// indicating whether the operation added a new element, and `index` is - /// the index of `item` in the resulting set. If `inserted` is false, then + /// the index of `usage` in the resulting set. If `inserted` is false, then /// the returned `index` may be different from the index requested. @inlinable @discardableResult diff --git a/Sources/X509/Extension Types/NameConstraints.swift b/Sources/X509/Extension Types/NameConstraints.swift index 7b616ead..91e2d16a 100644 --- a/Sources/X509/Extension Types/NameConstraints.swift +++ b/Sources/X509/Extension Types/NameConstraints.swift @@ -433,7 +433,7 @@ public struct NameConstraints { /// The DNS name trees that are forbidden in certificates issued by this CA. /// /// These restrictions are expressed in forms like `host.example.com`. Any DNS name that can be - /// constructed by adding zero or more labels to the left-hand side of the name satifies the constraint. + /// constructed by adding zero or more labels to the left-hand side of the name satisfies the constraint. public internal(set) var excludedDNSDomains: DNSNames { get { DNSNames(subtrees: excludedSubtrees) @@ -452,7 +452,7 @@ public struct NameConstraints { /// The IP ranges that are permitted in certificates issued by this CA. /// /// These restrictions are expressed as a subnet, represented in an ASN.1 octet string. - /// Due to the absence of a currency subnet and IP address type in Swift, these are preserved + /// Due to the absence of a standard subnet and IP address type in Swift, these are preserved /// as octet strings. /// /// As an example, the subnet 192.0.2.0/24 is encoded as the bytes `0xC0, 0x00, 0x02, 0x00, 0xFF, 0xFF, 0xFF, 0x00`. @@ -477,7 +477,7 @@ public struct NameConstraints { /// The IP ranges that are forbidden in certificates issued by this CA. /// /// These restrictions are expressed as a subnet, represented in an ASN.1 octet string. - /// Due to the absence of a currency subnet and IP address type in Swift, these are preserved + /// Due to the absence of a standard subnet and IP address type in Swift, these are preserved /// as octet strings. /// /// As an example, the subnet 192.0.2.0/24 is encoded as the bytes `0xC0, 0x00, 0x02, 0x00, 0xFF, 0xFF, 0xFF, 0x00`. @@ -519,7 +519,7 @@ public struct NameConstraints { } } - /// The email addresses that are permitted in certificates issued by this CA. + /// The email addresses that are excluded in certificates issued by this CA. /// /// This form may contain a specific mailbox (e.g. `user@example.com`), all /// addresses on a given host (e.g. `example.com`), or all mailboxes within a diff --git a/Sources/X509/Extensions.swift b/Sources/X509/Extensions.swift index 079011f3..879840e5 100644 --- a/Sources/X509/Extensions.swift +++ b/Sources/X509/Extensions.swift @@ -197,10 +197,10 @@ extension Certificate.Extensions { } /// Updates the ``Certificate/Extension`` stored in the dictionary for the ``Certificate/Extension/oid`` of the `extension`, - /// or appends `extension` if an ``Certificate/Extension`` with same ``Certificate/Extension/oid`` does not exist. + /// or appends `extension` if a ``Certificate/Extension`` with the same ``Certificate/Extension/oid`` does not exist. /// /// - Parameter extension: The ``Certificate/Extension`` to update or append. - /// - Returns: The old ``Certificate/Extension`` that was replaced or `nil` if no ``Certificate/Extension`` with same ``Certificate/Extension/oid`` was present + /// - Returns: The old ``Certificate/Extension`` that was replaced, or `nil` if no ``Certificate/Extension`` with the same ``Certificate/Extension/oid`` was present. @inlinable @discardableResult public mutating func update(_ extension: Certificate.Extension) -> Certificate.Extension? { @@ -214,9 +214,9 @@ extension Certificate.Extensions { } /// Removes the ``Certificate/Extension`` with the given `oid`. - /// - Parameter oid: The ``Certificate/Extension/oid`` of the``Certificate/Extension`` to remove. + /// - Parameter oid: The ``Certificate/Extension/oid`` of the ``Certificate/Extension`` to remove. /// - Returns: The ``Certificate/Extension`` that was removed, - /// or `nil` if an ``Certificate/Extension`` was not present in with the given `oid`. + /// or `nil` if a ``Certificate/Extension`` was not present with the given `oid`. @inlinable @discardableResult public mutating func remove(_ oid: ASN1ObjectIdentifier) -> Certificate.Extension? { diff --git a/Sources/X509/OCSP/OCSPPolicy.swift b/Sources/X509/OCSP/OCSPPolicy.swift index 0b02bbd7..240e1458 100644 --- a/Sources/X509/OCSP/OCSPPolicy.swift +++ b/Sources/X509/OCSP/OCSPPolicy.swift @@ -32,7 +32,7 @@ public protocol OCSPRequester: Sendable { /// - Parameters: /// - request: DER-encoded request bytes /// - uri: uri of the OCSP responder - /// - Returns: DER-encoded response bytes if they request was successful or a terminal or non-terminal error. + /// - Returns: DER-encoded response bytes if the request was successful, or a terminal or non-terminal error. func query(request: [UInt8], uri: String) async -> OCSPRequesterQueryResult } @@ -164,7 +164,7 @@ enum OCSPRequestHashAlgorithm { } } -/// Defines the behaviour of ``OCSPVerifierPolicy`` in the event of a failure. +/// Defines the behavior of ``OCSPVerifierPolicy`` in the event of a failure. /// ``soft`` should be used most of the time and will only fail verification if a verified OCSP response reports a status of revoked. public struct OCSPFailureMode: Hashable, Sendable { /// ``soft`` failure mode will only fail verification if a verified and valid OCSP response reports a status of revoked. diff --git a/Sources/X509/RDNAttribute.swift b/Sources/X509/RDNAttribute.swift index f70748a1..985ef4ab 100644 --- a/Sources/X509/RDNAttribute.swift +++ b/Sources/X509/RDNAttribute.swift @@ -348,11 +348,11 @@ extension ASN1ObjectIdentifier { /// avenue, and the house number). public static let streetAddress: ASN1ObjectIdentifier = [2, 5, 4, 9] - /// The `domainComponent` attribute type contains parts (labels) of a DNS domain name + /// The `domainComponent` attribute type contains parts (labels) of a DNS domain name. public static let domainComponent: ASN1ObjectIdentifier = [0, 9, 2342, 19_200_300, 100, 1, 25] - /// The `emailAddress` attribute type contains email address defined in PCKS#9 (RFC2985). - /// Be aware that, modern best practices (e.g., RFC 5280) discourage embedding email addresses in the `Subject DN` instead it should be in `Subject Alternative Name (SAN) + /// The `emailAddress` attribute type contains an email address defined in PKCS#9 (RFC 2985). + /// Modern best practices (e.g., RFC 5280) discourage embedding email addresses in the `Subject DN`; use the `Subject Alternative Name` (SAN) instead. public static let emailAddress: ASN1ObjectIdentifier = [1, 2, 840, 113549, 1, 9, 1] } } diff --git a/Sources/X509/RelativeDistinguishedName.swift b/Sources/X509/RelativeDistinguishedName.swift index 1242e237..2bc7aff8 100644 --- a/Sources/X509/RelativeDistinguishedName.swift +++ b/Sources/X509/RelativeDistinguishedName.swift @@ -26,7 +26,7 @@ import _CertificateInternals /// contains one or more ``RelativeDistinguishedName/Attribute`` which are considered equivalent: that is, they /// are each a representation of the same piece of information. /// -/// ``RelativeDistinguishedName``s are organised into a hierarchy in a ``DistinguishedName``, in order from least +/// ``RelativeDistinguishedName``s are organized into a hierarchy in a ``DistinguishedName``, in order from least /// to most specific. In almost all current use-cases a ``RelativeDistinguishedName`` will contain only a single /// ``Attribute``. /// diff --git a/Sources/X509/Signature.swift b/Sources/X509/Signature.swift index 1a378d58..b5b5064f 100644 --- a/Sources/X509/Signature.swift +++ b/Sources/X509/Signature.swift @@ -35,7 +35,7 @@ extension Certificate { /// ``Certificate/PublicKey-swift.struct/isValidSignature(_:for:)-3cbor``, and it /// can be generated by ``Certificate/PrivateKey``s automatically when /// used by ``Certificate/init(version:serialNumber:publicKey:notValidBefore:notValidAfter:issuer:subject:signatureAlgorithm:extensions:issuerPrivateKey:)``. - /// Otherwise, this type has essentially no behaviours. + /// Otherwise, this type has essentially no behaviors. public struct Signature { @usableFromInline var backing: BackingSignature diff --git a/Sources/X509/Verifier/CertificateStore.swift b/Sources/X509/Verifier/CertificateStore.swift index 1f3abe81..e21fc97c 100644 --- a/Sources/X509/Verifier/CertificateStore.swift +++ b/Sources/X509/Verifier/CertificateStore.swift @@ -27,7 +27,7 @@ public struct CertificateStore: Sendable, Hashable { } /// Wrap a ``CustomCertificateStore`` in a ``CertificateStore`` so the custom - /// implementation it can be used interchangeably. For details on why one + /// implementation can be used interchangeably. For details on why one /// may decide to implement a ``CustomCertificateStore``, please see the /// documentation on that protocol. @inlinable @@ -66,7 +66,7 @@ public struct CertificateStore: Sendable, Hashable { public func appending(_ certificate: Certificate) -> Self { var copy = self copy.append(certificate) - return self + return copy } func resolve(diagnosticsCallback: ((VerificationDiagnostic) -> Void)?) async -> Resolved { diff --git a/Sources/X509/Verifier/CustomCertificateStore.swift b/Sources/X509/Verifier/CustomCertificateStore.swift index df711da4..830e8cd0 100644 --- a/Sources/X509/Verifier/CustomCertificateStore.swift +++ b/Sources/X509/Verifier/CustomCertificateStore.swift @@ -16,7 +16,7 @@ /// certificate lookup, or if you need custom logic when matching the /// ``DistinguishedName`` of an Issuer with the Subject of the issuer /// certificate, then implement a custom certificate store used by the -/// ```Verifier```. +/// ``Verifier``. @available(macOS 10.15, iOS 13, watchOS 6, tvOS 13, macCatalyst 13, visionOS 1.0, *) public protocol CustomCertificateStore: Sendable, Hashable { /// Obtain a list of certificates which has a given subject. Note that this From 9b56eb99f846750a535e036ab1dd9d0daeb106f8 Mon Sep 17 00:00:00 2001 From: Joe Heck Date: Mon, 27 Apr 2026 14:03:33 -0700 Subject: [PATCH 3/8] code example fixes within the existing articles: - SubjectAlternativeName (singular) corrected to SubjectAlternativeNames (plural) - Missing try on DistinguishedName builder call - Made KeyUsage arguments consistent - Article didn't provide any detail avout CSR, but mentioned it - so now links to CertificateSigningRequest - Added content that explains the derEncodedPrivateKey variable --- .../X509/Docs.docc/Creating Certificates.md | 98 ++++++++++--------- .../X509/Docs.docc/Examining Certificates.md | 44 ++++----- 2 files changed, 72 insertions(+), 70 deletions(-) diff --git a/Sources/X509/Docs.docc/Creating Certificates.md b/Sources/X509/Docs.docc/Creating Certificates.md index 2c54de05..29f52d2b 100644 --- a/Sources/X509/Docs.docc/Creating Certificates.md +++ b/Sources/X509/Docs.docc/Creating Certificates.md @@ -1,25 +1,24 @@ # Creating Certificates -Users often have a need to create certificates. Whether for testing or some other interaction with an -existing system, creating a certificate programmatically is a powerful tool for users. +Create certificates programmatically for testing or other interactions with existing systems. -``X509`` provides a number of conveniences for making this easy. There are two common ways to create -a certificate: directly, or from a CSR. +``X509`` provides a number of conveniences for making this easy. The most common way to create +a certificate is directly, though you can also create one from a ``CertificateSigningRequest``. -## Creating Certificates Directly +## Create certificates directly -There are many kinds of certificates that users may want to create, but the most common is to want -to create a self-signed certificate. These are valuable in testing scenarios, as they can serve -both as a trusted root and as an end-entity certificate. +Many kinds of certificates are useful to create, but the most common is a self-signed certificate. +These are valuable in testing scenarios, as they can serve both as a trusted root and as an +end-entity certificate. Fortunately, creating a self-signed certificate is very similar to creating an intermediate or leaf -certificate. This document will call out the steps that might need to be different. +certificate. This document calls out the steps that might need to be different. -### Gathering our requirements +### Gather your requirements -To create a certificate directly we're going to call the +To create a certificate directly, call the ``Certificate/init(version:serialNumber:publicKey:notValidBefore:notValidAfter:issuer:subject:signatureAlgorithm:extensions:issuerPrivateKey:)`` -initializer. This requires that we put together the following information: +initializer. This requires the following information: - Version - Serial number @@ -31,12 +30,12 @@ initializer. This requires that we put together the following information: - Public Key - Issuer private key -### Version +### Choose a version -Working out the version to use is easy. When creating our own certificates, we should always create a +Working out the version to use is easy. When creating your own certificates, always create a ``Certificate/Version-swift.struct/v3`` certificate. There is no reason to use any version older than this. -### Serial Number +### Set a serial number The certificate serial number forms part of the identifier of the certificate, along with the issuer name. For a given CA, each certificate it issues must have a unique serial number. Additionally, the @@ -45,24 +44,24 @@ need to conform to this profile the serial number must contain at least 64-bits ``Certificate/SerialNumber-swift.struct`` has a helper initializer that will create a 20-byte serial number consisting entirely of randomness: ``Certificate/SerialNumber-swift.struct/init()``. That's a good default choice for -this kind of use-case. +this kind of use case. -### Validity Period +### Define a validity period -The validity period of a certificate is made up of two dates: "not valid before" and "not valid after". The "not valid before" date +The validity period of a certificate consists of two dates: "not valid before" and "not valid after". The "not valid before" date refers to the point in time before which a certificate must not be trusted, and is typically set to the date and time at which the certificate was signed by the issuer. The "not valid after" date refers to the point in time after which the certificate has expired and should not be trusted. -Together these two dates define a validity period. We'll set "not valid before" to the current time (`Date()`), and we'll set the +Together these two dates define a validity period. Set "not valid before" to the current time (`Date()`), and set the certificate to be valid for a year, making the "not valid after" date `now.addingTimeInterval(60 * 60 * 24 * 365)`. -### Subject Name +### Set a subject name The subject name identifies the entity to whom the certificate is being issued. This is a hierarchical name type called a ``DistinguishedName``. The full complexity of distinguished names is tackled in the ``DistinguishedName`` API documentation, -but for our use-case it's sufficient to know that we don't need to set anything other than the common name. Additionally, in -all modern use-cases the common name is nothing more than an identifier, so we can set it to whatever we like. +but for this use case it's sufficient to know that you don't need to set anything other than the common name. Additionally, in +all modern use cases the common name is nothing more than an identifier, so you can set it to whatever you like. ```swift let subjectName = try DistinguishedName { @@ -70,26 +69,26 @@ let subjectName = try DistinguishedName { } ``` -### Issuer Name +### Set an issuer name Just as the subject name identifies the entity to whom the certificate is being issued, the issuer name identifies the entity that issued the certificate. In particular, the issuer name should be exactly equivalent to the subject name in the issuing entity's certificate. -If we were creating a non-self-signed certificate, we'd set `issuerName` equal to ``Certificate/subject`` from the parent -certificate. For self-signed certificates, the issuer and the subject are identical, so we can set `issuerName = subjectName`. +When creating a non-self-signed certificate, set `issuerName` equal to ``Certificate/subject`` from the parent +certificate. For self-signed certificates, the issuer and the subject are identical, so you can set `issuerName = subjectName`. -### Extensions +### Add extensions -The bulk of the semantic information in a certificate is contained in its extensions. For our case, we care about only a small -few. +The bulk of the semantic information in a certificate is contained in its extensions. For this case, only a small +few matter. -We need ``BasicConstraints`` to be present, and set to -`isCertificateAuthority`. We also need ``KeyUsage`` with the appropriate bits -set. Finally, we want to set ``SubjectAlternativeNames`` to include the domain -name we're going to be self-signing for, which in this case we'll set to `localhost`. +You need ``BasicConstraints`` to be present, and set to +`isCertificateAuthority`. You also need ``KeyUsage`` with the appropriate bits +set. Finally, set ``SubjectAlternativeNames`` to include the domain +name you're going to be self-signing for, which in this case is `localhost`. -We can use the helpful builder syntax for this: +You can use the helpful builder syntax for this: ```swift let extensions = try Certificate.Extensions { @@ -103,27 +102,27 @@ let extensions = try Certificate.Extensions { } ``` -### Cryptographic Material +### Provide cryptographic material -In our case, the public key, signature algorithm, and issuer private key are intimately bound together. For self-signed certs, the +In this case, the public key, signature algorithm, and issuer private key are intimately bound together. For self-signed certs, the public key is the public key that belongs to the issuer private key. Relatedly, the signature algorithm is constrained to only those -that are supported by our private key, as that'll be the one doing the signing. +supported by the private key, as that key does the signing. -If we weren't creating a self-signed certificate, the issuer private key would be the private key for the issuing certificate, +When creating a non-self-signed certificate, the issuer private key would be the private key for the issuing certificate, and the signature algorithm would be constrained to what that key is capable of. The public key could be anything, but it needs to match the private key that the subject entity has attested to possessing. -We can use the keys from `swift-crypto` for this operation. We'll select `P256.Signing.PrivateKey` as our private key, which -we can wrap up in ``Certificate/PrivateKey/init(_:)-2we15`` to get `issuerPrivateKey`. We can then derive `publicKey` via -``Certificate/PrivateKey/publicKey``. Finally, we'll pick the only signature algorithm compatible with that key, which is +You can use the keys from `swift-crypto` for this operation. Select `P256.Signing.PrivateKey` as the private key, which +you can wrap in ``Certificate/PrivateKey/init(_:)-2we15`` to get `issuerPrivateKey`. Then derive `publicKey` via +``Certificate/PrivateKey/publicKey``. Finally, pick the only signature algorithm compatible with that key, which is ``Certificate/SignatureAlgorithm-swift.struct/ecdsaWithSHA256``. -### Serializing +### Serialize the certificate -Once the certificate is created, we'll want to write it out to a file so we can use it! We can easily output this in DER format, +Once the certificate is created, you'll want to write it out to a file so you can use it. You can easily output this in DER format, which is commonly indicated using a `.crt` file extension. -We get the DER representation by using code from `SwiftASN1`. ``Certificate`` conforms to `DERSerializable`, so we can serialize +Get the DER representation by using code from `SwiftASN1`. ``Certificate`` conforms to `DERSerializable`, so you can serialize it like this: ```swift @@ -131,9 +130,12 @@ var serializer = DER.Serializer() try serializer.serialize(certificate) ``` -### Putting it all together +You may also want to save the private key for later use. You can obtain its DER representation from the +original `swift-crypto` key via its `derRepresentation` property. -That leaves us with the following code for generating a self-signed certificate. +### Put it all together + +Here is the complete code for generating a self-signed certificate. ```swift import Crypto @@ -179,7 +181,7 @@ let derEncodedCertificate = serializer.serializedBytes let derEncodedPrivateKey = swiftCryptoKey.derRepresentation ``` -### Creating Certificates from SecCertificate and vice versa +### Convert between Certificate and SecCertificate -An instance of ``Certificate`` can be created from `Security/SecCertificate` (from the `Security` framework) with `Certificate/init(_:)`. -The opposite, that is, creating an instance of `Security/SecCertificate` from `Certificate`, can be achieved with `Security/SecCertificate/makeWithCertificate(_:)`. +Create an instance of ``Certificate`` from `Security/SecCertificate` (from the `Security` framework) with ``Certificate/init(_:)``. +To create an instance of `Security/SecCertificate` from ``Certificate``, use ``Security/SecCertificate/makeWithCertificate(_:)``. diff --git a/Sources/X509/Docs.docc/Examining Certificates.md b/Sources/X509/Docs.docc/Examining Certificates.md index 98220917..33f8c9e4 100644 --- a/Sources/X509/Docs.docc/Examining Certificates.md +++ b/Sources/X509/Docs.docc/Examining Certificates.md @@ -2,15 +2,15 @@ Decoding and introspecting certificates. -A common use-case is to have received an X.509 certificate from some source, and to want -to inspect it and make decisions based on its content. X.509 certificates are complex -objects with highly dynamic and nested content, so this article attempts to outline -how to work with ``Certificate`` in order to get the most out of the content of an +A common use case is to receive an X.509 certificate from some source and +inspect it to make decisions based on its content. X.509 certificates are complex +objects with highly dynamic and nested content, so this article outlines +how to work with ``Certificate`` to get the most out of the content of an X.509 certificate. -## Parsing +## Parse certificates -Certificates are commonly provided in two formats: PEM and DER. The two formats are closely +Two formats are common for certificates: PEM and DER. The two formats are closely related. PEM stands for "Privacy Enhanced Mail", and a PEM certificate commonly looks like this: @@ -33,17 +33,17 @@ This format is the DER representation of a certificate, base64-encoded, line wra bytes, and bracketed by the `BEGIN X`/`END X` delimiters. As PEMs can easily be translated to DER bytes and vice-versa, the rest of this document will focus on the DER format. -To parse a certificate from DER, we can construct it with a helper initializer from `SwiftASN1`: +To parse a certificate from DER, construct it with a helper initializer from `SwiftASN1`: ```swift let derBytes: [UInt8] = // get these from somewhere let certificate = try Certificate(derEncoded: derBytes) ``` -Assuming the certificate is well-formed, this constructor will build us a ``Certificate`` object -that we can work with. +Assuming the certificate is well-formed, this constructor builds a ``Certificate`` object +that you can work with. -## Introspecting Certificates +## Inspect certificate fields Certificates have a wide range of fields. Many of them are not that interesting (for example, ``Certificate/version-swift.property``). However, a number of them make useful attestations about the @@ -57,8 +57,8 @@ Certificates have at least two names attached to them: ``Certificate/issuer`` an these names are represented using ``DistinguishedName``. ``DistinguishedName`` is a fairly complex type that is described in more detail in its API documentation. -In short, it's a hierarchical collection of ``RelativeDistinguishedName``s, each of which is made up -of a set of equivalent ``RelativeDistinguishedName/Attribute``s. These ``RelativeDistinguishedName``s +In short, it's a hierarchical collection of ``RelativeDistinguishedName`` values, each of which is made up +of a set of equivalent ``RelativeDistinguishedName/Attribute`` values. These ``RelativeDistinguishedName`` values are arranged in order from least specific to most specific, typically ending with a relative distinguished name containing a ``CommonName`` ``RelativeDistinguishedName/Attribute``. @@ -75,17 +75,17 @@ the one to whom the certificate was issued. This binding is achieved by embeddin to the underlying private key into the certificate object itself. This is modeled in the ``Certificate/publicKey-swift.property`` field. -In ``X509`` the ``Certificate/PublicKey-swift.struct`` type is opaque, but they can be compared for equality. +In ``X509`` the ``Certificate/PublicKey-swift.struct`` type is opaque, but values of this type can be compared for equality. ### Extensions -The majority of the semantic information in certificate objects is stored in their X509v3 extensions. +The majority of the semantic information in certificate objects is stored in their X.509v3 extensions. These are a typed key-value dictionary that contain additional information about both the subject of the certificate and about much of its content. ``X509`` stores the extension information in a ``Certificate/Extensions-swift.struct`` collection. This is a full-fidelity representation of all extensions found in the certificate. This stores the extensions -as ``Certificate/Extension`` objects, an opaque raw-bytes representation of the extension. +as ``Certificate/Extension`` values, an opaque raw-bytes representation of the extension. Implementations are generally not required to understand all extensions in a certificate, as many are strictly informational. However, some extensions must be understood for the certificate to safely @@ -109,7 +109,7 @@ Out of the box, ``X509`` ships support for the following extension types: - ``SubjectKeyIdentifier`` To decode an extension usually requires examining its ``Certificate/Extension/oid`` field. For example, to search -for the ``SubjectAlternativeNames``, the typical code would be: +for the ``SubjectAlternativeNames``, you would typically write: ```swift let opaqueSanExtension = certificate.extensions.first(where: { $0.oid == .X509ExtensionID.subjectAlternativeName }) @@ -118,8 +118,8 @@ if let opaqueSanExtension { } ``` -This is verbose and repetitive code, so users are encouraged to use the helper function ``Certificate/Extensions-swift.struct/subscript(oid:)`` -to search for a specific extension. The above code could be replaced by: +This is verbose and repetitive code, so consider using the helper function ``Certificate/Extensions-swift.struct/subscript(oid:)`` +to search for a specific extension. The above code becomes: ```swift if let opaqueSanExtension = certificate.extensions[oid: .X509ExtensionID.subjectAlternativeName] { @@ -139,14 +139,14 @@ that can be used to get a specific typed extension: - ``Certificate/Extensions-swift.struct/subjectAlternativeNames`` - ``Certificate/Extensions-swift.struct/subjectKeyIdentifier`` -This lets us reduce the above code to a single line: +This reduces the above code to a single line: ```swift let unwrappedSanExtension = try certificate.extensions.subjectAlternativeNames ``` -This shorthand is most useful when searching for a specific extension. When attempting to work with an entire certificate, -users are encouraged to iterate the ``Certificate/Extensions-swift.struct`` and unwrap each extension in turn. This ensures +This shorthand is most useful when searching for a specific extension. When working with an entire certificate, +iterate the ``Certificate/Extensions-swift.struct`` and unwrap each extension in turn. This ensures that unknown or invalid ``Certificate/Extension/critical`` extensions are not incorrectly tolerated. ### Signatures @@ -166,5 +166,5 @@ Together these objects make it possible to validate that a signature was correct ``Certificate/PublicKey-swift.struct/isValidSignature(_:for:)-3cbor``. > Warning: While this is a necessary condition for determining the issuer of a certificate, it is not a sufficient one. -> Users are strongly discouraged from hand-rolling their own verification logic, and should instead prefer using an +> Don't hand-roll your own verification logic. Instead, prefer using an > existing verifier. Wherever possible, prefer to use the platform verifier. From e6a38f8640d5f30b33f3bdc3c965c183bd8e8607 Mon Sep 17 00:00:00 2001 From: Joe Heck Date: Mon, 27 Apr 2026 14:06:00 -0700 Subject: [PATCH 4/8] Lots of smaller fixes across the board - some copy/pasta errors, consistency in usage, typos and grammar issues - added documentation comments where missing to get all public types covered - applied internal style guidelines for representing the types --- .../X509/CSR/CertificateSigningRequest.swift | 8 +++---- Sources/X509/CSR/ExtensionRequest.swift | 6 ++--- Sources/X509/CertificatePrivateKey.swift | 10 ++++----- Sources/X509/CertificatePublicKey.swift | 18 +++++++-------- .../CMSOperations.swift | 1 + Sources/X509/DistinguishedName.swift | 2 +- .../DistinguishedNameBuilder/DNBuilder.swift | 2 ++ Sources/X509/Error.swift | 2 +- .../AuthorityInformationAccess.swift | 2 +- .../ExtensionIdentifiers.swift | 2 +- .../Extension Types/NameConstraints.swift | 6 ++++- .../SubjectAlternativeName.swift | 2 +- .../SubjectKeyIdentifier.swift | 2 +- Sources/X509/Extension.swift | 2 +- Sources/X509/Extensions.swift | 2 +- Sources/X509/ExtensionsBuilder.swift | 8 +++---- Sources/X509/GeneralName.swift | 13 +++++++++++ Sources/X509/RelativeDistinguishedName.swift | 2 +- Sources/X509/Signature.swift | 9 ++++---- Sources/X509/SignatureAlgorithm.swift | 4 ++-- Sources/X509/Verifier/CertificateStore.swift | 2 +- .../Verifier/CustomCertificateStore.swift | 22 +++++++++---------- Sources/X509/Verifier/PolicyBuilder.swift | 4 ++-- .../X509/Verifier/RFC5280/RFC5280Policy.swift | 2 +- .../X509/Verifier/ServerIdentityPolicy.swift | 4 ++-- .../Verifier/ValidatedCertificateChain.swift | 9 ++++---- .../Verifier/VerificationDiagnostic.swift | 2 +- Sources/X509/Verifier/Verifier.swift | 21 ++++++++++++++++++ Sources/X509/Verifier/VerifierPolicy.swift | 10 ++++++--- 29 files changed, 112 insertions(+), 67 deletions(-) diff --git a/Sources/X509/CSR/CertificateSigningRequest.swift b/Sources/X509/CSR/CertificateSigningRequest.swift index a134bb69..338eb9b4 100644 --- a/Sources/X509/CSR/CertificateSigningRequest.swift +++ b/Sources/X509/CSR/CertificateSigningRequest.swift @@ -77,7 +77,7 @@ public struct CertificateSigningRequest { @usableFromInline let signatureBytes: ArraySlice - /// Construct a Certificate Signing Request from constituent parts. + /// Creates a certificate signing request from constituent parts. /// /// This API is generally not recommended for use, as it makes it very easy to construct a ``CertificateSigningRequest`` /// whose ``signature`` is not valid. However, for testing and validation purposes it is useful to be @@ -114,7 +114,7 @@ public struct CertificateSigningRequest { self.signatureBytes = try DER.Serializer.serialized(element: ASN1BitString(self.signature))[...] } - /// Construct a CSR for a specific private key. + /// Creates a CSR for a specific private key. /// /// This API can be used to construct a certificate signing request that can be passed to a certificate /// authority. It will correctly generate a signature over the request. @@ -150,7 +150,7 @@ public struct CertificateSigningRequest { self.signatureBytes = try DER.Serializer.serialized(element: ASN1BitString(self.signature))[...] } - /// Construct a CSR for a specific private key. + /// Creates a CSR for a specific private key. /// /// This API can be used to construct a certificate signing request that can be passed to a certificate /// authority. It will correctly generate a signature over the request. @@ -189,7 +189,7 @@ public struct CertificateSigningRequest { self.signatureBytes = try DER.Serializer.serialized(element: ASN1BitString(self.signature))[...] } - /// Construct a CSR for a specific private key. + /// Creates a CSR for a specific private key. /// /// This API can be used to construct a certificate signing request that can be passed to a certificate /// authority. It will correctly generate a signature over the request. diff --git a/Sources/X509/CSR/ExtensionRequest.swift b/Sources/X509/CSR/ExtensionRequest.swift index 4e0512bf..5c6cf117 100644 --- a/Sources/X509/CSR/ExtensionRequest.swift +++ b/Sources/X509/CSR/ExtensionRequest.swift @@ -25,7 +25,7 @@ public struct ExtensionRequest: Hashable, Sendable { /// Construct an ``ExtensionRequest`` from a given set of extensions. /// - /// - parameters: + /// - Parameters: /// - extensions: The extensions to attach to this ``ExtensionRequest``. @inlinable public init(extensions: Certificate.Extensions) { @@ -34,7 +34,7 @@ public struct ExtensionRequest: Hashable, Sendable { /// Unwrap a ``CertificateSigningRequest/Attribute`` that contains an ``ExtensionRequest``. /// - /// - parameters: + /// - Parameters: /// - attribute: The attribute to unwrap /// - throws: If the attribute is ill-formed, or does not contain an ``ExtensionRequest``. @inlinable @@ -60,7 +60,7 @@ public struct ExtensionRequest: Hashable, Sendable { extension CertificateSigningRequest.Attribute { /// Wrap an ``ExtensionRequest`` into a ``CertificateSigningRequest/Attribute``. /// - /// - parameters: + /// - Parameters: /// - extensionRequest: The ``ExtensionRequest`` to wrap. @inlinable public init(_ extensionRequest: ExtensionRequest) throws { diff --git a/Sources/X509/CertificatePrivateKey.swift b/Sources/X509/CertificatePrivateKey.swift index cbe24d8d..751f6de0 100644 --- a/Sources/X509/CertificatePrivateKey.swift +++ b/Sources/X509/CertificatePrivateKey.swift @@ -23,14 +23,14 @@ import _CryptoExtras @available(macOS 10.15, iOS 13, watchOS 6, tvOS 13, macCatalyst 13, visionOS 1.0, *) extension Certificate { - /// A private key that can be used with a certificate. + /// A private key for use with a certificate. /// - /// This type provides an opaque wrapper around the various private key types - /// provided by `swift-crypto` and `Security`. Users are expected to construct this key from + /// This type is an opaque wrapper around the various private key types + /// provided by `swift-crypto` and `Security`. Construct this key from /// one of those types. /// - /// As private keys are never sent over the wire, this type does not offer - /// support for being unwrapped back into the underlying key types. + /// As private keys are never sent over the wire, this type does not support + /// unwrapping back into the underlying key types. public struct PrivateKey { @usableFromInline var backing: BackingPrivateKey diff --git a/Sources/X509/CertificatePublicKey.swift b/Sources/X509/CertificatePublicKey.swift index 1b7014d4..bcb7cd09 100644 --- a/Sources/X509/CertificatePublicKey.swift +++ b/Sources/X509/CertificatePublicKey.swift @@ -23,11 +23,11 @@ import Foundation @available(macOS 10.15, iOS 13, watchOS 6, tvOS 13, macCatalyst 13, visionOS 1.0, *) extension Certificate { - /// A public key that can be used with a certificate. + /// A public key for use with a certificate. /// - /// This type provides an opaque wrapper around the various public key types - /// provided by `swift-crypto`. Users are expected to construct this key from - /// one of those types, or to decode it from the network. + /// This type is an opaque wrapper around the various public key types + /// provided by `swift-crypto`. Construct this key from + /// one of those types, or decode it from the network. public struct PublicKey { @usableFromInline var backing: BackingPublicKey @@ -311,7 +311,7 @@ extension P256.Signing.PublicKey { /// /// Fails if the key is not a P256 key. /// - /// - parameters: + /// - Parameters: /// - key: The key to unwrap. public init?(_ key: Certificate.PublicKey) { guard case .p256(let inner) = key.backing else { @@ -327,7 +327,7 @@ extension P384.Signing.PublicKey { /// /// Fails if the key is not a P384 key. /// - /// - parameters: + /// - Parameters: /// - key: The key to unwrap. public init?(_ key: Certificate.PublicKey) { guard case .p384(let inner) = key.backing else { @@ -343,7 +343,7 @@ extension P521.Signing.PublicKey { /// /// Fails if the key is not a P521 key. /// - /// - parameters: + /// - Parameters: /// - key: The key to unwrap. public init?(_ key: Certificate.PublicKey) { guard case .p521(let inner) = key.backing else { @@ -359,7 +359,7 @@ extension _RSA.Signing.PublicKey { /// /// Fails if the key is not an RSA key. /// - /// - parameters: + /// - Parameters: /// - key: The key to unwrap. public init?(_ key: Certificate.PublicKey) { guard case .rsa(let inner) = key.backing else { @@ -375,7 +375,7 @@ extension Curve25519.Signing.PublicKey { /// /// Fails if the key is not a Curve25519 key. /// - /// - parameters: + /// - Parameters: /// - key: The key to unwrap. public init?(_ key: Certificate.PublicKey) { guard case .ed25519(let inner) = key.backing else { diff --git a/Sources/X509/CryptographicMessageSyntax/CMSOperations.swift b/Sources/X509/CryptographicMessageSyntax/CMSOperations.swift index ba9aff5c..cc49e35a 100644 --- a/Sources/X509/CryptographicMessageSyntax/CMSOperations.swift +++ b/Sources/X509/CryptographicMessageSyntax/CMSOperations.swift @@ -19,6 +19,7 @@ import Foundation import SwiftASN1 import Crypto +/// A namespace for Cryptographic Message Syntax (CMS) operations. @available(macOS 10.15, iOS 13, watchOS 6, tvOS 13, macCatalyst 13, visionOS 1.0, *) public enum CMS: Sendable { @_spi(CMS) diff --git a/Sources/X509/DistinguishedName.swift b/Sources/X509/DistinguishedName.swift index 95392487..4e6309b7 100644 --- a/Sources/X509/DistinguishedName.swift +++ b/Sources/X509/DistinguishedName.swift @@ -20,7 +20,7 @@ import SwiftASN1 /// distinguished names were the primary key, enabling the identification of a specific entity /// within the directory. /// -/// These use-cases are largely obsolete, but distinguished names continue to be used to identify +/// These use cases are largely obsolete, but distinguished names continue to be used to identify /// both the subject of and issuer of a given X.509 certificate. In this context, the distinguished /// name is a largely opaque identifier that just happens to have a human-readable string representation. /// diff --git a/Sources/X509/DistinguishedNameBuilder/DNBuilder.swift b/Sources/X509/DistinguishedNameBuilder/DNBuilder.swift index e3eae009..0b3b3bca 100644 --- a/Sources/X509/DistinguishedNameBuilder/DNBuilder.swift +++ b/Sources/X509/DistinguishedNameBuilder/DNBuilder.swift @@ -87,6 +87,8 @@ public struct DistinguishedNameBuilder: Sendable { } } +/// A type that can be converted to a ``RelativeDistinguishedName``. public protocol RelativeDistinguishedNameConvertible { + /// Creates a ``RelativeDistinguishedName`` from this value. func makeRDN() throws -> RelativeDistinguishedName } diff --git a/Sources/X509/Error.swift b/Sources/X509/Error.swift index 5d096d03..151a7b55 100644 --- a/Sources/X509/Error.swift +++ b/Sources/X509/Error.swift @@ -12,7 +12,7 @@ // //===----------------------------------------------------------------------===// -/// Represents an error that may be thrown from the ``Certificate`` module. +/// An error thrown by the ``Certificate`` module. /// /// This object contains both an error ``code`` and a textual reason for the error, /// as well as source code context for the error. When attempting to process a specific diff --git a/Sources/X509/Extension Types/AuthorityInformationAccess.swift b/Sources/X509/Extension Types/AuthorityInformationAccess.swift index ad72b4a1..23df1b94 100644 --- a/Sources/X509/Extension Types/AuthorityInformationAccess.swift +++ b/Sources/X509/Extension Types/AuthorityInformationAccess.swift @@ -14,7 +14,7 @@ import SwiftASN1 -/// Provides details on how to access information about the certificate issuer. +/// Describes how to access information about the certificate issuer. /// /// This extension behaves as a collection of ``AuthorityInformationAccess/AccessDescription`` objects. /// diff --git a/Sources/X509/Extension Types/ExtensionIdentifiers.swift b/Sources/X509/Extension Types/ExtensionIdentifiers.swift index 46f210c5..b237e8c3 100644 --- a/Sources/X509/Extension Types/ExtensionIdentifiers.swift +++ b/Sources/X509/Extension Types/ExtensionIdentifiers.swift @@ -15,7 +15,7 @@ import SwiftASN1 extension ASN1ObjectIdentifier { - /// OIDs that identify known X509 extensions. + /// OIDs that identify known X.509 extensions. public enum X509ExtensionID: Sendable { /// Identifies the authority key identifier extension, corresponding to /// ``AuthorityKeyIdentifier``. diff --git a/Sources/X509/Extension Types/NameConstraints.swift b/Sources/X509/Extension Types/NameConstraints.swift index 91e2d16a..92a12005 100644 --- a/Sources/X509/Extension Types/NameConstraints.swift +++ b/Sources/X509/Extension Types/NameConstraints.swift @@ -14,7 +14,7 @@ import SwiftASN1 -/// Constraints the namespace within which all subject names issued by a given CA must reside. +/// Constrains the namespace within which all subject names issued by a given CA must reside. /// /// These constraints apply both to the ``Certificate/subject`` and also to any /// ``SubjectAlternativeNames`` that may be present. Restrictions are applied to @@ -25,6 +25,7 @@ import SwiftASN1 /// the same name is also matched in a permitted tree. @available(macOS 10.15, iOS 13, watchOS 6, tvOS 13, macCatalyst 13, visionOS 1.0, *) public struct NameConstraints { + /// A collection of DNS domain name constraints. public struct DNSNames: Hashable, Sendable, Collection, ExpressibleByArrayLiteral, CustomStringConvertible { public typealias Element = String @@ -123,6 +124,7 @@ public struct NameConstraints { } } + /// A collection of IP address range constraints, encoded as ASN.1 octet strings. public struct IPRanges: Hashable, Sendable, Collection, ExpressibleByArrayLiteral, CustomStringConvertible { @inlinable public static func == (lhs: Self, rhs: Self) -> Bool { @@ -219,6 +221,7 @@ public struct NameConstraints { } } + /// A collection of email address constraints. public struct EmailAddresses: Hashable, Sendable, Collection, ExpressibleByArrayLiteral, CustomStringConvertible { @inlinable public static func == (lhs: Self, rhs: Self) -> Bool { @@ -315,6 +318,7 @@ public struct NameConstraints { } } + /// A collection of URI domain constraints. public struct URIDomains: Hashable, Sendable, Collection, ExpressibleByArrayLiteral, CustomStringConvertible { @inlinable public static func == (lhs: Self, rhs: Self) -> Bool { diff --git a/Sources/X509/Extension Types/SubjectAlternativeName.swift b/Sources/X509/Extension Types/SubjectAlternativeName.swift index d47712d7..67189dac 100644 --- a/Sources/X509/Extension Types/SubjectAlternativeName.swift +++ b/Sources/X509/Extension Types/SubjectAlternativeName.swift @@ -14,7 +14,7 @@ import SwiftASN1 -/// Allows identities to be bound to the subject of a certificate. +/// Binds identities to the subject of a certificate. /// /// The identities attested in this extension belong to the subject of the certificate. /// Users of the certificate may validate that these names correspond to a name they are diff --git a/Sources/X509/Extension Types/SubjectKeyIdentifier.swift b/Sources/X509/Extension Types/SubjectKeyIdentifier.swift index 669f29e8..75bf1257 100644 --- a/Sources/X509/Extension Types/SubjectKeyIdentifier.swift +++ b/Sources/X509/Extension Types/SubjectKeyIdentifier.swift @@ -16,7 +16,7 @@ import SwiftASN1 import Crypto import struct Foundation.Data -/// Provides a means of identifying a certificate that contains a particular public key. +/// Identifies a certificate by its public key. /// /// This extension contains a value derived from the public key of the certificate in which it appears. /// That value can be used to build the ``AuthorityKeyIdentifier/keyIdentifier`` field in diff --git a/Sources/X509/Extension.swift b/Sources/X509/Extension.swift index 34d4a20e..9ef7bddb 100644 --- a/Sources/X509/Extension.swift +++ b/Sources/X509/Extension.swift @@ -16,7 +16,7 @@ import SwiftASN1 @available(macOS 10.15, iOS 13, watchOS 6, tvOS 13, macCatalyst 13, visionOS 1.0, *) extension Certificate { - /// A general-purpose representation of a specific X.509 extension. + /// A type-erased representation of a specific X.509 extension. /// /// X.509 extensions are a general representation, with three properties: an identifier, a critical flag, and the encoded value. /// The specific data contained in the encoded value is determined by the value of the identifier stored in ``oid``. diff --git a/Sources/X509/Extensions.swift b/Sources/X509/Extensions.swift index 879840e5..895be258 100644 --- a/Sources/X509/Extensions.swift +++ b/Sources/X509/Extensions.swift @@ -16,7 +16,7 @@ import SwiftASN1 @available(macOS 10.15, iOS 13, watchOS 6, tvOS 13, macCatalyst 13, visionOS 1.0, *) extension Certificate { - /// A representation of a collection of X.509 extensions. + /// An ordered collection of X.509 certificate extensions. /// /// The majority of semantic information in an X.509 certificate is contained within its /// collection of extensions. These extensions can add additional constraints or capabilities diff --git a/Sources/X509/ExtensionsBuilder.swift b/Sources/X509/ExtensionsBuilder.swift index 04241b79..0b2b99d0 100644 --- a/Sources/X509/ExtensionsBuilder.swift +++ b/Sources/X509/ExtensionsBuilder.swift @@ -12,9 +12,9 @@ // //===----------------------------------------------------------------------===// -/// Provides a result-builder style DSL for constructing ``Certificate/Extensions-swift.struct`` values. +/// A result-builder DSL for constructing ``Certificate/Extensions-swift.struct`` values. /// -/// This DSL allows us to construct extensions straightforwardly, using their high-level representation instead of +/// This DSL allows constructing extensions straightforwardly, using their high-level representation instead of /// the erased representation provided by ``Certificate/Extension``. For example, a simple set of /// extensions can be produced like this: /// @@ -114,9 +114,9 @@ public struct ExtensionsBuilder: Sendable { } } -/// Conforming types are capable of being erased into ``Certificate/Extension`` values. +/// A type that converts to a ``Certificate/Extension`` value. /// -/// Note that for most extension types, the returned ``Certificate/Extension`` should have its +/// For most extension types, the returned ``Certificate/Extension`` should have its /// ``Certificate/Extension/critical`` value set to `false`. This allows the ``Critical`` helper /// type to fulfill its function as expected. @available(macOS 10.15, iOS 13, watchOS 6, tvOS 13, macCatalyst 13, visionOS 1.0, *) diff --git a/Sources/X509/GeneralName.swift b/Sources/X509/GeneralName.swift index 491a9e3c..ea89e916 100644 --- a/Sources/X509/GeneralName.swift +++ b/Sources/X509/GeneralName.swift @@ -14,15 +14,25 @@ import SwiftASN1 +/// A general-purpose name type used in X.509 certificate extensions. public enum GeneralName: Hashable, Sendable, DERParseable, DERSerializable { + /// A name in a form not directly supported by the other cases. case otherName(OtherName) + /// An RFC 822 email address, such as `user@example.com`. case rfc822Name(String) + /// A DNS domain name, such as `example.com`. case dnsName(String) + /// An X.400 address, preserved as a raw ASN.1 value. case x400Address(ASN1Any) + /// An X.500 directory name. case directoryName(DistinguishedName) + /// An EDI party name, preserved as a raw ASN.1 value. case ediPartyName(ASN1Any) + /// A URI, such as `https://example.com`. case uniformResourceIdentifier(String) + /// An IP address, encoded as an ASN.1 octet string. case ipAddress(ASN1OctetString) + /// A registered object identifier. case registeredID(ASN1ObjectIdentifier) @usableFromInline @@ -153,14 +163,17 @@ extension GeneralName: CustomStringConvertible { // partyName [1] DirectoryString } extension GeneralName { + /// A name identified by an OID and an arbitrary ASN.1 value. public struct OtherName: Hashable, Sendable, DERImplicitlyTaggable { @inlinable public static var defaultIdentifier: ASN1Identifier { .sequence } + /// The OID that identifies the name form. public var typeID: ASN1ObjectIdentifier + /// The name value, encoded as a raw ASN.1 element. public var value: ASN1Any? @inlinable diff --git a/Sources/X509/RelativeDistinguishedName.swift b/Sources/X509/RelativeDistinguishedName.swift index 2bc7aff8..54f2e3b1 100644 --- a/Sources/X509/RelativeDistinguishedName.swift +++ b/Sources/X509/RelativeDistinguishedName.swift @@ -27,7 +27,7 @@ import _CertificateInternals /// are each a representation of the same piece of information. /// /// ``RelativeDistinguishedName``s are organized into a hierarchy in a ``DistinguishedName``, in order from least -/// to most specific. In almost all current use-cases a ``RelativeDistinguishedName`` will contain only a single +/// to most specific. In almost all current use cases a ``RelativeDistinguishedName`` will contain only a single /// ``Attribute``. /// /// Note that ``RelativeDistinguishedName`` does not have a stable ordering of its elements. Inserting an element diff --git a/Sources/X509/Signature.swift b/Sources/X509/Signature.swift index b5b5064f..cdbce5d8 100644 --- a/Sources/X509/Signature.swift +++ b/Sources/X509/Signature.swift @@ -23,12 +23,12 @@ import Foundation @available(macOS 10.15, iOS 13, watchOS 6, tvOS 13, macCatalyst 13, visionOS 1.0, *) extension Certificate { - /// An abstract representation of the cryptographic signature on a certificate. + /// A cryptographic signature on a certificate. /// /// Certificates may have a wide range of signature types. This type provides a runtime - /// abstraction across these types. It ensures that we understand the algorithm used to - /// sign the certificate, and enables us to provide verification logic, without forcing - /// users to wrestle with the wide variety of runtime types that may represent a + /// abstraction across these types. It ensures that the algorithm used to + /// sign the certificate is understood, and enables verification logic, without forcing + /// you to wrestle with the wide variety of runtime types that may represent a /// signature. /// /// This type is almost entirely opaque. It can be validated by way of @@ -45,6 +45,7 @@ extension Certificate { self.backing = backing } + /// Creates a signature from an algorithm identifier and raw signature bytes. @inlinable public init(signatureAlgorithm: SignatureAlgorithm, signatureBytes: ASN1BitString) throws { switch signatureAlgorithm { diff --git a/Sources/X509/SignatureAlgorithm.swift b/Sources/X509/SignatureAlgorithm.swift index 603b82f3..ff445dbe 100644 --- a/Sources/X509/SignatureAlgorithm.swift +++ b/Sources/X509/SignatureAlgorithm.swift @@ -14,13 +14,13 @@ @available(macOS 10.15, iOS 13, watchOS 6, tvOS 13, macCatalyst 13, visionOS 1.0, *) extension Certificate { - /// A representation of a kind of signature algorithm. + /// A signature algorithm identifier. /// /// X.509 certificates support a wide range of signature algorithms. This type /// identifies the algorithm, independently of the signature itself. /// /// This type represents an unbounded enumeration. There are potentially infinite - /// signature algorithms. Users are able to create representations of the signature + /// signature algorithms. You can create representations of the signature /// algorithms this library supports by using static fields on this type. public struct SignatureAlgorithm { @usableFromInline diff --git a/Sources/X509/Verifier/CertificateStore.swift b/Sources/X509/Verifier/CertificateStore.swift index e21fc97c..c20f3a15 100644 --- a/Sources/X509/Verifier/CertificateStore.swift +++ b/Sources/X509/Verifier/CertificateStore.swift @@ -14,7 +14,7 @@ import _CertificateInternals -/// A collection of ``Certificate`` objects for use in a verifier. +/// A collection of certificates for use in verification. @available(macOS 10.15, iOS 13, watchOS 6, tvOS 13, macCatalyst 13, visionOS 1.0, *) public struct CertificateStore: Sendable, Hashable { diff --git a/Sources/X509/Verifier/CustomCertificateStore.swift b/Sources/X509/Verifier/CustomCertificateStore.swift index 830e8cd0..5829b4f6 100644 --- a/Sources/X509/Verifier/CustomCertificateStore.swift +++ b/Sources/X509/Verifier/CustomCertificateStore.swift @@ -12,23 +12,23 @@ // //===----------------------------------------------------------------------===// -/// Implement the ``CustomCertificateStore`` if you want to perform dynamic -/// certificate lookup, or if you need custom logic when matching the -/// ``DistinguishedName`` of an Issuer with the Subject of the issuer -/// certificate, then implement a custom certificate store used by the -/// ``Verifier``. +/// A protocol for certificate stores that perform dynamic lookup or custom matching logic. +/// +/// Implement this protocol if you need to perform dynamic certificate lookup +/// or custom logic when matching the ``DistinguishedName`` of an issuer with +/// the subject of the issuer certificate. @available(macOS 10.15, iOS 13, watchOS 6, tvOS 13, macCatalyst 13, visionOS 1.0, *) public protocol CustomCertificateStore: Sendable, Hashable { - /// Obtain a list of certificates which has a given subject. Note that this - /// is an async method so that database lookups can be performed - /// asynchronously. + /// Returns the certificates that have the given subject, if any. + /// + /// This is an async method so that database lookups can be performed asynchronously. subscript(subject: DistinguishedName) -> [Certificate]? { get async } - /// Validate if a given certificate is known to exist in this certificate - /// store. Note that this is an async method so that the existence check - /// can be performed against a database. + /// Returns a Boolean value that indicates whether this store contains the given certificate. + /// + /// This is an async method so that the existence check can be performed against a database. func contains(_ certificate: Certificate) async -> Bool /// Add a certificate to this certificate store. diff --git a/Sources/X509/Verifier/PolicyBuilder.swift b/Sources/X509/Verifier/PolicyBuilder.swift index ac331d30..831602bd 100644 --- a/Sources/X509/Verifier/PolicyBuilder.swift +++ b/Sources/X509/Verifier/PolicyBuilder.swift @@ -14,9 +14,9 @@ import SwiftASN1 -/// Provides a result-builder style DSL for constructing a ``VerifierPolicy``. +/// A result-builder DSL for constructing a ``VerifierPolicy``. /// -/// This DSL allows us to construct dynamic ``VerifierPolicy`` at runtime without using type erasure. +/// This DSL allows constructing dynamic ``VerifierPolicy`` values at runtime without using type erasure. /// The resulting ``VerifierPolicy`` will use the listed policy in the order of declaration to check if a chain meets all policies. /// For Example, a simple ``Verifier`` with a simple policy can be constructed like this: /// ```swift diff --git a/Sources/X509/Verifier/RFC5280/RFC5280Policy.swift b/Sources/X509/Verifier/RFC5280/RFC5280Policy.swift index 4161afd3..9f37c102 100644 --- a/Sources/X509/Verifier/RFC5280/RFC5280Policy.swift +++ b/Sources/X509/Verifier/RFC5280/RFC5280Policy.swift @@ -18,7 +18,7 @@ import Foundation #endif import SwiftASN1 -/// A ``VerifierPolicy`` that implements the core chain verifying policies from RFC 5280. +/// A ``VerifierPolicy`` that implements the core chain verification policies from RFC 5280. /// /// Almost all verifiers should use this policy as the initial component of their policy set. The policy checks the /// following things: diff --git a/Sources/X509/Verifier/ServerIdentityPolicy.swift b/Sources/X509/Verifier/ServerIdentityPolicy.swift index 23c7287b..76a8339e 100644 --- a/Sources/X509/Verifier/ServerIdentityPolicy.swift +++ b/Sources/X509/Verifier/ServerIdentityPolicy.swift @@ -28,7 +28,7 @@ import Glibc import Musl #endif -/// A ``VerifierPolicy`` that validates that the leaf certificate is authoritative +/// A ``VerifierPolicy`` that checks whether the leaf certificate is authoritative /// for a given hostname or IP address. /// /// This policy is most commonly used to validate the leaf certificate presented by a server @@ -48,7 +48,7 @@ public struct ServerIdentityPolicy: Sendable { /// Constructs a new ``ServerIdentityPolicy``. /// - /// - parameters: + /// - Parameters: /// - serverHostname: The hostname used to connect to the server. /// - serverIP: The IP address of the server, if known. @inlinable diff --git a/Sources/X509/Verifier/ValidatedCertificateChain.swift b/Sources/X509/Verifier/ValidatedCertificateChain.swift index ce8f2a0f..073027c3 100644 --- a/Sources/X509/Verifier/ValidatedCertificateChain.swift +++ b/Sources/X509/Verifier/ValidatedCertificateChain.swift @@ -12,9 +12,9 @@ // //===----------------------------------------------------------------------===// -/// A validated certificate chain that traces the trust from a leaf to a root certificate. This type does not perform any validation -/// itself. It is a container that gives information about the contained certificates. The safe method to acquire it goes through -/// the certificate validation processes in a `Verifier`. +/// A validated certificate chain from leaf to root. This type does not perform any validation +/// itself. It gives information about the contained certificates. Acquire it through +/// the certificate validation process in a ``Verifier``. @available(macOS 10.15, iOS 13, watchOS 6, tvOS 13, macCatalyst 13, visionOS 1.0, *) public struct ValidatedCertificateChain: Sendable, Collection, RandomAccessCollection, Hashable { @usableFromInline @@ -34,8 +34,7 @@ public struct ValidatedCertificateChain: Sendable, Collection, RandomAccessColle self.validatedChain[index] } - /// Creates a `ValidatedCertificateChain` that represents a chain of trust. The chain should be ordered - /// from leaf to root and (with the exception of the root) each certificate should be issued by the owner of the subsequent + /// Creates a `ValidatedCertificateChain`. Order the chain from leaf to root; each certificate (except the root) should be issued by the owner of the subsequent /// certificate. Since the leaf and root certificate can be identical, a certificate chain must have at least one element. /// /// It is recommended to go through the verification process using `X509/Verifier/Verifier` with a diff --git a/Sources/X509/Verifier/VerificationDiagnostic.swift b/Sources/X509/Verifier/VerificationDiagnostic.swift index 03b4e0a9..a15e37e7 100644 --- a/Sources/X509/Verifier/VerificationDiagnostic.swift +++ b/Sources/X509/Verifier/VerificationDiagnostic.swift @@ -69,7 +69,7 @@ public struct VerificationDiagnostic: Sendable { } /// - Note: all ``LoadingTrustRootsFailed`` are considered equal, - /// best we can because the underlying storage type ``Error`` doesn't conform to Eqautable + /// best we can because the underlying storage type ``Error`` doesn't conform to Equatable struct LoadingTrustRootsFailed: Hashable, Sendable { var error: any Error diff --git a/Sources/X509/Verifier/Verifier.swift b/Sources/X509/Verifier/Verifier.swift index e164df2d..923de47c 100644 --- a/Sources/X509/Verifier/Verifier.swift +++ b/Sources/X509/Verifier/Verifier.swift @@ -13,18 +13,33 @@ //===----------------------------------------------------------------------===// import SwiftASN1 +/// Validates an X.509 certificate chain against a set of root certificates and a ``VerifierPolicy``. @available(macOS 10.15, iOS 13, watchOS 6, tvOS 13, macCatalyst 13, visionOS 1.0, *) public struct Verifier { + /// The trusted root certificates used to anchor chain validation. public var rootCertificates: CertificateStore + /// The policy applied to candidate chains during validation. public var policy: Policy + /// Creates a verifier with the given root certificates and policy. + /// + /// - Parameters: + /// - rootCertificates: The trusted root certificates. + /// - policy: A ``PolicyBuilder`` closure that returns the verification policy. @inlinable public init(rootCertificates: CertificateStore, @PolicyBuilder policy: () throws -> Policy) rethrows { self.rootCertificates = rootCertificates self.policy = try policy() } + /// Validates a leaf certificate by building chains through intermediate certificates to the root store. + /// + /// - Parameters: + /// - leaf: The leaf certificate to validate. + /// - intermediates: A store of intermediate certificates that may form part of the chain. + /// - diagnosticCallback: An optional closure invoked with diagnostic events during validation. + /// - Returns: A ``CertificateValidationResult`` indicating whether the certificate is valid. @available(macOS 10.15, iOS 13, watchOS 6, tvOS 13, macCatalyst 13, visionOS 1.0, *) public mutating func validate( leaf: Certificate, @@ -244,16 +259,22 @@ extension VerificationResult { } } +/// The result of validating a certificate chain. @available(macOS 10.15, iOS 13, watchOS 6, tvOS 13, macCatalyst 13, visionOS 1.0, *) public enum CertificateValidationResult: Hashable, Sendable { + /// The certificate chain is valid and trusted. case validCertificate(ValidatedCertificateChain) + /// The certificate chain could not be validated, with the associated policy failures. case couldNotValidate([PolicyFailure]) } @available(macOS 10.15, iOS 13, watchOS 6, tvOS 13, macCatalyst 13, visionOS 1.0, *) extension CertificateValidationResult { + /// A candidate chain that failed to meet a verification policy. public struct PolicyFailure: Hashable, Sendable { + /// The candidate certificate chain that failed validation. public var chain: UnverifiedCertificateChain + /// The reason the policy rejected this chain. public var policyFailureReason: PolicyFailureReason @inlinable diff --git a/Sources/X509/Verifier/VerifierPolicy.swift b/Sources/X509/Verifier/VerifierPolicy.swift index 2826407e..b00381b0 100644 --- a/Sources/X509/Verifier/VerifierPolicy.swift +++ b/Sources/X509/Verifier/VerifierPolicy.swift @@ -13,7 +13,7 @@ //===----------------------------------------------------------------------===// import SwiftASN1 -/// A ``VerifierPolicy`` implements a series of checks on an ``UnverifiedCertificateChain`` in order to determine +/// A ``VerifierPolicy`` implements a series of checks on an ``UnverifiedCertificateChain`` to determine /// whether that chain should be trusted. /// /// Certificate verification is split into two parts: chain building and policy enforcement. Chain building is general: @@ -21,13 +21,13 @@ import SwiftASN1 /// the form of ``UnverifiedCertificateChain``. /// /// Each of these candidate chains is then handed to a ``VerifierPolicy`` to be checked against the certificate policy. -/// The reason for this is to allow different use-cases to share the same chain building code, but to enforce +/// The reason for this is to allow different use cases to share the same chain building code, but to enforce /// different requirements on the chain. /// /// Some ``VerifierPolicy`` objects are used frequently and are very common, such as ``RFC5280Policy`` which implements /// the basic checks from that RFC. Other objects are less common, such as ``OCSPVerifierPolicy``, which performs live /// revocation checking. Users can also implement their own policies to enable swift-certificates to support other -/// use-cases. +/// use cases. @preconcurrency @available(macOS 10.15, iOS 13, watchOS 6, tvOS 13, macCatalyst 13, visionOS 1.0, *) public protocol VerifierPolicy: _X509SendableMetatype { @@ -55,8 +55,11 @@ public protocol VerifierPolicy: _X509SendableMetatype { mutating func chainMeetsPolicyRequirements(chain: UnverifiedCertificateChain) async -> PolicyEvaluationResult } +/// The result of evaluating a ``VerifierPolicy`` against a candidate certificate chain. public enum PolicyEvaluationResult: Sendable { + /// The chain meets the policy requirements. case meetsPolicy + /// The chain fails to meet the policy requirements, with the associated reason. case failsToMeetPolicy(PolicyFailureReason) public static func failsToMeetPolicy(reason makeReason: @autoclosure @Sendable @escaping () -> String) -> Self { @@ -68,6 +71,7 @@ public enum PolicyEvaluationResult: Sendable { } } +/// A description of why a certificate chain failed to meet a verification policy. public struct PolicyFailureReason: Sendable { var storage: @Sendable () -> String From c31fd040b6cac2d88a97c898d910a0cf568c103c Mon Sep 17 00:00:00 2001 From: Joseph Heck Date: Mon, 27 Apr 2026 14:40:39 -0700 Subject: [PATCH 5/8] Update Sources/X509/Verifier/CertificateStore.swift Co-authored-by: David Nadoba --- Sources/X509/Verifier/CertificateStore.swift | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Sources/X509/Verifier/CertificateStore.swift b/Sources/X509/Verifier/CertificateStore.swift index c20f3a15..23257bcf 100644 --- a/Sources/X509/Verifier/CertificateStore.swift +++ b/Sources/X509/Verifier/CertificateStore.swift @@ -63,7 +63,10 @@ public struct CertificateStore: Sendable, Hashable { } @inlinable - public func appending(_ certificate: Certificate) -> Self { + consuming func appending(_ certificate: Certificate) -> Self { + append(certificate) + return self + } var copy = self copy.append(certificate) return copy From a4b15f22f7537aee3fd0b3953c6130dc4d7d471e Mon Sep 17 00:00:00 2001 From: Joe Heck Date: Mon, 27 Apr 2026 15:00:14 -0700 Subject: [PATCH 6/8] fix bug by switching to consuming function --- Sources/X509/Verifier/CertificateStore.swift | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/Sources/X509/Verifier/CertificateStore.swift b/Sources/X509/Verifier/CertificateStore.swift index 23257bcf..0a5b9c79 100644 --- a/Sources/X509/Verifier/CertificateStore.swift +++ b/Sources/X509/Verifier/CertificateStore.swift @@ -63,14 +63,10 @@ public struct CertificateStore: Sendable, Hashable { } @inlinable - consuming func appending(_ certificate: Certificate) -> Self { + public consuming func appending(_ certificate: Certificate) -> Self { append(certificate) return self } - var copy = self - copy.append(certificate) - return copy - } func resolve(diagnosticsCallback: ((VerificationDiagnostic) -> Void)?) async -> Resolved { switch self.backing { From 692e921835f0476eddd336f78ac4b91a0da0c662 Mon Sep 17 00:00:00 2001 From: Joe Heck Date: Tue, 28 Apr 2026 13:10:11 -0700 Subject: [PATCH 7/8] fixing the disambiguation hashs - different on Linux --- Sources/X509/Docs.docc/Creating Certificates.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Sources/X509/Docs.docc/Creating Certificates.md b/Sources/X509/Docs.docc/Creating Certificates.md index 29f52d2b..9356fa04 100644 --- a/Sources/X509/Docs.docc/Creating Certificates.md +++ b/Sources/X509/Docs.docc/Creating Certificates.md @@ -113,7 +113,7 @@ and the signature algorithm would be constrained to what that key is capable of. match the private key that the subject entity has attested to possessing. You can use the keys from `swift-crypto` for this operation. Select `P256.Signing.PrivateKey` as the private key, which -you can wrap in ``Certificate/PrivateKey/init(_:)-2we15`` to get `issuerPrivateKey`. Then derive `publicKey` via +you can wrap in ``Certificate/PrivateKey/init(_:)-(P256.Signing.PrivateKey)`` to get `issuerPrivateKey`. Then derive `publicKey` via ``Certificate/PrivateKey/publicKey``. Finally, pick the only signature algorithm compatible with that key, which is ``Certificate/SignatureAlgorithm-swift.struct/ecdsaWithSHA256``. @@ -183,5 +183,5 @@ let derEncodedPrivateKey = swiftCryptoKey.derRepresentation ### Convert between Certificate and SecCertificate -Create an instance of ``Certificate`` from `Security/SecCertificate` (from the `Security` framework) with ``Certificate/init(_:)``. -To create an instance of `Security/SecCertificate` from ``Certificate``, use ``Security/SecCertificate/makeWithCertificate(_:)``. +Create an instance of ``Certificate`` from `SecCertificate` (from the `Security` framework) with `Certificate.init(_: SecCertificate)`. +To create an instance of `SecCertificate` from ``Certificate``, use `SecCertificate.makeWithCertificate(_:)`. From 8dbd8f722debab30a1a73d86657cc7b8e1e2dea0 Mon Sep 17 00:00:00 2001 From: Joseph Heck Date: Wed, 29 Apr 2026 08:22:32 -0700 Subject: [PATCH 8/8] Update Sources/X509/Docs.docc/Examining Certificates.md Co-authored-by: Raphael --- Sources/X509/Docs.docc/Examining Certificates.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/X509/Docs.docc/Examining Certificates.md b/Sources/X509/Docs.docc/Examining Certificates.md index 33f8c9e4..f16936c3 100644 --- a/Sources/X509/Docs.docc/Examining Certificates.md +++ b/Sources/X509/Docs.docc/Examining Certificates.md @@ -4,7 +4,7 @@ Decoding and introspecting certificates. A common use case is to receive an X.509 certificate from some source and inspect it to make decisions based on its content. X.509 certificates are complex -objects with highly dynamic and nested content, so this article outlines +objects with highly dynamic and nested content. This article outlines how to work with ``Certificate`` to get the most out of the content of an X.509 certificate.