///|
#cfg(target="native")
priv suberror X509DerError {
X509DerTruncated
X509DerUnsupported
X509DerUnexpectedTag
X509DerMissingField
X509HostnameMismatch
} derive(Debug, ToJson)
///|
#cfg(target="native")
let x509_tag_sequence : Int = 0x30
///|
#cfg(target="native")
let x509_tag_set : Int = 0x31
///|
#cfg(target="native")
let x509_tag_oid : Int = 0x06
///|
#cfg(target="native")
let x509_tag_bit_string : Int = 0x03
///|
#cfg(target="native")
let x509_tag_octet_string : Int = 0x04
///|
#cfg(target="native")
let x509_tag_ia5_string : Int = 0x16
///|
#cfg(target="native")
let x509_tag_utf8_string : Int = 0x0c
///|
#cfg(target="native")
let x509_tag_printable_string : Int = 0x13
///|
#cfg(target="native")
let x509_tag_context_0 : Int = 0xa0
///|
#cfg(target="native")
let x509_tag_context_3 : Int = 0xa3
///|
#cfg(target="native")
let x509_tag_dns_name : Int = 0x82
///|
#cfg(target="native")
let x509_oid_common_name : String = "2.5.4.3"
///|
#cfg(target="native")
let x509_oid_subject_alt_name : String = "2.5.29.17"
///|
#cfg(target="native")
#warnings("-unused_field")
priv struct X509DerElement {
tag : Int
header_start : Int
content_start : Int
content_end : Int
end : Int
}
///|
#cfg(target="native")
#warnings("-unused_field")
priv struct X509CertificateInfo {
tbs_certificate : Bytes
signature_algorithm : String
signature : Bytes
public_key_algorithm : String
public_key : Bytes
dns_names : Array[String]
common_names : Array[String]
}
///|
#cfg(target="native")
fn x509_der_read_len(data : Bytes, offset : Int) -> (Int, Int) raise {
guard offset < data.length() else { raise X509DerTruncated }
let first = data[offset].to_int()
if first < 0x80 {
(first, offset + 1)
} else {
let len_len = first & 0x7f
guard len_len > 0 && len_len <= 4 else { raise X509DerUnsupported }
guard offset + 1 + len_len <= data.length() else { raise X509DerTruncated }
let mut len = 0
for i in 0.. X509DerElement raise {
guard offset + 2 <= data.length() else { raise X509DerTruncated }
let tag = data[offset].to_int()
let (len, content_start) = x509_der_read_len(data, offset + 1)
let content_end = content_start + len
guard content_end <= data.length() else { raise X509DerTruncated }
{ tag, header_start: offset, content_start, content_end, end: content_end }
}
///|
#cfg(target="native")
fn x509_der_expect_tag(element : X509DerElement, tag : Int) -> Unit raise {
guard element.tag == tag else { raise X509DerUnexpectedTag }
}
///|
#cfg(target="native")
fn x509_der_bytes(data : Bytes, element : X509DerElement) -> Bytes {
tls13_copy_slice(data, element.header_start, element.end)
}
///|
#cfg(target="native")
fn x509_der_content(data : Bytes, element : X509DerElement) -> Bytes {
tls13_copy_slice(data, element.content_start, element.content_end)
}
///|
#cfg(target="native")
fn x509_der_children(
data : Bytes,
element : X509DerElement,
) -> Array[X509DerElement] raise {
let children = []
let mut offset = element.content_start
while offset < element.content_end {
let child = x509_der_read_element(data, offset)
guard child.end <= element.content_end else { raise X509DerTruncated }
children.push(child)
offset = child.end
}
children
}
///|
#cfg(target="native")
fn x509_der_oid(data : Bytes, element : X509DerElement) -> String raise {
x509_der_expect_tag(element, x509_tag_oid)
guard element.content_start < element.content_end else {
raise X509DerTruncated
}
let first = data[element.content_start].to_int()
let out = StringBuilder::new()
out.write_string("\{first / 40}")
out.write_string(".")
out.write_string("\{first % 40}")
let mut value = 0
for i in (element.content_start + 1).. Bytes raise {
x509_der_expect_tag(element, x509_tag_bit_string)
guard element.content_start < element.content_end else {
raise X509DerTruncated
}
guard data[element.content_start].to_int() == 0 else {
raise X509DerUnsupported
}
tls13_copy_slice(data, element.content_start + 1, element.content_end)
}
///|
#cfg(target="native")
fn x509_der_string(data : Bytes, element : X509DerElement) -> String raise {
guard element.tag == x509_tag_utf8_string ||
element.tag == x509_tag_printable_string ||
element.tag == x509_tag_ia5_string else {
raise X509DerUnexpectedTag
}
@utf8.decode_lossy(data[element.content_start:element.content_end])
}
///|
#cfg(target="native")
fn x509_parse_algorithm_identifier(
data : Bytes,
element : X509DerElement,
) -> String raise {
x509_der_expect_tag(element, x509_tag_sequence)
let children = x509_der_children(data, element)
guard children.length() >= 1 else { raise X509DerMissingField }
x509_der_oid(data, children[0])
}
///|
#cfg(target="native")
fn x509_parse_common_names(
data : Bytes,
name_element : X509DerElement,
) -> Array[String] raise {
x509_der_expect_tag(name_element, x509_tag_sequence)
let names = []
for rdn in x509_der_children(data, name_element) {
if rdn.tag == x509_tag_set {
for attr in x509_der_children(data, rdn) {
if attr.tag == x509_tag_sequence {
let children = x509_der_children(data, attr)
if children.length() >= 2 &&
x509_der_oid(data, children[0]) == x509_oid_common_name {
names.push(x509_der_string(data, children[1]))
}
}
}
}
}
names
}
///|
#cfg(target="native")
fn x509_parse_subject_alt_names_from_value(
value : Bytes,
) -> Array[String] raise {
let root = x509_der_read_element(value, 0)
x509_der_expect_tag(root, x509_tag_sequence)
guard root.end == value.length() else { raise X509DerTruncated }
let names = []
let mut offset = root.content_start
while offset < root.content_end {
let name = x509_der_read_element(value, offset)
guard name.end <= root.content_end else { raise X509DerTruncated }
if name.tag == x509_tag_dns_name {
names.push(@utf8.decode_lossy(value[name.content_start:name.content_end]))
}
offset = name.end
}
names
}
///|
#cfg(target="native")
fn x509_parse_extensions(
data : Bytes,
extensions_element : X509DerElement,
) -> Array[String] raise {
x509_der_expect_tag(extensions_element, x509_tag_context_3)
let wrapped = x509_der_read_element(data, extensions_element.content_start)
x509_der_expect_tag(wrapped, x509_tag_sequence)
let dns_names = []
for extension in x509_der_children(data, wrapped) {
if extension.tag == x509_tag_sequence {
let fields = x509_der_children(data, extension)
guard fields.length() >= 2 else { raise X509DerMissingField }
if x509_der_oid(data, fields[0]) == x509_oid_subject_alt_name {
let value_index = if fields[1].tag == x509_tag_octet_string {
1
} else {
2
}
guard value_index < fields.length() else { raise X509DerMissingField }
x509_der_expect_tag(fields[value_index], x509_tag_octet_string)
let parsed_names = x509_parse_subject_alt_names_from_value(
x509_der_content(data, fields[value_index]),
)
for name in parsed_names {
dns_names.push(name)
}
}
}
}
dns_names
}
///|
#cfg(target="native")
fn x509_parse_subject_public_key_info(
data : Bytes,
spki : X509DerElement,
) -> (String, Bytes) raise {
x509_der_expect_tag(spki, x509_tag_sequence)
let fields = x509_der_children(data, spki)
guard fields.length() >= 2 else { raise X509DerMissingField }
(
x509_parse_algorithm_identifier(data, fields[0]),
x509_der_bit_string(data, fields[1]),
)
}
///|
#cfg(target="native")
#warnings("-unused_value")
fn x509_parse_certificate(data : Bytes) -> X509CertificateInfo raise {
let cert = x509_der_read_element(data, 0)
x509_der_expect_tag(cert, x509_tag_sequence)
guard cert.end == data.length() else { raise X509DerTruncated }
let cert_fields = x509_der_children(data, cert)
guard cert_fields.length() >= 3 else { raise X509DerMissingField }
let tbs = cert_fields[0]
x509_der_expect_tag(tbs, x509_tag_sequence)
let tbs_fields = x509_der_children(data, tbs)
let first_tbs_field = if tbs_fields.length() > 0 {
tbs_fields[0].tag
} else {
raise X509DerMissingField
}
let base = if first_tbs_field == x509_tag_context_0 { 1 } else { 0 }
guard base + 5 < tbs_fields.length() else { raise X509DerMissingField }
let (public_key_algorithm, public_key) = x509_parse_subject_public_key_info(
data,
tbs_fields[base + 5],
)
let common_names = x509_parse_common_names(data, tbs_fields[base + 4])
let dns_names = []
for i in (base + 6).. Bool {
value.length() >= suffix.length() &&
value[value.length() - suffix.length():] == suffix
}
///|
#cfg(target="native")
fn x509_dns_name_matches(pattern : String, host : String) -> Bool {
let pattern = pattern.to_lower()
let host = host.to_lower()
if pattern == host {
true
} else if pattern.length() > 2 && pattern[:2] == "*." {
let suffix = pattern[1:].to_owned()
if x509_string_ends_with(host, suffix) {
let prefix = host[:host.length() - suffix.length()].to_owned()
prefix.length() > 0 && prefix.find(".") is None
} else {
false
}
} else {
false
}
}
///|
#cfg(target="native")
#warnings("-unused_value")
fn x509_verify_hostname(
info : X509CertificateInfo,
host : String,
) -> Unit raise {
let names = if info.dns_names.length() > 0 {
info.dns_names
} else {
info.common_names
}
for name in names {
if x509_dns_name_matches(name, host) {
return
}
}
raise X509HostnameMismatch
}