Skip to content

Commit 9f68ac0

Browse files
authored
Merge pull request #1065 from jarthod/x509crl-by-serial
x509crl: add OpenSSL::X509::CRL#by_serial
2 parents 09dafdb + 83f4a0d commit 9f68ac0

2 files changed

Lines changed: 77 additions & 0 deletions

File tree

ext/openssl/ossl_x509crl.c

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -327,6 +327,44 @@ ossl_x509crl_set_revoked(VALUE self, VALUE ary)
327327
return ary;
328328
}
329329

330+
/*
331+
* call-seq:
332+
* crl.by_serial(serial) -> OpenSSL::X509::Revoked or nil
333+
*
334+
* Looks up the certificate _serial_ (an Integer or OpenSSL::BN) in the CRL and
335+
* returns the matching OpenSSL::X509::Revoked entry, or +nil+ if that serial is
336+
* not listed.
337+
*
338+
* Unlike iterating over #revoked, this does not instantiate the entire
339+
* revocation list: it performs a sorted lookup (wrapping the OpenSSL function
340+
* +X509_CRL_get0_by_serial+), which is significantly faster and uses far less
341+
* memory for large CRLs.
342+
*
343+
* crl.by_serial(cert.serial) #=> #<OpenSSL::X509::Revoked> or nil
344+
* crl.by_serial(cert.serial)&.time #=> revocation time, if revoked
345+
*/
346+
static VALUE
347+
ossl_x509crl_by_serial(VALUE self, VALUE serial)
348+
{
349+
X509_CRL *crl;
350+
X509_REVOKED *rev = NULL;
351+
ASN1_INTEGER *asn1_serial;
352+
int found;
353+
354+
GetX509CRL(self, crl);
355+
asn1_serial = num_to_asn1integer(serial, NULL);
356+
357+
/* 0 = not found, 1 = found, 2 = found with reason removeFromCRL */
358+
found = X509_CRL_get0_by_serial(crl, &rev, asn1_serial);
359+
ASN1_INTEGER_free(asn1_serial);
360+
361+
if (found == 0)
362+
return Qnil;
363+
364+
/* ossl_x509revoked_new dups, so the result outlives the CRL safely */
365+
return ossl_x509revoked_new(rev);
366+
}
367+
330368
static VALUE
331369
ossl_x509crl_add_revoked(VALUE self, VALUE revoked)
332370
{
@@ -525,6 +563,7 @@ Init_ossl_x509crl(void)
525563
rb_define_method(cX509CRL, "next_update=", ossl_x509crl_set_next_update, 1);
526564
rb_define_method(cX509CRL, "revoked", ossl_x509crl_get_revoked, 0);
527565
rb_define_method(cX509CRL, "revoked=", ossl_x509crl_set_revoked, 1);
566+
rb_define_method(cX509CRL, "by_serial", ossl_x509crl_by_serial, 1);
528567
rb_define_method(cX509CRL, "add_revoked", ossl_x509crl_add_revoked, 1);
529568
rb_define_method(cX509CRL, "sign", ossl_x509crl_sign, 2);
530569
rb_define_method(cX509CRL, "verify", ossl_x509crl_verify, 1);

test/openssl/test_x509crl.rb

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,44 @@ def test_revoked
105105
assert_equal(revoked.map(&:serial), revoked2.map(&:serial))
106106
end
107107

108+
def test_by_serial
109+
now = Time.at(Time.now.to_i)
110+
revoke_info = [
111+
[1, Time.at(0), 1], # keyCompromise
112+
[2, Time.at(0x7fffffff), 2], # cACompromise
113+
[3, now, 3], # affiliationChanged
114+
]
115+
cert = issue_cert(@ca, @rsa1, 1, [], nil, nil)
116+
crl = issue_crl(revoke_info, 1, Time.now, Time.now+1600, [],
117+
cert, @rsa1, "SHA256")
118+
119+
# Returns the matching Revoked entry for a listed serial
120+
rev = crl.by_serial(2)
121+
assert_instance_of(OpenSSL::X509::Revoked, rev)
122+
assert_equal(2, rev.serial)
123+
assert_equal(Time.at(0x7fffffff), rev.time)
124+
assert_equal("CRLReason", rev.extensions[0].oid)
125+
assert_equal("CA Compromise", rev.extensions[0].value)
126+
127+
# Accepts an OpenSSL::BN as well as an Integer
128+
assert_equal(3, crl.by_serial(OpenSSL::BN.new(3)).serial)
129+
130+
# Returns nil for a serial that is not listed
131+
assert_nil(crl.by_serial(42))
132+
133+
# Same answers after a DER round-trip, and consistent with #revoked
134+
crl = OpenSSL::X509::CRL.new(crl.to_der)
135+
revoke_info.each do |serial, time, _reason|
136+
by_serial = crl.by_serial(serial)
137+
from_list = crl.revoked.find {|r| r.serial == serial }
138+
assert_equal(by_serial, from_list)
139+
assert_equal(time, by_serial.time)
140+
end
141+
142+
# Rejects values that can't be an integer serial
143+
assert_raise(TypeError) { crl.by_serial(nil) }
144+
end
145+
108146
def test_extension
109147
cert_exts = [
110148
["basicConstraints", "CA:TRUE", true],

0 commit comments

Comments
 (0)