lib/r509/crl.rb in r509-0.8.1 vs lib/r509/crl.rb in r509-0.9

- old
+ new

@@ -2,378 +2,348 @@ require 'r509/config' require 'r509/exceptions' require 'r509/io_helpers' module R509 - # contains CRL related classes (generator and a pre-existing list loader) - module Crl + # contains CRL related classes (generator and a pre-existing list loader) + module CRL + # Parses CRLs + class SignedList + include R509::IOHelpers - class Parser - attr_reader :crl + attr_reader :crl, :issuer - # @param [String,OpenSSL::X509::CRL] crl - def initialize(crl) - @crl = OpenSSL::X509::CRL.new(crl) - end + # @param [String,OpenSSL::X509::CRL] crl + def initialize(crl) + @crl = OpenSSL::X509::CRL.new(crl) + @issuer = R509::Subject.new(@crl.issuer) + end - # Helper method to quickly load a CRL from the filesystem - # - # @param [String] filename Path to file you want to load - # @return [R509::Crl::Parser] CRL object - def self.load_from_file( filename ) - return R509::Crl::Parser.new( IOHelpers.read_data(filename) ) - end + # Helper method to quickly load a CRL from the filesystem + # + # @param [String] filename Path to file you want to load + # @return [R509::CRL::SignedList] CRL object + def self.load_from_file( filename ) + return R509::CRL::SignedList.new( IOHelpers.read_data(filename) ) + end - # @return [OpenSSL::X509::Name] - def issuer - @crl.issuer - end + # @return [String] + def signature_algorithm + @crl.signature_algorithm + end - # @return [String] The common name (CN) component of the issuer - def issuer_cn - return nil if self.issuer.nil? + # Writes the CRL into the PEM format + # + # @param [String, #write] filename_or_io Either a string of the path for + # the file that you'd like to write, or an IO-like object. + def write_pem(filename_or_io) + write_data(filename_or_io, @crl.to_pem) + end - self.issuer.to_a.each do |part, value, length| - return value if part.upcase == 'CN' - end + # Writes the CRL into the PEM format + # + # @param [String, #write] filename_or_io Either a string of the path for + # the file that you'd like to write, or an IO-like object. + def write_der(filename_or_io) + write_data(filename_or_io, @crl.to_der) + end - # return nil if we didn't find a CN part - return nil - end + # Returns the signing time of the CRL + # + # @return [Time] when the CRL was signed + def last_update + @crl.last_update + end - # @return [Time] - def last_update - @crl.last_update - end + # Returns the next update time for the CRL + # + # @return [Time] when it will be updated next + def next_update + @crl.next_update + end - # @return [Time] - def next_update - @crl.next_update - end + # Pass a public key to verify that the CRL is signed by a specific certificate (call cert.public_key on that object) + # + # @param [OpenSSL::PKey::PKey] public_key + # @return [Boolean] + def verify(public_key) + @crl.verify(public_key) + end - # @return [String] - def signature_algorithm - @crl.signature_algorithm - end + # @param [Integer] serial number + # @return [Boolean] + def revoked?(serial) + if @crl.revoked.find { |revoked| revoked.serial == serial } + true + else + false + end + end - # Pass a public key to verify that the CRL is signed by a specific certificate (call cert.public_key on that object) - # - # @param [OpenSSL::PKey::PKey] public_key - # @return [Boolean] - def verify(public_key) - @crl.verify(public_key) - end + # Returns the CRL in PEM format + # + # @return [String] the CRL in PEM format + def to_pem + @crl.to_pem + end - # @param [Integer] serial number - # @return [Boolean] - def revoked?(serial) - if @crl.revoked.find { |revoked| revoked.serial == serial } - true - else - false - end - end + alias :to_s :to_pem - # @return [Hash] hash of serial => { :time, :reason } hashes - def revoked - revoked_list = {} - @crl.revoked.each do |revoked| - reason = get_reason(revoked) - revoked_list[revoked.serial.to_i] = { :time => revoked.time, :reason => reason } - end + # Returns the CRL in DER format + # + # @return [String] the CRL in DER format + def to_der + @crl.to_der + end - revoked_list - end + # @return [Hash] hash of serial => { :time, :reason } hashes + def revoked + revoked_list = {} + @crl.revoked.each do |revoked| + reason = get_reason(revoked) + revoked_list[revoked.serial.to_i] = { :time => revoked.time, :reason => reason } + end - # @param [Integer] serial number - # @return [Hash] hash with :time and :reason - def revoked_cert(serial) - revoked = @crl.revoked.find { |revoked| revoked.serial == serial } - if revoked - reason = get_reason(revoked) - { :time => revoked.time, :reason => reason } - else - nil - end - end + revoked_list + end - private - def get_reason(revocation_object) - reason = nil - revocation_object.extensions.each do |extension| - if extension.oid == "CRLReason" - reason = extension.value - end - end + # @param [Integer] serial number + # @return [Hash] hash with :time and :reason + def revoked_cert(serial) + revoked = @crl.revoked.find { |revoked| revoked.serial == serial } + if revoked + reason = get_reason(revoked) + { :time => revoked.time, :reason => reason } + else + nil + end + end - reason - end + private + def get_reason(revocation_object) + reason = nil + revocation_object.extensions.each do |extension| + if extension.oid == "CRLReason" + reason = extension.value + end end - # Used to manage revocations and generate CRLs - class Administrator - include R509::IOHelpers + reason + end + end - attr_reader :crl_number,:crl_list_file,:crl_number_file, :validity_hours + # Used to manage revocations and generate CRLs + class Administrator + include R509::IOHelpers - # @param [R509::Config::CaConfig] config - def initialize(config) - @config = config + attr_reader :crl_number,:crl_list_file,:crl_number_file, :validity_hours, :crl - unless @config.kind_of?(R509::Config::CaConfig) - raise R509Error, "config must be a kind of R509::Config::CaConfig" - end + # @param [R509::Config::CAConfig] config + def initialize(config) + @config = config - @validity_hours = @config.crl_validity_hours - @start_skew_seconds = @config.crl_start_skew_seconds - @crl = nil + unless @config.kind_of?(R509::Config::CAConfig) + raise R509Error, "config must be a kind of R509::Config::CAConfig" + end - @crl_number_file = @config.crl_number_file - if not @crl_number_file.nil? - @crl_number = read_data(@crl_number_file).to_i - else - @crl_number = 0 - end + @validity_hours = @config.crl_validity_hours + @start_skew_seconds = @config.crl_start_skew_seconds + @crl = nil + @crl_number_file = @config.crl_number_file + if not @crl_number_file.nil? + @crl_number = read_data(@crl_number_file).to_i + else + @crl_number = 0 + end - @crl_list_file = @config.crl_list_file - load_crl_list(@crl_list_file) - end - # Indicates whether the serial number has been revoked, or not. - # - # @param [Integer] serial The serial number we want to check - # @return [Boolean] True if the serial number was revoked. False, otherwise. - def revoked?(serial) - @revoked_certs.has_key?(serial) - end + @crl_list_file = @config.crl_list_file + load_crl_list(@crl_list_file) + end - # @return [Array] serial, reason, revoke_time tuple - def revoked_cert(serial) - @revoked_certs[serial] - end + # Indicates whether the serial number has been revoked, or not. + # + # @param [Integer] serial The serial number we want to check + # @return [Boolean] True if the serial number was revoked. False, otherwise. + def revoked?(serial) + @revoked_certs.has_key?(serial) + end - # Returns the CRL in PEM format - # - # @return [String] the CRL in PEM format - def to_pem - @crl.to_pem - end + # @return [Array] serial, reason, revoke_time tuple + def revoked_cert(serial) + @revoked_certs[serial] + end - alias :to_s :to_pem + # Adds a certificate to the revocation list. After calling you must call generate_crl to sign a new CRL + # + # @param serial [Integer] serial number of the certificate to revoke + # @param reason [Integer] reason for revocation + # @param revoke_time [Integer] + # @param generate_and_save [Boolean] whether we want to generate the CRL and save its file (default=true) + # + # reason codes defined by rfc 5280 + # + # CRLReason ::= ENUMERATED { + # unspecified (0), + # keyCompromise (1), + # cACompromise (2), + # affiliationChanged (3), + # superseded (4), + # cessationOfOperation (5), + # certificateHold (6), + # removeFromCRL (8), + # privilegeWithdrawn (9), + # aACompromise (10) } + def revoke_cert(serial,reason=nil, revoke_time=Time.now.to_i, generate_and_save=true) + if not reason.to_i.between?(0,10) + reason = 0 + end + serial = serial.to_i + reason = reason.to_i + revoke_time = revoke_time.to_i + if revoked?(serial) + raise R509::R509Error, "Cannot revoke a previously revoked certificate" + end + @revoked_certs[serial] = {:reason => reason, :revoke_time => revoke_time} + if generate_and_save + generate_crl + save_crl_list + end + nil + end - # Returns the CRL in DER format - # - # @return [String] the CRL in DER format - def to_der - @crl.to_der - end + # Remove serial from revocation list. After unrevoking you must call generate_crl to sign a new CRL + # + # @param serial [Integer] serial number of the certificate to remove from revocation + def unrevoke_cert(serial) + @revoked_certs.delete(serial) + generate_crl + save_crl_list + nil + end - # @return [R509::Crl::Parser] - def to_crl - return nil if @crl.nil? - return R509::Crl::Parser.new(@crl) - end + # Remove serial from revocation list + # + # @return [String] PEM encoded signed CRL + def generate_crl + crl = OpenSSL::X509::CRL.new + crl.version = 1 + now = Time.at Time.now.to_i + crl.last_update = now-@start_skew_seconds + crl.next_update = now+@validity_hours*3600 + crl.issuer = @config.ca_cert.subject.name - # Writes the CRL into the PEM format - # - # @param [String, #write] filename_or_io Either a string of the path for - # the file that you'd like to write, or an IO-like object. - def write_pem(filename_or_io) - write_data(filename_or_io, @crl.to_pem) - end + self.revoked_certs.each do |serial, reason, revoke_time| + revoked = OpenSSL::X509::Revoked.new + revoked.serial = OpenSSL::BN.new serial.to_s + revoked.time = Time.at(revoke_time) + if !reason.nil? + enum = OpenSSL::ASN1::Enumerated(reason) #see reason codes below + ext = OpenSSL::X509::Extension.new("CRLReason", enum) + revoked.add_extension(ext) + end + #now add it to the crl + crl.add_revoked(revoked) + end - # Writes the CRL into the PEM format - # - # @param [String, #write] filename_or_io Either a string of the path for - # the file that you'd like to write, or an IO-like object. - def write_der(filename_or_io) - write_data(filename_or_io, @crl.to_der) - end + ef = OpenSSL::X509::ExtensionFactory.new + ef.issuer_certificate = @config.ca_cert.cert + ef.crl = crl + #grab crl number from file, increment, write back + crl_number = increment_crl_number + crlnum = OpenSSL::ASN1::Integer(crl_number) + crl.add_extension(OpenSSL::X509::Extension.new("crlNumber", crlnum)) + extensions = [] + extensions << ["authorityKeyIdentifier", "keyid:always,issuer:always", false] + extensions.each{|oid, value, critical| + crl.add_extension(ef.create_extension(oid, value, critical)) + } + crl.sign(@config.ca_cert.key.key, OpenSSL::Digest::SHA1.new) + @crl = R509::CRL::SignedList.new crl + @crl.to_pem + end - # Returns the signing time of the CRL - # - # @return [Time] when the CRL was signed - def last_update - @crl.last_update - end + # @return [Array<Array>] Returns an array of serial, reason, revoke_time + # tuples. + def revoked_certs + ret = [] + @revoked_certs.keys.sort.each do |serial| + ret << [serial, @revoked_certs[serial][:reason], @revoked_certs[serial][:revoke_time]] + end + ret + end - # Returns the next update time for the CRL - # - # @return [Time] when it will be updated next - def next_update - @crl.next_update - end + # Saves the CRL list to a filename or IO. If the class was initialized + # with :crl_list_file, then the filename specified by that will be used + # by default. + # @param [String, #write, nil] filename_or_io If provided, the generated + # crl will be written to either the file (if a string), or IO. If nil, + # then the @crl_list_file will be used. If that is nil, then an error + # will be raised. + def save_crl_list(filename_or_io = @crl_list_file) + return nil if filename_or_io.nil? - # Adds a certificate to the revocation list. After calling you must call generate_crl to sign a new CRL - # - # @param serial [Integer] serial number of the certificate to revoke - # @param reason [Integer] reason for revocation - # @param revoke_time [Integer] - # @param generate_and_save [Boolean] whether we want to generate the CRL and save its file (default=true) - # - # reason codes defined by rfc 5280 - # - # CRLReason ::= ENUMERATED { - # unspecified (0), - # keyCompromise (1), - # cACompromise (2), - # affiliationChanged (3), - # superseded (4), - # cessationOfOperation (5), - # certificateHold (6), - # removeFromCRL (8), - # privilegeWithdrawn (9), - # aACompromise (10) } - def revoke_cert(serial,reason=nil, revoke_time=Time.now.to_i, generate_and_save=true) - if not reason.to_i.between?(0,10) - reason = 0 - end - serial = serial.to_i - reason = reason.to_i - revoke_time = revoke_time.to_i - if revoked?(serial) - raise R509::R509Error, "Cannot revoke a previously revoked certificate" - end - @revoked_certs[serial] = {:reason => reason, :revoke_time => revoke_time} - if generate_and_save - generate_crl() - save_crl_list() - end - nil - end + data = [] + self.revoked_certs.each do |serial, reason, revoke_time| + data << [serial, revoke_time, reason].join(',') + end + write_data(filename_or_io, data.join("\n")) + nil + end - # Remove serial from revocation list. After unrevoking you must call generate_crl to sign a new CRL - # - # @param serial [Integer] serial number of the certificate to remove from revocation - def unrevoke_cert(serial) - @revoked_certs.delete(serial) - generate_crl() - save_crl_list() - nil - end + # Save the CRL number to a filename or IO. If the class was initialized + # with :crl_number_file, then the filename specified by that will be used + # by default. + # @param [String, #write, nil] filename_or_io If provided, the current + # crl number will be written to either the file (if a string), or IO. If nil, + # then the @crl_number_file will be used. If that is nil, then an error + # will be raised. + def save_crl_number(filename_or_io = @crl_number_file) + return nil if filename_or_io.nil? + # No valid filename or IO was specified, so bail. - # Remove serial from revocation list - # - # @return [String] PEM encoded signed CRL - def generate_crl - crl = OpenSSL::X509::CRL.new - crl.version = 1 - now = Time.at Time.now.to_i - crl.last_update = now-@start_skew_seconds - crl.next_update = now+@validity_hours*3600 - crl.issuer = @config.ca_cert.subject + write_data(filename_or_io, self.crl_number.to_s) + nil + end - self.revoked_certs.each do |serial, reason, revoke_time| - revoked = OpenSSL::X509::Revoked.new - revoked.serial = OpenSSL::BN.new serial.to_s - revoked.time = Time.at(revoke_time) - if !reason.nil? - enum = OpenSSL::ASN1::Enumerated(reason) #see reason codes below - ext = OpenSSL::X509::Extension.new("CRLReason", enum) - revoked.add_extension(ext) - end - #now add it to the crl - crl.add_revoked(revoked) - end + private - ef = OpenSSL::X509::ExtensionFactory.new - ef.issuer_certificate = @config.ca_cert.cert - ef.crl = crl - #grab crl number from file, increment, write back - crl_number = increment_crl_number - crlnum = OpenSSL::ASN1::Integer(crl_number) - crl.add_extension(OpenSSL::X509::Extension.new("crlNumber", crlnum)) - extensions = [] - extensions << ["authorityKeyIdentifier", "keyid:always,issuer:always", false] - extensions.each{|oid, value, critical| - crl.add_extension(ef.create_extension(oid, value, critical)) - } - crl.sign(@config.ca_cert.key.key, OpenSSL::Digest::SHA1.new) - @crl = crl - @crl.to_pem - end + # Increments the crl_number. + # @return [Integer] the new CRL number + # + def increment_crl_number + @crl_number += 1 + save_crl_number + @crl_number + end - # @return [Array<Array>] Returns an array of serial, reason, revoke_time - # tuples. - def revoked_certs - ret = [] - @revoked_certs.keys.sort.each do |serial| - ret << [serial, @revoked_certs[serial][:reason], @revoked_certs[serial][:revoke_time]] - end - ret - end + # Loads the certificate revocation list from file. + # @param [String, #read, nil] filename_or_io The + # crl will be read from either the file (if a string), or IO. + def load_crl_list(filename_or_io) + @revoked_certs = {} - # Saves the CRL list to a filename or IO. If the class was initialized - # with :crl_list_file, then the filename specified by that will be used - # by default. - # @param [String, #write, nil] filename_or_io If provided, the generated - # crl will be written to either the file (if a string), or IO. If nil, - # then the @crl_list_file will be used. If that is nil, then an error - # will be raised. - def save_crl_list(filename_or_io = @crl_list_file) - return nil if filename_or_io.nil? + if filename_or_io.nil? + generate_crl + return nil + end - data = [] - self.revoked_certs.each do |serial, reason, revoke_time| - data << [serial, revoke_time, reason].join(',') - end - write_data(filename_or_io, data.join("\n")) - nil - end + data = read_data(filename_or_io) - # Save the CRL number to a filename or IO. If the class was initialized - # with :crl_number_file, then the filename specified by that will be used - # by default. - # @param [String, #write, nil] filename_or_io If provided, the current - # crl number will be written to either the file (if a string), or IO. If nil, - # then the @crl_number_file will be used. If that is nil, then an error - # will be raised. - def save_crl_number(filename_or_io = @crl_number_file) - return nil if filename_or_io.nil? - # No valid filename or IO was specified, so bail. - - write_data(filename_or_io, self.crl_number.to_s) - nil - end - - private - - # Increments the crl_number. - # @return [Integer] the new CRL number - # - def increment_crl_number - @crl_number += 1 - save_crl_number() - @crl_number - end - - # Loads the certificate revocation list from file. - # @param [String, #read, nil] filename_or_io The - # crl will be read from either the file (if a string), or IO. - def load_crl_list(filename_or_io) - @revoked_certs = {} - - if filename_or_io.nil? - generate_crl - return nil - end - - data = read_data(filename_or_io) - - data.each_line do |line| - line.chomp! - serial, revoke_time, reason = line.split(',', 3) - serial = serial.to_i - reason = (reason == '') ? nil : reason.to_i - revoke_time = (revoke_time == '') ? nil : revoke_time.to_i - self.revoke_cert(serial, reason, revoke_time, false) - end - generate_crl - save_crl_list - nil - end - + data.each_line do |line| + line.chomp! + serial, revoke_time, reason = line.split(',', 3) + serial = serial.to_i + reason = (reason == '') ? nil : reason.to_i + revoke_time = (revoke_time == '') ? nil : revoke_time.to_i + self.revoke_cert(serial, reason, revoke_time, false) end + generate_crl + save_crl_list + nil + end + end + end end