Class: SSLTool::Certificate

Inherits:
OpenSSL::X509::Certificate
  • Object
show all
Defined in:
lib/ssltool/certificate.rb

Defined Under Namespace

Modules: Extensions

Constant Summary collapse

RX_DOMAIN_NAME =
/^(\*\.)?([a-zA-Z0-9-]+\.)+[a-zA-Z0-9]+$/
@@certificate_cache =

The certificate_cache stuff ensures we always get the same object every time we instantiate the same certificate. This is regardless of differences in the string used to instantiate the object. The weakrefs ensure we don’t hold on to certs that are not being used outside of the cache.

{}

Class Method Summary collapse

Instance Method Summary collapse

Class Method Details

.new(s) ⇒ Object



16
17
18
19
20
21
# File 'lib/ssltool/certificate.rb', line 16

def self.new(s)
  cert = super(s)
  k = cert.fingerprint
  @@certificate_cache.delete(k) if v = @@certificate_cache[k] and v.respond_to?(:weakref_alive?) && !v.weakref_alive?
  (@@certificate_cache[k] ||= WeakRef.new(cert)).__getobj__
end

.scan(s) ⇒ Object

returns an array of Certificate objects created from cert strings found in s



24
25
26
# File 'lib/ssltool/certificate.rb', line 24

def self.scan(s)
  PEMScanner.certs_from(s).uniq
end

Instance Method Details

#acceptable?Boolean

Returns:

  • (Boolean)


122
123
124
# File 'lib/ssltool/certificate.rb', line 122

def acceptable?
  valid? && (!key_size || key_size_secure?)
end

#authority_key_identifierObject



97
98
99
# File 'lib/ssltool/certificate.rb', line 97

def authority_key_identifier
  map_extension_value('authorityKeyIdentifier') { |s| s.sub(/^keyid:/, '').chomp }
end

#certificate_authority?Boolean

Returns:

  • (Boolean)


85
86
87
# File 'lib/ssltool/certificate.rb', line 85

def certificate_authority?
  map_extension_value('basicConstraints') { |s| s.split(", ").include?('CA:TRUE') }
end

#certificate_sign?Boolean

Returns:

  • (Boolean)


89
90
91
# File 'lib/ssltool/certificate.rb', line 89

def certificate_sign?
  map_extension_value('keyUsage') { |s| s.split(", ").include?('Certificate Sign') }
end

#chain_from(certs) ⇒ Object

chain



147
148
149
150
151
152
# File 'lib/ssltool/certificate.rb', line 147

def chain_from(certs)
  chain  = [self]
  parent = (certs - chain).find { |cert| cert.parent?(self) }
  chain.concat(parent.chain_from(certs - chain)) if parent
  chain
end

#common_namesObject



63
64
65
# File 'lib/ssltool/certificate.rb', line 63

def common_names
  subject.to_a.select { |k, _, _| k == "CN" }.map { |_, v, _| v }
end

#domain_namesObject



75
76
77
78
79
# File 'lib/ssltool/certificate.rb', line 75

def domain_names
  [ domain_names_from_common_names,
    domain_names_from_subject_alt_names,
  ].flatten.compact.sort.uniq
end

#domain_names_from_common_namesObject



67
68
69
# File 'lib/ssltool/certificate.rb', line 67

def domain_names_from_common_names
  common_names.select { |cn| cn =~ RX_DOMAIN_NAME }
end

#domain_names_from_subject_alt_namesObject



71
72
73
# File 'lib/ssltool/certificate.rb', line 71

def domain_names_from_subject_alt_names
  map_extension_value('subjectAltName') { |s| s.scan(/\bDNS:([^\s,]+)/) }
end

#extensionsObject



135
136
137
# File 'lib/ssltool/certificate.rb', line 135

def extensions
  super.tap { |a| class << a; include Extensions; end }
end

#fingerprintObject

properties



59
60
61
# File 'lib/ssltool/certificate.rb', line 59

def fingerprint
  @fingerprint ||= Digest::SHA1.hexdigest(to_der)
end

#for_domain_name?Boolean

Returns:

  • (Boolean)


81
82
83
# File 'lib/ssltool/certificate.rb', line 81

def for_domain_name?
  !domain_names.empty?
end

#key_sizeObject



101
102
103
104
105
106
107
# File 'lib/ssltool/certificate.rb', line 101

def key_size
  case public_key
  when OpenSSL::PKey::RSA ; public_key.n.num_bits
  when OpenSSL::PKey::EC  ; public_key.group.order.num_bits
  else                    ; nil
  end
end

#key_size_secure?Boolean

Returns:

  • (Boolean)


109
110
111
112
113
114
115
# File 'lib/ssltool/certificate.rb', line 109

def key_size_secure?
  case public_key
  when OpenSSL::PKey::RSA ; key_size >= 2048
  when OpenSSL::PKey::EC  ; key_size >= 224
  else                    ; nil
  end
end

#map_extension_value(extension_name, default = nil) ⇒ Object



139
140
141
142
143
# File 'lib/ssltool/certificate.rb', line 139

def map_extension_value(extension_name, default = nil)
  e = extensions[extension_name]
  return default if e.nil?
  block_given? ? yield(e.value) : e.value
end

#parent?(other_cert) ⇒ Boolean

Returns:

  • (Boolean)


51
52
53
54
55
# File 'lib/ssltool/certificate.rb', line 51

def parent?(other_cert)
  signs?(other_cert) \
  && subject == other_cert.issuer \
  && subject_key_identifier == other_cert.authority_key_identifier
end

#self_issued?Boolean

Returns:

  • (Boolean)


47
48
49
# File 'lib/ssltool/certificate.rb', line 47

def self_issued?
  subject.eql?(issuer)
end

#self_signed?Boolean

Returns:

  • (Boolean)


43
44
45
# File 'lib/ssltool/certificate.rb', line 43

def self_signed?
  signs?(self)
end

#signed_by?(other_cert) ⇒ Boolean

signing

Returns:

  • (Boolean)


30
31
32
33
34
35
36
37
# File 'lib/ssltool/certificate.rb', line 30

def signed_by?(other_cert)
  verify(other_cert.public_key)
rescue OpenSSL::X509::CertificateError => e
  OpenSSL.errors  # clear error queue to prevent pg errors
  return false if e.message == "wrong public key type"                                              # self.signature_algorithm is incompatible with type of other_cert.public_key; verify is definitely false
  return nil   if e.message == "unknown message digest algorithm" && signature_algorithm =~ /^md2/  # md2 is not present in later versions of openssl; can't tell signature verifies, so returning nil
  raise e
end

#signs?(other_cert) ⇒ Boolean

Returns:

  • (Boolean)


39
40
41
# File 'lib/ssltool/certificate.rb', line 39

def signs?(other_cert)
  other_cert.signed_by?(self)
end

#subject_key_identifierObject



93
94
95
# File 'lib/ssltool/certificate.rb', line 93

def subject_key_identifier
  map_extension_value('subjectKeyIdentifier') { |s| s.chomp }
end

#valid?Boolean

Returns:

  • (Boolean)


117
118
119
120
# File 'lib/ssltool/certificate.rb', line 117

def valid?
  now = Time.now.utc
  now <= not_after && now >= not_before
end