Zig 0.17.0-dev (Split by item)

This is an example of documentation generated by ZigDoc, an alternative to Zig's built-in Auto Doc feature. See also examples in other modes/formats. The project being documented here (as the example) is the Zig library itself.

Parsed

Certificate.Parsed
pub const Parsed = struct

File

lib/std/crypto/Certificate.zig:178

Code

pub const Parsed = struct {
    certificate: Certificate,
    issuer_slice: Slice,
    subject_slice: Slice,
    common_name_slice: Slice,
    signature_slice: Slice,
    signature_algorithm: Algorithm,
    pub_key_algo: PubKeyAlgo,
    pub_key_slice: Slice,
    message_slice: Slice,
    subject_alt_name_slice: Slice,
    validity: Validity,
    version: Version,

    pub const PubKeyAlgo = union(AlgorithmCategory) {
        rsaEncryption: void,
        rsassa_pss: void,
        X9_62_id_ecPublicKey: NamedCurve,
        curveEd25519: void,
    };

    pub const Validity = struct {
        not_before: u64,
        not_after: u64,
    };

    pub const Slice = der.Element.Slice;

    pub fn slice(p: Parsed, s: Slice) []const u8 {
        return p.certificate.buffer[s.start..s.end];
    }

    pub fn issuer(p: Parsed) []const u8 {
        return p.slice(p.issuer_slice);
    }

    pub fn subject(p: Parsed) []const u8 {
        return p.slice(p.subject_slice);
    }

    pub fn commonName(p: Parsed) []const u8 {
        return p.slice(p.common_name_slice);
    }

    pub fn signature(p: Parsed) []const u8 {
        return p.slice(p.signature_slice);
    }

    pub fn pubKey(p: Parsed) []const u8 {
        return p.slice(p.pub_key_slice);
    }

    pub fn message(p: Parsed) []const u8 {
        return p.slice(p.message_slice);
    }

    pub fn subjectAltName(p: Parsed) []const u8 {
        return p.slice(p.subject_alt_name_slice);
    }

    pub const VerifyError = error{
        CertificateIssuerMismatch,
        CertificateNotYetValid,
        CertificateExpired,
        CertificateSignatureAlgorithmUnsupported,
        CertificateSignatureAlgorithmMismatch,
        CertificateFieldHasInvalidLength,
        CertificateFieldHasWrongDataType,
        CertificatePublicKeyInvalid,
        CertificateSignatureInvalidLength,
        CertificateSignatureInvalid,
        CertificateSignatureUnsupportedBitCount,
        CertificateSignatureNamedCurveUnsupported,
    };

    /// This function verifies:
    ///  * That the subject's issuer is indeed the provided issuer.
    ///  * The time validity of the subject.
    ///  * The signature.
    pub fn verify(parsed_subject: Parsed, parsed_issuer: Parsed, now_sec: i64) VerifyError!void {
        // Check that the subject's issuer name matches the issuer's
        // subject name.
        if (!mem.eql(u8, parsed_subject.issuer(), parsed_issuer.subject())) {
            return error.CertificateIssuerMismatch;
        }

        if (now_sec < parsed_subject.validity.not_before)
            return error.CertificateNotYetValid;
        if (now_sec > parsed_subject.validity.not_after)
            return error.CertificateExpired;

        switch (parsed_subject.signature_algorithm) {
            inline .sha1WithRSAEncryption,
            .sha224WithRSAEncryption,
            .sha256WithRSAEncryption,
            .sha384WithRSAEncryption,
            .sha512WithRSAEncryption,
            => |algorithm| return verifyRsa(
                algorithm.Hash(),
                parsed_subject.message(),
                parsed_subject.signature(),
                parsed_issuer.pub_key_algo,
                parsed_issuer.pubKey(),
            ),

            inline .ecdsa_with_SHA224,
            .ecdsa_with_SHA256,
            .ecdsa_with_SHA384,
            .ecdsa_with_SHA512,
            => |algorithm| return verify_ecdsa(
                algorithm.Hash(),
                parsed_subject.message(),
                parsed_subject.signature(),
                parsed_issuer.pub_key_algo,
                parsed_issuer.pubKey(),
            ),

            .md2WithRSAEncryption, .md5WithRSAEncryption => {
                return error.CertificateSignatureAlgorithmUnsupported;
            },

            .curveEd25519 => return verifyEd25519(
                parsed_subject.message(),
                parsed_subject.signature(),
                parsed_issuer.pub_key_algo,
                parsed_issuer.pubKey(),
            ),
        }
    }

    pub const VerifyHostNameError = error{
        CertificateHostMismatch,
        CertificateFieldHasInvalidLength,
    };

    pub fn verifyHostName(parsed_subject: Parsed, host_name: []const u8) VerifyHostNameError!void {
        // If the Subject Alternative Names extension is present, this is
        // what to check. Otherwise, only the common name is checked.
        const subject_alt_name = parsed_subject.subjectAltName();
        if (subject_alt_name.len == 0) {
            if (checkHostName(host_name, parsed_subject.commonName())) {
                return;
            } else {
                return error.CertificateHostMismatch;
            }
        }

        const general_names = try der.Element.parse(subject_alt_name, 0);
        var name_i = general_names.slice.start;
        while (name_i < general_names.slice.end) {
            const general_name = try der.Element.parse(subject_alt_name, name_i);
            name_i = general_name.slice.end;
            switch (@as(GeneralNameTag, @fromBackingInt(@intCast(@backingInt(general_name.identifier.tag))))) {
                .dNSName => {
                    const dns_name = subject_alt_name[general_name.slice.start..general_name.slice.end];
                    if (checkHostName(host_name, dns_name)) return;
                },
                else => {},
            }
        }

        return error.CertificateHostMismatch;
    }

    // Check hostname according to RFC2818 specification:
    //
    // If more than one identity of a given type is present in
    // the certificate (e.g., more than one DNSName name, a match in any one
    // of the set is considered acceptable.) Names may contain the wildcard
    // character * which is considered to match any single domain name
    // component. E.g., *.a.com matches foo.a.com but not bar.foo.a.com.
    // Partial wildcards like f*.com are not supported.
    fn checkHostName(host_name: []const u8, dns_name: []const u8) bool {
        // Empty strings should not match
        if (host_name.len == 0 or dns_name.len == 0) return false;

        // RFC 6125 Section 6.4.1: Exact match (case-insensitive)
        if (std.ascii.eqlIgnoreCase(dns_name, host_name)) {
            return true; // exact match
        }

        // RFC 6125 Section 6.4.3: Wildcard certificates
        // Wildcard must be leftmost label and in the form "*.rest.of.domain"
        if (dns_name.len >= 3 and mem.startsWith(u8, dns_name, "*.")) {
            const wildcard_suffix = dns_name[2..];

            // No additional wildcards allowed in the suffix
            if (mem.find(u8, wildcard_suffix, "*") != null) return false;

            // Find the first dot in hostname to split first label from rest
            const dot_pos = mem.find(u8, host_name, ".") orelse return false;

            // Wildcard matches exactly one label, so compare the rest
            const host_suffix = host_name[dot_pos + 1 ..];

            // Match suffixes (case-insensitive per RFC 6125)
            return std.ascii.eqlIgnoreCase(wildcard_suffix, host_suffix);
        }

        return false;
    }
}