1
0
Fork 0
mirror of https://github.com/ruby/ruby.git synced 2022-11-09 12:17:21 -05:00

[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>
This commit is contained in:
Samuel Williams 2021-05-22 08:47:20 +12:00 committed by Kazuki Yamaguchi
parent a01daab656
commit 1146a94aee
8 changed files with 246 additions and 0 deletions

View file

@ -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

View file

@ -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);

Binary file not shown.

View file

View file

View file

@ -0,0 +1,56 @@
-----BEGIN CERTIFICATE-----
MIIFKTCCBBGgAwIBAgISBFspP+tJfRaC6xprreB4Rp9KMA0GCSqGSIb3DQEBCwUA
MDIxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1MZXQncyBFbmNyeXB0MQswCQYDVQQD
EwJSMzAeFw0yMTA0MTcwMjQzMTlaFw0yMTA3MTYwMjQzMTlaMBwxGjAYBgNVBAMT
EXd3dy5jb2Rlb3Rha3UuY29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKC
AQEAx6h5vNPfkkrtYWxn1PWDDLRAwrGmZbkYPttjHBRSwTcd7rsIX4PcSzw9fWxm
K4vIkAYoKAElIvsSE3xRUjyzMrACfdhK5J8rG25fq94iVyoYaNBQV0WMJkO6X47s
hGeIKkK91ohR5b2tMw3/z9zELP0TVo2TPG7rYsBZm34myldqDA8yVEBEOa+Qdpda
9xewPhkkdpAU55qgWTrD21m7vGq9WpsBz4wNKnwVsaugtkRH82VPIfaL4ZI9kox6
QoPWe/tHUBdlDkuT7ud77eLAWnC/5Clg28/9GU/Z8Nj8SrrKuXL6WUXmxxaAhWUR
Qx4VblZeuIpwd0nHyP0hz4CWKQIDAQABo4ICTTCCAkkwDgYDVR0PAQH/BAQDAgWg
MB0GA1UdJQQWMBQGCCsGAQUFBwMBBggrBgEFBQcDAjAMBgNVHRMBAf8EAjAAMB0G
A1UdDgQWBBTKiSGZuLFSIG2JPbFSZa9TxMu5WTAfBgNVHSMEGDAWgBQULrMXt1hW
y65QCUDmH6+dixTCxjBVBggrBgEFBQcBAQRJMEcwIQYIKwYBBQUHMAGGFWh0dHA6
Ly9yMy5vLmxlbmNyLm9yZzAiBggrBgEFBQcwAoYWaHR0cDovL3IzLmkubGVuY3Iu
b3JnLzAcBgNVHREEFTATghF3d3cuY29kZW90YWt1LmNvbTBMBgNVHSAERTBDMAgG
BmeBDAECATA3BgsrBgEEAYLfEwEBATAoMCYGCCsGAQUFBwIBFhpodHRwOi8vY3Bz
LmxldHNlbmNyeXB0Lm9yZzCCAQUGCisGAQQB1nkCBAIEgfYEgfMA8QB3AJQgvB6O
1Y1siHMfgosiLA3R2k1ebE+UPWHbTi9YTaLCAAABeN3s/lgAAAQDAEgwRgIhAKFY
Q+vBe3zyeBazxp8kVN7oLvcQ6Y9PPz199tVhYnEbAiEAhU/xdbQaY/6b93h+7NTF
sPG7X4lq/3UoNgoXcAVGZgoAdgD2XJQv0XcwIhRUGAgwlFaO400TGTO/3wwvIAvM
TvFk4wAAAXjd7P5OAAAEAwBHMEUCIQDWd79+jWaGuf3acm5/yV95jL2KvzeGFfdU
HZlKIeWFmAIgDSZ6ug7AyhYNKjzFV4ZSICln+L4yI92EpOa+8gDG6/0wDQYJKoZI
hvcNAQELBQADggEBAHIhMYm06lLFmJL+cfIg5fFEmFNdHmmZn88Hypv4/MtmqTKv
5asF/z3TvhW4hX2+TY+NdcqGT7cZFo/ZF/tS6oBXPgmBYM1dEfp2FAdnGNOySC5Y
7RC4Uk9TUpP2g101YBmj6dQKQluAwIQk+gO4MSlHE0J0U/lMpjvrLWcuHbV4/xWJ
IdM+iPq8GeYt5epYmNc7XeRIgv7V3RxDQdBv2OVM5mtPVerdiO0ISrdbe5mvz2+Z
rhSg+EJNHlmMwcq5HqtMwS8M8Ax+vLmWCOkPWXhyV8wQaQcFjZJfpIGUvCnMTqsh
kSIYXq2CbSDUUFRFssNN6EdVms0KnmW3BUu0xAk=
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIEZTCCA02gAwIBAgIQQAF1BIMUpMghjISpDBbN3zANBgkqhkiG9w0BAQsFADA/
MSQwIgYDVQQKExtEaWdpdGFsIFNpZ25hdHVyZSBUcnVzdCBDby4xFzAVBgNVBAMT
DkRTVCBSb290IENBIFgzMB4XDTIwMTAwNzE5MjE0MFoXDTIxMDkyOTE5MjE0MFow
MjELMAkGA1UEBhMCVVMxFjAUBgNVBAoTDUxldCdzIEVuY3J5cHQxCzAJBgNVBAMT
AlIzMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAuwIVKMz2oJTTDxLs
jVWSw/iC8ZmmekKIp10mqrUrucVMsa+Oa/l1yKPXD0eUFFU1V4yeqKI5GfWCPEKp
Tm71O8Mu243AsFzzWTjn7c9p8FoLG77AlCQlh/o3cbMT5xys4Zvv2+Q7RVJFlqnB
U840yFLuta7tj95gcOKlVKu2bQ6XpUA0ayvTvGbrZjR8+muLj1cpmfgwF126cm/7
gcWt0oZYPRfH5wm78Sv3htzB2nFd1EbjzK0lwYi8YGd1ZrPxGPeiXOZT/zqItkel
/xMY6pgJdz+dU/nPAeX1pnAXFK9jpP+Zs5Od3FOnBv5IhR2haa4ldbsTzFID9e1R
oYvbFQIDAQABo4IBaDCCAWQwEgYDVR0TAQH/BAgwBgEB/wIBADAOBgNVHQ8BAf8E
BAMCAYYwSwYIKwYBBQUHAQEEPzA9MDsGCCsGAQUFBzAChi9odHRwOi8vYXBwcy5p
ZGVudHJ1c3QuY29tL3Jvb3RzL2RzdHJvb3RjYXgzLnA3YzAfBgNVHSMEGDAWgBTE
p7Gkeyxx+tvhS5B1/8QVYIWJEDBUBgNVHSAETTBLMAgGBmeBDAECATA/BgsrBgEE
AYLfEwEBATAwMC4GCCsGAQUFBwIBFiJodHRwOi8vY3BzLnJvb3QteDEubGV0c2Vu
Y3J5cHQub3JnMDwGA1UdHwQ1MDMwMaAvoC2GK2h0dHA6Ly9jcmwuaWRlbnRydXN0
LmNvbS9EU1RST09UQ0FYM0NSTC5jcmwwHQYDVR0OBBYEFBQusxe3WFbLrlAJQOYf
r52LFMLGMB0GA1UdJQQWMBQGCCsGAQUFBwMBBggrBgEFBQcDAjANBgkqhkiG9w0B
AQsFAAOCAQEA2UzgyfWEiDcx27sT4rP8i2tiEmxYt0l+PAK3qB8oYevO4C5z70kH
ejWEHx2taPDY/laBL21/WKZuNTYQHHPD5b1tXgHXbnL7KqC401dk5VvCadTQsvd8
S8MXjohyc9z9/G2948kLjmE6Flh9dDYrVYA9x2O+hEPGOaEOa1eePynBgPayvUfL
qjBstzLhWVQLGAkXXmNs+5ZnPBxzDJOLxhF2JIbeQAcH5H0tZrUlo5ZYyOqA7s9p
O5b85o3AM/OJ+CktFBQtfvBhcJVd9wvlwPsk+uyOy2HI7mNxKKgsBTt375teA2Tw
UdHkhVNcsAKX1H7GNNLOEADksd86wuoXvg==
-----END CERTIFICATE-----

View file

@ -0,0 +1 @@
Hello World

View file

@ -288,6 +288,38 @@ class OpenSSL::TestX509Certificate < OpenSSL::TestCase
assert_equal cert.to_der, deserialized.to_der
end
def test_load_file_empty_pem
empty_path = Fixtures.file_path("pkey", "empty.pem")
assert_raise(OpenSSL::X509::CertificateError) do
OpenSSL::X509::Certificate.load_file(empty_path)
end
end
def test_load_file_fullchain_pem
fullchain_path = Fixtures.file_path("pkey", "fullchain.pem")
certificates = OpenSSL::X509::Certificate.load_file(fullchain_path)
assert_equal 2, certificates.size
assert_equal "/CN=www.codeotaku.com", certificates[0].subject.to_s
assert_equal "/C=US/O=Let's Encrypt/CN=R3", certificates[1].subject.to_s
end
def test_load_file_certificate_der
fullchain_path = Fixtures.file_path("pkey", "certificate.der")
certificates = OpenSSL::X509::Certificate.load_file(fullchain_path)
# DER encoding can only contain one certificate:
assert_equal 1, certificates.size
assert_equal "/CN=www.codeotaku.com", certificates[0].subject.to_s
end
def test_load_file_fullchain_garbage
fullchain_path = Fixtures.file_path("pkey", "garbage.txt")
assert_raise(OpenSSL::X509::CertificateError) do
certificates = OpenSSL::X509::Certificate.load_file(fullchain_path)
end
end
private
def certificate_error_returns_false