Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 39 additions & 0 deletions ext/openssl/ossl_x509crl.c
Original file line number Diff line number Diff line change
Expand Up @@ -297,6 +297,44 @@ ossl_x509crl_get_revoked(VALUE self)
return ary;
}

/*
* call-seq:
* crl.by_serial(serial) -> OpenSSL::X509::Revoked or nil
*
* Looks up the certificate _serial_ (an Integer or OpenSSL::BN) in the CRL and
* returns the matching OpenSSL::X509::Revoked entry, or +nil+ if that serial is
* not listed.
*
* Unlike iterating over #revoked, this does not instantiate the entire
* revocation list: it performs a sorted lookup (wrapping the OpenSSL function
* +X509_CRL_get0_by_serial+), which is significantly faster and uses far less
* memory for large CRLs.
*
* crl.by_serial(cert.serial) #=> #<OpenSSL::X509::Revoked> or nil
* crl.by_serial(cert.serial)&.time #=> revocation time, if revoked
*/
static VALUE
ossl_x509crl_by_serial(VALUE self, VALUE serial)
{
X509_CRL *crl;
X509_REVOKED *rev = NULL;
ASN1_INTEGER *asn1_serial;
int found;

GetX509CRL(self, crl);
asn1_serial = num_to_asn1integer(serial, NULL);

/* 0 = not found, 1 = found, 2 = found with reason removeFromCRL */
found = X509_CRL_get0_by_serial(crl, &rev, asn1_serial);
ASN1_INTEGER_free(asn1_serial);

if (found == 0)
return Qnil;

/* ossl_x509revoked_new dups, so the result outlives the CRL safely */
return ossl_x509revoked_new(rev);
}

static VALUE
ossl_x509crl_set_revoked(VALUE self, VALUE ary)
{
Expand Down Expand Up @@ -525,6 +563,7 @@ Init_ossl_x509crl(void)
rb_define_method(cX509CRL, "next_update=", ossl_x509crl_set_next_update, 1);
rb_define_method(cX509CRL, "revoked", ossl_x509crl_get_revoked, 0);
rb_define_method(cX509CRL, "revoked=", ossl_x509crl_set_revoked, 1);
rb_define_method(cX509CRL, "by_serial", ossl_x509crl_by_serial, 1);
rb_define_method(cX509CRL, "add_revoked", ossl_x509crl_add_revoked, 1);
rb_define_method(cX509CRL, "sign", ossl_x509crl_sign, 2);
rb_define_method(cX509CRL, "verify", ossl_x509crl_verify, 1);
Expand Down
38 changes: 38 additions & 0 deletions test/openssl/test_x509crl.rb
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,44 @@ def test_revoked
assert_equal(revoked.map(&:serial), revoked2.map(&:serial))
end

def test_by_serial
now = Time.at(Time.now.to_i)
revoke_info = [
[1, Time.at(0), 1], # keyCompromise
[2, Time.at(0x7fffffff), 2], # cACompromise
[3, now, 3], # affiliationChanged
]
cert = issue_cert(@ca, @rsa1, 1, [], nil, nil)
crl = issue_crl(revoke_info, 1, Time.now, Time.now+1600, [],
cert, @rsa1, "SHA256")

# Returns the matching Revoked entry for a listed serial
rev = crl.by_serial(2)
assert_instance_of(OpenSSL::X509::Revoked, rev)
assert_equal(2, rev.serial)
assert_equal(Time.at(0x7fffffff), rev.time)
assert_equal("CRLReason", rev.extensions[0].oid)
assert_equal("CA Compromise", rev.extensions[0].value)

# Accepts an OpenSSL::BN as well as an Integer
assert_equal(3, crl.by_serial(OpenSSL::BN.new(3)).serial)

# Returns nil for a serial that is not listed
assert_nil(crl.by_serial(42))

# Same answers after a DER round-trip, and consistent with #revoked
crl = OpenSSL::X509::CRL.new(crl.to_der)
revoke_info.each do |serial, time, _reason|
by_serial = crl.by_serial(serial)
from_list = crl.revoked.find {|r| r.serial == serial }
assert_equal(by_serial, from_list)
assert_equal(time, by_serial.time)
end

# Rejects values that can't be an integer serial
assert_raise(TypeError) { crl.by_serial(nil) }
end

def test_extension
cert_exts = [
["basicConstraints", "CA:TRUE", true],
Expand Down