mooncred
Mooncredthe referencei

The reference, by package.

Every public type, function and value of mooncred 0.6.5, read from each package's interface and the comments above its definitions. The guide shows them at work.

13 packages203 entriesgenerated by scripts/gen_docs.py
Mooncred@jwtii

jwt

JSON Web Signature, RFC 7515 · JSON Web Algorithms, RFC 7518 · JSON Web Token, RFC 7519 · EdDSA for JOSE, RFC 8037

Only the compact serialisation, which is the one tokens are written in. Signing and verification go through mooncrypt/spec's contracts, never through a particular algorithm, so a program that only issues HS256 tokens does not link an elliptic curve.

Refused Alg Clock Keyed OnClash Policy Wins policy claims_unverified header mac sign verify

errorRefused
pub(all) suberror Refused {
  Shape(String)
  Algorithm(want~ : String, got~ : String)
  Signature
  Expired(at~ : Double)
  TooEarly(at~ : Double)
  Issuer(want~ : String, got~ : String)
  Audience(want~ : Array[String])
  Subject(want~ : String, got~ : String)
  IssuedAt(at~ : Double)
  Type(want~ : String, got~ : String)
  Missing(claim~ : String)
} derive(Eq, @debug.Debug)

Why a token was not accepted.

Each reason is separate so a caller can map it to the right answer — an expired token deserves a different response from a forged one — and so a log says which check failed rather than that one did.

typeAlg
pub(all) enum Alg {
  HS256
  HS384
  HS512
  RS256
  RS384
  RS512
  PS256
  PS384
  PS512
  ES256
  ES384
  ES512
  EdDSA
} derive(Eq, @debug.Debug)

A JOSE signature algorithm (RFC 7518 §3.1), and the one RFC 8037 adds.

none is not here and never will be. It is the algorithm that made "verified" mean "not verified" in a dozen libraries, and a type that cannot express it cannot be talked into accepting it.

typeClock
pub(all) enum Clock {
  At(Int64)
  Ignored
} derive(Eq, @debug.Debug)

When the time claims are measured against.

A library has no clock — reading one is a side effect and there is no reason for a token verifier to have that power — so the instant comes in from the caller. Ignored is the way to read a token without asking whether it has expired: a debugging tool, an audit of tokens already collected, a migration that has to read what was issued last year. It is spelled out at the call site because the alternative is a verifier that quietly stopped checking.

typeKeyed
type Keyed

A keyed MAC, standing in as both signer and verifier.

HMAC is symmetric: the same key that signs verifies. What it is not is a signature — anyone who can check an HS256 token can mint one — so a token verified this way says only that someone holding the shared secret issued it.

implements @spec.Signer, @spec.Verifier
typeOnClash
pub(all) enum OnClash[T] {
  Ignore
  Panic
  Handle((T, T) -> T)
}

What to do when a setting is given twice.

Handle receives the base and the merged result and returns whichever it wants, so a caller can audit, refuse, or merge some other way entirely.

There is no Warn: a library has nowhere to warn to. It arrives when moonlog does.

typePolicy
pub(all) struct Policy {
  leeway : Int64
  expiry : Bool
  not_before : Bool
  issued_at : Bool
  issuer : String?
  audience : Array[String]
  subject : String?
  typ : String?
  required : Array[String]
} derive(Eq, @debug.Debug)

What a token has to satisfy beyond its signature.

Every field is a check that is either off or told what to expect, and every default is what the mainstream libraries do: exp and nbf are checked, iat is not, and iss, aud, sub and the header's typ are looked at only when the caller says what to expect. Nothing is enforced implicitly and nothing is skipped implicitly.

One of these is assembled once for a deployment and passed to every verification, which is what jsonwebtoken's Validation, .NET's TokenValidationParameters and jose's options object are for.

typeWins
pub(all) enum Wins {
  Base
  Extra
} derive(Eq, @debug.Debug)

Which side wins when a setting is given twice.

Every function here that takes both a base and something layered over it says which one wins, and lets a caller say otherwise. The defaults differ by function and each one says why: verify lets the override win, because a per-call argument is more specific than a policy assembled once; sign lets the base win, because the header it computes describes the signature it is about to make.

letpolicy
pub let policy : Policy

The policy a verification uses when the caller does not say otherwise.

fnclaims_unverified
pub fn claims_unverified(StringView) -> Map[String, Json] raise Refused

The claims of a token that has not been verified.

Whoever sent the token wrote these. Reading them before verify has returned is reading an attacker's input; it is here because a gateway sometimes must route on iss before it knows which key to fetch, and for no other reason.

fnheader
pub fn header(StringView) -> Map[String, Json] raise Refused

The header of a token that has not been verified.

This is for one thing: reading kid to choose which key to verify with. Nothing else in here has been checked by anybody.

fnmac
pub fn mac(() -> &@spec.Mac) -> Keyed

Take a way of making a keyed MAC and use it as a JWT key.

It takes a factory rather than a MAC because one state cannot authenticate two messages, and a key outlives the token it was used on.

fnsign
pub fn sign(Map[String, Json], Alg, &@spec.Signer, typ? : String?, extra? : Map[String, Json], wins? : Wins, clash? : OnClash[Map[String, Json]]) -> String

Sign a claims set as a compact JWT.

extra goes into the header beside alg and typ — kid is what it is usually for, so a verifier can pick the right key out of a set before it verifies anything.

typ is "JWT", which is what every library writes and what RFC 7519 §5.1 recommends. None leaves it out; another string puts that in — RFC 9068's access tokens want "at+jwt".

**extra cannot quietly rewrite alg or typ.** The header describes the signature this function is about to make; a token whose header says none while the bytes say HS256 is not a configuration choice, it is a mistake, so wins starts at Base and clash at Panic. A caller who means it can say wins=Extra, clash=Ignore.

The key is whatever can sign; this package never chooses an algorithm's implementation, so a program that issues only HS256 tokens links only HMAC.

fnverify
pub fn verify(StringView, Alg, &@spec.Verifier, now~ : Clock, policy? : Policy, wins? : Wins, clash? : OnClash[Policy], leeway? : Int64, expiry? : Bool, issuer? : String, audience? : Array[String]) -> Map[String, Json] raise Refused

Verify a compact JWT and return its claims.

**The algorithm is the caller's, not the token's.** A verifier that reads alg out of the header and then trusts it is how an RS256 public key gets used as an HS256 shared secret, and how none gets accepted; here the header must match what the caller already decided, or the token is refused.

The signature is checked before any claim is read, so a forged token is never inspected for its contents.

Configuration has three layers, each overriding the one before: @jwt.policy < the policy given here < the named arguments given here. wins turns the last two around; clash decides whether being given a setting twice is silent, fatal, or yours to arbitrate.

now is not configuration but data: the instant the time claims are measured against, or Ignored to read a token without asking whether it has expired — a debugging tool, an audit of tokens already collected, a migration that has to read what was issued last year.

fnAlg::name
pub fn Alg::name(Self) -> String

The name this algorithm goes by in a header.

fnAlg::of
pub fn Alg::of(StringView) -> Self raise Refused

Read an algorithm by name.

fnKeyed::sign
pub fn Keyed::sign(Self, BytesView) -> Bytes

@spec.Signer's sign, callable on the value directly.

fnKeyed::verify
pub fn Keyed::verify(Self, BytesView, BytesView) -> Bool

@spec.Verifier's verify, callable on the value directly.

fnPolicy::new
pub fn Policy::new(leeway? : Int64, expiry? : Bool, not_before? : Bool, issued_at? : Bool, issuer? : String, audience? : Array[String], subject? : String, typ? : String, required? : Array[String]) -> Self

Build a policy by naming the parts that differ from the default.

The same thing can be written { ..@jwt.policy, expiry: false }; this form exists because a named argument reads better when several parts differ. issuer, subject and typ can be set here but not cleared: the preset leaves all three unset, so there is nothing to clear. Starting from a base of one's own, { ..mine, issuer: None } is how a check is dropped.

moonbitstack/mooncred/jwt18 entries
Mooncred@jwkiii

jwk

JSON Web Key, RFC 7517 · Key parameters by type, RFC 7518 §6 · Octet key pairs, RFC 8037 §2

Reading and writing the format, and choosing a key out of a set. Turning the material into something that can sign belongs to the program, which knows which algorithms it wants linked.

Refused Jwk Material Set

errorRefused
pub(all) suberror Refused {
  Shape(String)
  Missing(field~ : String)
  NotBase64(field~ : String)
  Unknown(kty~ : String)
} derive(Eq, @debug.Debug)

Why a JWK could not be read.

typeJwk
type Jwk

One key, with the metadata a chooser needs.

typeMaterial
pub(all) enum Material {
  Oct(key~ : Bytes)
  Rsa(modulus~ : Bytes, exponent~ : Bytes, secret~ : Bytes?)
  Ec(curve~ : String, x~ : Bytes, y~ : Bytes, secret~ : Bytes?)
  Okp(curve~ : String, x~ : Bytes, secret~ : Bytes?)
} derive(Eq, @debug.Debug)

The key material a JWK carries, by key type (RFC 7518 §6).

Bytes, not keys: turning these into something that can sign would mean choosing an implementation for every algorithm, and then a program that reads one JWK would link all of them. The two lines that build a key from this material belong to the program, which knows which algorithms it wants.

typeSet
type Set

A JWK Set (RFC 7517 §5) — what a jwks_uri serves.

fnJwk::alg
pub fn Jwk::alg(Self) -> String?

alg, the one algorithm this key is for, when it says.

fnJwk::id
pub fn Jwk::id(Self) -> String?

kid, the name a verifier picks this key out of a set by.

fnJwk::is_private
pub fn Jwk::is_private(Self) -> Bool

Whether the key carries a private part.

A key set published at a well-known URL must answer false for every key in it, which is worth asserting in a test rather than discovering in a log.

fnJwk::json
pub fn Jwk::json(Self, secret? : Bool) -> Json

Write the key back out.

The private part is left out unless secret says otherwise, because the common reason to write a JWK is to publish it and the common accident is to publish the whole thing.

fnJwk::material
pub fn Jwk::material(Self) -> Material

The key material itself.

fnJwk::ops
pub fn Jwk::ops(Self) -> Array[String]

key_ops, empty when the key does not say.

fnJwk::parse
pub fn Jwk::parse(StringView) -> Self raise Refused

Read one key from its JSON text.

fnJwk::read
pub fn Jwk::read(Json) -> Self raise Refused

Read one key from a parsed document.

fnJwk::usage
pub fn Jwk::usage(Self) -> String?

use: "sig" or "enc".

fnSet::by_id
pub fn Set::by_id(Self, StringView) -> Jwk?

The key with this kid.

fnSet::keys
pub fn Set::keys(Self) -> Array[Jwk]

Every key that was understood.

fnSet::parse
pub fn Set::parse(StringView, skip? : Bool) -> Self raise Refused

Read a set from its JSON text.

fnSet::pick
pub fn Set::pick(Self, alg~ : String, id? : String, usage? : String?, first? : Bool) -> Jwk?

The key to verify a token with: the one named by kid if the token names one, otherwise the only candidate for this algorithm.

A key that says nothing about its algorithm is a candidate for any; one that names a different algorithm, or is marked for encryption, is not. When several remain, none is returned — picking one arbitrarily is how a rotated key silently keeps verifying — and first is how a caller asks for the other behaviour.

usage is the use a candidate must carry when it carries one; "sig" is the default because this is a signature library. None accepts any.

fnSet::read
pub fn Set::read(Json, skip? : Bool) -> Self raise Refused

Read a set from a parsed document.

A key the reader does not understand is skipped rather than fatal: RFC 7517 §5.1 says so, and a set that grows a post-quantum key must not stop every verifier that has not heard of it. skip turns that off for a caller who would rather hear about it.

moonbitstack/mooncred/jwk18 entries
Mooncred@jweiv

jwe

JSON Web Encryption, RFC 7516 · JSON Web Algorithms, RFC 7518 · AES Key Wrap, RFC 3394

The compact serialisation only, which is the one that travels in a header or a URL: five base64url parts — the header, the wrapped key, the IV, the ciphertext and the tag.

Refused Alg Enc Identity Key decrypt encrypt unwrap wrap

errorRefused
pub(all) suberror Refused {
  Malformed(String)
  Unsupported(String)
  Key
} derive(Eq, @debug.Debug)

Why a token could not be sealed or opened.

typeAlg
pub(all) enum Alg {
  Dir
  A128Kw
  A192Kw
  A256Kw
  RsaOaep256
} derive(Eq, @debug.Debug)

The key-management algorithm: how the content-encryption key reaches the recipient.

typeEnc
pub(all) enum Enc {
  A128Gcm
  A192Gcm
  A256Gcm
  A128CbcHs256
  A192CbcHs384
  A256CbcHs512
} derive(Eq, @debug.Debug)

The content-encryption algorithm: authenticated encryption over the payload.

typeIdentity
pub(all) enum Identity {
  Shared(Bytes)
  Rsa(@rsa.PrivateKey)
}

What a token is opened with.

typeKey
pub(all) enum Key {
  Shared(Bytes)
  Rsa(@rsa.PublicKey)
}

What a token is sealed to.

fndecrypt
pub fn decrypt(StringView, key~ : Identity, alg~ : Alg, enc~ : Enc) -> Bytes raise Refused

Open a token, checking that the header names the algorithms the caller expects.

The expected alg and enc are arguments and not read from the header on the caller's behalf for the reason JWT's none exists: a recipient that believes whatever the token says about itself can be talked into the weakest algorithm it supports. A token that names another algorithm is Malformed rather than opened under the caller's.

fnencrypt
pub fn encrypt(BytesView, key~ : Key, alg~ : Alg, enc~ : Enc, cek? : Bytes?, iv~ : BytesView, seed? : BytesView) -> String raise Refused

Seal a payload under a key, into the compact serialisation: five base64url parts, the header first, so the recipient knows what to do before it does anything.

cek is the content-encryption key and iv the nonce; both are the caller's, for the reason this library has no generator. Under dir the CEK is the shared key and cek must be None; under any other algorithm it is required and must be cek_size octets. seed is what RSA-OAEP-256 needs to make its padding: the algorithm is randomised, and the randomness has to come from somewhere that is not here.

fnunwrap
pub fn unwrap(BytesView, BytesView) -> Bytes raise Refused

The inverse: recover the key, or Malformed when the octets are not a wrapped key under this one. The integrity check is the initial value coming back out of the last round — that is the whole reason the algorithm mixes a counter into it.

fnwrap
pub fn wrap(BytesView, BytesView) -> Bytes raise Refused

Wrap a content-encryption key under a key-encryption key. The plaintext must be a whole number of eight-octet blocks and at least sixteen octets, which is what every CEK in RFC 7518 is.

moonbitstack/mooncred/jwe9 entries
Mooncred@tokenv

token

Signed tokens: HMAC over the caller's own octets, in a string a URL or a cookie can carry.

The shape is itsdangerous': a base64url payload, a base64url timestamp, and a base64url tag, separated by dots. What keeps two uses of one key apart is the salt, which is hashed into the tag and never travels with it, so a token signed for one purpose cannot be replayed against another even though the key is the same.

Refused Keys sign verify

errorRefused
pub(all) suberror Refused {
  Malformed(String)
  Signature
  Expired
} derive(Eq, @debug.Debug)

Why a token was not accepted.

typeKeys
pub(all) struct Keys {
  current : Bytes
  previous : Array[Bytes]
}

The keys a token may be verified against: the one signing now, and the ones retired but still honoured, newest first. Rotating is a push, and every token signed by the old key keeps working until the call that drops it.

fnsign
pub fn sign(BytesView, keys~ : Keys, salt? : String, at? : Int64?) -> String

Sign payload and return the token: payload.timestamp.tag, each part base64url without padding, so it survives a URL, a cookie and a header unchanged.

salt is what makes the signature specific to a purpose. It is not sent with the token; it is hashed into the tag, so a token issued by the password-reset path cannot be presented to the session path even when both use one key.

fnverify
pub fn verify(StringView, keys~ : Keys, salt? : String, max_age? : Int64?, now~ : Int64) -> Bytes raise Refused

The payload of a token, if the tag is one this key set could have made and the timestamp is inside max_age seconds of now.

A token with no timestamp has no age, so max_age does not apply to it: the format allows one and the caller that signs without at is saying the token does not expire. Pass at for anything that should.

fnKeys::new
pub fn Keys::new(BytesView) -> Self

A key set with one key in it.

fnKeys::rotate
pub fn Keys::rotate(Self, BytesView) -> Self

The same set with current retired and next signing from now on. A token signed a moment before the call still verifies; one signed a moment after verifies under the new key.

moonbitstack/mooncred/token6 entries
Mooncred@x509vi

x509

X.509 v3 certificates and certification paths, RFC 5280 · Service identity, RFC 6125 · PEM, RFC 7468 · Private keys, RFC 5958, RFC 5915, RFC 8017 §A.1.2

Read a certificate down to its extensions, check its signature under RSA, ECDSA or Ed25519, match it against a host name or address, and validate a chain to a set of trust anchors. Writes the self-signed P-256 certificate a TLS server needs, and reads its private key.

Refused Attr Cert Constraints Exts GeneralName Key Name Policy PrivateKey Roots Spki Usage Validity eku_any eku_client_auth eku_server_auth oid_cn policy alg_ec_key alg_ecdsa_sha256 algorithm common_name dns_match exts name parse pem pem_wrap pems self_signed signature_ok spki_of tbs_of validity_of verify

errorRefused
pub(all) suberror Refused {
  Malformed(at~ : Int32, String)
  Expired(at~ : Int32)
  Premature(at~ : Int32)
  Issuer
  Authority(at~ : Int32)
  Signature(at~ : Int32)
  Depth(limit~ : Int32)
  Budget(limit~ : Int32)
  Host(String)
  Purpose(at~ : Int32)
  Critical(at~ : Int32, oid~ : String)
  Constraint(at~ : Int32, name~ : String)
  Algorithm(at~ : Int32, oid~ : String)
  Weak(at~ : Int32)
  Comparisons(limit~ : Int32)
} derive(Eq, @debug.Debug)

Why a chain was refused. at is the position in the chain as given — 0 the leaf, then the intermediates in the order they arrived — of the certificate the refusal is about.

implements Show
typeAttr
pub(all) struct Attr {
  oid : String
  value : String
} derive(Eq, @debug.Debug)

One AttributeTypeAndValue: the attribute's OID in dotted-decimal form, and its value as text.

typeCert
pub(all) struct Cert {
  raw : BytesView
  tbs : BytesView
  version : Int32
  serial : @bigint.BigInt
  alg : String
  params : Bytes?
  issuer : Name
  validity : Validity
  subject : Name
  spki : Spki
  exts : Exts
  sig : @asn1.Bits
}

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. params is the encoding of the signature algorithm's parameters, which RSASSA-PSS needs.

typeConstraints
pub(all) struct Constraints {
  permitted : Array[GeneralName]
  excluded : Array[GeneralName]
} derive(Eq, @debug.Debug)

A NameConstraints (RFC 5280 §4.2.1.10): the subtrees every name below the CA must fall in, and the ones none may.

typeExts
pub(all) struct Exts {
  san : Array[GeneralName]
  ca : Bool
  path_len : Int32?
  key_usage : @asn1.Bits?
  eku : Array[String]?
  ski : Bytes?
  aki : Bytes?
  constraints : Constraints?
  unknown_critical : Array[String]
} derive(Eq, @debug.Debug)

The extensions a 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. ski and aki are the key identifiers path building uses to try the likeliest issuer first. 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.

typeGeneralName
pub(all) enum GeneralName {
  Dns(String)
  Email(String)
  Uri(String)
  Ip(Bytes)
  Dir(Name)
  Named(Int32, Bytes)
} derive(Eq, @debug.Debug)

A GeneralName (RFC 5280 §4.2.1.6). The forms that name a 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. In a name constraint an Ip is an address followed by its mask, eight or thirty-two octets; in a subjectAltName it is the bare address.

typeKey
pub(all) enum Key {
  Rsa(@rsa.PublicKey)
  Ec(@ecdsa.PublicKey)
  Ed25519(@ed25519.PublicKey)
}

A public key as a signature can be checked against. An RSA or EC key is held with the digest its own algorithm defaults to; a certificate's signature algorithm decides the digest it is actually checked with, so the key is re-read for each signature rather than fixed here.

implements Eq
typeName
pub(all) struct Name {
  rdns : Array[Array[Attr]]
  raw : Bytes
} derive(Eq, @debug.Debug)

A distinguished name as it is encoded — a sequence of relative distinguished names, each a set of attributes — together with the encoding itself. Path building matches an issuer to its subject by those octets, as webpki and Go do: two readings of one name must not decide whether a certificate chains.

typePolicy
pub(all) struct Policy {
  usage : Usage
  depth : Int32
  budget : Int32
  comparisons : Int32
  bits : Int32
} derive(Eq, @debug.Debug)

What a validation asks for and how far it may go. depth bounds the intermediates on the path, budget the signature checks spent finding it, comparisons the name-constraint comparisons, and bits the smallest RSA modulus a signature is accepted under.

typePrivateKey
pub(all) enum PrivateKey {
  Rsa(@rsa.PrivateKey)
  Ec(@ecdsa.PrivateKey)
  Ed25519(@ed25519.PrivateKey)
}

A private key as a server signs with: what a PRIVATE KEY, EC PRIVATE KEY or RSA PRIVATE KEY file holds.

typeRoots
pub struct Roots {

Trust anchors. An anchor is a name and a key: its own validity and signature are never checked, which is what lets a long-lived root carry a SHA-1 self-signature. Its name constraints, when it has them, bind everything below it.

typeSpki
pub(all) struct Spki {
  alg : String
  params : String?
  key : @asn1.Bits
} derive(Eq, @debug.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.

typeUsage
pub(all) enum Usage {
  Server
  Client
  Any
  Other(String)
} derive(Eq, @debug.Debug)

The key purpose the leaf must allow (RFC 5280 §4.2.1.12). Any asks for none and checks nothing; Other names a purpose by OID.

typeValidity
pub(all) struct Validity {
  not_before : Int64
  not_after : Int64
} derive(Eq, @debug.Debug)

A validity window, in seconds since the Unix epoch.

leteku_any
pub let eku_any : String

anyExtendedKeyUsage (RFC 5280 §4.2.1.12).

leteku_client_auth
pub let eku_client_auth : String

id-kp-clientAuth.

leteku_server_auth
pub let eku_server_auth : String

id-kp-serverAuth: the purpose a TLS server certificate needs (RFC 5280 §4.2.1.12).

letoid_cn
pub let oid_cn : String

commonName (X.520 §6.2.2), the attribute a name is usually read from.

letpolicy
pub let policy : Policy

A server certificate; webpki's limits — six intermediates, a hundred signature checks, a quarter of a million name-constraint comparisons; RSA keys of 2048 bits and up (CA/Browser Forum Baseline Requirements §6.1.5).

fnalg_ec_key
pub fn alg_ec_key() -> Bytes

The id-ecPublicKey with the prime256v1 (P-256) curve AlgorithmIdentifier (RFC 5480).

fnalg_ecdsa_sha256
pub fn alg_ecdsa_sha256() -> Bytes

The ecdsa-with-SHA256 AlgorithmIdentifier (RFC 5758): a SEQUENCE of just the OID, ECDSA taking no parameters.

fnalgorithm
pub fn algorithm(BytesView) -> (String, Bytes?) raise @asn1.Refused

An AlgorithmIdentifier from its DER: the OID in dotted-decimal form and the parameters as they were encoded, or None when the identifier has none. A certificate, a request, a revocation list and a response all carry one, and signature_ok takes exactly this pair.

fncommon_name
pub fn common_name(String) -> Bytes

A Name with a single commonName attribute: RDNSequence → RelativeDistinguishedName (SET) → AttributeTypeAndValue (SEQUENCE of the commonName OID 2.5.4.3 and the UTF8String value).

fndns_match
pub fn dns_match(String, String) -> Bool

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 and needs two more after it, so *.a.example covers b.a.example and neither a.example nor c.b.a.example, and *.com covers nothing. 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. A pattern or host that is not a well-formed DNS name matches nothing.

fnexts
pub fn exts(BytesView) -> Exts raise @asn1.Refused

The extensions a certificate carries, from the Extensions SEQUENCE on its own — the same encoding arrives in a certificate request's extensionRequest attribute and in a revocation list, so it is read here rather than at each of the three callers.

fnname
pub fn name(BytesView) -> Name raise @asn1.Refused

A name from the DER of its Name SEQUENCE, for the callers outside a certificate: a certificate request's subject, a revocation list's issuer, a responder's name.

fnparse
pub fn parse(BytesView) -> Cert raise @asn1.Refused

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.

fnpem
pub fn pem(String) -> Bytes raise @asn1.Refused

The DER inside a PEM envelope (RFC 7468).

Everything outside the -----BEGIN ...----- and -----END ...----- lines is dropped and the rest is base64-decoded. The label is not checked: what the octets are is the caller's question, and a CERTIFICATE and a PUBLIC KEY reach different readers from here.

Whitespace inside the body is ignored, which is what RFC 7468 §2 asks of a parser and what makes a CRLF file read the same as an LF one.

fnpem_wrap
pub fn pem_wrap(BytesView, label? : String) -> String

The PEM envelope around DER (RFC 7468 §2): the label, the base64 body broken at sixty-four columns, and the closing line. What this writes, openssl reads — the line length and the trailing newline are the parts a strict reader notices.

fnpems
pub fn pems(String, label? : String) -> Array[Bytes] raise @asn1.Refused

The DER of every PEM envelope in text with the label label, in order — a CA bundle such as ca-certificates.crt or curl's cacert.pem is a run of CERTIFICATE envelopes with prose between them. Envelopes with another label are passed over.

fnself_signed
pub fn self_signed(@ecdsa.PrivateKey, Bytes, String, String, String) -> Bytes

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.

fnsignature_ok
pub fn signature_ok(BytesView, alg~ : String, params~ : Bytes?, sig~ : @asn1.Bits, key~ : Spki) -> Bool raise @asn1.Refused

Check a signature made over tbs against key. The algorithm is named by its OID and the parameters of its AlgorithmIdentifier, exactly as a certificate, a certificate request, a revocation list or a response carries them.

RSA PKCS#1 v1.5 and PSS with SHA-256, SHA-384 or SHA-512, ECDSA with the same three digests on any of the four curves, and Ed25519. An algorithm outside that set raises Unsupported rather than returning false, so a caller cannot read "not checked" as "checked and bad". A key of the wrong kind for the algorithm returns false: no signature by that algorithm verifies under it.

fnspki_of
pub fn spki_of(@ecdsa.PublicKey) -> Bytes

A SubjectPublicKeyInfo for a P-256 key: the ecPublicKey/prime256v1 algorithm and the uncompressed point as a BIT STRING.

fntbs_of
pub fn tbs_of(Bytes, String, String, String, @ecdsa.PublicKey) -> Bytes

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).

fnvalidity_of
pub fn validity_of(String, String) -> Bytes

A Validity: notBefore and notAfter as UTCTime YYMMDDHHMMSSZ.

fnverify
pub fn verify(ArrayView[Bytes], Roots, now~ : Int64, host? : String, policy? : Policy) -> Array[Cert] raise Refused

Validate a chain and return the path found: the leaf, the intermediates it runs through, and the anchor it ends on.

chain is the leaf followed by the intermediates the peer sent, in any order; ones the path does not need are ignored, which is what a TLS server's habit of sending a cross-signed extra asks for. now is the time to judge validity at, in seconds since the Unix epoch. host, when given, is a DNS name or an IP literal the leaf must name; without it names are not compared.

The rules, and whose they are: - Names come from subjectAltName only, never the commonName (RFC 6125, Go since 1.15, webpki). - Every issuer must be a CA, have keyCertSign when it lists key usages, and respect its pathLenConstraint. A leaf that is a CA is refused, as webpki does. - Validity is checked on every certificate but the anchor. - The leaf must allow policy.usage; an issuer that lists key purposes must allow it too (Go and the Mozilla root programme chain the restriction). - An unknown critical extension anywhere on the path refuses it. - Name constraints bind dNSName, iPAddress, rfc822Name, directoryName and the host of a URI; a constraint of another form, over a name of that form, refuses rather than guesses. - Several candidate issuers with the issuer's name are each tried, preferring the one whose subjectKeyIdentifier matches the authorityKeyIdentifier; every signature check spends from policy.budget.

When no path works, the refusal is the first reason a candidate path failed, or Issuer when there was no candidate at all.

fnCert::matches
pub fn Cert::matches(Self, String) -> Bool

Whether host is one of the certificate's identities: a DNS name against the subjectAltName dNSName entries, an IPv4 or IPv6 literal against the iPAddress entries. The two never cross — reading an address as a name is how a certificate for a name comes to be trusted for an address. A certificate with no subjectAltName matches nothing: the commonName stopped being an identity with RFC 6125, and Go and webpki no longer fall back to it.

fnCert::signed_by
pub fn Cert::signed_by(Self, Spki) -> Bool raise @asn1.Refused

Check the certificate's signature under issuer — its issuer's key, or its own if it is self-signed.

fnCert::valid_at
pub fn Cert::valid_at(Self, Int64) -> Bool

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.

fnExts::empty
pub fn Exts::empty() -> Self

An Exts with nothing in it — the starting point for a caller assembling the extensions a certificate or a request should ask for.

fnExts::encode
pub fn Exts::encode(Self) -> Bytes raise @asn1.Refused

The Extensions SEQUENCE for these extensions (RFC 5280 §4.2), in the order this package writes them. An extension it reads but does not write — a name constraint, or an unknown critical one whose value was dropped on the way in — is Unsupported rather than silently left out, because leaving it out changes what the certificate means.

fnName::cn
pub fn Name::cn(Self) -> String?

The name's commonName, if it has one.

fnName::get
pub fn Name::get(Self, String) -> String?

The value of the first attribute with this OID, in encoding order.

fnPolicy::new
pub fn Policy::new(usage? : Usage, depth? : Int32, budget? : Int32, comparisons? : Int32, bits? : Int32) -> Self

A policy with any field changed from the preset.

fnPrivateKey::encrypt
pub fn PrivateKey::encrypt(Self, passphrase~ : StringView, salt~ : BytesView, iv~ : BytesView, scheme? : @pbe.Scheme, rounds? : Int32) -> Bytes raise @pbe.Refused

Encrypt a key under a passphrase, as an EncryptedPrivateKeyInfo (RFC 5958 §2).

salt and iv are the caller's, for the reason this library has no generator: a salt must be fresh for every file ever written, and only the caller knows where freshness comes from.

fnPrivateKey::encrypt_pem
pub fn PrivateKey::encrypt_pem(Self, passphrase~ : StringView, salt~ : BytesView, iv~ : BytesView, scheme? : @pbe.Scheme, rounds? : Int32) -> String raise @pbe.Refused

The same as an ENCRYPTED PRIVATE KEY file — the text openssl pkcs8 -topk8 writes and openssl pkcs8 -in reads.

fnPrivateKey::pem
pub fn PrivateKey::pem(String) -> Self raise @asn1.Refused

Read the first private key in a PEM file, under any of the labels openssl writes: PRIVATE KEY, EC PRIVATE KEY (after which an EC PARAMETERS block is passed over) or RSA PRIVATE KEY. An encrypted key, in either the PKCS#8 or the legacy Proc-Type form, is Unsupported here: pem_encrypted is the reader that has a passphrase to work with.

fnPrivateKey::pem_encrypted
pub fn PrivateKey::pem_encrypted(String, passphrase~ : StringView) -> Self raise @pbe.Refused

The same from the PEM text around it. A file that holds a key this package cannot decrypt is Passphrase when the octets do not open, which is what a wrong passphrase looks like.

fnPrivateKey::pkcs8
pub fn PrivateKey::pkcs8(Self) -> Bytes raise @asn1.Refused

The PKCS#8 PrivateKeyInfo for a key in hand (RFC 5958): the version, the algorithm, and the SEC1 or CurvePrivateKey octets inside an OCTET STRING.

An RSA key cannot be rebuilt from @rsa.PrivateKey: the type keeps its primes to itself and offers no way to read them back, so a key that arrived as PKCS#1 or PKCS#8 cannot be turned into the other without the octets it arrived in. The caller who has those octets — the file they read the key from — encrypts them directly.

fnPrivateKey::pkcs8_pem
pub fn PrivateKey::pkcs8_pem(Self) -> String raise @asn1.Refused

The plainkey PKCS#8 file this one would have been before a passphrase was put on it, for the keys where that can be rebuilt at all.

fnPrivateKey::public
pub fn PrivateKey::public(Self) -> Key

The public half: what the certificate for this key carries, so key.public() == spki.key() tells whether a certificate and a key belong together.

fnPrivateKey::read
pub fn PrivateKey::read(BytesView) -> Self raise @asn1.Refused

Read a private key from DER in any of the three forms in use: PKCS#8 (RFC 5958), SEC1's ECPrivateKey (RFC 5915), or PKCS#1's RSAPrivateKey (RFC 8017 §A.1.2). The form is told by the second element of the outer SEQUENCE, as OpenSSL tells it.

An RSA key whose primes do not multiply to its modulus, an EC key whose scalar is out of range or whose embedded public key is not its own, and an Ed25519 seed of the wrong length are all Malformed. An algorithm that does not sign (X25519) or a multi-prime RSA key is Unsupported.

fnPrivateKey::read_encrypted
pub fn PrivateKey::read_encrypted(BytesView, passphrase~ : StringView) -> Self raise @pbe.Refused

Read an encrypted PKCS#8 key — an ENCRYPTED PRIVATE KEY file — with the passphrase that protects it. PBES2 with PBKDF2 and AES or triple DES, and the PBES1 schemes a PKCS#12 file or an older openssl pkcs8 -v1 writes, all open here.

fnRoots::add
pub fn Roots::add(Self, BytesView) -> Unit raise @asn1.Refused

Add one anchor. The same certificate added twice is kept once.

fnRoots::iter
pub fn Roots::iter(Self) -> Iter[Cert]

The anchors, in the order they were added.

fnRoots::length
pub fn Roots::length(Self) -> Int32

How many anchors there are.

fnRoots::new
pub fn Roots::new(certs? : ArrayView[Bytes]) -> Self raise @asn1.Refused

Anchors from DER certificates.

fnRoots::pem
pub fn Roots::pem(String) -> Self raise @asn1.Refused

Anchors from a PEM bundle such as /etc/ssl/certs/ca-certificates.crt or curl's cacert.pem.

fnSpki::encode
pub fn Spki::encode(Key) -> Bytes

The SubjectPublicKeyInfo for a public key: the algorithm, the parameters that algorithm takes — NULL for RSA, the named curve for EC, none for Ed25519 — and the key as a BIT STRING. This is what a certificate, a request or a key file carries, built from a key in hand.

fnSpki::key
pub fn Spki::key(Self) -> Key raise @asn1.Refused

The public key a SubjectPublicKeyInfo carries: RSA, ECDSA on P-256, P-384, P-521 or secp256k1, or Ed25519. Another algorithm raises Unsupported.

fnSpki::p256
pub fn Spki::p256(Self) -> @ecdsa.PublicKey raise @asn1.Refused

The P-256 public key a SubjectPublicKeyInfo carries. Refuses any other curve.

fnSpki::read
pub fn Spki::read(BytesView) -> Self raise @asn1.Refused

Read a bare SubjectPublicKeyInfo.

A certificate carries one in Cert::spki; this is for the ones that travel on their own — a PUBLIC KEY PEM file, or the key MySQL's caching_sha2_password hands a client mid-handshake.

fnSpki::rsa
pub fn Spki::rsa(Self, digest? : @rsa.Digest, scheme? : @rsa.Scheme) -> @rsa.PublicKey raise @asn1.Refused

The RSA public key a SubjectPublicKeyInfo carries.

The key bits are an RSAPublicKey ::= SEQUENCE { modulus INTEGER, publicExponent INTEGER } (RFC 8017 §A.1.1). Refuses any algorithm but rsaEncryption.

digest and scheme are the signature parameters the key is built with. They do not enter encryption at all — @rsa.PublicKey::encrypt takes its own — so a caller that only encrypts can leave them alone.

moonbitstack/mooncred/x50963 entries
Mooncred@csrvii

csr

PKCS#10 certification requests, RFC 2986 · Attributes, RFC 2985

Read the request a client sends before it has a certificate, check the signature that proves it holds the key it asks to have certified, and write one for a key in hand.

Request parse self_signed self_signed_pem

typeRequest
pub(all) struct Request {
  raw : BytesView
  tbs : BytesView
  subject : @x509.Name
  spki : @x509.Spki
  exts : @x509.Exts
  alg : String
  params : Bytes?
  sig : @asn1.Bits
}

A parsed certification request. tbs is the exact CertificationRequestInfo the signature covers, kept as a view into the input for the same reason a certificate keeps its own: a signature is checked over the octets that arrived, never over a re-encoding of them.

fnparse
pub fn parse(BytesView) -> Request raise @asn1.Refused

Read a DER certification request.

Anything that is not exactly one well-formed request is Malformed: a version other than v1, a field past the attributes, an attribute that repeats, or an extensionRequest that does not carry exactly one Extensions. The signature is not checked here — verified does that, and keeping the two apart is what lets a caller read a request it cannot yet verify.

fnself_signed
pub fn self_signed(@x509.PrivateKey, subject~ : Bytes, exts? : @x509.Exts?) -> Bytes raise @asn1.Refused

Write a certification request for a key in hand (RFC 2986 §4).

subject is the DER of a Name — @x509.common_name for the usual case — and exts the extensions to ask for, which travel in an extensionRequest attribute. The request is signed by key, so the CA that reads it can tell the requester holds the private half.

fnself_signed_pem
pub fn self_signed_pem(@x509.PrivateKey, subject~ : Bytes, exts? : @x509.Exts?) -> String raise @asn1.Refused

The same request inside the PEM envelope openssl req writes.

fnRequest::asks_for
pub fn Request::asks_for(Self, @x509.Key) -> Bool

Whether the request asks for the certificate of this key: the key inside the request and this one are the same. A CA compares before it issues, so a request and the key it was made from cannot be swapped for another pair.

fnRequest::der
pub fn Request::der(Self) -> BytesView

The DER this request arrived as.

fnRequest::pem
pub fn Request::pem(String) -> Self raise @asn1.Refused

Read the first certification request in a PEM file. openssl req writes the label CERTIFICATE REQUEST; the older NEW CERTIFICATE REQUEST is read too, because files with it are still in the wild.

fnRequest::to_pem
pub fn Request::to_pem(Self) -> String

The PEM envelope around this request, in the label openssl req writes.

fnRequest::verified
pub fn Request::verified(Self) -> Bool raise @asn1.Refused

Whether the request's signature verifies under the public key it carries. A request is always self-signed — that is what separates it from a certificate — so the key to check against is the one inside it, and an algorithm this package does not implement raises Unsupported rather than returning false.

moonbitstack/mooncred/csr9 entries
Mooncred@pbeviii

pbe

Encrypted private keys, PKCS#8 · PBES2, PBKDF2 and PBES1, RFC 8018 · PKCS#12 password-based encryption, RFC 7292 §B

Reading and writing an EncryptedPrivateKeyInfo: the PBES2 schemes built on PBKDF2, the older PBES1 schemes PKCS#12 files still carry, and the three-key triple DES and RC2 the legacy ones are keyed with.

Refused Encrypted Kdf Prf Scheme decrypt encrypt pkcs12_key pkcs12_key_with

errorRefused
pub(all) suberror Refused {
  Malformed(String)
  Unsupported(String)
  Passphrase
} derive(Eq, @debug.Debug)

Why an encrypted key could not be read or written.

typeEncrypted
pub(all) struct Encrypted {
  raw : Bytes
  alg : String
  kdf : Kdf
  cipher : String?
  iv : Bytes?
  icv : Int32
  body : Bytes
} derive(Eq, @debug.Debug)

A parsed EncryptedPrivateKeyInfo. body is the ciphertext; everything else is what is needed to turn it back into a PrivateKeyInfo.

typeKdf
pub(all) enum Kdf {
  Pbkdf2(salt~ : Bytes, rounds~ : Int32, prf~ : String, length~ : Int32?)
  Pkcs12(salt~ : Bytes, rounds~ : Int32)
  Pbkdf1(salt~ : Bytes, rounds~ : Int32, md5~ : Bool)
} derive(Eq, @debug.Debug)

The key derivation a file names, with what it needs to run.

typePrf
pub(all) enum Prf {
  Sha1
  Sha224
  Sha256
  Sha384
  Sha512
}

The pseudorandom function PBKDF2 is keyed with (RFC 8018 §5.2).

typeScheme
pub(all) enum Scheme {
  Aes128Cbc
  Aes192Cbc
  Aes256Cbc
  Aes128Gcm
  Aes192Gcm
  Aes256Gcm
  TripleDesCbc
  LegacyTripleDes
  LegacyRc2
}

How to write an encrypted key. The first six are PBES2 with PBKDF2; the last two are the PBES1 schemes a PKCS#12 file needs when it has to be readable by a client from before 2010.

fndecrypt
pub fn decrypt(BytesView, BytesView, passphrase~ : StringView) -> Bytes raise Refused

Decrypt ciphertext under an AlgorithmIdentifier that arrived on its own, outside an EncryptedPrivateKeyInfo. A PKCS#12 EncryptedData carries its scheme this way, and so does a CMS EnvelopedData; wrapping the two fields back into the file they would have been is what lets one reader serve them all.

fnencrypt
pub fn encrypt(BytesView, passphrase~ : StringView, salt~ : BytesView, iv~ : BytesView, scheme? : Scheme, rounds? : Int32) -> Bytes raise Refused

Encrypt a PrivateKeyInfo under a passphrase.

salt and iv are the caller's: a salt must be new for every key ever written, and this library has no generator to draw one from. Sixteen octets of salt is what PBKDF2 is usually given; the IV must be the cipher's block size — sixteen for AES, eight for triple DES, and twelve for a GCM nonce.

fnpkcs12_key
pub fn pkcs12_key(StringView, BytesView, id~ : Int32, rounds~ : Int32, len~ : Int32) -> Bytes raise Refused

The same derivation with SHA-1, which is what PBES1 under the PKCS#12 OIDs is defined with.

fnpkcs12_key_with
pub fn pkcs12_key_with(prf~ : Prf, StringView, BytesView, id~ : Int32, rounds~ : Int32, len~ : Int32) -> Bytes raise Refused

The PKCS#12 key derivation (RFC 7292 §B.2). id says what the key is for: 1 an encryption key, 2 an IV, 3 a MAC key.

The hash is a parameter here because a PKCS#12 file's MAC names its own digest — OpenSSL 3 writes SHA-256 where the 1999 files had SHA-1 — and the width the derivation mixes in is that hash's block size, not its output length.

fnEncrypted::algorithm
pub fn Encrypted::algorithm(Self) -> Bytes raise Refused

The encryptionAlgorithm field of this file, as DER — the AlgorithmIdentifier a container needs when it stores these octets somewhere other than an EncryptedPrivateKeyInfo, as a PKCS#12 EncryptedData does.

fnEncrypted::decrypt
pub fn Encrypted::decrypt(Self, passphrase~ : StringView) -> Bytes raise Refused

Recover the PrivateKeyInfo these octets hold.

fnEncrypted::name
pub fn Encrypted::name(Self) -> String

What the file calls itself, for a log line or an error message.

fnEncrypted::parse
pub fn Encrypted::parse(BytesView) -> Self raise Refused

Read an EncryptedPrivateKeyInfo.

moonbitstack/mooncred/pbe13 entries
Mooncred@pkcs12ix

pkcs12

PKCS#12 personal information exchange, RFC 7292 · PKCS#7 content types, RFC 5652 §3

Read and write the container a key and its certificates travel in: the AuthenticatedSafe, the SafeBags inside it, both ways of encrypting a bag, and the digest that ties the whole file to the passphrase. The file's MAC is checked before anything is decrypted, because a container that does not authenticate should not be asked to decrypt.

Refused Contents Mac Pfx build parse

errorRefused
pub(all) suberror Refused {
  Malformed(String)
  Unsupported(String)
  Passphrase
}

Why a container could not be read or written: the octets are not a PFX, a scheme or a bag type is not one this package implements, or the passphrase is not the file's.

typeContents
pub(all) struct Contents {
  certs : Array[Bytes]
  key : Bytes?
}

What a container held once it was opened: the certificates in the order the bags carried them, and the private key as a PKCS#8 PrivateKeyInfo.

typeMac
pub(all) struct Mac {
  alg : String
  digest : Bytes
  salt : Bytes
  iterations : Int32
}

The MAC a PFX carries (RFC 7292 §4): the digest algorithm, the digest, the salt it was stretched with, and the round count. OpenSSL 3 writes SHA-256 where the 1999 files had SHA-1.

typePfx
pub(all) struct Pfx {
  raw : BytesView
  version : Int32
  auth_safe : BytesView
  mac : Mac?
}

A parsed PFX. auth_safe is the AuthenticatedSafe octets the MAC covers, kept as a view for the reason every signature in this library is checked over what arrived: re-encoding before verifying would check a file that nobody sent.

fnbuild
pub fn build(certs~ : ArrayView[Bytes], key~ : Bytes?, passphrase~ : StringView, salt~ : BytesView, iv~ : BytesView, mac_salt~ : BytesView, scheme? : @pbe.Scheme, rounds? : Int32, mac_rounds? : Int32) -> Bytes raise Refused

Write a container holding certs and, when there is one, key — a PKCS#8 PrivateKeyInfo.

The certificates go into an EncryptedData under scheme and the key into a shrouded key bag under the same one, which is the shape OpenSSL writes and the shape every client since 1999 knows how to open. salt and iv are the caller's for the reason this library has no generator; the same pair encrypts both bags, so they must be fresh for every file, and the MAC has a salt of its own because it is the one value a reader can check without a plaintext.

fnparse
pub fn parse(BytesView) -> Pfx raise Refused

Parse a PFX. Only the envelope is read here: the bags need the passphrase, so contents is the call that opens them, and verify is the one that says whether the file was edited.

fnPfx::contents
pub fn Pfx::contents(Self, passphrase~ : StringView) -> Contents raise Refused

Open the container: the certificates every bag carried, and the private key with its shrouding removed. The MAC is not checked here — verify is — because a caller may legitimately want to look inside a file it cannot authenticate, and a library that refuses to say what is in a file until it likes the file is a library that cannot be used to find out why.

fnPfx::has_mac
pub fn Pfx::has_mac(Self) -> Bool

Whether the file carries a MAC at all. A PFX without one is not tampered with by definition — there is nothing to check it against — but it is also not authenticated, and a caller that needs that distinction can only make it if it is told.

fnPfx::verify
pub fn Pfx::verify(Self, passphrase~ : StringView) -> Bool raise Refused

Whether the MAC this file carries is the one its passphrase produces over its AuthenticatedSafe.

False for a file with no MAC: nothing was verified, which is not the same as verified good. The comparison is over the whole digest and does not stop at the first octet that differs, so the answer leaks nothing about how close a guess was.

moonbitstack/mooncred/pkcs129 entries
Mooncred@revokex

revoke

Certificate revocation, RFC 5280 §5 · Online Certificate Status Protocol, RFC 6960

Both answer the same question — is this certificate still good — and neither answers it about the network. Fetching a list or a response is the caller's business; reading one, checking that the issuer signed it, and deciding what it says about a certificate is this package's.

Basic Crl Entry Ocsp Responder Revoked Single Status

typeBasic
pub(all) struct Basic {
  tbs : BytesView
  responder : Responder
  produced_at : Int64
  responses : Array[Single]
  alg : String
  params : Bytes?
  sig : @asn1.Bits
}

The signed part of a response (RFC 6960 §4.2.1): who answered, when, and what it said.

typeCrl
pub(all) struct Crl {
  raw : BytesView
  tbs : BytesView
  issuer : @x509.Name
  this_update : Int64
  next_update : Int64?
  entries : Array[Entry]
  alg : String
  params : Bytes?
  sig : @asn1.Bits
}

A parsed CertificateList (RFC 5280 §5.1). tbs is the exact TBSCertList the signature covers.

typeEntry
pub(all) struct Entry {
  serial : @bigint.BigInt
  revoked : Revoked
}

One line of a revocation list.

typeOcsp
pub(all) struct Ocsp {
  raw : BytesView
  status : Int32
  basic : Basic?
}

A whole OCSPResponse (RFC 6960 §4.2.1): the transport-level status, and the signed answer when that status is successful.

typeResponder
pub(all) enum Responder {
  ByName(@x509.Name)
  ByKey(Bytes)
} derive(Eq, @debug.Debug)

How the responder names itself (RFC 6960 §4.2.2.3): by its distinguished name, or by a hash of its public key.

typeRevoked
pub(all) struct Revoked {
  at : Int64
  reason : Int32?
} derive(Eq, @debug.Debug)

A withdrawal: the time it took effect and the reason the issuer gave.

typeSingle
pub(all) struct Single {
  hash : String
  issuer_name_hash : Bytes
  issuer_key_hash : Bytes
  serial : @bigint.BigInt
  status : Status
  this_update : Int64
  next_update : Int64?
}

One answer inside a response: which certificate it is about, what the answer is, and the window it is good for.

typeStatus
pub(all) enum Status {
  Good
  Revoked(Revoked)
  Unknown
} derive(Eq, @debug.Debug)

What a revocation source says about a certificate.

fnBasic::fresh
pub fn Basic::fresh(Self, Int64) -> Bool

Whether every answer in the response is current at now.

fnBasic::signed_by
pub fn Basic::signed_by(Self, @x509.Spki) -> Bool raise @asn1.Refused

Whether the response was signed by issuer. A responder may sign with a key of its own — the certs field exists for that — in which case the caller passes the responder's key instead.

fnCrl::fresh
pub fn Crl::fresh(Self, Int64) -> Bool

Whether the list is current at now, in seconds since the Unix epoch: thisUpdate has passed and nextUpdate has not. A list with no nextUpdate never expires by its own terms, which RFC 5280 §5.1.2.5 allows and which a caller has to weigh for itself.

fnCrl::parse
pub fn Crl::parse(BytesView) -> Self raise @asn1.Refused

Read a DER CertificateList.

The version is optional and the entry list is optional, so an empty list — a CA that has withdrawn nothing — is a list with no entries rather than a malformed one. Anything that is not exactly one well-formed list is Malformed.

fnCrl::pem
pub fn Crl::pem(String) -> Self raise @asn1.Refused

Read the first revocation list in a PEM file, under the label openssl ca -gencrl writes.

fnCrl::signed_by
pub fn Crl::signed_by(Self, @x509.Spki) -> Bool raise @asn1.Refused

Whether the list was signed by issuer — the key of the CA whose certificates it speaks for. Until this is true, nothing else the list says means anything.

fnCrl::status
pub fn Crl::status(Self, @x509.Cert) -> Status

What the list says about this certificate.

Unknown when the list belongs to another issuer: a list only speaks for the certificates its issuer signed, and serials are the issuer's own numbering. Freshness and the signature are the caller's to check — signed_by and fresh are why they can.

fnOcsp::parse
pub fn Ocsp::parse(BytesView) -> Self raise @asn1.Refused

Parse a DER OCSPResponse.

A response whose status is not successful carries no answer and is read as such rather than refused: tryLater and unauthorized are things a responder says, and a caller that treats them as a parse failure cannot retry the one it should.

fnOcsp::status
pub fn Ocsp::status(Self, @x509.Cert, @x509.Cert) -> Status raise @asn1.Refused

What the response says about this certificate, given the issuer that signed it.

The CertID is what makes the answer about this certificate: the hash of the issuer's name, the hash of its public key and the serial all have to match, so a response about another certificate under another CA is Unknown rather than an answer that was taken on faith.

moonbitstack/mooncred/revoke17 entries
Mooncred@policyxi

policy

Certificate policies, RFC 5280 §6.1

Additive: path validation in x509 is untouched, and a caller that never asks for a policy never links this.

Refused Cert any_policy policies read

errorRefused
pub(all) suberror Refused {
  Malformed(String)
} derive(Eq, @debug.Debug)

Why a certificate's policies could not be read.

typeCert
pub(all) struct Cert {
  policies : Array[String]?
  mappings : Array[(String, String)]
  require_explicit : Int32?
  inhibit_mapping : Int32?
  inhibit_any : Int32?
} derive(Eq, @debug.Debug)

The policy extensions of one certificate, as they bear on the tree.

letany_policy
pub let any_policy : String

anyPolicy (RFC 5280 §4.2.1.4): the identifier that stands for every policy.

fnpolicies
pub fn policies(Array[Cert], initial~ : Array[String], any? : Bool) -> Array[String]

The policies that survive the chain, in the order they were first named.

An empty result is the answer §6.1 gives a chain that is not valid for the initial policy set. initial is the set the relying party is willing to accept; empty means any policy, which is what a caller with no opinion means.

fnread
pub fn read(BytesView) -> Cert raise Refused

Read the four policy extensions out of a certificate.

fnCert::empty
pub fn Cert::empty() -> Self

A certificate that constrains nothing, which is what a certificate with none of these extensions is.

moonbitstack/mooncred/policy6 entries
Mooncred@cosexii

cose

COSE, RFC 9052 · COSE algorithms, RFC 9053 · CWT, RFC 8392

Three structures — COSE_Sign1, COSE_Mac0, COSE_Encrypt0 — and the claim set CWT signs.

Refused Alg Claims Clock Keyed Message decrypt0 encrypt0 mac mac0 macd parse sign1 signed uncovered verified verify1 verify_mac0

errorRefused
pub(all) suberror Refused {
  Malformed(String)
  Unsupported(String)
  Signature
  Key
  Expired(at~ : Int64)
  TooEarly(at~ : Int64)
} derive(Eq, @debug.Debug)

Why a message could not be made or opened.

typeAlg
pub(all) enum Alg {
  Es256
  EdDsa
  Rs256
  Hs256
  A128Gcm
  A192Gcm
  A256Gcm
} derive(Eq, @debug.Debug)

The algorithm, by its COSE identifier (RFC 9053).

One enumeration covers signing, MAC and encryption because a COSE header names all three in the same alg label; which of them a value means is decided by the structure it appears in, and a value that cannot be that structure's is refused.

typeClaims
pub(all) struct Claims {
  iss : String?
  sub : String?
  aud : String?
  exp : Int64?
  nbf : Int64?
  iat : Int64?
  cti : Bytes?
} derive(Eq, @debug.Debug)

The registered claims of RFC 8392 §3.1.

typeClock
pub(all) enum Clock {
  At(Int64)
  Ignored
} derive(Eq, @debug.Debug)

When the caller says it is.

The clock is an argument rather than something this library reads, for the reason none of these packages calls the system: a verifier that consults its own clock cannot be tested at the moment a token expires, and one that cannot be tested that way is one nobody checks.

typeKeyed
pub struct Keyed {

A keyed MAC, standing in as both signer and verifier, the way jwt.mac does: it takes a factory because one MAC state cannot authenticate two messages and a key outlives the message it was used on.

implements @spec.Signer, @spec.Verifier
typeMessage
pub(all) struct Message {
  head : Bytes
  payload : Bytes
  tail : Bytes
} derive(Eq, @debug.Debug)

A parsed COSE message: the head header as it arrived, the payload, and whatever else the structure carries.

fndecrypt0
pub fn decrypt0(BytesView, alg~ : Alg, key~ : BytesView, external? : BytesView) -> Bytes raise Refused

Open a COSE_Encrypt0: the nonce comes from the head header, which is authenticated, so a message whose header was changed does not open.

fnencrypt0
pub fn encrypt0(BytesView, alg~ : Alg, key~ : BytesView, iv~ : BytesView, kid? : BytesView?, external? : BytesView) -> Bytes raise Refused

Encrypt a payload into a COSE_Encrypt0, under a key the caller supplies.

The nonce is not generated here for the reason no other package generates one: entropy is the caller's to find, and a library that quietly reuses a nonce under a long-lived key is worse than one that asks.

fnmac
pub fn mac(() -> &@spec.Mac) -> Keyed

A MAC factory as a Keyed, which makes and checks COSE_Mac0 tags.

fnmac0
pub fn mac0(BytesView, alg~ : Alg, &@spec.Signer, kid? : BytesView?, external? : BytesView) -> Bytes raise Refused

MAC a payload into a COSE_Mac0.

fnmacd
pub fn macd(Claims, alg~ : Alg, &@spec.Signer, kid? : BytesView?, external? : BytesView) -> Bytes raise Refused

MAC a claim set into a COSE_Mac0, and verify one back.

fnparse
pub fn parse(BytesView, tag~ : UInt64) -> Message raise Refused

Read one of the three single-recipient structures, checking its tag and its shape.

tag is the number the caller expects, so a COSE_Mac0 handed to a verifier of signatures is refused rather than verified under the wrong rule.

fnsign1
pub fn sign1(BytesView, alg~ : Alg, &@spec.Signer, kid? : BytesView?, external? : BytesView) -> Bytes raise Refused

Sign a payload into a COSE_Sign1, wrapped in its tag.

alg is not consulted to choose a key — the signer knows its own — but it is written into the head header and it must be the algorithm the signer actually produces, or the message names something it is not. external is the additional data of RFC 9052 §4.4, which a protocol uses to bind a message to its context; the empty byte string is the usual choice.

fnsigned
pub fn signed(Claims, alg~ : Alg, &@spec.Signer, kid? : BytesView?, external? : BytesView) -> Bytes raise Refused

Sign a claim set into a COSE_Sign1.

fnuncovered
pub fn uncovered(BytesView, alg~ : Alg, &@spec.Verifier, now~ : Clock, leeway? : Int64, external? : BytesView) -> Claims raise Refused

Verify a COSE_Mac0 made by macd and read the claim set out of it, judging its times as verified does.

fnverified
pub fn verified(BytesView, alg~ : Alg, &@spec.Verifier, now~ : Clock, leeway? : Int64, external? : BytesView) -> Claims raise Refused

Verify a COSE_Sign1 that carries a claim set, and judge its times.

now has no default: exp and nbf are checked against it with leeway seconds of slack either side, and now=Ignored skips the check, which a caller has to write out. The sixty-second leeway is python-cwt's.

fnverify1
pub fn verify1(BytesView, alg~ : Alg, &@spec.Verifier, external? : BytesView) -> Bytes raise Refused

Check a COSE_Sign1 and hand back its payload.

fnverify_mac0
pub fn verify_mac0(BytesView, alg~ : Alg, &@spec.Verifier, external? : BytesView) -> Bytes raise Refused

Check a COSE_Mac0 and hand back its payload.

fnAlg::name
pub fn Alg::name(Self) -> String

The algorithm's name as JOSE spells it, for messages and logs; on the wire COSE writes the integer identifier instead.

fnClaims::check
pub fn Claims::check(Self, now~ : Clock, leeway? : Int64) -> Unit raise Refused

Whether the claim set is valid at now, which is the only judgement a verifier can make without knowing what the token is for.

The leeway is the seconds of slack allowed either side, for the reason a signer's clock and a verifier's rarely agree exactly: with a leeway of sixty, a token that expired half a minute ago still passes.

fnClaims::new
pub fn Claims::new(iss? : String, sub? : String, aud? : String, exp? : Int64, nbf? : Int64, iat? : Int64, cti? : Bytes) -> Self

A claim set from the claims given; the others stay absent, not empty.

fnClaims::of
pub fn Claims::of(@cbor.Value) -> Self raise Refused

Read a claim set back, keeping the seven claims and dropping the rest.

fnClaims::value
pub fn Claims::value(Self) -> @cbor.Value

The claim set as the CBOR map it is signed as: only the claims that are set, in the order the numbers put them in, so that two encodings of the same claims are the same octets.

fnKeyed::sign
pub fn Keyed::sign(Self, BytesView) -> Bytes

@spec.Signer's sign, callable on the value directly.

fnKeyed::verify
pub fn Keyed::verify(Self, BytesView, BytesView) -> Bool

@spec.Verifier's verify, callable on the value directly.

moonbitstack/mooncred/cose25 entries
Mooncred@cborxiii

cbor

CBOR, RFC 8949

What COSE and CWT are written in, and nothing more: the subset those two use, with definite lengths only.

Refused Value decode encode

errorRefused
pub(all) suberror Refused {
  Malformed(String)
  Unsupported(String)
} derive(Eq, @debug.Debug)

Why some octets are not a CBOR item.

typeValue
pub(all) enum Value {
  Int(Int64)
  Bytes(Bytes)
  Text(String)
  Array(Array[Value])
  Map(Array[(Value, Value)])
  Tag(UInt64, Value)
  Bool(Bool)
  Null
  Undefined
  Simple(Byte)
} derive(Eq, @debug.Debug)

A CBOR item.

Int covers both major types 0 and 1, so an integer reads as the number it is rather than as a sign and a magnitude the caller has to combine. That costs the top half of the unsigned range, which is refused on the way in: nothing in a credential or a token is a 2^63-byte count.

fndecode
pub fn decode(BytesView) -> Value raise Refused

Read one item, and insist that nothing follows it.

fnencode
pub fn encode(Value) -> Bytes

The octets of an item: definite lengths, and the shortest head that holds the argument.

fnValue::bytes
pub fn Value::bytes(Self) -> Bytes?

The octets a value holds, if it holds any.

fnValue::get
pub fn Value::get(Self, Self) -> Self?

Look a key up in a map, which is a sequence of pairs rather than a lookup: the first pair with that key wins, as it would in an object read in order.

fnValue::int
pub fn Value::int(Self) -> Int64?

The integer a key holds, if it holds one.

moonbitstack/mooncred/cbor7 entries
Mooncred@rootsxiv

roots

The Mozilla CA certificate store, from curl's extract

Its own package so a program that brings its own anchors never links the two hundred kilobytes of the bundle.

count version mozilla

letcount
pub let count : Int32

How many certificates the bundle holds.

letversion
pub let version : String

The date of the Mozilla store this bundle was extracted from.

fnmozilla
pub fn mozilla() -> @x509.Roots

The Mozilla CA store as trust anchors: the roots Firefox trusts for TLS servers, as curl extracts them. Each call parses the bundle afresh, so the set returned is the caller's to add to; build it once and keep it.