[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:
parent
a01daab656
commit
1146a94aee
@ -338,6 +338,10 @@ module OpenSSL
|
|||||||
q.text 'not_after='; q.pp self.not_after
|
q.text 'not_after='; q.pp self.not_after
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def self.load_file(path)
|
||||||
|
load(File.binread(path))
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
class CRL
|
class CRL
|
||||||
|
@ -707,6 +707,157 @@ ossl_x509_eq(VALUE self, VALUE other)
|
|||||||
return !X509_cmp(a, b) ? Qtrue : Qfalse;
|
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
|
* INIT
|
||||||
*/
|
*/
|
||||||
@ -815,6 +966,8 @@ Init_ossl_x509cert(void)
|
|||||||
*/
|
*/
|
||||||
cX509Cert = rb_define_class_under(mX509, "Certificate", rb_cObject);
|
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_alloc_func(cX509Cert, ossl_x509_alloc);
|
||||||
rb_define_method(cX509Cert, "initialize", ossl_x509_initialize, -1);
|
rb_define_method(cX509Cert, "initialize", ossl_x509_initialize, -1);
|
||||||
rb_define_method(cX509Cert, "initialize_copy", ossl_x509_copy, 1);
|
rb_define_method(cX509Cert, "initialize_copy", ossl_x509_copy, 1);
|
||||||
|
BIN
test/openssl/fixtures/pkey/certificate.der
Normal file
BIN
test/openssl/fixtures/pkey/certificate.der
Normal file
Binary file not shown.
0
test/openssl/fixtures/pkey/empty.der
Normal file
0
test/openssl/fixtures/pkey/empty.der
Normal file
0
test/openssl/fixtures/pkey/empty.pem
Normal file
0
test/openssl/fixtures/pkey/empty.pem
Normal file
56
test/openssl/fixtures/pkey/fullchain.pem
Normal file
56
test/openssl/fixtures/pkey/fullchain.pem
Normal 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-----
|
1
test/openssl/fixtures/pkey/garbage.txt
Normal file
1
test/openssl/fixtures/pkey/garbage.txt
Normal file
@ -0,0 +1 @@
|
|||||||
|
Hello World
|
@ -288,6 +288,38 @@ class OpenSSL::TestX509Certificate < OpenSSL::TestCase
|
|||||||
assert_equal cert.to_der, deserialized.to_der
|
assert_equal cert.to_der, deserialized.to_der
|
||||||
end
|
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
|
private
|
||||||
|
|
||||||
def certificate_error_returns_false
|
def certificate_error_returns_false
|
||||||
|
Loading…
x
Reference in New Issue
Block a user