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:
parent
a01daab656
commit
1146a94aee
8 changed files with 246 additions and 0 deletions
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
|
|
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
|
||||
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
|
||||
|
|
Loading…
Reference in a new issue