// A minimal X.509 v3 certificate (RFC 5280) for a P-256 / ECDSA-with-SHA256 server, built on
// the ASN.1 DER primitives. A TLS 1.3 server sends this in its Certificate message so a client
// can bind the handshake to a public key; mooncat signs it with the same ES256 primitive the
// CertificateVerify uses. This produces a real, self-signed DER certificate — the identity curl
// parses — replacing the opaque placeholder the handshake carried.
///|
/// The ecdsa-with-SHA256 AlgorithmIdentifier (RFC 5758): a SEQUENCE of just the OID, ECDSA
/// taking no parameters.
pub fn x509_alg_ecdsa_sha256() -> Bytes {
der_sequence([der_oid([1, 2, 840, 10045, 4, 3, 2])])
}
///|
/// The id-ecPublicKey with the prime256v1 (P-256) curve AlgorithmIdentifier (RFC 5480).
pub fn x509_alg_ec_public_key() -> Bytes {
der_sequence([
der_oid([1, 2, 840, 10045, 2, 1]),
der_oid([1, 2, 840, 10045, 3, 1, 7]),
])
}
///|
/// A Name with a single commonName attribute: RDNSequence → RelativeDistinguishedName (SET) →
/// AttributeTypeAndValue (SEQUENCE of the commonName OID 2.5.4.3 and the UTF8String value).
pub fn x509_common_name(cn : String) -> Bytes {
der_sequence([
der_set([der_sequence([der_oid([2, 5, 4, 3]), der_utf8_string(cn)])]),
])
}
///|
/// A Validity: notBefore and notAfter as UTCTime `YYMMDDHHMMSSZ`.
pub fn x509_validity(not_before : String, not_after : String) -> Bytes {
der_sequence([der_utc_time(not_before), der_utc_time(not_after)])
}
///|
/// The uncompressed SEC1 encoding of an EC public key: `0x04 || X || Y`, each coordinate 32
/// big-endian bytes for P-256.
fn x509_ec_point(pub_key : EcdsaPublicKey) -> Bytes {
let buf = Buffer()
buf.write_byte(b'\x04')
buf.write_bytes(pub_key.x.to_octets(length=32)[:])
buf.write_bytes(pub_key.y.to_octets(length=32)[:])
buf.to_bytes()
}
///|
/// A SubjectPublicKeyInfo for a P-256 key: the ecPublicKey/prime256v1 algorithm and the
/// uncompressed point as a BIT STRING.
pub fn x509_subject_public_key_info(pub_key : EcdsaPublicKey) -> Bytes {
der_sequence([
x509_alg_ec_public_key(),
der_bit_string(x509_ec_point(pub_key)),
])
}
///|
/// The TBSCertificate (RFC 5280 §4.1.2): version v3 `[0] EXPLICIT INTEGER 2`, the serial number,
/// the signature algorithm, the issuer, the validity, the subject, and the SubjectPublicKeyInfo.
/// Self-signed, so issuer and subject are the same commonName. Extensions are omitted (a valid,
/// minimal profile).
pub fn x509_tbs_certificate(
serial : Bytes,
common_name : String,
not_before : String,
not_after : String,
pub_key : EcdsaPublicKey,
) -> Bytes {
der_sequence([
der_explicit(0, der_integer(b"\x02")),
der_integer(serial),
x509_alg_ecdsa_sha256(),
x509_common_name(common_name),
x509_validity(not_before, not_after),
x509_common_name(common_name),
x509_subject_public_key_info(pub_key),
])
}
///|
/// A self-signed X.509 certificate: build the TBSCertificate for `key`'s public key, sign its
/// DER with ES256, DER-encode the `r`/`s` signature (RFC 5280 requires the ECDSA-Sig-Value
/// SEQUENCE, not the raw concatenation), and wrap TBS + algorithm + signature in the outer
/// Certificate SEQUENCE.
pub fn x509_self_signed(
key : EcdsaPrivateKey,
serial : Bytes,
common_name : String,
not_before : String,
not_after : String,
) -> Bytes {
let pub_key = key.public_key()
let tbs = x509_tbs_certificate(
serial, common_name, not_before, not_after, pub_key,
)
let raw = ecdsa_p256_sha256_sign(tbs, key)
let sig = der_sequence([
der_integer(raw[0:32].to_owned()),
der_integer(raw[32:64].to_owned()),
])
der_sequence([tbs, x509_alg_ecdsa_sha256(), der_bit_string(sig)])
}
// The other direction: a certificate the peer chose, read into something the TLS side can act
// on. RFC 5280 §4 layers rules on top of X.690 that a generator never has to think about — a
// DEFAULT that must be left out, an extension that must not repeat, an inner signature algorithm
// that must equal the outer one, a critical extension that must not be skipped — and each of
// them exists because a certificate with two readings has two identities. They are enforced
// here, so a caller holding a `Cert` is holding one certificate rather than a choice of them.
///|
/// commonName (X.520 §6.2.2), the attribute a name is usually read from.
pub let x509_oid_cn : String = "2.5.4.3"
///|
/// id-kp-serverAuth: the purpose a TLS server certificate needs (RFC 5280 §4.2.1.12).
pub let x509_eku_server_auth : String = "1.3.6.1.5.5.7.3.1"
///|
/// id-kp-clientAuth.
pub let x509_eku_client_auth : String = "1.3.6.1.5.5.7.3.2"
///|
let oid_ec_key : String = "1.2.840.10045.2.1"
///|
let oid_p256 : String = "1.2.840.10045.3.1.7"
///|
let oid_ecdsa_sha256 : String = "1.2.840.10045.4.3.2"
///|
/// One AttributeTypeAndValue: the attribute's OID in dotted-decimal form, and its value as text.
pub struct Attr {
oid : String
value : String
} derive(Eq, Debug)
///|
/// A distinguished name as it is encoded — a sequence of relative distinguished names, each a set
/// of attributes. Nearly every certificate puts one attribute in each RDN, but name comparison is
/// defined over the nesting, so the nesting is what is kept.
pub struct Name {
rdns : Array[Array[Attr]]
} derive(Eq, Debug)
///|
/// The value of the first attribute with this OID, in encoding order.
pub fn Name::get(self : Name, oid : String) -> String? {
for rdn in self.rdns {
for a in rdn {
if a.oid == oid {
return Some(a.value)
}
}
}
None
}
///|
/// The name's commonName, if it has one.
pub fn Name::cn(self : Name) -> String? {
self.get(x509_oid_cn)
}
///|
/// A subjectAltName entry (RFC 5280 §4.2.1.6). The forms that name a TLS peer are decoded; the
/// rest keep their field number and contents, so a caller can see what was there instead of
/// being told the extension was empty.
pub(all) enum GeneralName {
Dns(String)
Email(String)
Uri(String)
Ip(Bytes)
Named(Int, Bytes)
} derive(Eq, Debug)
///|
/// A validity window, in seconds since the Unix epoch.
pub struct Validity {
not_before : Int64
not_after : Int64
} derive(Eq, Debug)
///|
/// A SubjectPublicKeyInfo: the algorithm OID, the parameter OID when the algorithm takes one (for
/// an EC key, the named curve), and the key bits. Absent and NULL parameters both read as `None`,
/// which is what every algorithm reachable from here means by either.
pub struct Spki {
alg : String
params : String?
key : Bits
} derive(Eq, Debug)
///|
/// The extensions a peer's certificate is judged on (RFC 5280 §4.2). `key_usage` bits are
/// numbered from the top of the first octet — 0 digitalSignature, 5 keyCertSign. `eku` is `None`
/// when the extension is absent, which means any purpose, as against a listed set that does not
/// include the one being asked for. `unknown_critical` names the critical extensions this parser
/// does not understand: §4.2 makes such a certificate unusable, and a caller can only obey that
/// rule if it is told rather than left to assume the skip was safe.
pub struct Exts {
san : Array[GeneralName]
ca : Bool
path_len : Int?
key_usage : Bits?
eku : Array[String]?
unknown_critical : Array[String]
} derive(Eq, Debug)
///|
/// A parsed certificate. `tbs` is the exact encoding the signature covers and `raw` the whole
/// certificate, both as views into the input: a signature is checked over the bytes that arrived,
/// never over a re-encoding of what they were understood to mean.
pub struct Cert {
raw : BytesView
tbs : BytesView
version : Int
serial : BigInt
alg : String
issuer : Name
validity : Validity
subject : Name
spki : Spki
exts : Exts
sig : Bits
}
///|
/// An AlgorithmIdentifier (RFC 5280 §4.1.1.2) as its OID and its parameters, the latter kept only
/// when they are an OID.
fn Tlv::alg(self : Tlv) -> (String, String?) raise DerError {
if self.tag != 0x30 {
raise Malformed("AlgorithmIdentifier is not a SEQUENCE")
}
let items = self.items()
if items.length() == 0 || items.length() > 2 {
raise Malformed("AlgorithmIdentifier is not an OID and its parameters")
}
let oid = items[0].oid()
let params = if items.length() == 2 && items[1].tag == 0x06 {
Some(items[1].oid())
} else {
None
}
(oid, params)
}
///|
/// An RDNSequence (RFC 5280 §4.1.2.4).
fn Tlv::name(self : Tlv) -> Name raise DerError {
if self.tag != 0x30 {
raise Malformed("Name is not a SEQUENCE")
}
let rdns : Array[Array[Attr]] = []
for rdn in self.items() {
if rdn.tag != 0x31 {
raise Malformed("RelativeDistinguishedName is not a SET")
}
let attrs : Array[Attr] = []
for a in rdn.items() {
if a.tag != 0x30 {
raise Malformed("AttributeTypeAndValue is not a SEQUENCE")
}
let parts = a.items()
if parts.length() != 2 {
raise Malformed("AttributeTypeAndValue is not a type and a value")
}
attrs.push({ oid: parts[0].oid(), value: parts[1].text(), })
}
if attrs.length() == 0 {
raise Malformed("empty RelativeDistinguishedName")
}
rdns.push(attrs)
}
{ rdns, }
}
///|
/// A Validity (RFC 5280 §4.1.2.5).
fn Tlv::window(self : Tlv) -> Validity raise DerError {
if self.tag != 0x30 {
raise Malformed("Validity is not a SEQUENCE")
}
let items = self.items()
if items.length() != 2 {
raise Malformed("Validity is not a notBefore and a notAfter")
}
{ not_before: items[0].time(), not_after: items[1].time(), }
}
///|
/// A SubjectPublicKeyInfo (RFC 5280 §4.1.2.7).
fn Tlv::spki(self : Tlv) -> Spki raise DerError {
if self.tag != 0x30 {
raise Malformed("SubjectPublicKeyInfo is not a SEQUENCE")
}
let items = self.items()
if items.length() != 2 {
raise Malformed("SubjectPublicKeyInfo is not an algorithm and a key")
}
let (alg, params) = items[0].alg()
{ alg, params, key: items[1].bits(), }
}
///|
/// A GeneralNames (RFC 5280 §4.2.1.6). The entries are IMPLICIT-tagged, so the field number is
/// the only thing that says what the octets are; there is no string tag left to read.
fn x509_san(v : BytesView) -> Array[GeneralName] raise DerError {
let seq = der_only(v)
if seq.tag != 0x30 {
raise Malformed("subjectAltName is not a SEQUENCE")
}
let out : Array[GeneralName] = []
for g in seq.items() {
if (g.tag & 0xc0) != 0x80 {
raise Malformed("subjectAltName entry is not context-tagged")
}
match g.tag & 0x1f {
1 => out.push(Email(der_ascii(g.value)))
2 => out.push(Dns(der_ascii(g.value)))
6 => out.push(Uri(der_ascii(g.value)))
7 => {
if g.value.length() != 4 && g.value.length() != 16 {
raise Malformed("iPAddress is neither four nor sixteen octets")
}
out.push(Ip(g.value.to_owned()))
}
n => out.push(Named(n, g.value.to_owned()))
}
}
if out.length() == 0 {
raise Malformed("empty subjectAltName")
}
out
}
///|
/// A BasicConstraints (RFC 5280 §4.2.1.9) as `(cA, pathLenConstraint)`.
fn x509_basic(v : BytesView) -> (Bool, Int?) raise DerError {
let seq = der_only(v)
if seq.tag != 0x30 {
raise Malformed("basicConstraints is not a SEQUENCE")
}
let items = seq.items()
let mut i = 0
let mut ca = false
if i < items.length() && items[i].tag == 0x01 {
ca = items[i].bool()
if !ca {
raise Malformed("basicConstraints spells out the default cA")
}
i = i + 1
}
let mut path : Int? = None
if i < items.length() && items[i].tag == 0x02 {
if !ca {
raise Malformed("pathLenConstraint without cA")
}
let n = items[i].int()
if n < (0 : BigInt) {
raise Malformed("negative pathLenConstraint")
}
if n > (255 : BigInt) {
raise Unsupported("pathLenConstraint over 255")
}
path = Some(n.to_int())
i = i + 1
}
if i != items.length() {
raise Malformed("unexpected field in basicConstraints")
}
(ca, path)
}
///|
/// An ExtKeyUsageSyntax (RFC 5280 §4.2.1.12): the key purposes, of which there is at least one.
fn x509_eku(v : BytesView) -> Array[String] raise DerError {
let seq = der_only(v)
if seq.tag != 0x30 {
raise Malformed("extKeyUsage is not a SEQUENCE")
}
let out : Array[String] = []
for o in seq.items() {
out.push(o.oid())
}
if out.length() == 0 {
raise Malformed("empty extKeyUsage")
}
out
}
///|
/// The `[3] EXPLICIT Extensions` of a v3 certificate (RFC 5280 §4.1.2.9).
fn x509_exts(t : Tlv) -> Exts raise DerError {
let seq = der_only(t.value)
if seq.tag != 0x30 {
raise Malformed("extensions are not a SEQUENCE")
}
let items = seq.items()
if items.length() == 0 {
raise Malformed("empty extensions")
}
let seen : Array[String] = []
let mut san : Array[GeneralName] = []
let mut ca = false
let mut path_len : Int? = None
let mut key_usage : Bits? = None
let mut eku : Array[String]? = None
let unknown_critical : Array[String] = []
for e in items {
if e.tag != 0x30 {
raise Malformed("extension is not a SEQUENCE")
}
let parts = e.items()
if parts.length() < 2 || parts.length() > 3 {
raise Malformed("extension is not an id, a criticality and a value")
}
let oid = parts[0].oid()
if seen.contains(oid) {
raise Malformed("extension " + oid + " appears twice")
}
seen.push(oid)
let critical = if parts.length() == 3 {
if !parts[1].bool() {
raise Malformed("extension spells out the default criticality")
}
true
} else {
false
}
let v = parts[parts.length() - 1].octets()
match oid {
"2.5.29.17" => san = x509_san(v)
"2.5.29.19" => {
let (c, p) = x509_basic(v)
ca = c
path_len = p
}
"2.5.29.15" => key_usage = Some(der_only(v).bits())
"2.5.29.37" => eku = Some(x509_eku(v))
_ => if critical { unknown_critical.push(oid) }
}
}
{ san, ca, path_len, key_usage, eku, unknown_critical, }
}
///|
/// Parse a DER certificate (RFC 5280 §4.1). Raises on anything that is not exactly one
/// well-formed certificate: a truncated or over-long field, a payload glued to the end, a version
/// that does not admit the fields present, or an outer signature algorithm that disagrees with
/// the one the signature actually covers.
pub fn x509_parse(der : BytesView) -> Cert raise DerError {
let cert = der_only(der)
if cert.tag != 0x30 {
raise Malformed("Certificate is not a SEQUENCE")
}
let top = cert.items()
if top.length() != 3 {
raise Malformed(
"Certificate is not a TBSCertificate, an algorithm and a signature",
)
}
let tbs = top[0]
if tbs.tag != 0x30 {
raise Malformed("TBSCertificate is not a SEQUENCE")
}
let f = tbs.items()
let mut version = 1
let mut i = 0
if f.length() > 0 && f[0].tag == 0xa0 {
let v = der_only(f[0].value).int()
if v < (0 : BigInt) || v > (2 : BigInt) {
raise Malformed("unknown certificate version")
}
version = v.to_int() + 1
i = 1
}
if f.length() < i + 6 {
raise Malformed("TBSCertificate is missing fields")
}
let serial = f[i].int()
let (alg, _) = f[i + 1].alg()
// RFC 5280 §4.1.1.2: the two algorithm identifiers are the same field written twice, and only
// the inner one is signed. A peer that can make them differ chooses the algorithm a verifier
// reads without touching what the issuer signed.
if top[1].raw != f[i + 1].raw {
raise Malformed(
"the signature algorithm differs from the one the TBSCertificate names",
)
}
let issuer = f[i + 2].name()
let validity = f[i + 3].window()
let subject = f[i + 4].name()
let spki = f[i + 5].spki()
let mut exts : Exts = {
san: [],
ca: false,
path_len: None,
key_usage: None,
eku: None,
unknown_critical: [],
}
let mut prev = 0
for j = i + 6; j < f.length(); j = j + 1 {
let t = f[j]
if (t.tag & 0xc0) != 0x80 {
raise Malformed("unexpected field in TBSCertificate")
}
let n = t.tag & 0x1f
if n < 1 || n > 3 || n <= prev {
raise Malformed("unexpected field in TBSCertificate")
}
prev = n
if n == 3 {
if t.tag != 0xa3 {
raise Malformed("extensions are not EXPLICIT-tagged")
}
if version != 3 {
raise Malformed("extensions in a certificate below v3")
}
exts = x509_exts(t)
} else if version == 1 {
raise Malformed("unique identifier in a v1 certificate")
}
}
{
raw: cert.raw,
tbs: tbs.raw,
version,
serial,
alg,
issuer,
validity,
subject,
spki,
exts,
sig: top[2].bits(),
}
}
///|
/// The P-256 public key a SubjectPublicKeyInfo carries. Refuses any algorithm but id-ecPublicKey
/// on prime256v1, refuses the compressed and hybrid point forms, and refuses a point that is not
/// on the curve.
pub fn Spki::p256(self : Spki) -> EcdsaPublicKey raise DerError {
if self.alg != oid_ec_key {
raise Unsupported("public key algorithm " + self.alg)
}
if self.params != Some(oid_p256) {
raise Unsupported("elliptic curve other than prime256v1")
}
if self.key.unused != 0 {
raise Malformed("public key BIT STRING has unused bits")
}
let pt = self.key.bytes
if pt.length() != 65 || pt[0] != b'\x04' {
raise Malformed("public key is not an uncompressed P-256 point")
}
let key = {
x: BigInt::from_octets(pt[1:33]),
y: BigInt::from_octets(pt[33:65]),
}
if !key.on_curve() {
raise Malformed("public key is not a point on P-256")
}
key
}
///|
/// Whether `now`, in seconds since the Unix epoch, falls inside the certificate's validity
/// window. Both ends are inclusive: RFC 5280 §4.1.2.5 gives them as the first and last instant
/// the certificate is valid, not as an open interval.
pub fn Cert::valid_at(self : Cert, now : Int64) -> Bool {
now >= self.validity.not_before && now <= self.validity.not_after
}
///|
/// Whether the dNSName `pattern` names `host` (RFC 6125 §6.4.3): label by label, ASCII case
/// insensitive. A first label of exactly `*` stands for one whole label, so `*.a.example` covers
/// `b.a.example` and neither `a.example` nor `c.b.a.example`. A `*` anywhere else, or fused to
/// the rest of its label as in `b*.a.example`, matches nothing — a partial wildcard is a claim on
/// a name the issuer never checked.
fn x509_dns_match(pattern : String, host : String) -> Bool {
let p = pattern.split(".").to_array()
let h = host.split(".").to_array()
if p.length() != h.length() || p.length() == 0 {
return false
}
// A lone `*` would otherwise name every single-label host. How much of the tail a wildcard may
// cover past that is a question about public suffixes, which needs a list this package has no
// business carrying; that rule belongs to the caller.
if p[0] == "*" && p.length() < 2 {
return false
}
for i in 0.. Bool {
for n in self.exts.san {
match n {
Dns(p) => if x509_dns_match(p, host) { return true }
_ => ()
}
}
false
}
///|
/// Check the certificate's signature under `key` — its issuer's public key, or its own if it is
/// self-signed. Only ecdsa-with-SHA256 is checked; another algorithm raises `Unsupported` rather
/// than returning false, so a caller cannot read "not checked" as "checked and bad".
pub fn Cert::signed_by(
self : Cert,
key : EcdsaPublicKey,
) -> Bool raise DerError {
if self.alg != oid_ecdsa_sha256 {
raise Unsupported("signature algorithm " + self.alg)
}
if self.sig.unused != 0 {
raise Malformed("signature BIT STRING has unused bits")
}
let sig = der_only(self.sig.bytes[:])
if sig.tag != 0x30 {
raise Malformed("ECDSA-Sig-Value is not a SEQUENCE")
}
let rs = sig.items()
if rs.length() != 2 {
raise Malformed("ECDSA-Sig-Value is not an r and an s")
}
let r = rs[0].int()
let s = rs[1].int()
if r < (1 : BigInt) || r >= p256_n || s < (1 : BigInt) || s >= p256_n {
return false
}
ecdsa_p256_sha256_verify(
self.tbs.to_owned(),
ecdsa_concat([r.to_octets(length=32), s.to_octets(length=32)]),
key,
)
}