diff --git a/assets/ponylang.pem b/assets/ponylang.pem new file mode 100644 index 0000000..569a77f --- /dev/null +++ b/assets/ponylang.pem @@ -0,0 +1,25 @@ +-----BEGIN CERTIFICATE----- +MIIESTCCAzGgAwIBAgISAyiN9Tg8FyZYAt5ifETG04U1MA0GCSqGSIb3DQEBCwUA +MDIxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1MZXQncyBFbmNyeXB0MQswCQYDVQQD +EwJSMzAeFw0yMzA4MTUwMDMwMjdaFw0yMzExMTMwMDMwMjZaMBYxFDASBgNVBAMT +C3BvbnlsYW5nLmlvMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEznT8wpdDL2ry +ck92h8gKHAzw+RsqmnmFy+UmMjpNfEreps/Brw0kJy4u6qTD2y7Mo5Z27lm6yLmr +Q1oH2zSZ46OCAj4wggI6MA4GA1UdDwEB/wQEAwIHgDAdBgNVHSUEFjAUBggrBgEF +BQcDAQYIKwYBBQUHAwIwDAYDVR0TAQH/BAIwADAdBgNVHQ4EFgQUtU2t7EpXuiF3 +WwoJiYyRUdjzNgswHwYDVR0jBBgwFoAUFC6zF7dYVsuuUAlA5h+vnYsUwsYwVQYI +KwYBBQUHAQEESTBHMCEGCCsGAQUFBzABhhVodHRwOi8vcjMuby5sZW5jci5vcmcw +IgYIKwYBBQUHMAKGFmh0dHA6Ly9yMy5pLmxlbmNyLm9yZy8wRwYDVR0RBEAwPoIL +cG9ueWxhbmcuaW+CDHBvbnlsYW5nLm9yZ4IPd3d3LnBvbnlsYW5nLmlvghB3d3cu +cG9ueWxhbmcub3JnMBMGA1UdIAQMMAowCAYGZ4EMAQIBMIIBBAYKKwYBBAHWeQIE +AgSB9QSB8gDwAHYAtz77JN+cTbp18jnFulj0bF38Qs96nzXEnh0JgSXttJkAAAGJ +9tDSVwAABAMARzBFAiA9JskK//tV8DKBjGG4r82v7RP7be7nZaWOGZCT7DFVhwIh +AO/PZjQsLfrKn3I8cWkaud7GawC+BPepg+b4noISjMIrAHYAejKMVNi3LbYg6jjg +Uh7phBZwMhOFTTvSK8E6V6NS61IAAAGJ9tDSaQAABAMARzBFAiB09LtPj5dVRlpZ +bu2aM4fEbmEVetZNRa73EtvyFx6A0AIhAK2a4UTXOceheNUTvVe3EH184/mPJK97 +AT3u8yPwkAq5MA0GCSqGSIb3DQEBCwUAA4IBAQCHFrS1D9yhRD+ci2Om6Y/W/++1 +OHH+xJ3+rGNC4YtTnv1im/SsXeN5TJBDHFtvXIwuwFotsShBUIbUahpUgwG3pTkz +vtIB48MG0r1UJoEfzxVc3Vc0fnAoLezO8mBVJjg50JaiUGSoW0s6pSE113OPC8gM +l1g5o9GwbUImtks/9oqT4nWpNWAjT6tzpC2U2BZ11zVT6CpkEdPClpyclZp/rwbE +nrN/pEiH251LvkER8eCQRXSAW6t+cWjpWjy9g2kcuj99TQbh9ocmaX10/uWJisIg +LtgRjmQzkQC7gWl5QMXSiZhXG31+4MsQGjpQiYFsDZry+7KxYniJ4qcsV7Sn +-----END CERTIFICATE----- diff --git a/net_ssl/_asn1_string.pony b/net_ssl/_asn1_string.pony new file mode 100644 index 0000000..050c91e --- /dev/null +++ b/net_ssl/_asn1_string.pony @@ -0,0 +1,40 @@ +use @ASN1_TIME_to_tm[I32](asn1str: Pointer[ASN1String] tag, tm: Tm) +use @ASN1_STRING_get0_data[Pointer[U8] ref](asn1s: Pointer[ASN1String] tag) +use @ASN1_STRING_length[I32](asn1s: Pointer[ASN1String] tag) +use @ASN1_STRING_type[I32](asn1s: Pointer[ASN1String] tag) +use @printf[I32](fmt: Pointer[U8] tag, ...) + +primitive ASN1String + """ + FIXME + Functions used to do transformations on ASN1Strings. + (Note - this will almost certainly transition to a class when it comes + time to wrap this in an API to be able to create these objects) + + NOTE: OpenSSL stores almost ALL data in ASN1Strings and almost all of + the other ASN1 types are typedef'd to this under the hood (hence the + time_to_posix function here) + """ + fun array(asn1str: Pointer[ASN1String] tag): Array[U8] val ? => + """ + Returns the underlying data as an Array[U8] + """ + if (asn1str.is_null()) then error end + recover val + let len: I32 = @ASN1_STRING_length(asn1str) + let osslptr: Pointer[U8] ref = @ASN1_STRING_get0_data(asn1str) + + (Array[U8].from_cpointer(osslptr, len.usize())).clone() + end + + fun time_to_posix(asn1str: Pointer[ASN1String] tag): I64 => + """ + Assuming this is a valid entry for time, will return the unix epoch time. + FIXME -- add check for this + """ + let tm: Tm = Tm + @ASN1_TIME_to_tm(asn1str, tm) + tm.timegm() +// let t: I64 = tm.timegm() +// @printf("%d : %d : %d\n".cstring(), tm.tm_isdst, tm.tm_gmtoff, t) +// t + tm.tm_gmtoff diff --git a/net_ssl/_bio.pony b/net_ssl/_bio.pony new file mode 100644 index 0000000..1ff8fc9 --- /dev/null +++ b/net_ssl/_bio.pony @@ -0,0 +1,65 @@ +use @BIO_new[Pointer[_BIO]](typ: Pointer[U8]) +use @BIO_s_mem[Pointer[U8]]() +use @BIO_free[I32](a: Pointer[_BIO] tag) +use @BIO_free_all[None](a: Pointer[_BIO] tag) +use @BIO_write[I32](bio: Pointer[_BIO] tag, buf: Pointer[U8] tag, len: U32) +use @BIO_read[I32](b: Pointer[_BIO] tag, data: Pointer[U8] tag, dlen: U32) +use @BIO_ctrl_pending[USize](bio: Pointer[_BIO] tag) +use @BIO_gets[U32](bio: Pointer[_BIO] tag, buffer: Pointer[U8] tag, size: U32) +use @PEM_write_bio_X509[U32](bio: Pointer[_BIO] tag, cert: Pointer[X509] tag) + +primitive _BIO + """ + BIO is OpenSSL's generic datastructure that allows the rest of the library + to write "contiguous" binary data without having to worry about memory + reällocation. + + In this API it is mainly used for reading data in and out + + // Notate why this isn't used in the SSL setup. (double-free) + """ + + fun new_ptr(): Pointer[_BIO] tag => + @BIO_new(@BIO_s_mem()) + + fun array(bio: Pointer[_BIO] tag): Array[U8] val => + """ + Returns the contents of the BIO as an Array[U8] val + /// use "buffered" + """ + recover val + var tarr: Array[U8] ref = Array[U8] + let buffer: Array[U8] ref = Array[U8].init(0, 1024) // Look for the 95% percentile + var len: I32 = 0 + while ((len = @BIO_read(bio, buffer.cpointer(), buffer.size().u32())); len > 0) do + buffer.copy_to(tarr, 0, tarr.size(), len.usize()) + end + tarr + end + + fun write(bio: Pointer[_BIO] tag, data: (Array[U8] val | String val)): Bool => + """ + Writes the contents of the provided Array[U8] or String to the + BIO + """ + let readdata: I32 = @BIO_write(bio, data.cpointer(), data.size().u32()) + (readdata == data.size().i32()) + + fun string(bio: Pointer[_BIO] tag): String val => + """ + Returns the contents of the BIO as a String val + """ + String.from_array(array(bio)) + + + fun write_pem_x509(bio: Pointer[_BIO] tag, cert: X509Certificate box): U32 => + """ + Writes the certificate in pem form into this Bio object + """ + @PEM_write_bio_X509(bio, cert._ptr()) + + + fun free(bio: Pointer[_BIO] tag) => + @BIO_free_all(bio) + + diff --git a/net_ssl/_test.pony b/net_ssl/_test.pony index 51a3d30..600c5af 100755 --- a/net_ssl/_test.pony +++ b/net_ssl/_test.pony @@ -15,6 +15,7 @@ actor \nodoc\ Main is TestList test(_TestTCPSSLExpect) test(_TestTCPSSLMute) test(_TestTCPSSLUnmute) + test(_TestX509Certificate) ifdef windows then test(_TestWindowsLoadRootCertificates) else @@ -684,3 +685,56 @@ primitive \nodoc\ _TestSSLContext end (consume ssl_client, consume ssl_server) + +// test(_TestX509Certificates) + +class \nodoc\ iso _TestX509Certificate is UnitTest + """ + Test loading a certificate and analyze it's metadata. + """ + fun name(): String => "x509/_TestX509Certificate" + + fun ref apply(h: TestHelper) => + try + let auth = FileAuth(h.env.root) + let fp: FilePath = FilePath(auth, "assets/ponylang.pem") + let f: File = File.open(fp) + let pemdata: String val = f.read_string(f.size()) + + let certificate: X509Certificate val = X509Certificate.from_pem(pemdata)? + h.assert_false(certificate.is_null()) + h.assert_eq[String](certificate.get_pem(), pemdata.clone().>remove("\r")) + h.assert_eq[String](certificate.issuer_name()?, "R3") + h.assert_eq[String](certificate.subject_name()?, "ponylang.io") + h.assert_eq[String](certificate.fingerprint_sha1(), "6F6E4D79A77B44D524F8C2FB62E46559B3FF7EB4") + h.assert_eq[String](certificate.fingerprint_sha256(), "35E5ACDA20A72168E05E93813A4B62F91E9E2594A243FE73213F4323BA264BFF") + h.assert_eq[String](certificate.fingerprint_md5(), "62AE902FC8BB8A66856CCC3A1066D663") + h.assert_eq[I64](certificate.not_before_posix(), 1692059427) + h.assert_eq[I64](certificate.not_after_posix(), 1699835426) + h.assert_eq[String](certificate.serial()?, "03:28:8D:F5:38:3C:17:26:58:02:DE:62:7C:44:C6:D3:85:35") + h.assert_false(certificate.is_ca()) + + ifdef "openssl_1.1.x" or "openssl_3.0.x" then + h.assert_eq[String](certificate.authority_key_id()?, "14:2E:B3:17:B7:58:56:CB:AE:50:09:40:E6:1F:AF:9D:8B:14:C2:C6") + h.assert_eq[String](certificate.key_id()?, "B5:4D:AD:EC:4A:57:BA:21:77:5B:0A:09:89:8C:91:51:D8:F3:36:0B") + else + h.assert_error({(): None ? => certificate.authority_key_id()?}) + h.assert_error({(): None ? => certificate.key_id()?}) + end + + ifdef "openssl_3.0.x" then + h.assert_false(certificate.is_self_signed()?) + else + h.assert_error({(): None ? => certificate.is_self_signed()?}) + end + + let mcert: X509Certificate iso = certificate.clone() + h.assert_true(true) + h.assert_false(mcert.is_null()) + h.assert_eq[String](mcert.get_pem(), pemdata.clone().>remove("\r")) + + + else + h.fail("Certificate Tests failed") + end + diff --git a/net_ssl/_tm.pony b/net_ssl/_tm.pony new file mode 100644 index 0000000..18e81c6 --- /dev/null +++ b/net_ssl/_tm.pony @@ -0,0 +1,25 @@ +use @timegm[I64](tm: Tm) + +struct Tm + """ + Pony mapping of the C struct tm + """ + var tm_sec: I32 = I32(0) // FundamentalType + var tm_min: I32 = I32(0) // FundamentalType + var tm_hour: I32 = I32(0) // FundamentalType + var tm_mday: I32 = I32(0) // FundamentalType + var tm_mon: I32 = I32(0) // FundamentalType + var tm_year: I32 = I32(0) // FundamentalType + var tm_wday: I32 = I32(0) // FundamentalType + var tm_yday: I32 = I32(0) // FundamentalType + var tm_isdst: I32 = I32(0) // FundamentalType + var tm_gmtoff: I64 = I64(0) // FundamentalType + var tm_zone: Pointer[U8] = Pointer[U8] // PointerType + + fun ref timegm(): I64 => + """ + Mapping to the C function call which takes a Tm struct and returns + UTC UNIX epoch time. + """ + @timegm(this) + diff --git a/net_ssl/_x509_name.pony b/net_ssl/_x509_name.pony new file mode 100644 index 0000000..0df2bd4 --- /dev/null +++ b/net_ssl/_x509_name.pony @@ -0,0 +1,20 @@ +use @X509_NAME_get_text_by_NID[I32](x509name: Pointer[_X509Name] tag, nid: I32, buf: Pointer[U8] tag, len: I32) + +primitive _X509Name + """ + Functions to convert X509Name to Strings for display + (Will likely become a class in the future to support generating them + """ + fun string(x509name: Pointer[_X509Name] tag): String val ? => + """ + Converts X509Names into String val + """ + if (x509name.is_null()) then error end + let len: I32 = @X509_NAME_get_text_by_NID(x509name, I32(13), Pointer[U8], I32(0)) + if (len == -1) then error end + let str: String ref = recover String(len.usize()) end + @X509_NAME_get_text_by_NID(x509name, I32(13), str.cstring(), len+1) + str.recalc() + if (str.size() != len.usize()) then error end + str.clone() + diff --git a/net_ssl/ssl.pony b/net_ssl/ssl.pony index edbd22b..c4996f0 100755 --- a/net_ssl/ssl.pony +++ b/net_ssl/ssl.pony @@ -8,8 +8,6 @@ use @SSL_ctrl[ILong]( use @SSL_new[Pointer[_SSL]](ctx: Pointer[_SSLContext] tag) use @SSL_free[None](ssl: Pointer[_SSL] tag) use @SSL_set_verify[None](ssl: Pointer[_SSL], mode: I32, cb: Pointer[U8]) -use @BIO_s_mem[Pointer[U8]]() -use @BIO_new[Pointer[_BIO]](typ: Pointer[U8]) use @SSL_set_bio[None](ssl: Pointer[_SSL], rbio: Pointer[_BIO] tag, wbio: Pointer[_BIO] tag) use @SSL_set_accept_state[None](ssl: Pointer[_SSL]) use @SSL_set_connect_state[None](ssl: Pointer[_SSL]) @@ -19,16 +17,12 @@ use @SSL_get0_alpn_selected[None](ssl: Pointer[_SSL] tag, data: Pointer[Pointer[ use @SSL_pending[I32](ssl: Pointer[_SSL]) use @SSL_read[I32](ssl: Pointer[_SSL], buf: Pointer[U8] tag, len: U32) use @SSL_write[I32](ssl: Pointer[_SSL], buf: Pointer[U8] tag, len: U32) -use @BIO_read[I32](bio: Pointer[_BIO] tag, buf: Pointer[U8] tag, len: U32) -use @BIO_write[I32](bio: Pointer[_BIO] tag, buf: Pointer[U8] tag, len: U32) use @SSL_get_error[I32](ssl: Pointer[_SSL], ret: I32) -use @BIO_ctrl_pending[USize](bio: Pointer[_BIO] tag) use @SSL_has_pending[I32](ssl: Pointer[_SSL]) if "openssl_1.1.x" or "openssl_3.0.x" -use @SSL_get_peer_certificate[Pointer[X509]](ssl: Pointer[_SSL]) if "openssl_1.1.x" or "openssl_0.9.0" -use @SSL_get1_peer_certificate[Pointer[X509]](ssl: Pointer[_SSL]) if "openssl_3.0.x" +use @SSL_get_peer_certificate[Pointer[X509]](ssl: Pointer[_SSL] tag) if "openssl_1.1.x" or "openssl_0.9.0" +use @SSL_get1_peer_certificate[Pointer[X509]](ssl: Pointer[_SSL] tag) if "openssl_3.0.x" primitive _SSL -primitive _BIO primitive SSLHandshake primitive SSLAuthFail @@ -92,6 +86,17 @@ class SSL @SSL_do_handshake(_ssl) end + fun get_peer_certificate(): X509Certificate val => + """ + Returns the X509Certificate from this SSL connection + """ + ifdef "openssl_3.0.x" then + return X509Certificate._from_cert_ptr( + @SSL_get1_peer_certificate[Pointer[X509] tag](_ssl) + ) + end + recover val X509Certificate end + fun box alpn_selected(): (ALPNProtocolName | None) => """ Get the protocol identifier negotiated via ALPN diff --git a/net_ssl/ssl_context.pony b/net_ssl/ssl_context.pony index a91a744..1d4d851 100755 --- a/net_ssl/ssl_context.pony +++ b/net_ssl/ssl_context.pony @@ -29,7 +29,6 @@ use @CertEnumCertificatesInStore[NullablePointer[_CertContext]](cert_store: Poin use @d2i_X509[Pointer[X509] tag](val_out: Pointer[U8] tag, der_in: Pointer[Pointer[U8]], length: U32) use @X509_STORE_add_cert[U32](store: Pointer[U8] tag, x509: Pointer[X509] tag) -use @X509_free[None](x509: Pointer[X509] tag) use @SSL_CTX_set_cert_store[None](ctx: Pointer[_SSLContext] tag, store: Pointer[U8] tag) use @X509_STORE_free[None](store: Pointer[U8] tag) use @CertCloseStore[Bool](store: Pointer[U8] tag, flags: U32) if windows diff --git a/net_ssl/x509.pony b/net_ssl/x509.pony index 05da113..7ac52c8 100755 --- a/net_ssl/x509.pony +++ b/net_ssl/x509.pony @@ -2,9 +2,6 @@ use "collections" use "net" use @pony_os_ip_string[Pointer[U8]](src: Pointer[U8], len: I32) -use @X509_get_subject_name[Pointer[_X509Name]](cert: Pointer[X509]) -use @X509_NAME_get_text_by_NID[I32](name: Pointer[_X509Name], nid: I32, - buf: Pointer[U8] tag, len: I32) use @X509_get_ext_d2i[Pointer[_GeneralNameStack]](cert: Pointer[X509], nid: I32, crit: Pointer[U8], idx: Pointer[U8]) use @OPENSSL_sk_pop[Pointer[_GeneralName]](stack: Pointer[_GeneralNameStack]) @@ -13,16 +10,12 @@ use @sk_pop[Pointer[_GeneralName]](stack: Pointer[_GeneralNameStack]) if "openssl_0.9.0" use @GENERAL_NAME_get0_value[Pointer[U8] tag](name: Pointer[_GeneralName], ptype: Pointer[I32]) -use @ASN1_STRING_type[I32](value: Pointer[U8] tag) -use @ASN1_STRING_get0_data[Pointer[U8]](value: Pointer[U8] tag) -use @ASN1_STRING_length[I32](value: Pointer[U8] tag) use @GENERAL_NAME_free[None](name: Pointer[_GeneralName]) use @OPENSSL_sk_free[None](stack: Pointer[_GeneralNameStack]) if "openssl_1.1.x" or "openssl_3.0.x" use @sk_free[None](stack: Pointer[_GeneralNameStack]) if "openssl_0.9.0" -primitive _X509Name primitive _GeneralName primitive _GeneralNameStack @@ -95,7 +88,7 @@ primitive X509 while not name.is_null() do var ptype = I32(0) let value = - @GENERAL_NAME_get0_value(name, addressof ptype) + @GENERAL_NAME_get0_value[Pointer[ASN1String] tag](name, addressof ptype) match ptype | 2 => // GEN_DNS diff --git a/net_ssl/x509_certificate.pony b/net_ssl/x509_certificate.pony new file mode 100644 index 0000000..32149c9 --- /dev/null +++ b/net_ssl/x509_certificate.pony @@ -0,0 +1,275 @@ +use "format" +use "collections" + +use @X509_new[Pointer[X509] tag]() +use @X509_dup[Pointer[X509] tag](x: Pointer[X509] tag) +use @X509_free[None](x: Pointer[X509] tag) +use @X509_print[I32](bp: Pointer[_BIO] tag, x: Pointer[X509] tag) +use @PEM_read_bio_X509[Pointer[X509] tag](bio: Pointer[_BIO] tag, x509: Pointer[X509] tag, cb: Pointer[None], u: Pointer[None]) +use @X509_get_issuer_name[Pointer[_X509Name] tag](a: Pointer[X509] tag) +use @X509_get_subject_name[Pointer[_X509Name] tag](a: Pointer[X509] tag) +use @X509_digest[U32](data: Pointer[X509] tag, dtype: Pointer[_EVPMD], str: Pointer[U8] tag, len: Pointer[U32] tag) +use @EVP_sha1[Pointer[_EVPMD]]() +use @EVP_sha256[Pointer[_EVPMD]]() +use @EVP_md5[Pointer[_EVPMD]]() +use @X509_get_version[I64](x: Pointer[X509] tag) +use @X509_get0_notAfter[Pointer[ASN1String] tag](x: Pointer[X509] tag) +use @X509_get0_notBefore[Pointer[ASN1String] tag](x: Pointer[X509] tag) +use @X509_get0_serialNumber[Pointer[ASN1String] tag](x: Pointer[X509] tag) +use @X509_get0_subject_key_id[Pointer[ASN1String] tag](x: Pointer[X509] tag) if "openssl_1.1.x" or "openssl_3.0.x" +use @X509_get0_authority_key_id[Pointer[ASN1String] tag](x: Pointer[X509] tag) if "openssl_1.1.x" or "openssl_3.0.x" + + +use @X509_check_ca[ISize](x: Pointer[X509] tag) +use @X509_self_signed[ISize](x: Pointer[X509] tag, verify: ISize) if "openssl_3.0.x" + + + +primitive _EVPMD +primitive _X509Unsupported + +class X509Certificate + """ + An X509Certificate with all that that entails + """ + + var _cert: Pointer[X509] tag = Pointer[X509] + + new create() => + _cert = @X509_new() + + new iso _from_cert_ptr(cert: Pointer[X509] tag) => + """ + Private method used to create the X509Certificate from an Established SSL + connection + """ + _cert = cert + + new val from_pem(data: (Array[U8] val | String val)) ? => + """ + A constructor that takes a certificate as PEM data. + """ + let bio: Pointer[_BIO] tag = _BIO.new_ptr() + if (not _BIO.write(bio, data)) then + _BIO.free(bio) + error + end + _cert = @PEM_read_bio_X509(bio, Pointer[X509], Pointer[None], Pointer[None]) + if (_cert.is_null()) then + _BIO.free(bio) + error + end + _BIO.free(bio) + + fun _ptr(): Pointer[X509] tag => + _cert + + fun clone(): X509Certificate iso^ => + X509Certificate._from_cert_ptr(@X509_dup(_cert)) + + + fun version(): I64 => + @X509_get_version(_cert) + 1 + + fun serial(): String val ? => + """ + Returns the Serial Number of the Certificate in its binary form. + """ + _format_colon_hex(serial_raw()?)? + + + + + + + + + fun issuer_name(): String val ? => + """ + Returns the issuer's CN. + """ + _X509Name.string(@X509_get_issuer_name(_cert))? + + fun subject_name(): String val ? => + """ + Returns the certificate's CN. + """ + _X509Name.string(@X509_get_subject_name(_cert))? + + + fun not_before_posix(): I64 => + """ + Returns the notBefore time for the certificate in UNIX epoch form. + """ + let notb4: Pointer[ASN1String] tag = @X509_get0_notBefore(_cert) + ASN1String.time_to_posix(notb4) + + fun not_after_posix(): I64 => + """ + Returns the notAfter time for the certificate in UNIX epoch form. + """ + let notb4: Pointer[ASN1String] tag = @X509_get0_notAfter(_cert) + ASN1String.time_to_posix(notb4) + +// fun get_extensions(): StackX509Extension => +// """ +// Returns an object that represents a collection of Extensions. +// """ +// StackX509Extension.create_from_x509(_cert) + + fun print(): String val ? => + """ + Returns a textual representation of the certificate in a form which + is identical to openssl x509 -text. + """ + let bio: Pointer[_BIO] tag = _BIO.new_ptr() + if (@X509_print(bio, _cert) != 1) then + _BIO.free(bio) + error + end + let t: String val = _BIO.string(bio) + _BIO.free(bio) + t + + + fun authority_key_id(): String val ? => + """ + Returns the Key ID of the Issuing CA Certificate as a string. + """ + _format_colon_hex(authority_key_id_raw()?)? + + fun authority_key_id_raw(): Array[U8] val ? => + """ + Returns the Key ID of the Issuing CA Certificate in its binary form. + """ + ifdef "openssl_1.1.x" or "openssl_3.0.x" then + let asn1s: Pointer[ASN1String] tag = @X509_get0_authority_key_id(_cert) + ASN1String.array(asn1s)? + else + error + end + + fun key_id(): String val ? => + """ + Returns the Key ID of the Certificate as a String. + """ + _format_colon_hex(key_id_raw()?)? + + fun key_id_raw(): Array[U8] val ? => + """ + Returns the Key ID of the Certificate in its binary form. + """ + ifdef "openssl_1.1.x" or "openssl_3.0.x" then + let asn1s: Pointer[ASN1String] tag = @X509_get0_subject_key_id(_cert) + ASN1String.array(asn1s)? + else + error + end + + + fun serial_raw(): Array[U8] val ? => + """ + Returns the Serial Number in its binary form. + """ + let asn1s: Pointer[ASN1String] tag = @X509_get0_serialNumber(_cert) + ASN1String.array(asn1s)? + + fun _format_colon_hex(raw: Array[U8] val): String ? => + var string: String trn = recover trn String end + var cnt: USize = 0 + while (cnt < raw.size()) do + if (cnt > 0) then + string.append(":") + end + string.append(Format.int[U8](raw(cnt)? where width = 2, fmt = FormatHexBare, prec = 2)) + cnt = cnt + 1 + end + consume string + + fun _format_hex(raw: Array[U8] val): String ? => + var string: String trn = recover trn String end + var cnt: USize = 0 + while (cnt < raw.size()) do + string.append(Format.int[U8](raw(cnt)? where width = 2, fmt = FormatHexBare, prec = 2)) + cnt = cnt + 1 + end + consume string + + fun is_null(): Bool => _cert.is_null() + + fun fingerprint_sha1(): String val => + let size_array: Array[U32] = Array[U32].init(0,1) + let sha1: Pointer[_EVPMD] = @EVP_sha1() + let sbuff: Array[U8] ref = recover ref Array[U8].init(0,64) end + + let r: U32 = @X509_digest(_cert, sha1, sbuff.cpointer(), size_array.cpointer()) + var fpr: String trn = recover String end + try + for f in Range(0, size_array(0)?.usize()) do + fpr = fpr + Format.int[U8](sbuff(f)? where width = 2, fmt = FormatHexBare, prec=2) + end + end + consume fpr + + fun fingerprint_sha256(): String val => + let size_array: Array[U32] = Array[U32].init(0,1) + let sha256: Pointer[_EVPMD] = @EVP_sha256() + let sbuff: Array[U8] ref = recover ref Array[U8].init(0,64) end + + let r: U32 = @X509_digest(_cert, sha256, sbuff.cpointer(), size_array.cpointer()) + var fpr: String trn = recover String end + try + for f in Range(0, size_array(0)?.usize()) do + fpr = fpr + Format.int[U8](sbuff(f)? where width = 2, fmt = FormatHexBare, prec=2) + end + end + consume fpr + + fun fingerprint_md5(): String val => + let size_array: Array[U32] = Array[U32].init(0,1) + let md5: Pointer[_EVPMD] = @EVP_md5() + let sbuff: Array[U8] ref = recover ref Array[U8].init(0,64) end + + let r: U32 = @X509_digest(_cert, md5, sbuff.cpointer(), size_array.cpointer()) + var fpr: String trn = recover String end + try + for f in Range(0, size_array(0)?.usize()) do + fpr = fpr + Format.int[U8](sbuff(f)? where width = 2, fmt = FormatHexBare, prec=2) + end + end + consume fpr + + fun get_pem(): String iso^ => + let bio: Pointer[_BIO] tag = _BIO.new_ptr() + _BIO.write_pem_x509(bio, this) + + let pem: String iso = recover iso + let certpem: String ref = recover ref String end + let buffer: Array[U8] ref = recover ref Array[U8].>undefined(1024) end + + while (true) do + let amount_read: U32 = @BIO_gets(bio, buffer.cpointer(), U32(1024)) + if (amount_read == 0) then break end + certpem.concat(buffer.values(), 0, amount_read.usize()) + end + certpem + end + _BIO.free(bio) + consume pem + + fun is_ca(): Bool => + if (@X509_check_ca(_cert) == 1) then true else false end + + fun is_self_signed(): Bool ? => + ifdef "openssl_3.0.x" then + match @X509_self_signed(_cert, 1) + |let t: ISize if (t == 0) => return false + |let t: ISize if (t == 1) => return true + end + error + else + error + end + + fun _final() => + @X509_free(_cert)