diff options
author | Samuel Williams <samuel.williams@oriontransfer.co.nz> | 2021-05-22 08:47:20 +1200 |
---|---|---|
committer | Kazuki Yamaguchi <k@rhe.jp> | 2021-07-18 17:44:55 +0900 |
commit | 1146a94aeea7d2ea1ead3bfcbddd0f4de696abe6 (patch) | |
tree | ea3cf733e6c7e7c141b6ffd1c2b89a7392498a93 /ext | |
parent | a01daab656a3d32b52bd236503e3d9aebaf39483 (diff) | |
download | ruby-1146a94aeea7d2ea1ead3bfcbddd0f4de696abe6.tar.gz |
[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')
-rw-r--r-- | ext/openssl/lib/openssl/x509.rb | 4 | ||||
-rw-r--r-- | ext/openssl/ossl_x509cert.c | 153 |
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); |