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)


107
108
109
# File 'lib/ssltool/certificate.rb', line 107

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

#certificate_authority?Boolean

Returns:

  • (Boolean)


78
79
80
# File 'lib/ssltool/certificate.rb', line 78

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

#certificate_sign?Boolean

Returns:

  • (Boolean)


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

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

#chain_from(certs) ⇒ Object

chain



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

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

#common_namesObject



56
57
58
# File 'lib/ssltool/certificate.rb', line 56

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

#domain_namesObject



68
69
70
71
72
# File 'lib/ssltool/certificate.rb', line 68

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

#domain_names_from_common_namesObject



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

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

#domain_names_from_subject_alt_namesObject



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

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

#extensionsObject



120
121
122
# File 'lib/ssltool/certificate.rb', line 120

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

#fingerprintObject

properties



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

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

#for_domain_name?Boolean

Returns:

  • (Boolean)


74
75
76
# File 'lib/ssltool/certificate.rb', line 74

def for_domain_name?
  !domain_names.empty?
end

#key_sizeObject



86
87
88
89
90
91
92
# File 'lib/ssltool/certificate.rb', line 86

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)


94
95
96
97
98
99
100
# File 'lib/ssltool/certificate.rb', line 94

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) {|e.value| ... } ⇒ Object

Yields:

  • (e.value)


124
125
126
127
128
# File 'lib/ssltool/certificate.rb', line 124

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

#self_issued?Boolean

Returns:

  • (Boolean)


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

def self_issued?
  subject.eql?(issuer)
end

#self_signed?Boolean

Returns:

  • (Boolean)


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

def self_signed?
  signs?(self)
end

#signed_by?(other_cert) ⇒ Boolean

signing

Returns:

  • (Boolean)


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

def signed_by?(other_cert)
  verify(other_cert.public_key)
rescue OpenSSL::X509::CertificateError => e
  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)


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

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

#valid?Boolean

Returns:

  • (Boolean)


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

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