lib/r509/ocsp/signer.rb in r509-ocsp-responder-0.3.1 vs lib/r509/ocsp/signer.rb in r509-ocsp-responder-0.3.2
- old
+ new
@@ -2,243 +2,243 @@
require 'r509/exceptions'
require 'r509/config'
require 'dependo'
# OCSP related classes (signing, response, request)
-module R509::Ocsp
- # A class for signing OCSP responses
- class Signer
- attr_reader :validity_checker,:request_checker
+module R509::OCSP
+ # A class for signing OCSP responses
+ class Signer
+ attr_reader :validity_checker,:request_checker
- # @option options [Boolean] :copy_nonce copy nonce from request to response?
- # @option options [R509::Config::CaConfigPool] :configs CaConfigPool object
- # possible OCSP issuance roots that we want to issue OCSP responses for
- def initialize(options)
- if options.has_key?(:validity_checker)
- @validity_checker = options[:validity_checker]
- else
- @validity_checker = R509::Validity::DefaultChecker.new
- end
- @request_checker = Helper::RequestChecker.new(options[:configs], @validity_checker)
- @response_signer = Helper::ResponseSigner.new(options)
- end
+ # @option options [Boolean] :copy_nonce copy nonce from request to response?
+ # @option options [R509::Config::CAConfigPool] :configs CAConfigPool object
+ # possible OCSP issuance roots that we want to issue OCSP responses for
+ def initialize(options)
+ if options.has_key?(:validity_checker)
+ @validity_checker = options[:validity_checker]
+ else
+ @validity_checker = R509::Validity::DefaultChecker.new
+ end
+ @request_checker = Helper::RequestChecker.new(options[:configs], @validity_checker)
+ @response_signer = Helper::ResponseSigner.new(options)
+ end
- # @param request [String,OpenSSL::OCSP::Request] OCSP request (string or parsed object)
- # @return [Hash]
- # * :request [OpenSSL::OCSP::Request] parsed request object
- # * :response [OpenSSL::OCSP::Response] full response object
- def handle_request(request)
- begin
- parsed_request = OpenSSL::OCSP::Request.new request
- rescue
- return {:response => @response_signer.create_response(OpenSSL::OCSP::RESPONSE_STATUS_MALFORMEDREQUEST), :request => nil}
- end
+ # @param request [String,OpenSSL::OCSP::Request] OCSP request (string or parsed object)
+ # @return [Hash]
+ # * :request [OpenSSL::OCSP::Request] parsed request object
+ # * :response [OpenSSL::OCSP::Response] full response object
+ def handle_request(request)
+ begin
+ parsed_request = OpenSSL::OCSP::Request.new request
+ rescue
+ return {:response => @response_signer.create_response(OpenSSL::OCSP::RESPONSE_STATUS_MALFORMEDREQUEST), :request => nil}
+ end
- statuses = @request_checker.check_statuses(parsed_request)
- if not @request_checker.validate_statuses(statuses)
- return {:response => @response_signer.create_response(OpenSSL::OCSP::RESPONSE_STATUS_UNAUTHORIZED), :request => nil}
- end
+ statuses = @request_checker.check_statuses(parsed_request)
+ if not @request_checker.validate_statuses(statuses)
+ return {:response => @response_signer.create_response(OpenSSL::OCSP::RESPONSE_STATUS_UNAUTHORIZED), :request => nil}
+ end
- basic_response = @response_signer.create_basic_response(parsed_request,statuses)
+ basic_response = @response_signer.create_basic_response(parsed_request,statuses)
- {:response => @response_signer.create_response(
- OpenSSL::OCSP::RESPONSE_STATUS_SUCCESSFUL,
- basic_response
- ), :request => parsed_request}
- end
-
+ {:response => @response_signer.create_response(
+ OpenSSL::OCSP::RESPONSE_STATUS_SUCCESSFUL,
+ basic_response
+ ), :request => parsed_request}
end
+
+ end
end
#Helper module for OCSP handling
-module R509::Ocsp::Helper
- # checks requests for validity against a set of configs
- class RequestChecker
- include Dependo::Mixin
- attr_reader :configs,:configs_hash
+module R509::OCSP::Helper
+ # checks requests for validity against a set of configs
+ class RequestChecker
+ include Dependo::Mixin
+ attr_reader :configs,:configs_hash
- # @param [R509::Config::CaConfigPool] configs CaConfigPool object
- # @param [R509::Validity::Checker] validity_checker an implementation of the R509::Validity::Checker class
- def initialize(configs, validity_checker)
- unless configs.kind_of?(R509::Config::CaConfigPool)
- raise R509::R509Error, "Must pass R509::Config::CaConfigPool object"
- end
- if configs.all.empty?
- raise R509::R509Error, "Must be at least one R509::Config object"
- end
- @configs = configs.all
- test_cid = OpenSSL::OCSP::CertificateId.new(OpenSSL::X509::Certificate.new,OpenSSL::X509::Certificate.new)
- if test_cid.respond_to?(:issuer_key_hash)
- @configs_hash = {}
- @configs.each do |config|
- ee_cert = OpenSSL::X509::Certificate.new
- ee_cert.issuer = config.ca_cert.cert.subject
- # per RFC 5019
- # Clients MUST use SHA1 as the hashing algorithm for the
- # CertID.issuerNameHash and the CertID.issuerKeyHash values.
- # so we can safely assume that our inbound hashes will be SHA1
- issuer_certid = OpenSSL::OCSP::CertificateId.new(ee_cert,config.ca_cert.cert,OpenSSL::Digest::SHA1.new)
- @configs_hash[issuer_certid.issuer_key_hash] = config
- end
- end
- @validity_checker = validity_checker
- if @validity_checker.nil?
- raise R509::R509Error, "Must supply a R509::Validity::Checker"
- end
- if not @validity_checker.respond_to?(:check)
- raise R509::R509Error, "The validity checker must have a check method"
- end
+ # @param [R509::Config::CAConfigPool] configs CAConfigPool object
+ # @param [R509::Validity::Checker] validity_checker an implementation of the R509::Validity::Checker class
+ def initialize(configs, validity_checker)
+ unless configs.kind_of?(R509::Config::CAConfigPool)
+ raise R509::R509Error, "Must pass R509::Config::CAConfigPool object"
+ end
+ if configs.all.empty?
+ raise R509::R509Error, "Must be at least one R509::Config object"
+ end
+ @configs = configs.all
+ test_cid = OpenSSL::OCSP::CertificateId.new(OpenSSL::X509::Certificate.new,OpenSSL::X509::Certificate.new)
+ if test_cid.respond_to?(:issuer_key_hash)
+ @configs_hash = {}
+ @configs.each do |config|
+ ee_cert = OpenSSL::X509::Certificate.new
+ ee_cert.issuer = config.ca_cert.cert.subject.name
+ # per RFC 5019
+ # Clients MUST use SHA1 as the hashing algorithm for the
+ # CertID.issuerNameHash and the CertID.issuerKeyHash values.
+ # so we can safely assume that our inbound hashes will be SHA1
+ issuer_certid = OpenSSL::OCSP::CertificateId.new(ee_cert,config.ca_cert.cert,OpenSSL::Digest::SHA1.new)
+ @configs_hash[issuer_certid.issuer_key_hash] = config
end
+ end
+ @validity_checker = validity_checker
+ if @validity_checker.nil?
+ raise R509::R509Error, "Must supply a R509::Validity::Checker"
+ end
+ if not @validity_checker.respond_to?(:check)
+ raise R509::R509Error, "The validity checker must have a check method"
+ end
+ end
- # Loads and checks a raw OCSP request
- #
- # @param request [OpenSSL::OCSP::Request] OpenSSL OCSP Request object
- # @return [Hash] hash from the check_status method
- def check_statuses(request)
- request.certid.map { |certid|
- if certid.respond_to?(:issuer_key_hash)
- validated_config = @configs_hash[certid.issuer_key_hash]
- else
- validated_config = @configs.find do |config|
- #we need to create an OCSP::CertificateId object that has the right
- #issuer so we can pass it to #cmp_issuer. This is annoying because
- #CertificateId wants a cert and its issuer, but we don't want to
- #force users to provide an end entity cert just to make this comparison
- #work. So, we create a fake new cert and pass it in.
- ee_cert = OpenSSL::X509::Certificate.new
- ee_cert.issuer = config.ca_cert.cert.subject
- issuer_certid = OpenSSL::OCSP::CertificateId.new(ee_cert,config.ca_cert.cert)
- certid.cmp_issuer(issuer_certid)
- end
- end
-
- log.info "#{validated_config.ca_cert.subject.to_s} found for issuer" if validated_config
- check_status(certid, validated_config)
- }
+ # Loads and checks a raw OCSP request
+ #
+ # @param request [OpenSSL::OCSP::Request] OpenSSL OCSP Request object
+ # @return [Hash] hash from the check_status method
+ def check_statuses(request)
+ request.certid.map { |certid|
+ if certid.respond_to?(:issuer_key_hash)
+ validated_config = @configs_hash[certid.issuer_key_hash]
+ else
+ validated_config = @configs.find do |config|
+ #we need to create an OCSP::CertificateId object that has the right
+ #issuer so we can pass it to #cmp_issuer. This is annoying because
+ #CertificateId wants a cert and its issuer, but we don't want to
+ #force users to provide an end entity cert just to make this comparison
+ #work. So, we create a fake new cert and pass it in.
+ ee_cert = OpenSSL::X509::Certificate.new
+ ee_cert.issuer = config.ca_cert.cert.subject
+ issuer_certid = OpenSSL::OCSP::CertificateId.new(ee_cert,config.ca_cert.cert)
+ certid.cmp_issuer(issuer_certid)
+ end
end
- # Determines whether the statuses constitute a request that is compliant.
- # No config means we don't know the CA, different configs means there are
- # requests from two different CAs in there. Both are invalid.
- #
- # @param statuses [Array<Hash>] array of hashes from check_statuses
- # @return [Boolean]
- def validate_statuses(statuses)
- validity = true
- config = nil
+ log.info "#{validated_config.ca_cert.subject.to_s} found for issuer" if validated_config
+ check_status(certid, validated_config)
+ }
+ end
- statuses.each do |status|
- if status[:config].nil?
- validity = false
- end
- if config.nil?
- config = status[:config]
- end
- if config != status[:config]
- validity = false
- end
- end
+ # Determines whether the statuses constitute a request that is compliant.
+ # No config means we don't know the CA, different configs means there are
+ # requests from two different CAs in there. Both are invalid.
+ #
+ # @param statuses [Array<Hash>] array of hashes from check_statuses
+ # @return [Boolean]
+ def validate_statuses(statuses)
+ validity = true
+ config = nil
- validity
+ statuses.each do |status|
+ if status[:config].nil?
+ validity = false
end
-
- private
-
- # Checks the status of a certificate with the corresponding CA
- # @param certid [OpenSSL::OCSP::CertificateId] The certId object from check_statuses
- # @param validated_config [R509::Config]
- def check_status(certid, validated_config)
- if(validated_config == nil) then
- return {
- :certid => certid,
- :config => nil
- }
- else
- validity_status = @validity_checker.check(validated_config.ca_cert.subject.to_s,certid.serial)
- return {
- :certid => certid,
- :status => validity_status.ocsp_status,
- :revocation_reason => validity_status.revocation_reason,
- :revocation_time => validity_status.revocation_time,
- :config => validated_config
- }
- end
+ if config.nil?
+ config = status[:config]
end
+ if config != status[:config]
+ validity = false
+ end
+ end
+
+ validity
end
- #signs OCSP responses
- class ResponseSigner
- # @option options [Boolean] :copy_nonce
- def initialize(options)
- if options.has_key?(:copy_nonce)
- @copy_nonce = options[:copy_nonce]
- else
- @copy_nonce = false
- end
- end
+ private
- # It is UNWISE to call this method directly because it assumes that the request is
- # validated. You probably want to take a look at R509::Ocsp::Signer#handle_request
- #
- # @param request [OpenSSL::OCSP::Request]
- # @param statuses [Hash] hash from R509::Ocsp::Helper::RequestChecker#check_statuses
- # @return [OpenSSL::OCSP::BasicResponse]
- def create_basic_response(request,statuses)
- basic_response = OpenSSL::OCSP::BasicResponse.new
+ # Checks the status of a certificate with the corresponding CA
+ # @param certid [OpenSSL::OCSP::CertificateId] The certId object from check_statuses
+ # @param validated_config [R509::Config]
+ def check_status(certid, validated_config)
+ if(validated_config == nil) then
+ return {
+ :certid => certid,
+ :config => nil
+ }
+ else
+ validity_status = @validity_checker.check(validated_config.ca_cert.subject.to_s,certid.serial)
+ return {
+ :certid => certid,
+ :status => validity_status.ocsp_status,
+ :revocation_reason => validity_status.revocation_reason,
+ :revocation_time => validity_status.revocation_time,
+ :config => validated_config
+ }
+ end
+ end
+ end
- basic_response.copy_nonce(request) if @copy_nonce
+ #signs OCSP responses
+ class ResponseSigner
+ # @option options [Boolean] :copy_nonce
+ def initialize(options)
+ if options.has_key?(:copy_nonce)
+ @copy_nonce = options[:copy_nonce]
+ else
+ @copy_nonce = false
+ end
+ end
- statuses.each do |status|
- #revocation time is retarded and is relative to now, so
- #let's figure out what that is.
- if status[:status] == OpenSSL::OCSP::V_CERTSTATUS_REVOKED
- revocation_time = status[:revocation_time].to_i - Time.now.to_i
- end
- basic_response.add_status(status[:certid],
- status[:status],
- status[:revocation_reason],
- revocation_time,
- -1*status[:config].ocsp_start_skew_seconds,
- status[:config].ocsp_validity_hours*3600,
- [] #array of OpenSSL::X509::Extensions
- )
- end
+ # It is UNWISE to call this method directly because it assumes that the request is
+ # validated. You probably want to take a look at R509::OCSP::Signer#handle_request
+ #
+ # @param request [OpenSSL::OCSP::Request]
+ # @param statuses [Hash] hash from R509::OCSP::Helper::RequestChecker#check_statuses
+ # @return [OpenSSL::OCSP::BasicResponse]
+ def create_basic_response(request,statuses)
+ basic_response = OpenSSL::OCSP::BasicResponse.new
- #this method assumes the request data is validated by validate_request so all configs will be the same and
- #we can choose to use the first one safely
- config = statuses[0][:config]
+ basic_response.copy_nonce(request) if @copy_nonce
- #confusing, but R509::Cert contains R509::PrivateKey under #key. PrivateKey#key gives the OpenSSL object
- #turns out BasicResponse#sign can take up to 4 params
- #cert, key, array of OpenSSL::X509::Certificates, flags (not sure what the enumeration of those are)
- basic_response.sign(config.ocsp_cert.cert,config.ocsp_cert.key.key,config.ocsp_chain)
+ statuses.each do |status|
+ #revocation time is retarded and is relative to now, so
+ #let's figure out what that is.
+ if status[:status] == OpenSSL::OCSP::V_CERTSTATUS_REVOKED
+ revocation_time = status[:revocation_time].to_i - Time.now.to_i
end
+ basic_response.add_status(status[:certid],
+ status[:status],
+ status[:revocation_reason],
+ revocation_time,
+ -1*status[:config].ocsp_start_skew_seconds,
+ status[:config].ocsp_validity_hours*3600,
+ [] #array of OpenSSL::X509::Extensions
+ )
+ end
- # Builds final response.
- #
- # @param response_status [OpenSSL::OCSP::RESPONSE_STATUS_*] the primary response status
- # @param basic_response [OpenSSL::OCSP::BasicResponse] an optional basic response object
- # generated by create_basic_response
- # @return [OpenSSL::OCSP::OCSPResponse]
- def create_response(response_status,basic_response=nil)
+ #this method assumes the request data is validated by validate_request so all configs will be the same and
+ #we can choose to use the first one safely
+ config = statuses[0][:config]
- # first arg is the response status code, comes from this list
- # these can also be enumerated via OpenSSL::OCSP::RESPONSE_STATUS_*
- #OCSPResponseStatus ::= ENUMERATED {
- # successful (0), --Response has valid confirmations
- # malformedRequest (1), --Illegal confirmation request
- # internalError (2), --Internal error in issuer
- # tryLater (3), --Try again later
- # --(4) is not used
- # sigRequired (5), --Must sign the request
- # unauthorized (6) --Request unauthorized
- #}
- #
- R509::Ocsp::Response.new(
- OpenSSL::OCSP::Response.create(
- response_status, basic_response
- )
- )
- end
+ #confusing, but R509::Cert contains R509::PrivateKey under #key. PrivateKey#key gives the OpenSSL object
+ #turns out BasicResponse#sign can take up to 4 params
+ #cert, key, array of OpenSSL::X509::Certificates, flags (not sure what the enumeration of those are)
+ basic_response.sign(config.ocsp_cert.cert,config.ocsp_cert.key.key,config.ocsp_chain)
end
+
+ # Builds final response.
+ #
+ # @param response_status [OpenSSL::OCSP::RESPONSE_STATUS_*] the primary response status
+ # @param basic_response [OpenSSL::OCSP::BasicResponse] an optional basic response object
+ # generated by create_basic_response
+ # @return [OpenSSL::OCSP::OCSPResponse]
+ def create_response(response_status,basic_response=nil)
+
+ # first arg is the response status code, comes from this list
+ # these can also be enumerated via OpenSSL::OCSP::RESPONSE_STATUS_*
+ #OCSPResponseStatus ::= ENUMERATED {
+ # successful (0), --Response has valid confirmations
+ # malformedRequest (1), --Illegal confirmation request
+ # internalError (2), --Internal error in issuer
+ # tryLater (3), --Try again later
+ # --(4) is not used
+ # sigRequired (5), --Must sign the request
+ # unauthorized (6) --Request unauthorized
+ #}
+ #
+ R509::OCSP::Response.new(
+ OpenSSL::OCSP::Response.create(
+ response_status, basic_response
+ )
+ )
+ end
+ end
end