mirror of
https://github.com/odin-lang/Odin.git
synced 2026-07-14 20:00:31 +00:00
752 lines
21 KiB
Odin
752 lines
21 KiB
Odin
package x509
|
|
|
|
import "core:bytes"
|
|
import "core:crypto/hash"
|
|
import "core:encoding/asn1"
|
|
|
|
/*
|
|
Ref: RFC 5280, Section 4.1, wire format:
|
|
|
|
This grammar is decoded via a cursor over the DER. The contents flatten into
|
|
the certificate (see x509.odin).
|
|
|
|
Certificate / TBSCertificate -> Certificate (flattened; raw, raw_tbs)
|
|
signatureAlgorithm + value -> signature_algorithm, signature_oid, signature
|
|
Version -> version (int: 1 / 2 / 3)
|
|
CertificateSerialNumber -> serial ([]byte, raw INTEGER content)
|
|
Validity / Time -> not_before, not_after (time.Time)
|
|
Name (issuer / subject) -> raw_issuer, raw_subject ([]byte DER; not decoded)
|
|
SubjectPublicKeyInfo -> raw_spki, public_key_algorithm, rsa_n/rsa_e/ec_point
|
|
Extension -> Extension struct, in `extensions`
|
|
issuer/subjectUniqueID -> skipped (obsolete)
|
|
|
|
Certificate ::= SEQUENCE {
|
|
tbsCertificate TBSCertificate,
|
|
signatureAlgorithm AlgorithmIdentifier,
|
|
signatureValue BIT STRING
|
|
}
|
|
|
|
TBSCertificate ::= SEQUENCE {
|
|
version [0] EXPLICIT Version DEFAULT v1,
|
|
serialNumber CertificateSerialNumber,
|
|
signature AlgorithmIdentifier,
|
|
issuer Name,
|
|
validity Validity,
|
|
subject Name,
|
|
subjectPublicKeyInfo SubjectPublicKeyInfo,
|
|
issuerUniqueID [1] IMPLICIT UniqueIdentifier OPTIONAL,
|
|
subjectUniqueID [2] IMPLICIT UniqueIdentifier OPTIONAL,
|
|
extensions [3] EXPLICIT Extensions OPTIONAL
|
|
}
|
|
|
|
Version ::= INTEGER { v1(0), v2(1), v3(2) }
|
|
|
|
CertificateSerialNumber ::= INTEGER
|
|
|
|
Validity ::= SEQUENCE {
|
|
notBefore Time,
|
|
notAfter Time }
|
|
|
|
Time ::= CHOICE {
|
|
utcTime UTCTime,
|
|
generalTime GeneralizedTime }
|
|
|
|
UniqueIdentifier ::= BIT STRING
|
|
|
|
SubjectPublicKeyInfo ::= SEQUENCE {
|
|
algorithm AlgorithmIdentifier,
|
|
subjectPublicKey BIT STRING }
|
|
|
|
Extensions ::= SEQUENCE SIZE (1..MAX) OF Extension
|
|
|
|
Extension ::= SEQUENCE {
|
|
extnID OBJECT IDENTIFIER,
|
|
critical BOOLEAN DEFAULT FALSE,
|
|
extnValue OCTET STRING
|
|
-- contains the DER encoding of an ASN.1 value
|
|
-- corresponding to the extension type identified
|
|
-- by extnID
|
|
}
|
|
|
|
AlgorithmIdentifier ::= SEQUENCE {
|
|
algorithm OBJECT IDENTIFIER,
|
|
parameters ANY DEFINED BY algorithm OPTIONAL
|
|
}
|
|
|
|
Name ::= CHOICE { -- only one possibility for now --
|
|
rdnSequence RDNSequence
|
|
}
|
|
|
|
RDNSequence ::= SEQUENCE OF RelativeDistinguishedName
|
|
|
|
RelativeDistinguishedName ::= SET SIZE (1..MAX) OF AttributeTypeAndValue
|
|
|
|
AttributeTypeAndValue ::= SEQUENCE {
|
|
type AttributeType,
|
|
value AttributeValue
|
|
}
|
|
|
|
AttributeType ::= OBJECT IDENTIFIER
|
|
|
|
AttributeValue ::= ANY -- DEFINED BY AttributeType
|
|
|
|
DirectoryString ::= CHOICE {
|
|
teletexString TeletexString (SIZE (1..MAX)),
|
|
printableString PrintableString (SIZE (1..MAX)),
|
|
universalString UniversalString (SIZE (1..MAX)),
|
|
utf8String UTF8String (SIZE (1..MAX)),
|
|
bmpString BMPString (SIZE (1..MAX))
|
|
}
|
|
|
|
*/
|
|
|
|
// parse decodes one DER certificate. The returned Certificate holds views into `der`, which must outlive it;
|
|
// the allocated tables are released with destroy(). Trailing bytes after the certificate are an error.
|
|
@(require_results)
|
|
parse :: proc(der: []byte, allocator := context.allocator) -> (cert: Certificate, err: Error) {
|
|
r: asn1.Cursor
|
|
asn1.cursor_init(&r, der)
|
|
|
|
outer, oerr := asn1.read_sequence(&r)
|
|
if oerr != .None || asn1.done(&r) != .None {
|
|
return {}, .Malformed
|
|
}
|
|
cert.raw = der[:r.pos]
|
|
|
|
// tbsCertificate: capture the full element (header included);
|
|
tbs_start := outer.pos
|
|
tbs, terr := asn1.read_sequence(&outer)
|
|
if terr != .None {
|
|
return {}, .Malformed
|
|
}
|
|
cert.raw_tbs = outer.data[tbs_start:outer.pos]
|
|
|
|
// signatureAlgorithm + signatureValue.
|
|
sig_oid, sig_params, serr := _read_algorithm_identifier(&outer)
|
|
if serr != .None {
|
|
return {}, .Malformed
|
|
}
|
|
cert.signature_oid = sig_oid
|
|
cert.signature_algorithm = _signature_algorithm(sig_oid)
|
|
if cert.signature_algorithm == .RSA_PSS {
|
|
if perr := _parse_pss_params(&cert, sig_params); perr != .None {
|
|
return {}, perr
|
|
}
|
|
}
|
|
|
|
sig_bits, sberr := asn1.read_bit_string_octets(&outer)
|
|
if sberr != .None || asn1.done(&outer) != .None {
|
|
return {}, .Malformed
|
|
}
|
|
cert.signature = sig_bits
|
|
|
|
// ---- TBSCertificate ----
|
|
|
|
// [0] EXPLICIT version (DEFAULT v1).
|
|
cert.version = 1
|
|
vr, has_version, verr := asn1.read_explicit(&tbs, 0)
|
|
if verr != .None {
|
|
return {}, .Malformed
|
|
}
|
|
if has_version {
|
|
v, vierr := asn1.read_i64(&vr)
|
|
if vierr != .None || asn1.done(&vr) != .None {
|
|
return {}, .Malformed
|
|
}
|
|
if v < 0 || v > 2 {
|
|
return {}, .Unsupported_Version
|
|
}
|
|
cert.version = int(v) + 1
|
|
}
|
|
|
|
// The serial is read as a raw INTEGER (not unsigned): RFC 5280 requires it to be positive, but non-conformant CAs issue negative
|
|
// serials and rejecting them is a validation policy, not a parsing one. The two's-complement content is preserved verbatim.
|
|
serial, snerr := asn1.read_integer_bytes(&tbs)
|
|
if snerr != .None {
|
|
return {}, .Malformed
|
|
}
|
|
cert.serial = serial
|
|
|
|
// signature must match the outer signatureAlgorithm per RFC 5280 section 4.1.1.2,
|
|
// the OID always, and for RSA-PSS the parameters too (they carry the digest).
|
|
tbs_sig_oid, tbs_sig_params, tserr := _read_algorithm_identifier(&tbs)
|
|
if tserr != .None {
|
|
return {}, .Malformed
|
|
}
|
|
if !bytes.equal(tbs_sig_oid, sig_oid) {
|
|
return {}, .Malformed
|
|
}
|
|
if cert.signature_algorithm == .RSA_PSS && !bytes.equal(tbs_sig_params, sig_params) {
|
|
return {}, .Malformed
|
|
}
|
|
|
|
// issuer
|
|
issuer_start := tbs.pos
|
|
if _, ierr := asn1.read_sequence(&tbs); ierr != .None {
|
|
return {}, .Malformed
|
|
}
|
|
cert.raw_issuer = tbs.data[issuer_start:tbs.pos]
|
|
|
|
// validity
|
|
validity, vderr := asn1.read_sequence(&tbs)
|
|
if vderr != .None {
|
|
return {}, .Invalid_Validity
|
|
}
|
|
nb, nberr := asn1.read_time(&validity)
|
|
na, naerr := asn1.read_time(&validity)
|
|
if nberr != .None || naerr != .None || asn1.done(&validity) != .None {
|
|
return {}, .Invalid_Validity
|
|
}
|
|
cert.not_before = nb
|
|
cert.not_after = na
|
|
|
|
// subject
|
|
subject_start := tbs.pos
|
|
if _, suberr := asn1.read_sequence(&tbs); suberr != .None {
|
|
return {}, .Malformed
|
|
}
|
|
cert.raw_subject = tbs.data[subject_start:tbs.pos]
|
|
|
|
// subjectPublicKeyInfo, full element preserved for hashing
|
|
spki_start := tbs.pos
|
|
spki, sperr := asn1.read_sequence(&tbs)
|
|
if sperr != .None {
|
|
return {}, .Malformed
|
|
}
|
|
cert.raw_spki = tbs.data[spki_start:tbs.pos]
|
|
if kerr := _parse_spki(&cert, &spki); kerr != .None {
|
|
return {}, kerr
|
|
}
|
|
|
|
// issuerUniqueID / subjectUniqueID - obsolete; skip if present
|
|
for number in u32(1) ..= u32(2) {
|
|
if asn1.is_empty(&tbs) {
|
|
break
|
|
}
|
|
tag, perr := asn1.peek_tag(&tbs)
|
|
if perr != .None {
|
|
return {}, .Malformed
|
|
}
|
|
if tag.class == .Context_Specific && tag.number == number {
|
|
if asn1.skip(&tbs) != .None {
|
|
return {}, .Malformed
|
|
}
|
|
}
|
|
}
|
|
|
|
// [3] EXPLICIT extensions.
|
|
cert.max_path_len = -1
|
|
er, has_exts, eerr := asn1.read_explicit(&tbs, 3)
|
|
if eerr != .None || asn1.done(&tbs) != .None {
|
|
return {}, .Malformed
|
|
}
|
|
if has_exts {
|
|
if cert.version != 3 {
|
|
return {}, .Malformed
|
|
}
|
|
if xerr := _parse_extensions(&cert, &er, allocator); xerr != .None {
|
|
destroy(&cert, allocator)
|
|
return {}, xerr
|
|
}
|
|
}
|
|
|
|
return cert, .None
|
|
}
|
|
|
|
// Internals.
|
|
|
|
// _read_algorithm_identifier reads SEQUENCE { OID, params ANY OPTIONAL }, returning the OID content and the raw parameter element
|
|
// (nil when absent).
|
|
@(private)
|
|
_read_algorithm_identifier :: proc(
|
|
r: ^asn1.Cursor,
|
|
) -> (
|
|
oid: []byte,
|
|
params: []byte,
|
|
err: asn1.Error,
|
|
) {
|
|
alg, aerr := asn1.read_sequence(r)
|
|
if aerr != .None {
|
|
return nil, nil, aerr
|
|
}
|
|
oid, err = asn1.read_oid(&alg)
|
|
if err != .None {
|
|
return nil, nil, err
|
|
}
|
|
if !asn1.is_empty(&alg) {
|
|
params_start := alg.pos
|
|
if serr := asn1.skip(&alg); serr != .None {
|
|
return nil, nil, serr
|
|
}
|
|
params = alg.data[params_start:alg.pos]
|
|
}
|
|
if derr := asn1.done(&alg); derr != .None {
|
|
return nil, nil, derr
|
|
}
|
|
return oid, params, .None
|
|
}
|
|
|
|
@(private)
|
|
_signature_algorithm :: proc(oid: []byte) -> Signature_Algorithm {
|
|
switch {
|
|
case bytes.equal(oid, _OID_SIG_RSA_SHA256):
|
|
return .RSA_SHA256
|
|
case bytes.equal(oid, _OID_SIG_ECDSA_SHA256):
|
|
return .ECDSA_SHA256
|
|
case bytes.equal(oid, _OID_SIG_RSA_SHA384):
|
|
return .RSA_SHA384
|
|
case bytes.equal(oid, _OID_SIG_RSA_SHA512):
|
|
return .RSA_SHA512
|
|
case bytes.equal(oid, _OID_SIG_ECDSA_SHA384):
|
|
return .ECDSA_SHA384
|
|
case bytes.equal(oid, _OID_SIG_ECDSA_SHA512):
|
|
return .ECDSA_SHA512
|
|
case bytes.equal(oid, _OID_ED25519):
|
|
return .Ed25519
|
|
case bytes.equal(oid, _OID_SIG_RSA_PSS):
|
|
return .RSA_PSS
|
|
case bytes.equal(oid, _OID_SIG_RSA_SHA1):
|
|
return .RSA_SHA1
|
|
}
|
|
return .Unknown
|
|
}
|
|
|
|
// _hash_from_oid maps a bare hash-algorithm OID (as it appears in an
|
|
// RSASSA-PSS AlgorithmIdentifier) to a hash.Algorithm, reporting ok=false for
|
|
// digests this package does not verify (leaving the field .Invalid so the
|
|
// verifier fails closed rather than the parser rejecting the certificate).
|
|
@(private)
|
|
_hash_from_oid :: proc(oid: []byte) -> (hash.Algorithm, bool) {
|
|
switch {
|
|
case bytes.equal(oid, _OID_HASH_SHA256):
|
|
return .SHA256, true
|
|
case bytes.equal(oid, _OID_HASH_SHA384):
|
|
return .SHA384, true
|
|
case bytes.equal(oid, _OID_HASH_SHA512):
|
|
return .SHA512, true
|
|
case bytes.equal(oid, _OID_HASH_SHA1):
|
|
return .Insecure_SHA1, true
|
|
}
|
|
return .Invalid, false
|
|
}
|
|
|
|
// _parse_pss_params decodes RSASSA-PSS-params (RFC 4055 section 3.1) from the
|
|
// signatureAlgorithm parameters into cert.pss_*:
|
|
// SEQUENCE { hashAlgorithm [0], maskGenAlgorithm [1], saltLength [2] INTEGER,
|
|
// trailerField [3] INTEGER }, all EXPLICIT and all with defaults.
|
|
// Omitted fields take their RFC 4055 defaults (SHA-1 / MGF1-SHA-1 / salt 20).
|
|
// A structurally broken params element is .Malformed; an unrecognized digest is
|
|
// NOT an error here, the hash is left .Invalid for verify_signature to reject.
|
|
@(private)
|
|
_parse_pss_params :: proc(cert: ^Certificate, params: []byte) -> Error {
|
|
// RFC 4055 defaults (applied when a field is absent).
|
|
cert.pss_hash = .Insecure_SHA1
|
|
cert.pss_mgf_hash = .Insecure_SHA1
|
|
cert.pss_salt_len = 20
|
|
if len(params) == 0 {
|
|
return .None // absent parameters: all defaults
|
|
}
|
|
|
|
cur: asn1.Cursor
|
|
asn1.cursor_init(&cur, params)
|
|
seq, e := asn1.read_sequence(&cur)
|
|
if e != .None || asn1.done(&cur) != .None {
|
|
return .Malformed
|
|
}
|
|
|
|
// hashAlgorithm [0] EXPLICIT AlgorithmIdentifier
|
|
if inner, present, ie := asn1.read_explicit(&seq, 0); ie != .None {
|
|
return .Malformed
|
|
} else if present {
|
|
oid, _, oe := _read_algorithm_identifier(&inner)
|
|
if oe != .None || asn1.done(&inner) != .None {
|
|
return .Malformed
|
|
}
|
|
cert.pss_hash, _ = _hash_from_oid(oid) // .Invalid when unrecognized
|
|
}
|
|
|
|
// maskGenAlgorithm [1] EXPLICIT AlgorithmIdentifier { id-mgf1, hashAlgorithm }
|
|
if inner, present, ie := asn1.read_explicit(&seq, 1); ie != .None {
|
|
return .Malformed
|
|
} else if present {
|
|
mgf_oid, mgf_params, me := _read_algorithm_identifier(&inner)
|
|
if me != .None || asn1.done(&inner) != .None {
|
|
return .Malformed
|
|
}
|
|
if !bytes.equal(mgf_oid, _OID_MGF1) {
|
|
cert.pss_mgf_hash = .Invalid // an MGF other than MGF1: unverifiable here
|
|
} else {
|
|
mp: asn1.Cursor
|
|
asn1.cursor_init(&mp, mgf_params)
|
|
hoid, _, he := _read_algorithm_identifier(&mp)
|
|
if he != .None || asn1.done(&mp) != .None {
|
|
return .Malformed
|
|
}
|
|
cert.pss_mgf_hash, _ = _hash_from_oid(hoid)
|
|
}
|
|
}
|
|
|
|
// saltLength [2] EXPLICIT INTEGER
|
|
if inner, present, ie := asn1.read_explicit(&seq, 2); ie != .None {
|
|
return .Malformed
|
|
} else if present {
|
|
sl, se := asn1.read_i64(&inner)
|
|
if se != .None || asn1.done(&inner) != .None || sl < 0 {
|
|
return .Malformed
|
|
}
|
|
cert.pss_salt_len = int(sl)
|
|
}
|
|
|
|
// trailerField [3] EXPLICIT INTEGER, only trailerFieldBC (1) is defined.
|
|
if inner, present, ie := asn1.read_explicit(&seq, 3); ie != .None {
|
|
return .Malformed
|
|
} else if present {
|
|
tf, te := asn1.read_i64(&inner)
|
|
if te != .None || asn1.done(&inner) != .None || tf != 1 {
|
|
return .Malformed
|
|
}
|
|
}
|
|
|
|
if asn1.done(&seq) != .None {
|
|
return .Malformed
|
|
}
|
|
return .None
|
|
}
|
|
|
|
@(private)
|
|
_parse_spki :: proc(cert: ^Certificate, spki: ^asn1.Cursor) -> Error {
|
|
key_oid, key_params, aerr := _read_algorithm_identifier(spki)
|
|
if aerr != .None {
|
|
return .Malformed
|
|
}
|
|
key_bits, kberr := asn1.read_bit_string_octets(spki)
|
|
if kberr != .None || asn1.done(spki) != .None {
|
|
return .Malformed
|
|
}
|
|
|
|
switch {
|
|
case bytes.equal(key_oid, _OID_KEY_RSA):
|
|
cert.public_key_algorithm = .RSA
|
|
// RSAPublicKey ::= SEQUENCE { modulus INTEGER, publicExponent INTEGER }
|
|
kr: asn1.Cursor
|
|
asn1.cursor_init(&kr, key_bits)
|
|
rsa, rerr := asn1.read_sequence(&kr)
|
|
if rerr != .None || asn1.done(&kr) != .None {
|
|
return .Malformed
|
|
}
|
|
n, nerr := asn1.read_unsigned_integer_bytes(&rsa)
|
|
e, eerr := asn1.read_unsigned_integer_bytes(&rsa)
|
|
if nerr != .None || eerr != .None || asn1.done(&rsa) != .None {
|
|
return .Malformed
|
|
}
|
|
cert.rsa_n = n
|
|
cert.rsa_e = e
|
|
|
|
case bytes.equal(key_oid, _OID_KEY_EC):
|
|
// Parameters carry the named curve: OID wrapped in the params element we captured raw
|
|
pr: asn1.Cursor
|
|
asn1.cursor_init(&pr, key_params)
|
|
curve_oid, cerr := asn1.read_oid(&pr)
|
|
if cerr != .None || asn1.done(&pr) != .None {
|
|
return .Malformed
|
|
}
|
|
switch {
|
|
case bytes.equal(curve_oid, _OID_CURVE_P256):
|
|
cert.public_key_algorithm = .ECDSA_P256
|
|
case bytes.equal(curve_oid, _OID_CURVE_P384):
|
|
cert.public_key_algorithm = .ECDSA_P384
|
|
case bytes.equal(curve_oid, _OID_CURVE_P521):
|
|
cert.public_key_algorithm = .ECDSA_P521
|
|
case:
|
|
cert.public_key_algorithm = .Unknown
|
|
}
|
|
cert.ec_point = key_bits
|
|
|
|
case bytes.equal(key_oid, _OID_ED25519):
|
|
cert.public_key_algorithm = .Ed25519
|
|
if len(key_bits) != 32 {
|
|
return .Malformed
|
|
}
|
|
cert.ec_point = key_bits
|
|
|
|
case:
|
|
cert.public_key_algorithm = .Unknown
|
|
}
|
|
return .None
|
|
}
|
|
|
|
@(private)
|
|
_parse_extensions :: proc(
|
|
cert: ^Certificate,
|
|
er: ^asn1.Cursor,
|
|
allocator := context.allocator,
|
|
) -> Error {
|
|
exts, xerr := asn1.read_sequence(er)
|
|
if xerr != .None || asn1.done(er) != .None {
|
|
return .Malformed
|
|
}
|
|
|
|
// Find allocation size
|
|
count := 0
|
|
{
|
|
tmp := exts
|
|
for !asn1.is_empty(&tmp) {
|
|
if asn1.skip(&tmp) != .None {
|
|
return .Malformed
|
|
}
|
|
count += 1
|
|
}
|
|
}
|
|
exts_table, merr := make([]Extension, count, allocator)
|
|
if merr != nil {
|
|
return .Allocation_Failed
|
|
}
|
|
cert.extensions = exts_table
|
|
|
|
for i in 0 ..< count {
|
|
// Extension ::= SEQUENCE { extnID OID, critical BOOLEAN DEFAULT FALSE, extnValue OCTET STRING }
|
|
ext, eerr := asn1.read_sequence(&exts)
|
|
if eerr != .None {
|
|
return .Malformed
|
|
}
|
|
oid, oerr := asn1.read_oid(&ext)
|
|
if oerr != .None {
|
|
return .Malformed
|
|
}
|
|
critical := false
|
|
tag, perr := asn1.peek_tag(&ext)
|
|
if perr != .None {
|
|
return .Malformed
|
|
}
|
|
if tag == asn1.universal(.Boolean) {
|
|
c, berr := asn1.read_boolean(&ext)
|
|
if berr != .None {
|
|
return .Malformed
|
|
}
|
|
critical = c
|
|
}
|
|
value, verr := asn1.read_octet_string(&ext)
|
|
if verr != .None || asn1.done(&ext) != .None {
|
|
return .Malformed
|
|
}
|
|
|
|
// RFC 5280 section 4.2: "A certificate MUST NOT include more than one instance of a particular extension."
|
|
for j in 0 ..< i {
|
|
if bytes.equal(cert.extensions[j].oid, oid) {
|
|
return .Duplicate_Extension
|
|
}
|
|
}
|
|
cert.extensions[i] = Extension {
|
|
oid = oid,
|
|
critical = critical,
|
|
value = value,
|
|
}
|
|
|
|
if herr := _parse_known_extension(cert, oid, critical, value, allocator); herr != .None {
|
|
return herr
|
|
}
|
|
}
|
|
return .None
|
|
}
|
|
|
|
@(private)
|
|
_parse_known_extension :: proc(
|
|
cert: ^Certificate,
|
|
oid: []byte,
|
|
critical: bool,
|
|
value: []byte,
|
|
allocator := context.allocator,
|
|
) -> Error {
|
|
vr: asn1.Cursor
|
|
asn1.cursor_init(&vr, value)
|
|
|
|
switch {
|
|
case bytes.equal(oid, _OID_EXT_BASIC_CONSTRAINTS):
|
|
// BasicConstraints ::= SEQUENCE { cA BOOLEAN DEFAULT FALSE, pathLenConstraint INTEGER OPTIONAL }
|
|
bc, err := asn1.read_sequence(&vr)
|
|
if err != .None || asn1.done(&vr) != .None {
|
|
return .Invalid_Extension
|
|
}
|
|
if !asn1.is_empty(&bc) {
|
|
tag, perr := asn1.peek_tag(&bc)
|
|
if perr != .None {
|
|
return .Invalid_Extension
|
|
}
|
|
if tag == asn1.universal(.Boolean) {
|
|
ca, berr := asn1.read_boolean(&bc)
|
|
if berr != .None {
|
|
return .Invalid_Extension
|
|
}
|
|
cert.is_ca = ca
|
|
}
|
|
}
|
|
if !asn1.is_empty(&bc) {
|
|
depth, derr := asn1.read_i64(&bc)
|
|
if derr != .None || depth < 0 {
|
|
return .Invalid_Extension
|
|
}
|
|
cert.max_path_len = int(depth)
|
|
}
|
|
if asn1.done(&bc) != .None {
|
|
return .Invalid_Extension
|
|
}
|
|
cert.basic_constraints_valid = true
|
|
|
|
case bytes.equal(oid, _OID_EXT_KEY_USAGE):
|
|
bits, unused, err := asn1.read_bit_string(&vr)
|
|
if err != .None || asn1.done(&vr) != .None {
|
|
return .Invalid_Extension
|
|
}
|
|
total := len(bits) * 8 - unused
|
|
usage: Key_Usage
|
|
for bit in Key_Usage_Bit {
|
|
i := int(bit)
|
|
if i >= total {
|
|
continue
|
|
}
|
|
if bits[i / 8] & (0x80 >> uint(i % 8)) != 0 {
|
|
usage += {bit}
|
|
}
|
|
}
|
|
cert.key_usage = usage
|
|
cert.has_key_usage = true
|
|
|
|
case bytes.equal(oid, _OID_EXT_EXT_KEY_USAGE):
|
|
seq, err := asn1.read_sequence(&vr)
|
|
if err != .None || asn1.done(&vr) != .None {
|
|
return .Invalid_Extension
|
|
}
|
|
for !asn1.is_empty(&seq) {
|
|
purpose, perr := asn1.read_oid(&seq)
|
|
if perr != .None {
|
|
return .Invalid_Extension
|
|
}
|
|
switch {
|
|
case bytes.equal(purpose, _OID_EKU_SERVER_AUTH):
|
|
cert.ext_key_usage += {.Server_Auth}
|
|
case bytes.equal(purpose, _OID_EKU_CLIENT_AUTH):
|
|
cert.ext_key_usage += {.Client_Auth}
|
|
case bytes.equal(purpose, _OID_EKU_CODE_SIGNING):
|
|
cert.ext_key_usage += {.Code_Signing}
|
|
case bytes.equal(purpose, _OID_EKU_EMAIL_PROTECTION):
|
|
cert.ext_key_usage += {.Email_Protection}
|
|
case bytes.equal(purpose, _OID_EKU_TIME_STAMPING):
|
|
cert.ext_key_usage += {.Time_Stamping}
|
|
case bytes.equal(purpose, _OID_EKU_OCSP_SIGNING):
|
|
cert.ext_key_usage += {.OCSP_Signing}
|
|
case bytes.equal(purpose, _OID_EKU_ANY):
|
|
cert.ext_key_usage += {.Any}
|
|
case:
|
|
cert.eku_has_unknown = true
|
|
}
|
|
}
|
|
cert.has_ext_key_usage = true
|
|
|
|
case bytes.equal(oid, _OID_EXT_SAN):
|
|
return _parse_san(cert, &vr, allocator)
|
|
|
|
case bytes.equal(oid, _OID_EXT_SUBJECT_KEY_ID):
|
|
ski, err := asn1.read_octet_string(&vr)
|
|
if err != .None || asn1.done(&vr) != .None {
|
|
return .Invalid_Extension
|
|
}
|
|
cert.subject_key_id = ski
|
|
|
|
case bytes.equal(oid, _OID_EXT_AUTHORITY_KEY_ID):
|
|
// AuthorityKeyIdentifier ::= SEQUENCE { keyIdentifier [0] IMPLICIT OCTET STRING OPTIONAL, ... }
|
|
aki, err := asn1.read_sequence(&vr)
|
|
if err != .None || asn1.done(&vr) != .None {
|
|
return .Invalid_Extension
|
|
}
|
|
if !asn1.is_empty(&aki) {
|
|
tag, perr := asn1.peek_tag(&aki)
|
|
if perr != .None {
|
|
return .Invalid_Extension
|
|
}
|
|
if tag == asn1.context_specific(0, false) {
|
|
kid, kerr := asn1.expect(&aki, tag)
|
|
if kerr != .None {
|
|
return .Invalid_Extension
|
|
}
|
|
cert.authority_key_id = kid
|
|
}
|
|
}
|
|
// authorityCertIssuer [1] and authorityCertSerialNumber [2] are intentionally not decoded: keyIdentifier is the form used in
|
|
// practice, and AKI is only a path-building hint (issuers are matched by DN + signature), so the other fields carry no
|
|
// validation weight here.
|
|
|
|
case bytes.equal(oid, _OID_EXT_NAME_CONSTRAINTS):
|
|
// Left raw in `extensions` and enforced during chain validation (see _check_name_constraints,
|
|
// dNSName/iPAddress with fail-closed on other forms). Recognized here so a critical
|
|
// nameConstraints does not trip unhandled_critical.
|
|
|
|
case:
|
|
if critical {
|
|
cert.unhandled_critical = true
|
|
}
|
|
}
|
|
return .None
|
|
}
|
|
|
|
// GeneralNames ::= SEQUENCE OF GeneralName; we extract dNSName ([2] IA5String) and iPAddress ([7] OCTET STRING).
|
|
@(private)
|
|
_parse_san :: proc(cert: ^Certificate, vr: ^asn1.Cursor, allocator := context.allocator) -> Error {
|
|
names, err := asn1.read_sequence(vr)
|
|
if err != .None || asn1.done(vr) != .None {
|
|
return .Invalid_Extension
|
|
}
|
|
|
|
dns_count, ip_count := 0, 0
|
|
{
|
|
tmp := names
|
|
for !asn1.is_empty(&tmp) {
|
|
tag, content, gerr := asn1.read_any(&tmp)
|
|
if gerr != .None || tag.class != .Context_Specific {
|
|
return .Invalid_Extension
|
|
}
|
|
switch tag.number {
|
|
case 2:
|
|
dns_count += 1
|
|
case 7:
|
|
if len(content) != 4 && len(content) != 16 {
|
|
return .Invalid_Extension
|
|
}
|
|
ip_count += 1
|
|
}
|
|
}
|
|
}
|
|
|
|
// On allocation failure the caller (parse) unwinds every table via destroy.
|
|
if dns_count > 0 {
|
|
dns_table, derr := make([]string, dns_count, allocator)
|
|
if derr != nil {
|
|
return .Allocation_Failed
|
|
}
|
|
cert.dns_names = dns_table
|
|
}
|
|
if ip_count > 0 {
|
|
ip_table, ierr := make([][]byte, ip_count, allocator)
|
|
if ierr != nil {
|
|
return .Allocation_Failed
|
|
}
|
|
cert.ip_addresses = ip_table
|
|
}
|
|
|
|
di, ii := 0, 0
|
|
for !asn1.is_empty(&names) {
|
|
tag, content, gerr := asn1.read_any(&names)
|
|
if gerr != .None {
|
|
return .Invalid_Extension
|
|
}
|
|
switch tag.number {
|
|
case 2:
|
|
cert.dns_names[di] = string(content)
|
|
di += 1
|
|
case 7:
|
|
cert.ip_addresses[ii] = content
|
|
ii += 1
|
|
}
|
|
}
|
|
return .None
|
|
}
|