summaryrefslogtreecommitdiff
path: root/ext/openssl
diff options
context:
space:
mode:
authorSamuel Williams <samuel.williams@oriontransfer.co.nz>2021-05-22 08:47:20 +1200
committerKazuki Yamaguchi <k@rhe.jp>2021-07-18 17:44:55 +0900
commit1146a94aeea7d2ea1ead3bfcbddd0f4de696abe6 (patch)
treeea3cf733e6c7e7c141b6ffd1c2b89a7392498a93 /ext/openssl
parenta01daab656a3d32b52bd236503e3d9aebaf39483 (diff)
[ruby/openssl] Implement `Certificate.load` to load certificate chain. (https://github.com/ruby/openssl/pull/441)
* Add feature for loading the chained certificate into Certificate array. https://github.com/ruby/openssl/commit/05e1c015d6 Co-authored-by: Sao I Kuan <saoikuan@gmail.com>
Diffstat (limited to 'ext/openssl')
-rw-r--r--ext/openssl/lib/openssl/x509.rb4
-rw-r--r--ext/openssl/ossl_x509cert.c153
2 files changed, 157 insertions, 0 deletions
diff --git a/ext/openssl/lib/openssl/x509.rb b/ext/openssl/lib/openssl/x509.rb
index 6771b90c1a..367a899e21 100644
--- a/ext/openssl/lib/openssl/x509.rb
+++ b/ext/openssl/lib/openssl/x509.rb
@@ -338,6 +338,10 @@ module OpenSSL
q.text 'not_after='; q.pp self.not_after
}
end
+
+ def self.load_file(path)
+ load(File.binread(path))
+ end
end
class CRL
diff --git a/ext/openssl/ossl_x509cert.c b/ext/openssl/ossl_x509cert.c
index 1385411069..4884fc89a0 100644
--- a/ext/openssl/ossl_x509cert.c
+++ b/ext/openssl/ossl_x509cert.c
@@ -707,6 +707,157 @@ ossl_x509_eq(VALUE self, VALUE other)
return !X509_cmp(a, b) ? Qtrue : Qfalse;
}
+struct load_chained_certificates_arguments {
+ VALUE certificates;
+ X509 *certificate;
+};
+
+static VALUE
+load_chained_certificates_append_push(VALUE _arguments) {
+ struct load_chained_certificates_arguments *arguments = (struct load_chained_certificates_arguments*)_arguments;
+
+ if (arguments->certificates == Qnil) {
+ arguments->certificates = rb_ary_new();
+ }
+
+ rb_ary_push(arguments->certificates, ossl_x509_new(arguments->certificate));
+
+ return Qnil;
+}
+
+static VALUE
+load_chained_certificate_append_ensure(VALUE _arguments) {
+ struct load_chained_certificates_arguments *arguments = (struct load_chained_certificates_arguments*)_arguments;
+
+ X509_free(arguments->certificate);
+
+ return Qnil;
+}
+
+inline static VALUE
+load_chained_certificates_append(VALUE certificates, X509 *certificate) {
+ struct load_chained_certificates_arguments arguments;
+ arguments.certificates = certificates;
+ arguments.certificate = certificate;
+
+ rb_ensure(load_chained_certificates_append_push, (VALUE)&arguments, load_chained_certificate_append_ensure, (VALUE)&arguments);
+
+ return arguments.certificates;
+}
+
+static VALUE
+load_chained_certificates_PEM(BIO *in) {
+ VALUE certificates = Qnil;
+ X509 *certificate = PEM_read_bio_X509(in, NULL, NULL, NULL);
+
+ /* If we cannot read even one certificate: */
+ if (certificate == NULL) {
+ /* If we cannot read one certificate because we could not read the PEM encoding: */
+ if (ERR_GET_REASON(ERR_peek_last_error()) == PEM_R_NO_START_LINE) {
+ ossl_clear_error();
+ }
+
+ if (ERR_peek_last_error())
+ ossl_raise(eX509CertError, NULL);
+ else
+ return Qnil;
+ }
+
+ certificates = load_chained_certificates_append(Qnil, certificate);
+
+ while ((certificate = PEM_read_bio_X509(in, NULL, NULL, NULL))) {
+ load_chained_certificates_append(certificates, certificate);
+ }
+
+ /* We tried to read one more certificate but could not read start line: */
+ if (ERR_GET_REASON(ERR_peek_last_error()) == PEM_R_NO_START_LINE) {
+ /* This is not an error, it means we are finished: */
+ ossl_clear_error();
+
+ return certificates;
+ }
+
+ /* Alternatively, if we reached the end of the file and there was no error: */
+ if (BIO_eof(in) && !ERR_peek_last_error()) {
+ return certificates;
+ } else {
+ /* Otherwise, we tried to read a certificate but failed somewhere: */
+ ossl_raise(eX509CertError, NULL);
+ }
+}
+
+static VALUE
+load_chained_certificates_DER(BIO *in) {
+ X509 *certificate = d2i_X509_bio(in, NULL);
+
+ /* If we cannot read one certificate: */
+ if (certificate == NULL) {
+ /* Ignore error. We could not load. */
+ ossl_clear_error();
+
+ return Qnil;
+ }
+
+ return load_chained_certificates_append(Qnil, certificate);
+}
+
+static VALUE
+load_chained_certificates(VALUE _io) {
+ BIO *in = (BIO*)_io;
+ VALUE certificates = Qnil;
+
+ /*
+ DER is a binary format and it may contain octets within it that look like
+ PEM encoded certificates. So we need to check DER first.
+ */
+ certificates = load_chained_certificates_DER(in);
+
+ if (certificates != Qnil)
+ return certificates;
+
+ OSSL_BIO_reset(in);
+
+ certificates = load_chained_certificates_PEM(in);
+
+ if (certificates != Qnil)
+ return certificates;
+
+ /* Otherwise we couldn't read the output correctly so fail: */
+ ossl_raise(eX509CertError, "Could not detect format of certificate data!");
+}
+
+static VALUE
+load_chained_certificates_ensure(VALUE _io) {
+ BIO *in = (BIO*)_io;
+
+ BIO_free(in);
+
+ return Qnil;
+}
+
+/*
+ * call-seq:
+ * OpenSSL::X509::Certificate.load(string) -> [certs...]
+ * OpenSSL::X509::Certificate.load(file) -> [certs...]
+ *
+ * Read the chained certificates from the given input. Supports both PEM
+ * and DER encoded certificates.
+ *
+ * PEM is a text format and supports more than one certificate.
+ *
+ * DER is a binary format and only supports one certificate.
+ *
+ * If the file is empty, or contains only unrelated data, an
+ * +OpenSSL::X509::CertificateError+ exception will be raised.
+ */
+static VALUE
+ossl_x509_load(VALUE klass, VALUE buffer)
+{
+ BIO *in = ossl_obj2bio(&buffer);
+
+ return rb_ensure(load_chained_certificates, (VALUE)in, load_chained_certificates_ensure, (VALUE)in);
+}
+
/*
* INIT
*/
@@ -815,6 +966,8 @@ Init_ossl_x509cert(void)
*/
cX509Cert = rb_define_class_under(mX509, "Certificate", rb_cObject);
+ rb_define_singleton_method(cX509Cert, "load", ossl_x509_load, 1);
+
rb_define_alloc_func(cX509Cert, ossl_x509_alloc);
rb_define_method(cX509Cert, "initialize", ossl_x509_initialize, -1);
rb_define_method(cX509Cert, "initialize_copy", ossl_x509_copy, 1);