lib/r509/cert/extensions.rb in r509-0.9.2 vs lib/r509/cert/extensions.rb in r509-0.10.0
- old
+ new
@@ -1,676 +1,13 @@
-require 'openssl'
-require 'r509/asn1'
-require 'set'
-
-module R509
- class Cert
- # module to contain extension classes for R509::Cert
- module Extensions
-
- private
- R509_EXTENSION_CLASSES = Set.new
-
- # Registers a class as being an R509 certificate extension class. Registered
- # classes are used by #wrap_openssl_extensions to wrap OpenSSL extensions
- # in R509 extensions, based on the OID.
- def self.register_class( r509_ext_class )
- raise ArgumentError.new("R509 certificate extensions must have an OID") if r509_ext_class::OID.nil?
- R509_EXTENSION_CLASSES << r509_ext_class
- end
-
-
- public
- # Implements the BasicConstraints certificate extension, with methods to
- # provide access to the components and meaning of the extension's contents.
- class BasicConstraints < OpenSSL::X509::Extension
- # friendly name for BasicConstraints OID
- OID = "basicConstraints"
- Extensions.register_class(self)
-
- attr_reader :path_length
-
- # See OpenSSL::X509::Extension#initialize
- def initialize(*args)
- super(*args)
-
- data = R509::ASN1.get_extension_payload(self)
- @is_ca = false
- # BasicConstraints ::= SEQUENCE {
- # cA BOOLEAN DEFAULT FALSE,
- # pathLenConstraint INTEGER (0..MAX) OPTIONAL }
- data.entries.each do |entry|
- if entry.kind_of?(OpenSSL::ASN1::Boolean)
- # since the boolean is optional it may not be present
- @is_ca = entry.value
- else
- # There are only two kinds of entries permitted so anything
- # else is an integer pathlength
- @path_length = entry.value
- end
- end
- end
-
- def is_ca?()
- return @is_ca == true
- end
-
- # Returns true if the path length allows this certificate to be used to
- # create subordinate signing certificates beneath it. Does not check if
- # there is a pathlen restriction in the cert chain above the current cert
- def allows_sub_ca?()
- return false if @path_length.nil?
- return @path_length > 0
- end
- end
-
- # Implements the KeyUsage certificate extension, with methods to
- # provide access to the components and meaning of the extension's contents.
- class KeyUsage < OpenSSL::X509::Extension
- # friendly name for KeyUsage OID
- OID = "keyUsage"
- Extensions.register_class(self)
-
- # An array of the key uses allowed.
- attr_reader :allowed_uses
-
- # OpenSSL short name for Digital Signature
- AU_DIGITAL_SIGNATURE = "digitalSignature"
- # OpenSSL short name for Non Repudiation (also known as content commitment)
- AU_NON_REPUDIATION = "nonRepudiation"
- # OpenSSL short name for Key Encipherment
- AU_KEY_ENCIPHERMENT = "keyEncipherment"
- # OpenSSL short name for Data Encipherment
- AU_DATA_ENCIPHERMENT = "dataEncipherment"
- # OpenSSL short name for Key Agreement
- AU_KEY_AGREEMENT = "keyAgreement"
- # OpenSSL short name for Certificate Sign
- AU_KEY_CERT_SIGN = "keyCertSign"
- # OpenSSL short name for CRL Sign
- AU_CRL_SIGN = "cRLSign"
- # OpenSSL short name for Encipher Only
- AU_ENCIPHER_ONLY = "encipherOnly"
- # OpenSSL short name for Decipher Only
- AU_DECIPHER_ONLY = "decipherOnly"
-
- # See OpenSSL::X509::Extension#initialize
- def initialize(*args)
- super(*args)
-
- data = R509::ASN1.get_extension_payload(self)
-
- # There are 9 possible bits, which means we need 2 bytes
- # to represent them all. When the last bit is not set
- # the second byte is not encoded. let's add it back so we can
- # have the full bitmask for comparison
- if data.size == 1
- data = data + "\0"
- end
- bit_mask = data.unpack('n')[0] # treat it as a 16-bit unsigned big endian
- # KeyUsage ::= BIT STRING {
- # digitalSignature (0),
- # nonRepudiation (1), -- recent editions of X.509 have
- # -- renamed this bit to contentCommitment
- # keyEncipherment (2),
- # dataEncipherment (3),
- # keyAgreement (4),
- # keyCertSign (5),
- # cRLSign (6),
- # encipherOnly (7),
- # decipherOnly (8) }
- @allowed_uses = []
- if bit_mask & 0b1000000000000000 > 0
- @digital_signature = true
- @allowed_uses << AU_DIGITAL_SIGNATURE
- end
- if bit_mask & 0b0100000000000000 > 0
- @non_repudiation = true
- @allowed_uses << AU_NON_REPUDIATION
- end
- if bit_mask & 0b0010000000000000 > 0
- @key_encipherment = true
- @allowed_uses << AU_KEY_ENCIPHERMENT
- end
- if bit_mask & 0b0001000000000000 > 0
- @data_encipherment = true
- @allowed_uses << AU_DATA_ENCIPHERMENT
- end
- if bit_mask & 0b0000100000000000 > 0
- @key_agreement = true
- @allowed_uses << AU_KEY_AGREEMENT
- end
- if bit_mask & 0b0000010000000000 > 0
- @key_cert_sign = true
- @allowed_uses << AU_KEY_CERT_SIGN
- end
- if bit_mask & 0b0000001000000000 > 0
- @crl_sign = true
- @allowed_uses << AU_CRL_SIGN
- end
- if bit_mask & 0b0000000100000000 > 0
- @encipher_only = true
- @allowed_uses << AU_ENCIPHER_ONLY
- end
- if bit_mask & 0b0000000010000000 > 0
- @decipher_only = true
- @allowed_uses << AU_DECIPHER_ONLY
- end
- end
-
- # Returns true if the given use is allowed by this extension.
- # @param [String] friendly_use_name key usage short name (e.g. digitalSignature, cRLSign, etc)
- # or one of the AU_* constants in this class
- # @return [Boolean]
- def allows?( friendly_use_name )
- @allowed_uses.include?( friendly_use_name )
- end
-
- def digital_signature?
- (@digital_signature == true)
- end
-
- def non_repudiation?
- (@non_repudiation == true)
- end
-
- def key_encipherment?
- (@key_encipherment == true)
- end
-
- def data_encipherment?
- (@data_encipherment == true)
- end
-
- def key_agreement?
- (@key_agreement == true)
- end
-
- def key_cert_sign?
- (@key_cert_sign == true)
- end
-
- def crl_sign?
- (@crl_sign == true)
- end
-
- def encipher_only?
- (@encipher_only == true)
- end
-
- def decipher_only?
- (@decipher_only == true)
- end
- end
-
- # Implements the ExtendedKeyUsage certificate extension, with methods to
- # provide access to the components and meaning of the extension's contents.
- class ExtendedKeyUsage < OpenSSL::X509::Extension
- # friendly name for EKU OID
- OID = "extendedKeyUsage"
- Extensions.register_class(self)
-
- # The OpenSSL short name for TLS Web Server Authentication
- AU_WEB_SERVER_AUTH = "serverAuth"
- # The OpenSSL short name for TLS Web Client Authentication
- AU_WEB_CLIENT_AUTH = "clientAuth"
- # The OpenSSL short name for Code Signing
- AU_CODE_SIGNING = "codeSigning"
- # The OpenSSL short name for E-mail Protection
- AU_EMAIL_PROTECTION = "emailProtection"
- # The OpenSSL short name for OCSP Signing
- AU_OCSP_SIGNING = "OCSPSigning"
- # The OpenSSL short name for Time Stamping
- AU_TIME_STAMPING = "timeStamping"
- # The OpenSSL short name for Any Extended Key Usage
- AU_ANY_EXTENDED_KEY_USAGE = "anyExtendedKeyUsage"
-
- attr_reader :allowed_uses
-
- # See OpenSSL::X509::Extension#initialize
- def initialize(*args)
- super(*args)
-
- @allowed_uses = []
- data = R509::ASN1.get_extension_payload(self)
-
- data.entries.each do |eku|
- # The following key usage purposes are defined:
- #
- # anyExtendedKeyUsage OBJECT IDENTIFIER ::= { id-ce-extKeyUsage 0 }
- #
- # id-kp OBJECT IDENTIFIER ::= { id-pkix 3 }
- # id-kp-serverAuth OBJECT IDENTIFIER ::= { id-kp 1 }
- # -- TLS WWW server authentication
- # -- Key usage bits that may be consistent: digitalSignature,
- # -- keyEncipherment or keyAgreement
- #
- # id-kp-clientAuth OBJECT IDENTIFIER ::= { id-kp 2 }
- # -- TLS WWW client authentication
- # -- Key usage bits that may be consistent: digitalSignature
- # -- and/or keyAgreement
- #
- # id-kp-codeSigning OBJECT IDENTIFIER ::= { id-kp 3 }
- # -- Signing of downloadable executable code
- # -- Key usage bits that may be consistent: digitalSignature
- #
- # id-kp-emailProtection OBJECT IDENTIFIER ::= { id-kp 4 }
- # -- Email protection
- # -- Key usage bits that may be consistent: digitalSignature,
- # -- nonRepudiation, and/or (keyEncipherment or keyAgreement)
- #
- # id-kp-timeStamping OBJECT IDENTIFIER ::= { id-kp 8 }
- # -- Binding the hash of an object to a time
- # -- Key usage bits that may be consistent: digitalSignature
- # -- and/or nonRepudiation
- #
- # id-kp-OCSPSigning OBJECT IDENTIFIER ::= { id-kp 9 }
- # -- Signing OCSP responses
- # -- Key usage bits that may be consistent: digitalSignature
- # -- and/or nonRepudiation
-
- case eku.value
- when AU_WEB_SERVER_AUTH
- @web_server_authentication = true
- when AU_WEB_CLIENT_AUTH
- @web_client_authentication = true
- when AU_CODE_SIGNING
- @code_signing = true
- when AU_EMAIL_PROTECTION
- @email_protection = true
- when AU_OCSP_SIGNING
- @ocsp_signing = true
- when AU_TIME_STAMPING
- @time_stamping = true
- when AU_ANY_EXTENDED_KEY_USAGE
- @any_extended_key_usage = true
- end
- @allowed_uses << eku.value
- end
- end
-
- # Returns true if the given use is allowed by this extension.
- # @param [string] friendly_use_name One of the AU_* constants in this class.
- def allows?( friendly_use_name )
- @allowed_uses.include?( friendly_use_name )
- end
-
- def web_server_authentication?
- (@web_server_authentication == true)
- end
-
- def web_client_authentication?
- (@web_client_authentication == true)
- end
-
- def code_signing?
- (@code_signing == true)
- end
-
- def email_protection?
- (@email_protection == true)
- end
-
- def ocsp_signing?
- (@ocsp_signing == true)
- end
-
- def time_stamping?
- (@time_stamping == true)
- end
-
- def any_extended_key_usage?
- (@any_extended_key_usage == true)
- end
- end
-
- # Implements the SubjectKeyIdentifier certificate extension, with methods to
- # provide access to the components and meaning of the extension's contents.
- class SubjectKeyIdentifier < OpenSSL::X509::Extension
- # friendly name for Subject Key Identifier OID
- OID = "subjectKeyIdentifier"
- Extensions.register_class(self)
-
- # @return value of key
- def key()
- return self.value
- end
- end
-
- # Implements the AuthorityKeyIdentifier certificate extension, with methods to
- # provide access to the components and meaning of the extension's contents.
- class AuthorityKeyIdentifier < OpenSSL::X509::Extension
- # friendly name for Authority Key Identifier OID
- OID = "authorityKeyIdentifier"
- Extensions.register_class(self)
-
- # key_identifier, if present, will be a hex string delimited by colons
- # authority_cert_issuer, if present, will be a GeneralName object
- # authority_cert_serial_number, if present, will be a hex string delimited by colons
- attr_reader :key_identifier, :authority_cert_issuer, :authority_cert_serial_number
-
- def initialize(*args)
- super(*args)
-
- data = R509::ASN1.get_extension_payload(self)
- # AuthorityKeyIdentifier ::= SEQUENCE {
- # keyIdentifier [0] KeyIdentifier OPTIONAL,
- # authorityCertIssuer [1] GeneralNames OPTIONAL,
- # authorityCertSerialNumber [2] CertificateSerialNumber OPTIONAL }
- data.entries.each do |el|
- case el.tag
- when 0
- @key_identifier = el.value.unpack("H*")[0].upcase.scan(/../).join(":")
- when 1
- @authority_cert_issuer = R509::ASN1::GeneralName.new(el.value.first)
- when 2
- arr = el.value.unpack("H*")[0].upcase.scan(/../)
- # OpenSSL's convention is to drop leading 00s, so let's strip that off if
- # present
- if arr[0] == "00"
- arr.delete_at(0)
- end
- @authority_cert_serial_number = arr.join(":")
- end
- end
-
- end
-
- end
-
- # Implements the SubjectAlternativeName certificate extension, with methods to
- # provide access to the components and meaning of the extension's contents.
- class SubjectAlternativeName < OpenSSL::X509::Extension
- # friendly name for SAN OID
- OID = "subjectAltName"
- Extensions.register_class(self)
-
- attr_reader :general_names
-
- # See OpenSSL::X509::Extension#initialize
- def initialize(*args)
- super(*args)
-
- data = R509::ASN1.get_extension_payload(self)
- @general_names = R509::ASN1::GeneralNames.new
- data.entries.each do |gn|
- @general_names.add_item(gn)
- end
- end
-
- # @return [Array<String>] DNS names
- def dns_names
- @general_names.dns_names
- end
-
- # @return [Array<String>] IP addresses formatted as dotted quad
- def ip_addresses
- @general_names.ip_addresses
- end
-
- # @return [Array<String>] email addresses
- def rfc_822_names
- @general_names.rfc_822_names
- end
-
- # @return [Array<String>] URIs (not typically found in SAN extensions)
- def uris
- @general_names.uris
- end
-
- # @return [Array<R509::Subject>] directory names
- def directory_names
- @general_names.directory_names
- end
-
- # @return [Array] array of GeneralName objects preserving order found in the extension
- def names
- @general_names.names
- end
- end
-
- # Implements the AuthorityInfoAccess certificate extension, with methods to
- # provide access to the components and meaning of the extension's contents.
- class AuthorityInfoAccess < OpenSSL::X509::Extension
- # friendly name for AIA OID
- OID = "authorityInfoAccess"
- Extensions.register_class(self)
-
- # An array of the OCSP data, if any
- attr_reader :ocsp
- # An array of the CA issuers data, if any
- attr_reader :ca_issuers
-
- # See OpenSSL::X509::Extension#initialize
- def initialize(*args)
- super(*args)
-
- data = R509::ASN1.get_extension_payload(self)
- @ocsp= R509::ASN1::GeneralNames.new
- @ca_issuers= R509::ASN1::GeneralNames.new
- data.entries.each do |access_description|
- # AccessDescription ::= SEQUENCE {
- # accessMethod OBJECT IDENTIFIER,
- # accessLocation GeneralName }
- case access_description.entries[0].value
- when "OCSP"
- @ocsp.add_item(access_description.entries[1])
- when "caIssuers"
- @ca_issuers.add_item(access_description.entries[1])
- end
- end
- end
- end
-
- # Implements the CRLDistributionPoints certificate extension, with methods to
- # provide access to the components and meaning of the extension's contents.
- class CRLDistributionPoints < OpenSSL::X509::Extension
- # friendly name for CDP OID
- OID = "crlDistributionPoints"
- Extensions.register_class(self)
-
- # An array of the CRL URIs, if any
- attr_reader :crl
-
- # See OpenSSL::X509::Extension#initialize
- def initialize(*args)
- super(*args)
-
- @crl= R509::ASN1::GeneralNames.new
- data = R509::ASN1.get_extension_payload(self)
- data.entries.each do |distribution_point|
- # DistributionPoint ::= SEQUENCE {
- # distributionPoint [0] DistributionPointName OPTIONAL,
- # reasons [1] ReasonFlags OPTIONAL,
- # cRLIssuer [2] GeneralNames OPTIONAL }
- # DistributionPointName ::= CHOICE {
- # fullName [0] GeneralNames,
- # nameRelativeToCRLIssuer [1] RelativeDistinguishedName }
- # We're only going to handle DistributionPointName [0] for now
- # so grab entries[0] and then get the fullName with value[0]
- # and the value of that ASN1Data with value[0] again
- @crl.add_item(distribution_point.entries[0].value[0].value[0])
- end
- end
- end
-
- # Implements the OCSP noCheck certificate extension
- class OCSPNoCheck < OpenSSL::X509::Extension
- # friendly name for OCSP No Check
- OID = "noCheck"
- Extensions.register_class(self)
-
- # See OpenSSL::X509::Extension#initialize
- def initialize(*args)
- super(*args)
- end
- end
-
-
- # Implements the CertificatePolicies certificate extension, with methods to
- # provide access to the components and meaning of the extension's contents.
- class CertificatePolicies < OpenSSL::X509::Extension
- # friendly name for CP OID
- OID = "certificatePolicies"
- Extensions.register_class(self)
- attr_reader :policies
-
- def initialize(*args)
- @policies = []
- super(*args)
-
- data = R509::ASN1.get_extension_payload(self)
-
- # each element of this sequence should be part of a policy + qualifiers
- # certificatePolicies ::= SEQUENCE SIZE (1..MAX) OF PolicyInformation
- data.each do |cp|
- @policies << R509::ASN1::PolicyInformation.new(cp)
- end if data.respond_to?(:each)
- end
- end
-
- # Implements the InhibitAnyPolicy certificate extension, with methods to
- # provide access to the component and meaning of the extension's contents.
- class InhibitAnyPolicy < OpenSSL::X509::Extension
- # friendly name for CP OID
- OID = "inhibitAnyPolicy"
- Extensions.register_class(self)
-
- attr_reader :skip_certs
-
- def initialize(*args)
- super(*args)
-
- # id-ce-inhibitAnyPolicy OBJECT IDENTIFIER ::= { id-ce 54 }
- # InhibitAnyPolicy ::= SkipCerts
- # SkipCerts ::= INTEGER (0..MAX)
- @skip_certs = R509::ASN1.get_extension_payload(self) # returns a non-negative integer
- end
- end
-
- # Implements the PolicyConstraints certificate extension, with methods to
- # provide access to the components and meaning of the extension's contents.
- class PolicyConstraints < OpenSSL::X509::Extension
- # friendly name for CP OID
- OID = "policyConstraints"
- Extensions.register_class(self)
-
- attr_reader :require_explicit_policy
- attr_reader :inhibit_policy_mapping
-
- def initialize(*args)
- super(*args)
-
- # id-ce-policyConstraints OBJECT IDENTIFIER ::= { id-ce 36 }
- # PolicyConstraints ::= SEQUENCE {
- # requireExplicitPolicy [0] SkipCerts OPTIONAL,
- # inhibitPolicyMapping [1] SkipCerts OPTIONAL }
- #
- # SkipCerts ::= INTEGER (0..MAX)
- data = R509::ASN1.get_extension_payload(self)
- data.each do |pc|
- if pc.tag == 0
- @require_explicit_policy = pc.value.bytes.to_a[0]
- elsif pc.tag == 1
- @inhibit_policy_mapping = pc.value.bytes.to_a[0]
- end
- end
- end
- end
-
- # Implements the NameConstraints certificate extension, with methods to
- # provide access to the components and meaning of the extension's contents.
- class NameConstraints < OpenSSL::X509::Extension
- # friendly name for CP OID
- OID = "nameConstraints"
- Extensions.register_class(self)
-
- attr_reader :permitted_names, :excluded_names
-
- # id-ce-nameConstraints OBJECT IDENTIFIER ::= { id-ce 30 }
- # NameConstraints ::= SEQUENCE {
- # permittedSubtrees [0] GeneralSubtrees OPTIONAL,
- # excludedSubtrees [1] GeneralSubtrees OPTIONAL }
- #
- # GeneralSubtrees ::= SEQUENCE SIZE (1..MAX) OF GeneralSubtree
- #
- # per RFC 5280
- # Within this profile, the minimum and maximum fields are not used with
- # any name forms, thus, the minimum MUST be zero, and maximum MUST be
- # absent
- # GeneralSubtree ::= SEQUENCE {
- # base GeneralName,
- # minimum [0] BaseDistance DEFAULT 0,
- # maximum [1] BaseDistance OPTIONAL }
- #
- # BaseDistance ::= INTEGER (0..MAX)
- def initialize(*args)
- super(*args)
-
- @permitted_names = []
- @excluded_names = []
-
- data = R509::ASN1.get_extension_payload(self)
- data.each do |gs|
- gs.value.each do |asn_data|
- asn_data.value.each do |obj|
- gn = R509::ASN1::GeneralName.new(obj)
- if gs.tag == 0 # permittedSubtrees
- @permitted_names << gn
- elsif gs.tag == 1 #excludedSubtrees
- @excluded_names << gn
- end
- end
- end
- end
- end
- end
-
-
-
- #
- # Helper class methods
- #
-
- # Takes OpenSSL::X509::Extension objects and wraps each in the appropriate
- # R509::Cert::Extensions object, and returns them in a hash. The hash is
- # keyed with the R509 extension class. Extensions without an R509
- # implementation are ignored (see #get_unknown_extensions).
- def self.wrap_openssl_extensions( extensions )
- r509_extensions = {}
- extensions.each do |openssl_extension|
- R509_EXTENSION_CLASSES.each do |r509_class|
- if ( r509_class::OID.downcase == openssl_extension.oid.downcase )
- if r509_extensions.has_key?(r509_class)
- raise ArgumentError.new("Only one extension object allowed per OID")
- end
-
- r509_extensions[r509_class] = r509_class.new( openssl_extension )
- break
- end
- end
- end
-
- return r509_extensions
- end
-
- # Given a list of OpenSSL::X509::Extension objects, returns those without
- # an R509 implementation.
- def self.get_unknown_extensions( extensions )
- unknown_extensions = []
- extensions.each do |openssl_extension|
- match_found = false
- R509_EXTENSION_CLASSES.each do |r509_class|
- if ( r509_class::OID.downcase == openssl_extension.oid.downcase )
- match_found = true
- break
- end
- end
- # if we make it this far (without breaking), we didn't match
- unknown_extensions << openssl_extension unless match_found
- end
-
- return unknown_extensions
- end
- end
- end
-end
-
+require 'r509/cert/extensions/authority_info_access'
+require 'r509/cert/extensions/authority_key_identifier'
+require 'r509/cert/extensions/basic_constraints'
+require 'r509/cert/extensions/certificate_policies'
+require 'r509/cert/extensions/crl_distribution_points'
+require 'r509/cert/extensions/extended_key_usage'
+require 'r509/cert/extensions/inhibit_any_policy'
+require 'r509/cert/extensions/key_usage'
+require 'r509/cert/extensions/name_constraints'
+require 'r509/cert/extensions/ocsp_no_check'
+require 'r509/cert/extensions/policy_constraints'
+require 'r509/cert/extensions/subject_alternative_name'
+require 'r509/cert/extensions/subject_key_identifier'