///|
/// A general name (RFC 5280, 4.2.1.6).
///
/// The address forms that this library does not model (`otherName`,
/// `x400Address` and `ediPartyName`) keep their raw DER content.
pub(all) enum GeneralName {
OtherName(Bytes)
Rfc822Name(String)
DnsName(String)
X400Address(Bytes)
DirectoryName(Name)
EdiPartyName(Bytes)
UniformResourceIdentifier(String)
IpAddress(Bytes)
RegisteredId(@asn1.ObjectIdentifier)
}
///|
/// A short kind label for a general name, used in summaries.
pub fn GeneralName::kind(self : GeneralName) -> String {
match self {
OtherName(_) => "otherName"
Rfc822Name(_) => "email"
DnsName(_) => "DNS"
X400Address(_) => "x400"
DirectoryName(_) => "dirname"
EdiPartyName(_) => "ediParty"
UniformResourceIdentifier(_) => "URI"
IpAddress(_) => "IP"
RegisteredId(_) => "RID"
}
}
///|
/// Renders a general name in the form used by certificate display tools.
pub fn GeneralName::to_string(self : GeneralName) -> String {
match self {
OtherName(bytes) => "#otherName: " + @hex.encode(bytes)
Rfc822Name(name) => name
DnsName(name) => name
X400Address(bytes) => "#x400Address: " + @hex.encode(bytes)
DirectoryName(name) => name.to_string()
EdiPartyName(bytes) => "#ediPartyName: " + @hex.encode(bytes)
UniformResourceIdentifier(uri) => uri
IpAddress(bytes) => format_ip(bytes)
RegisteredId(oid) => oid.to_string()
}
}
///|
/// Formats a 4 or 16 octet IP address.
fn format_ip(bytes : Bytes) -> String {
if bytes.length() == 4 {
bytes.unsafe_get(0).to_int().to_string() +
"." +
bytes.unsafe_get(1).to_int().to_string() +
"." +
bytes.unsafe_get(2).to_int().to_string() +
"." +
bytes.unsafe_get(3).to_int().to_string()
} else {
@hex.encode(bytes)
}
}
///|
/// The named bits of the keyUsage extension (RFC 5280, 4.2.1.3).
pub(all) enum KeyUsageBit {
DigitalSignature
NonRepudiation
KeyEncipherment
DataEncipherment
KeyAgreement
KeyCertSign
CrlSign
EncipherOnly
DecipherOnly
}
///|
/// The bit index of a key usage, matching the order in RFC 5280.
fn KeyUsageBit::index(self : KeyUsageBit) -> Int {
match self {
DigitalSignature => 0
NonRepudiation => 1
KeyEncipherment => 2
DataEncipherment => 3
KeyAgreement => 4
KeyCertSign => 5
CrlSign => 6
EncipherOnly => 7
DecipherOnly => 8
}
}
///|
/// The display name of a key usage bit.
pub fn KeyUsageBit::name(self : KeyUsageBit) -> String {
match self {
DigitalSignature => "digitalSignature"
NonRepudiation => "nonRepudiation"
KeyEncipherment => "keyEncipherment"
DataEncipherment => "dataEncipherment"
KeyAgreement => "keyAgreement"
KeyCertSign => "keyCertSign"
CrlSign => "cRLSign"
EncipherOnly => "encipherOnly"
DecipherOnly => "decipherOnly"
}
}
///|
/// The keyUsage extension (RFC 5280, 4.2.1.3).
pub struct KeyUsage {
value : Bytes
}
///|
/// Whether a named bit is set.
pub fn KeyUsage::has(self : KeyUsage, bit : KeyUsageBit) -> Bool {
let index = bit.index()
let octet = index / 8
if octet >= self.value.length() {
return false
}
((self.value.unsafe_get(octet).to_int() >> (7 - index % 8)) & 1) != 0
}
///|
/// Renders the set bits as a comma separated list.
pub fn KeyUsage::to_string(self : KeyUsage) -> String {
let names : Array[String] = []
for
bit in [
DigitalSignature,
NonRepudiation,
KeyEncipherment,
DataEncipherment,
KeyAgreement,
KeyCertSign,
CrlSign,
EncipherOnly,
DecipherOnly,
] {
if self.has(bit) {
names.push(bit.name())
}
}
names.join(", ")
}
///|
/// The basicConstraints extension (RFC 5280, 4.2.1.9).
pub struct BasicConstraints {
ca : Bool
path_len : Int?
}
///|
/// The authorityKeyIdentifier extension (RFC 5280, 4.2.1.1).
pub struct AuthorityKeyIdentifier {
key_identifier : Bytes?
authority_cert_issuer : Array[GeneralName]?
authority_cert_serial : @bigint.BigInt?
}
///|
/// A decoded extension value, for the extensions that this library understands.
pub(all) enum ExtensionValue {
SubjectAltName(Array[GeneralName])
IssuerAltName(Array[GeneralName])
KeyUsage(KeyUsage)
BasicConstraints(BasicConstraints)
ExtendedKeyUsage(Array[@asn1.ObjectIdentifier])
SubjectKeyIdentifier(Bytes)
AuthorityKeyIdentifier(AuthorityKeyIdentifier)
/// An extension this library does not decode; the raw value is on the
/// `Extension`.
Other
}
///|
/// Parses a general name.
fn parse_general_name(value : @asn1.Der) -> GeneralName raise X509Error {
let tag = value.tag()
if tag.cls != @asn1.ContextSpecific {
raise Malformed("GeneralName must use a context specific tag")
}
match tag.number {
0 => GeneralName::OtherName(content_of(value))
1 => GeneralName::Rfc822Name(ascii_of(value))
2 => GeneralName::DnsName(ascii_of(value))
3 => GeneralName::X400Address(content_of(value))
4 => GeneralName::DirectoryName(parse_implicit_name(value))
5 => GeneralName::EdiPartyName(content_of(value))
6 => GeneralName::UniformResourceIdentifier(ascii_of(value))
7 => GeneralName::IpAddress(content_of(value))
8 =>
GeneralName::RegisteredId(
@asn1.decode_object_identifier(content_of(value)) catch {
err => raise InvalidDer(err.to_string())
},
)
_ => raise Malformed("unknown GeneralName tag " + tag.number.to_string())
}
}
///|
/// The content octets of a primitive value, copied into an owned buffer.
fn content_of(value : @asn1.Der) -> Bytes {
match value {
@asn1.Primitive(_, content) => @buffer.from_bytes(content).to_bytes()
@asn1.Constructed(_, children) => {
let writer = @asn1.DerWriter::new()
for child in children {
let encoded = child.encode() catch { _ => b"" }
writer.add(encoded[:])
}
writer.to_content()
}
}
}
///|
/// Decodes the content octets of an implicitly tagged IA5String.
fn ascii_of(value : @asn1.Der) -> String raise X509Error {
@asn1.decode_ia5_string(content_of(value)[:]) catch {
err => raise InvalidDer(err.to_string())
}
}
///|
/// Decodes `[4] directoryName`, which is an implicitly tagged RDNSequence.
fn parse_implicit_name(value : @asn1.Der) -> Name raise X509Error {
match value {
@asn1.Constructed(_, children) => {
let rdns : Array[RelativeDistinguishedName] = []
for child in children {
let atv_elements = child.as_sequence() catch {
err => raise InvalidDer(err.to_string())
}
let attributes : Array[AttributeTypeAndValue] = []
for atv in atv_elements {
let parts = atv.as_sequence() catch {
err => raise InvalidDer(err.to_string())
}
if parts.length() != 2 {
raise Malformed("AttributeTypeAndValue must have two elements")
}
attributes.push({
oid: parts[0].as_object_identifier() catch {
err => raise InvalidDer(err.to_string())
},
value: attribute_value_text(parts[1]),
})
}
rdns.push({ attributes, })
}
{ rdns, }
}
@asn1.Primitive(_, _) =>
raise Malformed("directoryName must be a constructed value")
}
}
///|
/// Parses a GeneralNames sequence (the value of subjectAltName and friends).
fn parse_general_names(value : @asn1.Der) -> Array[GeneralName] raise X509Error {
let children = value.as_sequence() catch {
err => raise InvalidDer(err.to_string())
}
let names : Array[GeneralName] = []
for child in children {
names.push(parse_general_name(child))
}
names
}
///|
/// Parses the keyUsage extension.
fn parse_key_usage(value : @asn1.Der) -> KeyUsage raise X509Error {
let (_, bits) = value.as_bit_string() catch {
err => raise InvalidDer(err.to_string())
}
{ value: bits, }
}
///|
/// Parses the basicConstraints extension.
fn parse_basic_constraints(
value : @asn1.Der,
) -> BasicConstraints raise X509Error {
let children = value.as_sequence() catch {
err => raise InvalidDer(err.to_string())
}
let mut ca = false
let mut path_len : Int? = None
let mut index = 0
if index < children.length() &&
children[index].tag().number == @asn1.TAG_BOOLEAN {
ca = children[index].as_boolean() catch {
err => raise InvalidDer(err.to_string())
}
index = index + 1
}
if index < children.length() {
let encoded = children[index].as_integer() catch {
err => raise InvalidDer(err.to_string())
}
path_len = Some(encoded.to_int())
index = index + 1
}
if index < children.length() {
raise Malformed("basicConstraints has unexpected trailing elements")
}
{ ca, path_len, }
}
///|
/// Parses the extKeyUsage extension.
fn parse_extended_key_usage(
value : @asn1.Der,
) -> Array[@asn1.ObjectIdentifier] raise X509Error {
let children = value.as_sequence() catch {
err => raise InvalidDer(err.to_string())
}
let usages : Array[@asn1.ObjectIdentifier] = []
for child in children {
usages.push(
child.as_object_identifier() catch {
err => raise InvalidDer(err.to_string())
},
)
}
usages
}
///|
/// Parses the authorityKeyIdentifier extension.
fn parse_authority_key_identifier(
value : @asn1.Der,
) -> AuthorityKeyIdentifier raise X509Error {
let children = value.as_sequence() catch {
err => raise InvalidDer(err.to_string())
}
let mut key_identifier : Bytes? = None
let mut authority_cert_issuer : Array[GeneralName]? = None
let mut authority_cert_serial : @bigint.BigInt? = None
for child in children {
let tag = child.tag()
if tag.cls != @asn1.ContextSpecific {
raise Malformed("authorityKeyIdentifier fields must be context specific")
}
match tag.number {
0 => key_identifier = Some(content_of(child))
1 => authority_cert_issuer = Some(parse_general_names(child))
2 =>
authority_cert_serial = Some(
@asn1.decode_integer(content_of(child)[:]) catch {
err => raise InvalidDer(err.to_string())
},
)
_ =>
raise Malformed(
"unknown authorityKeyIdentifier field " + tag.number.to_string(),
)
}
}
{ key_identifier, authority_cert_issuer, authority_cert_serial, }
}
///|
/// Decodes the extension value for the extensions this library understands.
pub fn Extension::decode(self : Extension) -> ExtensionValue raise X509Error {
let dotted = self.oid.to_string()
match dotted {
"2.5.29.17" => SubjectAltName(parse_general_names(self.decode_value()))
"2.5.29.18" => IssuerAltName(parse_general_names(self.decode_value()))
"2.5.29.15" => KeyUsage(parse_key_usage(self.decode_value()))
"2.5.29.19" =>
BasicConstraints(parse_basic_constraints(self.decode_value()))
"2.5.29.37" =>
ExtendedKeyUsage(parse_extended_key_usage(self.decode_value()))
"2.5.29.14" =>
SubjectKeyIdentifier(
self.decode_value().as_octet_string() catch {
err => raise InvalidDer(err.to_string())
},
)
"2.5.29.35" =>
AuthorityKeyIdentifier(
parse_authority_key_identifier(self.decode_value()),
)
_ => Other
}
}
///|
/// Finds an extension by its dotted object identifier.
pub fn Certificate::extension(self : Certificate, oid : String) -> Extension? {
for extension in self.tbs.extensions {
if extension.oid.to_string() == oid {
return Some(extension)
}
}
None
}
///|
/// The subject alternative names, or an empty array when the extension is
/// absent.
pub fn Certificate::subject_alt_names(self : Certificate) -> Array[GeneralName] {
let extension = match self.extension("2.5.29.17") {
Some(extension) => extension
None => return []
}
let decoded = extension.decode() catch { _ => Other }
match decoded {
SubjectAltName(names) => names
_ => []
}
}
///|
/// The key usage, or `None` when the extension is absent.
pub fn Certificate::key_usage(self : Certificate) -> KeyUsage? {
let extension = match self.extension("2.5.29.15") {
Some(extension) => extension
None => return None
}
let decoded = extension.decode() catch { _ => Other }
match decoded {
KeyUsage(usage) => Some(usage)
_ => None
}
}
///|
/// The basic constraints, or `None` when the extension is absent.
pub fn Certificate::basic_constraints(self : Certificate) -> BasicConstraints? {
let extension = match self.extension("2.5.29.19") {
Some(extension) => extension
None => return None
}
let decoded = extension.decode() catch { _ => Other }
match decoded {
BasicConstraints(constraints) => Some(constraints)
_ => None
}
}
///|
/// Whether the certificate is a CA, according to basicConstraints.
pub fn Certificate::is_ca(self : Certificate) -> Bool {
match self.basic_constraints() {
Some(constraints) => constraints.ca
None => false
}
}
///|
/// The extended key usages, or `None` when the extension is absent.
pub fn Certificate::extended_key_usage(
self : Certificate,
) -> Array[@asn1.ObjectIdentifier]? {
let extension = match self.extension("2.5.29.37") {
Some(extension) => extension
None => return None
}
let decoded = extension.decode() catch { _ => Other }
match decoded {
ExtendedKeyUsage(usages) => Some(usages)
_ => None
}
}