commit 338719c951d8e8884684f94d3bc519ab017998ae
parent e9b63afe4adbc088b6c103548a50b10fdf20189f
Author: Drew DeVault <sir@cmpwn.com>
Date: Mon, 2 May 2022 11:40:13 +0200
encoding::pem: initial commit
Signed-off-by: Drew DeVault <sir@cmpwn.com>
Diffstat:
4 files changed, 391 insertions(+), 0 deletions(-)
diff --git a/encoding/pem/+test.ha b/encoding/pem/+test.ha
@@ -0,0 +1,147 @@
+use bufio;
+use bytes;
+use io;
+use strings;
+
+@test fn read() void = {
+ const in = bufio::fixed(strings::toutf8(testcert_str), io::mode::READ);
+ const dec = newdecoder(&in);
+ defer finish(&dec);
+
+ const stream = next(&dec)! as (str, pemdecoder);
+ assert(stream.0 == "CERTIFICATE");
+ static let buf: [1024]u8 = [0...];
+ assert(len(buf) >= len(testcert_bin));
+
+ const data = io::drain(&stream.1)!;
+ assert(bytes::equal(data, testcert_bin));
+
+ assert(next(&dec) is io::EOF);
+};
+
+@test fn read_many() void = {
+ const in = bufio::fixed(strings::toutf8(testmany), io::mode::READ);
+ const dec = newdecoder(&in);
+ defer finish(&dec);
+
+ static let buf: [1024]u8 = [0...];
+ const stream = next(&dec)! as (str, pemdecoder);
+ assert(stream.0 == "CERTIFICATE");
+ const data = io::drain(&stream.1)!;
+ assert(bytes::equal(data, testcert_bin));
+
+ const stream = next(&dec)! as (str, pemdecoder);
+ assert(stream.0 == "PRIVATE KEY");
+ const data = io::drain(&stream.1)!;
+ assert(bytes::equal(data, testprivkey_bin));
+
+ assert(next(&dec) is io::EOF);
+};
+
+const testcert_str: str = `
+garbage
+garbage
+garbage
+-----BEGIN CERTIFICATE-----
+MIICLDCCAdKgAwIBAgIBADAKBggqhkjOPQQDAjB9MQswCQYDVQQGEwJCRTEPMA0G
+A1UEChMGR251VExTMSUwIwYDVQQLExxHbnVUTFMgY2VydGlmaWNhdGUgYXV0aG9y
+aXR5MQ8wDQYDVQQIEwZMZXV2ZW4xJTAjBgNVBAMTHEdudVRMUyBjZXJ0aWZpY2F0
+ZSBhdXRob3JpdHkwHhcNMTEwNTIzMjAzODIxWhcNMTIxMjIyMDc0MTUxWjB9MQsw
+CQYDVQQGEwJCRTEPMA0GA1UEChMGR251VExTMSUwIwYDVQQLExxHbnVUTFMgY2Vy
+dGlmaWNhdGUgYXV0aG9yaXR5MQ8wDQYDVQQIEwZMZXV2ZW4xJTAjBgNVBAMTHEdu
+dVRMUyBjZXJ0aWZpY2F0ZSBhdXRob3JpdHkwWTATBgcqhkjOPQIBBggqhkjOPQMB
+BwNCAARS2I0jiuNn14Y2sSALCX3IybqiIJUvxUpj+oNfzngvj/Niyv2394BWnW4X
+uQ4RTEiywK87WRcWMGgJB5kX/t2no0MwQTAPBgNVHRMBAf8EBTADAQH/MA8GA1Ud
+DwEB/wQFAwMHBgAwHQYDVR0OBBYEFPC0gf6YEr+1KLlkQAPLzB9mTigDMAoGCCqG
+SM49BAMCA0gAMEUCIDGuwD1KPyG+hRf88MeyMQcqOFZD0TbVleF+UsAGQ4enAiEA
+l4wOuDwKQa+upc8GftXE2C//4mKANBC6It01gUaTIpo=
+-----END CERTIFICATE-----
+garbage
+`;
+
+const testcert_bin: [_]u8 = [
+ 0x30, 0x82, 0x02, 0x2c, 0x30, 0x82, 0x01, 0xd2, 0xa0, 0x03, 0x02, 0x01,
+ 0x02, 0x02, 0x01, 0x00, 0x30, 0x0a, 0x06, 0x08, 0x2a, 0x86, 0x48, 0xce,
+ 0x3d, 0x04, 0x03, 0x02, 0x30, 0x7d, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03,
+ 0x55, 0x04, 0x06, 0x13, 0x02, 0x42, 0x45, 0x31, 0x0f, 0x30, 0x0d, 0x06,
+ 0x03, 0x55, 0x04, 0x0a, 0x13, 0x06, 0x47, 0x6e, 0x75, 0x54, 0x4c, 0x53,
+ 0x31, 0x25, 0x30, 0x23, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x1c, 0x47,
+ 0x6e, 0x75, 0x54, 0x4c, 0x53, 0x20, 0x63, 0x65, 0x72, 0x74, 0x69, 0x66,
+ 0x69, 0x63, 0x61, 0x74, 0x65, 0x20, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72,
+ 0x69, 0x74, 0x79, 0x31, 0x0f, 0x30, 0x0d, 0x06, 0x03, 0x55, 0x04, 0x08,
+ 0x13, 0x06, 0x4c, 0x65, 0x75, 0x76, 0x65, 0x6e, 0x31, 0x25, 0x30, 0x23,
+ 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x1c, 0x47, 0x6e, 0x75, 0x54, 0x4c,
+ 0x53, 0x20, 0x63, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74,
+ 0x65, 0x20, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x30,
+ 0x1e, 0x17, 0x0d, 0x31, 0x31, 0x30, 0x35, 0x32, 0x33, 0x32, 0x30, 0x33,
+ 0x38, 0x32, 0x31, 0x5a, 0x17, 0x0d, 0x31, 0x32, 0x31, 0x32, 0x32, 0x32,
+ 0x30, 0x37, 0x34, 0x31, 0x35, 0x31, 0x5a, 0x30, 0x7d, 0x31, 0x0b, 0x30,
+ 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x42, 0x45, 0x31, 0x0f,
+ 0x30, 0x0d, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x06, 0x47, 0x6e, 0x75,
+ 0x54, 0x4c, 0x53, 0x31, 0x25, 0x30, 0x23, 0x06, 0x03, 0x55, 0x04, 0x0b,
+ 0x13, 0x1c, 0x47, 0x6e, 0x75, 0x54, 0x4c, 0x53, 0x20, 0x63, 0x65, 0x72,
+ 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x20, 0x61, 0x75, 0x74,
+ 0x68, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x31, 0x0f, 0x30, 0x0d, 0x06, 0x03,
+ 0x55, 0x04, 0x08, 0x13, 0x06, 0x4c, 0x65, 0x75, 0x76, 0x65, 0x6e, 0x31,
+ 0x25, 0x30, 0x23, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x1c, 0x47, 0x6e,
+ 0x75, 0x54, 0x4c, 0x53, 0x20, 0x63, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69,
+ 0x63, 0x61, 0x74, 0x65, 0x20, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69,
+ 0x74, 0x79, 0x30, 0x59, 0x30, 0x13, 0x06, 0x07, 0x2a, 0x86, 0x48, 0xce,
+ 0x3d, 0x02, 0x01, 0x06, 0x08, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x03, 0x01,
+ 0x07, 0x03, 0x42, 0x00, 0x04, 0x52, 0xd8, 0x8d, 0x23, 0x8a, 0xe3, 0x67,
+ 0xd7, 0x86, 0x36, 0xb1, 0x20, 0x0b, 0x09, 0x7d, 0xc8, 0xc9, 0xba, 0xa2,
+ 0x20, 0x95, 0x2f, 0xc5, 0x4a, 0x63, 0xfa, 0x83, 0x5f, 0xce, 0x78, 0x2f,
+ 0x8f, 0xf3, 0x62, 0xca, 0xfd, 0xb7, 0xf7, 0x80, 0x56, 0x9d, 0x6e, 0x17,
+ 0xb9, 0x0e, 0x11, 0x4c, 0x48, 0xb2, 0xc0, 0xaf, 0x3b, 0x59, 0x17, 0x16,
+ 0x30, 0x68, 0x09, 0x07, 0x99, 0x17, 0xfe, 0xdd, 0xa7, 0xa3, 0x43, 0x30,
+ 0x41, 0x30, 0x0f, 0x06, 0x03, 0x55, 0x1d, 0x13, 0x01, 0x01, 0xff, 0x04,
+ 0x05, 0x30, 0x03, 0x01, 0x01, 0xff, 0x30, 0x0f, 0x06, 0x03, 0x55, 0x1d,
+ 0x0f, 0x01, 0x01, 0xff, 0x04, 0x05, 0x03, 0x03, 0x07, 0x06, 0x00, 0x30,
+ 0x1d, 0x06, 0x03, 0x55, 0x1d, 0x0e, 0x04, 0x16, 0x04, 0x14, 0xf0, 0xb4,
+ 0x81, 0xfe, 0x98, 0x12, 0xbf, 0xb5, 0x28, 0xb9, 0x64, 0x40, 0x03, 0xcb,
+ 0xcc, 0x1f, 0x66, 0x4e, 0x28, 0x03, 0x30, 0x0a, 0x06, 0x08, 0x2a, 0x86,
+ 0x48, 0xce, 0x3d, 0x04, 0x03, 0x02, 0x03, 0x48, 0x00, 0x30, 0x45, 0x02,
+ 0x20, 0x31, 0xae, 0xc0, 0x3d, 0x4a, 0x3f, 0x21, 0xbe, 0x85, 0x17, 0xfc,
+ 0xf0, 0xc7, 0xb2, 0x31, 0x07, 0x2a, 0x38, 0x56, 0x43, 0xd1, 0x36, 0xd5,
+ 0x95, 0xe1, 0x7e, 0x52, 0xc0, 0x06, 0x43, 0x87, 0xa7, 0x02, 0x21, 0x00,
+ 0x97, 0x8c, 0x0e, 0xb8, 0x3c, 0x0a, 0x41, 0xaf, 0xae, 0xa5, 0xcf, 0x06,
+ 0x7e, 0xd5, 0xc4, 0xd8, 0x2f, 0xff, 0xe2, 0x62, 0x80, 0x34, 0x10, 0xba,
+ 0x22, 0xdd, 0x35, 0x81, 0x46, 0x93, 0x22, 0x9a,
+];
+
+const testmany: str = `
+-----BEGIN CERTIFICATE-----
+MIICLDCCAdKgAwIBAgIBADAKBggqhkjOPQQDAjB9MQswCQYDVQQGEwJCRTEPMA0G
+A1UEChMGR251VExTMSUwIwYDVQQLExxHbnVUTFMgY2VydGlmaWNhdGUgYXV0aG9y
+aXR5MQ8wDQYDVQQIEwZMZXV2ZW4xJTAjBgNVBAMTHEdudVRMUyBjZXJ0aWZpY2F0
+ZSBhdXRob3JpdHkwHhcNMTEwNTIzMjAzODIxWhcNMTIxMjIyMDc0MTUxWjB9MQsw
+CQYDVQQGEwJCRTEPMA0GA1UEChMGR251VExTMSUwIwYDVQQLExxHbnVUTFMgY2Vy
+dGlmaWNhdGUgYXV0aG9yaXR5MQ8wDQYDVQQIEwZMZXV2ZW4xJTAjBgNVBAMTHEdu
+dVRMUyBjZXJ0aWZpY2F0ZSBhdXRob3JpdHkwWTATBgcqhkjOPQIBBggqhkjOPQMB
+BwNCAARS2I0jiuNn14Y2sSALCX3IybqiIJUvxUpj+oNfzngvj/Niyv2394BWnW4X
+uQ4RTEiywK87WRcWMGgJB5kX/t2no0MwQTAPBgNVHRMBAf8EBTADAQH/MA8GA1Ud
+DwEB/wQFAwMHBgAwHQYDVR0OBBYEFPC0gf6YEr+1KLlkQAPLzB9mTigDMAoGCCqG
+SM49BAMCA0gAMEUCIDGuwD1KPyG+hRf88MeyMQcqOFZD0TbVleF+UsAGQ4enAiEA
+l4wOuDwKQa+upc8GftXE2C//4mKANBC6It01gUaTIpo=
+-----END CERTIFICATE-----
+-----BEGIN PRIVATE KEY-----
+MIGEAgEAMBAGByqGSM49AgEGBSuBBAAKBG0wawIBAQQgVcB/UNPxalR9zDYAjQIf
+jojUDiQuGnSJrFEEzZPT/92hRANCAASc7UJtgnF/abqWM60T3XNJEzBv5ez9TdwK
+H0M6xpM2q+53wmsN/eYLdgtjgBd3DBmHtPilCkiFICXyaA8z9LkJ
+-----END PRIVATE KEY-----
+`;
+
+const testprivkey_bin: [_]u8 = [
+ 0x30, 0x81, 0x84, 0x02, 0x01, 0x00, 0x30, 0x10, 0x06, 0x07, 0x2a, 0x86,
+ 0x48, 0xce, 0x3d, 0x02, 0x01, 0x06, 0x05, 0x2b, 0x81, 0x04, 0x00, 0x0a,
+ 0x04, 0x6d, 0x30, 0x6b, 0x02, 0x01, 0x01, 0x04, 0x20, 0x55, 0xc0, 0x7f,
+ 0x50, 0xd3, 0xf1, 0x6a, 0x54, 0x7d, 0xcc, 0x36, 0x00, 0x8d, 0x02, 0x1f,
+ 0x8e, 0x88, 0xd4, 0x0e, 0x24, 0x2e, 0x1a, 0x74, 0x89, 0xac, 0x51, 0x04,
+ 0xcd, 0x93, 0xd3, 0xff, 0xdd, 0xa1, 0x44, 0x03, 0x42, 0x00, 0x04, 0x9c,
+ 0xed, 0x42, 0x6d, 0x82, 0x71, 0x7f, 0x69, 0xba, 0x96, 0x33, 0xad, 0x13,
+ 0xdd, 0x73, 0x49, 0x13, 0x30, 0x6f, 0xe5, 0xec, 0xfd, 0x4d, 0xdc, 0x0a,
+ 0x1f, 0x43, 0x3a, 0xc6, 0x93, 0x36, 0xab, 0xee, 0x77, 0xc2, 0x6b, 0x0d,
+ 0xfd, 0xe6, 0x0b, 0x76, 0x0b, 0x63, 0x80, 0x17, 0x77, 0x0c, 0x19, 0x87,
+ 0xb4, 0xf8, 0xa5, 0x0a, 0x48, 0x85, 0x20, 0x25, 0xf2, 0x68, 0x0f, 0x33,
+ 0xf4, 0xb9, 0x09,
+];
diff --git a/encoding/pem/pem.ha b/encoding/pem/pem.ha
@@ -0,0 +1,196 @@
+use ascii;
+use bufio;
+use encoding::base64;
+use errors;
+use io;
+use os;
+use strings;
+use strio;
+
+const begin: str = "-----BEGIN ";
+const end: str = "-----END ";
+const suffix: str = "-----";
+
+export type decoder = struct {
+ in: bufio::bufstream,
+ label: strio::stream,
+ buf: []u8,
+};
+
+export type b64stream = struct {
+ stream: io::stream,
+ in: *bufio::bufstream,
+ readend: bool,
+};
+
+export type pemdecoder = struct {
+ stream: io::stream,
+ in: *bufio::bufstream,
+ b64_in: b64stream,
+ b64: base64::decoder,
+ // XXX: kind of dumb but it saves us some memory management problems
+ b64_ready: bool,
+};
+
+const pemdecoder_vt: io::vtable = io::vtable {
+ reader = &pem_read,
+ ...
+};
+
+const b64stream_r_vt: io::vtable = io::vtable {
+ reader = &b64_read,
+ ...
+};
+
+// Creates a new PEM decoder. The caller must either read it until it returns
+// [[io::EOF]], or call [[finish]] to free state associated with the parser.
+export fn newdecoder(in: io::handle) decoder = {
+ let buf: []u8 = alloc([0...], os::BUFSIZ);
+ return decoder {
+ in = bufio::buffered(in, buf, []),
+ buf = buf,
+ label = strio::dynamic(),
+ };
+};
+
+// Frees state associated with this [[decoder]].
+export fn finish(dec: *decoder) void = {
+ io::close(&dec.label)!;
+ free(dec.buf);
+};
+
+// Converts an I/O error returned from a PEM decoder into a human-friendly
+// string.
+export fn strerror(err: io::error) const str = {
+ match (err) {
+ case errors::invalid =>
+ return "Invalid PEM data";
+ case =>
+ return io::strerror(err);
+ };
+};
+
+// Finds the next PEM boundary in the stream, ignoring any non-PEM data, and
+// returns the label and a [[pemdecoder]] from which the encoded data may be
+// read, or [[io::EOF]] if no further PEM boundaries are found. The user must
+// completely read the pemdecoder until it returns [[io::EOF]] before calling
+// [[next]] again.
+//
+// The label returned by this function is borrowed from the decoder state and
+// does not contain "-----BEGIN " or "-----".
+export fn next(dec: *decoder) ((str, pemdecoder) | io::EOF | io::error) = {
+ for (true) {
+ // XXX: This can be improved following
+ // https://todo.sr.ht/~sircmpwn/hare/562
+ const line = match (bufio::scanline(&dec.in)?) {
+ case io::EOF =>
+ return io::EOF;
+ case let line: []u8 =>
+ yield match (strings::try_fromutf8(line)) {
+ case let s: str =>
+ yield s;
+ case =>
+ return errors::invalid;
+ };
+ };
+ defer free(line);
+
+ if (!strings::hasprefix(line, begin)
+ || !strings::hassuffix(line, suffix)) {
+ continue;
+ };
+
+ strio::reset(&dec.label);
+ strio::concat(&dec.label, strings::sub(line,
+ len(begin), len(line) - len(suffix)))!;
+
+ return (strio::string(&dec.label), pemdecoder {
+ stream = &pemdecoder_vt,
+ in = &dec.in,
+ b64_ready = false,
+ ...
+ });
+ };
+
+ abort(); // Unreachable
+};
+
+fn pem_read(st: *io::stream, buf: []u8) (size | io::EOF | io::error) = {
+ // We need to set up two streams. This is the stream which is actually
+ // returned to the caller, which calls the base64 decoder against a
+ // special stream (b64stream) which trims out whitespace and EOF's on
+ // -----END.
+ const st = st: *pemdecoder;
+ assert(st.stream.reader == &pem_read);
+ if (!st.b64_ready) {
+ st.b64_in = b64stream {
+ stream = &b64stream_r_vt,
+ in = st.in,
+ readend = false,
+ };
+ st.b64 = base64::newdecoder(&base64::std_encoding, &st.b64_in);
+ st.b64_ready = true;
+ };
+
+ match (io::read(&st.b64, buf)?) {
+ case let z: size =>
+ return z;
+ case io::EOF =>
+ yield;
+ };
+
+ const line = match (bufio::scanline(st.in)?) {
+ case io::EOF =>
+ return io::EOF;
+ case let line: []u8 =>
+ yield match (strings::try_fromutf8(line)) {
+ case let s: str =>
+ yield s;
+ case =>
+ return errors::invalid;
+ };
+ };
+ defer free(line);
+
+ if (!strings::hasprefix(line, end)
+ || !strings::hassuffix(line, suffix)) {
+ return errors::invalid;
+ };
+
+ // XXX: We could verify the trailer matches but the RFC says it's
+ // optional.
+ return io::EOF;
+};
+
+fn b64_read(st: *io::stream, buf: []u8) (size | io::EOF | io::error) = {
+ const st = st: *b64stream;
+ assert(st.stream.reader == &b64_read);
+
+ const z = match (io::read(st.in, buf)?) {
+ case let z: size =>
+ yield z;
+ case io::EOF =>
+ return errors::invalid; // Missing -----END
+ };
+
+ // Trim off whitespace and look for -----END
+ let sub = buf[..z];
+ for (let i = 0z; i < len(sub); i += 1) {
+ if (sub[i] == '-') {
+ bufio::unread(st.in, sub[i..]);
+ sub = sub[..i];
+ break;
+ };
+ if (ascii::isspace(sub[i]: u32: rune)) {
+ static delete(sub[i]);
+ i -= 1;
+ continue;
+ };
+ };
+
+ if (len(sub) == 0) {
+ return io::EOF;
+ };
+
+ return len(sub);
+};
diff --git a/scripts/gen-stdlib b/scripts/gen-stdlib
@@ -495,6 +495,20 @@ encoding_hex() {
gen_ssa encoding::hex ascii bytes fmt io strconv strio strings
}
+encoding_pem() {
+ if [ $testing -eq 0 ]
+ then
+ gen_srcs encoding::pem \
+ pem.ha
+ else
+ gen_srcs encoding::pem \
+ pem.ha \
+ +test.ha
+ fi
+ gen_ssa encoding::pem strings bufio strio io errors bytes \
+ encoding::base64 ascii os
+}
+
encoding_utf8() {
gen_srcs encoding::utf8 \
decode.ha \
@@ -1334,6 +1348,7 @@ dirs
encoding::base64
encoding::base32
encoding::hex
+encoding::pem
encoding::utf8
endian
errors
diff --git a/stdlib.mk b/stdlib.mk
@@ -302,6 +302,12 @@ stdlib_deps_any += $(stdlib_encoding_hex_any)
stdlib_encoding_hex_linux = $(stdlib_encoding_hex_any)
stdlib_encoding_hex_freebsd = $(stdlib_encoding_hex_any)
+# gen_lib encoding::pem (any)
+stdlib_encoding_pem_any = $(HARECACHE)/encoding/pem/encoding_pem-any.o
+stdlib_deps_any += $(stdlib_encoding_pem_any)
+stdlib_encoding_pem_linux = $(stdlib_encoding_pem_any)
+stdlib_encoding_pem_freebsd = $(stdlib_encoding_pem_any)
+
# gen_lib encoding::utf8 (any)
stdlib_encoding_utf8_any = $(HARECACHE)/encoding/utf8/encoding_utf8-any.o
stdlib_deps_any += $(stdlib_encoding_utf8_any)
@@ -1029,6 +1035,16 @@ $(HARECACHE)/encoding/hex/encoding_hex-any.ssa: $(stdlib_encoding_hex_any_srcs)
@HARECACHE=$(HARECACHE) $(HAREC) $(HAREFLAGS) -o $@ -Nencoding::hex \
-t$(HARECACHE)/encoding/hex/encoding_hex.td $(stdlib_encoding_hex_any_srcs)
+# encoding::pem (+any)
+stdlib_encoding_pem_any_srcs = \
+ $(STDLIB)/encoding/pem/pem.ha
+
+$(HARECACHE)/encoding/pem/encoding_pem-any.ssa: $(stdlib_encoding_pem_any_srcs) $(stdlib_rt) $(stdlib_strings_$(PLATFORM)) $(stdlib_bufio_$(PLATFORM)) $(stdlib_strio_$(PLATFORM)) $(stdlib_io_$(PLATFORM)) $(stdlib_errors_$(PLATFORM)) $(stdlib_bytes_$(PLATFORM)) $(stdlib_encoding_base64_$(PLATFORM)) $(stdlib_ascii_$(PLATFORM)) $(stdlib_os_$(PLATFORM))
+ @printf 'HAREC \t$@\n'
+ @mkdir -p $(HARECACHE)/encoding/pem
+ @HARECACHE=$(HARECACHE) $(HAREC) $(HAREFLAGS) -o $@ -Nencoding::pem \
+ -t$(HARECACHE)/encoding/pem/encoding_pem.td $(stdlib_encoding_pem_any_srcs)
+
# encoding::utf8 (+any)
stdlib_encoding_utf8_any_srcs = \
$(STDLIB)/encoding/utf8/decode.ha \
@@ -2332,6 +2348,12 @@ testlib_deps_any += $(testlib_encoding_hex_any)
testlib_encoding_hex_linux = $(testlib_encoding_hex_any)
testlib_encoding_hex_freebsd = $(testlib_encoding_hex_any)
+# gen_lib encoding::pem (any)
+testlib_encoding_pem_any = $(TESTCACHE)/encoding/pem/encoding_pem-any.o
+testlib_deps_any += $(testlib_encoding_pem_any)
+testlib_encoding_pem_linux = $(testlib_encoding_pem_any)
+testlib_encoding_pem_freebsd = $(testlib_encoding_pem_any)
+
# gen_lib encoding::utf8 (any)
testlib_encoding_utf8_any = $(TESTCACHE)/encoding/utf8/encoding_utf8-any.o
testlib_deps_any += $(testlib_encoding_utf8_any)
@@ -3078,6 +3100,17 @@ $(TESTCACHE)/encoding/hex/encoding_hex-any.ssa: $(testlib_encoding_hex_any_srcs)
@HARECACHE=$(TESTCACHE) $(HAREC) $(TESTHAREFLAGS) -o $@ -Nencoding::hex \
-t$(TESTCACHE)/encoding/hex/encoding_hex.td $(testlib_encoding_hex_any_srcs)
+# encoding::pem (+any)
+testlib_encoding_pem_any_srcs = \
+ $(STDLIB)/encoding/pem/pem.ha \
+ $(STDLIB)/encoding/pem/+test.ha
+
+$(TESTCACHE)/encoding/pem/encoding_pem-any.ssa: $(testlib_encoding_pem_any_srcs) $(testlib_rt) $(testlib_strings_$(PLATFORM)) $(testlib_bufio_$(PLATFORM)) $(testlib_strio_$(PLATFORM)) $(testlib_io_$(PLATFORM)) $(testlib_errors_$(PLATFORM)) $(testlib_bytes_$(PLATFORM)) $(testlib_encoding_base64_$(PLATFORM)) $(testlib_ascii_$(PLATFORM)) $(testlib_os_$(PLATFORM))
+ @printf 'HAREC \t$@\n'
+ @mkdir -p $(TESTCACHE)/encoding/pem
+ @HARECACHE=$(TESTCACHE) $(HAREC) $(TESTHAREFLAGS) -o $@ -Nencoding::pem \
+ -t$(TESTCACHE)/encoding/pem/encoding_pem.td $(testlib_encoding_pem_any_srcs)
+
# encoding::utf8 (+any)
testlib_encoding_utf8_any_srcs = \
$(STDLIB)/encoding/utf8/decode.ha \