commit 75b51ad7502ee08bc8f8a1c6593d645f03b33aaf
parent 9e60065c8e93c6dd7b99322768ec0f9216fc93f7
Author: Armin Preiml <apreiml@strohwolke.at>
Date: Wed, 7 Feb 2024 19:53:33 +0100
asn1: oid support
encoding/asn1/oiddb/db.txt contains all oids required by the stdlib. It
is in a separate module so that projects may create their own oid list
by appending to the stdlib and generating the db with the genoiddb
command.
It is still no ideal solution if multiple libs will require their own
oids added to the db.
Signed-off-by: Armin Preiml <apreiml@strohwolke.at>
Diffstat:
5 files changed, 538 insertions(+), 7 deletions(-)
diff --git a/cmd/genoiddb/main.ha b/cmd/genoiddb/main.ha
@@ -0,0 +1,209 @@
+// SPDX-License-Identifier: MPL-2.0
+// (c) Hare authors <https://harelang.org>
+
+use ascii;
+use bufio;
+use fmt;
+use io;
+use os;
+use strconv;
+use strings;
+use types;
+
+type entry = struct {
+ name: str,
+ val: str,
+ idx: size,
+};
+
+// Parses an oid database from stdin and writes the database as hare code to
+// stdout.
+export fn main() void = {
+ let oids = parse_oids();
+ defer free_oids(oids);
+
+ fmt::println("// SPDX-License-Identifier: MPL-2.0\n"
+ "// (c) Hare authors <https://harelang.org>\n"
+ "// This is an auto generated file. Do not edit.\n"
+ "\n"
+ "use encoding::asn1;\n")!;
+
+ fmt::println("const _db = asn1::oiddb {")!;
+
+ write_db(os::stdout, oids)!;
+
+ fmt::println("\tnames = [")!;
+ for (let i = 0z; i < len(oids); i += 1) {
+ fmt::printfln("\t\t\"{}\",", oids[i].name)!;
+ };
+ fmt::println("\t],")!;
+ fmt::println("};\n")!;
+
+ fmt::println("export const db = &_db;\n")!;
+
+ for (let i = 0z; i < len(oids); i += 1) {
+ fmt::print("export def ")!;
+ write_varname(os::stdout, oids[i].name)!;
+ fmt::printfln(": asn1::oid = {};", i)!;
+ };
+};
+
+fn parse_oids() []entry = {
+ let s = bufio::newscanner(os::stdin, types::SIZE_MAX);
+ defer bufio::finish(&s);
+ let oids: []entry = [];
+
+ for (true) {
+ const l = match (bufio::scan_line(&s)!) {
+ case io::EOF =>
+ break;
+ case let s: const str =>
+ yield s;
+ };
+
+ if (l == "" || strings::hasprefix(l, '#')) {
+ continue;
+ };
+
+
+ const p = strings::split(l, " ");
+ defer free(p);
+ const name = p[0];
+ const val = p[len(p)-1];
+
+ append(oids, entry {
+ name = strings::dup(name),
+ val = strings::dup(val),
+ ...
+ });
+ };
+
+ return oids;
+};
+
+fn free_oids(oids: []entry) void = {
+ for (let i = 0z; i < len(oids); i += 1) {
+ free(oids[i].name);
+ free(oids[i].val);
+ };
+
+ free(oids);
+};
+
+fn write_db(h: io::handle, oids: []entry) (void | io::error) = {
+ fmt::print("\tlut = [")?;
+
+ const maxcols = 12z;
+ let idx = 0z;
+
+ for (let i = 0z; i < len(oids); i += 1) {
+ let e = &oids[i];
+ e.idx = idx;
+
+ let der = oidtoder(e.val);
+ assert(len(der) <= 0xff);
+ insert(der[0], len(der): u8);
+ defer free(der);
+
+ for (let j = 0z; j < len(der); j += 1) {
+ fmt::print(if (idx % maxcols == 0) "\n\t\t" else " ")?;
+ fmt::printf("0x{:.2x},", der[j])?;
+ idx += 1;
+ };
+ };
+ fmt::println("\n\t],")?;
+
+ const maxcols = 9z;
+ fmt::print("\tindex = [")?;
+ for (let i = 0z; i < len(oids); i += 1) {
+ fmt::print(if (i % maxcols == 0) "\n\t\t" else " ")?;
+ fmt::printf("0x{:.4x},", oids[i].idx)?;
+ };
+ fmt::println("\n\t],")?;
+};
+
+fn oidtoder(oid: str) []u8 = {
+ let nums = oidtou64s(oid);
+ defer free(nums);
+
+ let der: []u8 = alloc([0...], 1);
+ assert(nums[0] <= 6);
+ assert(nums[1] < 40);
+ der[0] = nums[0]: u8 * 40 + nums[1]: u8;
+ let end = 1z;
+
+ for (let i = 2z; i < len(nums); i += 1) {
+ let n = nums[i];
+ if (n == 0) {
+ insert(der[end], 0u8);
+ end = len(der);
+ continue;
+ };
+
+ let first = true;
+ for (n > 0) {
+ let p: u8 = n: u8 & 0x7f;
+ n >>= 7;
+ if (first) {
+ first = false;
+ } else {
+ p |= 0x80;
+ };
+ insert(der[end], p);
+ };
+
+ end = len(der);
+ };
+
+ return der;
+};
+
+fn oidtou64s(oid: str) []u64 = {
+ let nums = strings::tokenize(oid, ".");
+ let intnums: []u64 = [];
+
+ for (true) {
+ match (strings::next_token(&nums)) {
+ case let s: str =>
+ append(intnums, strconv::stou64(s)!);
+ case void =>
+ break;
+ };
+ };
+
+ return intnums;
+};
+
+fn write_varname(h: io::handle, name: str) (void | io::error) = {
+ // assume that names are in ascii
+ let i = strings::iter(name);
+ let prevlow = false;
+ for (true) {
+ match (strings::next(&i)) {
+ case void =>
+ break;
+ case let r: rune =>
+ let r = if (r == '-') {
+ prevlow = false;
+ yield '_';
+ } else if (ascii::isdigit(r)) {
+ prevlow = true;
+ yield r;
+ } else if (ascii::isupper(r)) {
+ if (prevlow) {
+ fmt::fprint(h, "_")?;
+ prevlow = false;
+ };
+ yield r;
+ } else if (ascii::islower(r)) {
+ prevlow = true;
+ yield ascii::toupper(r);
+ } else {
+ fmt::fatalf("Unexpected character in oid name: {}", r);
+ };
+
+ fmt::fprint(h, r)?;
+ };
+ };
+};
+
diff --git a/encoding/asn1/+test/decoder_test.ha b/encoding/asn1/+test/decoder_test.ha
@@ -243,13 +243,6 @@ fn d(i: []u8) decoder = {
assert(read_bitstr(&d([0x03, 0x03, 0x07, 0xab, 0x40]), buf) is invalid);
};
-@test fn read_oid() void = {
- assert(read_oid(&d([0x06, 0x03, 0x55, 0x04, 0x03]))! == oid::ID_AT_COMMON_NAME);
-
- assert(bytes::equal([0x55, 0x04, 0x03],
- read_rawoid(&d([0x06, 0x03, 0x55, 0x04, 0x03]))!));
-};
-
let datbuf: [64]u8 = [0...];
fn newdatetime(s: str, tag: utag) []u8 = {
@@ -329,3 +322,17 @@ fn newdatetime(s: str, tag: utag) []u8 = {
// TODO midnight is YYYYMMDD000000Z
};
+
+@test fn read_oid() void = {
+ let db = oiddb {
+ lut = [0x03, 0x2b, 0x65, 0x70, 0x03, 0x55, 0x04, 0x03],
+ index = [0, 4],
+ names = ["ed25519", "id-at-commonName"],
+ };
+
+ assert(read_oid(&d([0x06, 0x03, 0x55, 0x04, 0x03]), &db)! == 1);
+ assert(stroid(&db, 1) == "id-at-commonName");
+
+ assert(bytes::equal([0x55, 0x04, 0x03],
+ read_rawoid(&d([0x06, 0x03, 0x55, 0x04, 0x03]))!));
+};
diff --git a/encoding/asn1/oid.ha b/encoding/asn1/oid.ha
@@ -0,0 +1,137 @@
+// SPDX-License-Identifier: MPL-2.0
+// (c) Hare authors <https://harelang.org>
+
+use bytes;
+use errors;
+use fmt;
+use io;
+use math::{divu};
+use memio;
+use strings;
+
+
+// An oid database that contains a lookup table of known oids in the DER format.
+// A database of oids required by the standard library can be found in
+// [[encoding::asn1::stdoid]].
+//
+// The database can be used with [[oid_from_der]] and [[oid_to_der]] to convert
+// an oid between integer and DER encoding. [[read_oid]] and [[write_oid]] can
+// be used to decode or encode the oid directly from and to DER.
+//
+// If the standard oid database is missing entries for the given use case, an
+// individual database can be generated using the genoiddb command found in
+// cmd/. Take a look at encoding/asn1/stdoid/db.txt for an example database
+// file.
+export type oiddb = struct {
+ lut: []u8,
+ index: []size,
+ names: []str,
+};
+
+// Numeric id of an oid which is unique within an [[oiddb]].
+export type oid = u32;
+
+// Reads an oid if present in 'db'. Returns [[badformat]] if the oid is unknown.
+export fn read_oid(d: *decoder, db: *oiddb) (oid | error) = {
+ let raw = read_rawoid(d)?;
+
+ match (oid_from_der(db, raw)) {
+ case let o: oid =>
+ return o;
+ case =>
+ return badformat;
+ };
+};
+
+// Reads any [[oid]] and returns the DER encoded form. The returned value is
+// borrowed from a static buffer.
+export fn read_rawoid(d: *decoder) ([]u8 | error) = {
+ def OIDBUFSZ: size = 64; // estimated
+ static let oidbuf: [OIDBUFSZ]u8 = [0...];
+
+ const dh = next(d)?;
+ expect_utag(dh, utag::OID)?;
+ if (dsz(dh) < 2) {
+ return invalid;
+ };
+ const n = read_bytes(d, oidbuf)?;
+ return oidbuf[..n];
+};
+
+// Writes given [[oid]] from the [[oiddb]] 'db'.
+export fn write_oid(e: *encoder, db: *oiddb, oid: oid) (void | overflow) = {
+ let doid = oid_to_der(db, oid);
+ write_fixedprim(e, class::UNIVERSAL, utag::OID, doid)?;
+};
+
+// Looks up DER encoded oid 'raw' in 'db' and returns an [[oid]] if found, or
+// void otheriwse.
+export fn oid_from_der(db: *oiddb, raw: []u8) (void | oid) = {
+ for (let i = 0z; i < len(db.index); i += 1) {
+ const off = db.index[i];
+ const l = db.lut[off];
+ if (bytes::equal(raw, db.lut[off + 1..off + 1 + l])) {
+ return i: oid;
+ };
+ };
+};
+
+// Borrows the DER representation of a known oid from 'db'.
+export fn oid_to_der(db: *oiddb, o: oid) []u8 = {
+ const off = db.index[o];
+ const l = db.lut[off];
+ return db.lut[off + 1..off + 1 + l];
+};
+
+// Looks up a str representation of an oid from the database.
+export fn stroid(db: *oiddb, o: oid) str = {
+ return db.names[o];
+};
+
+// Returns the dot id as string. The caller must free returned value. This
+// function may fail if the oid overflows the internal buffer, or an invalid
+// value is provided.
+export fn strrawoid(der: []u8) (str | io::error) = {
+ let s = memio::dynamic();
+ let ok = false;
+ defer if (!ok) io::close(&s)!;
+
+ if (len(der) < 1) {
+ return errors::invalid;
+ };
+
+ const (a, b) = divu(0, der[0], 40);
+ fmt::fprintf(&s, "{}.{}", a, b)?;
+
+ let j = 2z;
+ let el = 0u32;
+ let bits: int = size(u32): int * 8;
+
+ for (let i = 1z; i < len(der); i += 1) {
+ el += der[i] & 0x7f;
+
+ if (der[i] & 0x80 != 0) {
+ if (bits - 7 < 0) {
+ return errors::overflow;
+ };
+ el <<= 7;
+ bits -= 7;
+ } else {
+ fmt::fprintf(&s, ".{}", el)?;
+ el = 0;
+ j += 1;
+ bits = size(u32): int * 8;
+ };
+ };
+
+ ok = true;
+ return memio::string(&s)!;
+};
+
+@test fn strrawoid() void = {
+ let der: [_]u8 = [0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x01];
+ let s = strrawoid(der)!;
+ defer free(s);
+ assert(s == "1.2.840.113549.1.1.1");
+};
+
diff --git a/encoding/asn1/stdoid/db.ha b/encoding/asn1/stdoid/db.ha
@@ -0,0 +1,127 @@
+// SPDX-License-Identifier: MPL-2.0
+// (c) Hare authors <https://harelang.org>
+// This is an auto generated file. Do not edit.
+
+use encoding::asn1;
+
+const _db = asn1::oiddb {
+ lut = [
+ 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x01, 0x03, 0x2b,
+ 0x65, 0x70, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05,
+ 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x0e, 0x09, 0x2a,
+ 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x0b, 0x09, 0x2a, 0x86, 0x48,
+ 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x0c, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7,
+ 0x0d, 0x01, 0x01, 0x0d, 0x05, 0x2b, 0x0e, 0x03, 0x02, 0x1a, 0x09, 0x60,
+ 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x04, 0x09, 0x60, 0x86, 0x48,
+ 0x01, 0x65, 0x03, 0x04, 0x02, 0x01, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65,
+ 0x03, 0x04, 0x02, 0x02, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04,
+ 0x02, 0x03, 0x07, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x02, 0x01, 0x08, 0x2a,
+ 0x86, 0x48, 0xce, 0x3d, 0x03, 0x01, 0x07, 0x08, 0x2a, 0x86, 0x48, 0xce,
+ 0x3d, 0x03, 0x01, 0x07, 0x05, 0x2b, 0x81, 0x04, 0x00, 0x22, 0x05, 0x2b,
+ 0x81, 0x04, 0x00, 0x23, 0x07, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x04, 0x01,
+ 0x08, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x04, 0x03, 0x01, 0x08, 0x2a, 0x86,
+ 0x48, 0xce, 0x3d, 0x04, 0x03, 0x02, 0x08, 0x2a, 0x86, 0x48, 0xce, 0x3d,
+ 0x04, 0x03, 0x03, 0x08, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x04, 0x03, 0x04,
+ 0x03, 0x55, 0x04, 0x03, 0x03, 0x55, 0x04, 0x04, 0x03, 0x55, 0x04, 0x05,
+ 0x03, 0x55, 0x04, 0x06, 0x03, 0x55, 0x04, 0x07, 0x03, 0x55, 0x04, 0x08,
+ 0x03, 0x55, 0x04, 0x0a, 0x03, 0x55, 0x04, 0x0b, 0x03, 0x55, 0x04, 0x0c,
+ 0x03, 0x55, 0x04, 0x2a, 0x03, 0x55, 0x04, 0x2b, 0x03, 0x55, 0x04, 0x2b,
+ 0x03, 0x55, 0x04, 0x2e, 0x03, 0x55, 0x04, 0x41, 0x0a, 0x09, 0x92, 0x26,
+ 0x89, 0x93, 0xf2, 0x2c, 0x64, 0x01, 0x19, 0x03, 0x55, 0x1d, 0x0f, 0x03,
+ 0x55, 0x1d, 0x11, 0x03, 0x55, 0x1d, 0x13, 0x03, 0x55, 0x1d, 0x25,
+ ],
+ index = [
+ 0x0000, 0x000a, 0x000e, 0x0018, 0x0022, 0x002c, 0x0036, 0x0040, 0x0046,
+ 0x0050, 0x005a, 0x0064, 0x006e, 0x0076, 0x007f, 0x0088, 0x008e, 0x0094,
+ 0x009c, 0x00a5, 0x00ae, 0x00b7, 0x00c0, 0x00c4, 0x00c8, 0x00cc, 0x00d0,
+ 0x00d4, 0x00d8, 0x00dc, 0x00e0, 0x00e4, 0x00e8, 0x00ec, 0x00f0, 0x00f4,
+ 0x00f8, 0x0103, 0x0107, 0x010b, 0x010f,
+ ],
+ names = [
+ "rsaEncryption",
+ "ed25519",
+ "sha1WithRSAEncryption",
+ "sha224WithRSAEncryption",
+ "sha256WithRSAEncryption",
+ "sha384WithRSAEncryption",
+ "sha512WithRSAEncryption",
+ "id-sha1",
+ "id-sha224",
+ "id-sha256",
+ "id-sha384",
+ "id-sha512",
+ "id-ecPublicKey",
+ "prime256v1",
+ "ansix9p256r1",
+ "ansix9p384r1",
+ "ansix9p521r1",
+ "ecdsa-with-SHA1",
+ "ecdsa-with-SHA224",
+ "ecdsa-with-SHA256",
+ "ecdsa-with-SHA384",
+ "ecdsa-with-SHA512",
+ "id-at-commonName",
+ "id-at-surname",
+ "id-at-serialNumber",
+ "id-at-countryName",
+ "id-at-localityName",
+ "id-at-stateOrProvinceName",
+ "id-at-organizationName",
+ "id-at-organizationalUnitName",
+ "id-at-title",
+ "id-at-givenName",
+ "id-at-initials",
+ "id-at-generationQualifier",
+ "id-at-dnQualifier",
+ "id-at-pseudonym",
+ "id-domainComponent",
+ "id-ce-keyUsage",
+ "id-ce-subjectAltName",
+ "id-ce-basicConstraints",
+ "id-ce-extKeyUsage",
+ ],
+};
+
+export const db = &_db;
+
+export def RSA_ENCRYPTION: asn1::oid = 0;
+export def ED25519: asn1::oid = 1;
+export def SHA1_WITH_RSAENCRYPTION: asn1::oid = 2;
+export def SHA224_WITH_RSAENCRYPTION: asn1::oid = 3;
+export def SHA256_WITH_RSAENCRYPTION: asn1::oid = 4;
+export def SHA384_WITH_RSAENCRYPTION: asn1::oid = 5;
+export def SHA512_WITH_RSAENCRYPTION: asn1::oid = 6;
+export def ID_SHA1: asn1::oid = 7;
+export def ID_SHA224: asn1::oid = 8;
+export def ID_SHA256: asn1::oid = 9;
+export def ID_SHA384: asn1::oid = 10;
+export def ID_SHA512: asn1::oid = 11;
+export def ID_EC_PUBLIC_KEY: asn1::oid = 12;
+export def PRIME256V1: asn1::oid = 13;
+export def ANSIX9P256R1: asn1::oid = 14;
+export def ANSIX9P384R1: asn1::oid = 15;
+export def ANSIX9P521R1: asn1::oid = 16;
+export def ECDSA_WITH_SHA1: asn1::oid = 17;
+export def ECDSA_WITH_SHA224: asn1::oid = 18;
+export def ECDSA_WITH_SHA256: asn1::oid = 19;
+export def ECDSA_WITH_SHA384: asn1::oid = 20;
+export def ECDSA_WITH_SHA512: asn1::oid = 21;
+export def ID_AT_COMMON_NAME: asn1::oid = 22;
+export def ID_AT_SURNAME: asn1::oid = 23;
+export def ID_AT_SERIAL_NUMBER: asn1::oid = 24;
+export def ID_AT_COUNTRY_NAME: asn1::oid = 25;
+export def ID_AT_LOCALITY_NAME: asn1::oid = 26;
+export def ID_AT_STATE_OR_PROVINCE_NAME: asn1::oid = 27;
+export def ID_AT_ORGANIZATION_NAME: asn1::oid = 28;
+export def ID_AT_ORGANIZATIONAL_UNIT_NAME: asn1::oid = 29;
+export def ID_AT_TITLE: asn1::oid = 30;
+export def ID_AT_GIVEN_NAME: asn1::oid = 31;
+export def ID_AT_INITIALS: asn1::oid = 32;
+export def ID_AT_GENERATION_QUALIFIER: asn1::oid = 33;
+export def ID_AT_DN_QUALIFIER: asn1::oid = 34;
+export def ID_AT_PSEUDONYM: asn1::oid = 35;
+export def ID_DOMAIN_COMPONENT: asn1::oid = 36;
+export def ID_CE_KEY_USAGE: asn1::oid = 37;
+export def ID_CE_SUBJECT_ALT_NAME: asn1::oid = 38;
+export def ID_CE_BASIC_CONSTRAINTS: asn1::oid = 39;
+export def ID_CE_EXT_KEY_USAGE: asn1::oid = 40;
diff --git a/encoding/asn1/stdoid/db.txt b/encoding/asn1/stdoid/db.txt
@@ -0,0 +1,51 @@
+# OIDs that will be translated into db.ha using `genoiddb`
+
+rsaEncryption 1.2.840.113549.1.1.1
+ed25519 1.3.101.112
+
+sha1WithRSAEncryption 1.2.840.113549.1.1.5
+sha224WithRSAEncryption 1.2.840.113549.1.1.14
+sha256WithRSAEncryption 1.2.840.113549.1.1.11
+sha384WithRSAEncryption 1.2.840.113549.1.1.12
+sha512WithRSAEncryption 1.2.840.113549.1.1.13
+
+id-sha1 1.3.14.3.2.26
+id-sha224 2.16.840.1.101.3.4.2.4
+id-sha256 2.16.840.1.101.3.4.2.1
+id-sha384 2.16.840.1.101.3.4.2.2
+id-sha512 2.16.840.1.101.3.4.2.3
+
+id-ecPublicKey 1.2.840.10045.2.1
+prime256v1 1.2.840.10045.3.1.7
+
+ansix9p256r1 1.2.840.10045.3.1.7
+ansix9p384r1 1.3.132.0.34
+ansix9p521r1 1.3.132.0.35
+
+ecdsa-with-SHA1 1.2.840.10045.4.1
+ecdsa-with-SHA224 1.2.840.10045.4.3.1
+ecdsa-with-SHA256 1.2.840.10045.4.3.2
+ecdsa-with-SHA384 1.2.840.10045.4.3.3
+ecdsa-with-SHA512 1.2.840.10045.4.3.4
+
+id-at-commonName 2.5.4.3
+id-at-surname 2.5.4.4
+id-at-serialNumber 2.5.4.5
+id-at-countryName 2.5.4.6
+id-at-localityName 2.5.4.7
+id-at-stateOrProvinceName 2.5.4.8
+id-at-organizationName 2.5.4.10
+id-at-organizationalUnitName 2.5.4.11
+id-at-title 2.5.4.12
+id-at-givenName 2.5.4.42
+id-at-initials 2.5.4.43
+id-at-generationQualifier 2.5.4.43
+id-at-dnQualifier 2.5.4.46
+id-at-pseudonym 2.5.4.65
+
+id-domainComponent 0.9.2342.19200300.100.1.25
+
+id-ce-keyUsage 2.5.29.15
+id-ce-subjectAltName 2.5.29.17
+id-ce-basicConstraints 2.5.29.19
+id-ce-extKeyUsage 2.5.29.37