Class: CertificateDepot::Certificate

Inherits:
Object
  • Object
show all
Defined in:
lib/certificate_depot/certificate.rb

Overview

Represents an OpenSSL certificate. TLS/SSL certificates are a rather complicated mix of several standards. If you're not familiar with TLS certificates, it might help to start by reading the Wikipedia article on the subject: en.wikipedia.org/wiki/Public_key_certificate.

certificate = CertificateDepot::Certificate.from_file('server.pem')
certificate.issuer

Constant Summary collapse

DEFAULT_VALIDITY_PERIOD =

Our generated certificates are valid for 10 years by default.

3600 * 24 * 365 * 10
X509v3 =

We create version 3 certificates. Count starts a 0 so 2 is actually 3.

2
ATTRIBUTE_MAP =

Used to map programmer readable attributes to X509 subject attributes.

{
  :common_name              => 'CN',
  :locality_name            => 'L',
  :state_or_province_name   => 'ST',
  :organization             => 'O',
  :organizational_unit_name => 'OU',
  :country_name             => 'C',
  :street_address           => 'STREET',
  :domain_component         => 'DC',
  :user_id                  => 'UID',
  :email_address            => 'emailAddress'
}

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(certificate = nil) ⇒ Certificate

Creates a new certificate instance. The certificate argument should be a OpenSSL::X509::Certificate instance.


33
34
35
# File 'lib/certificate_depot/certificate.rb', line 33

def initialize(certificate=nil)
  @certificate = certificate
end

Dynamic Method Handling

This class handles dynamic methods through the method_missing method

#method_missing(method, *attributes, &block) ⇒ Object

Used to support easy querying of subject attributes. See ATTRIBUTE_MAP for a complete list of supported attributes.

certificate.common_name #=> '*.example.com'

208
209
210
211
212
213
214
# File 'lib/certificate_depot/certificate.rb', line 208

def method_missing(method, *attributes, &block)
  if x509_attribute = ATTRIBUTE_MAP[method.to_sym]
    self[x509_attribute]
  else
    super
  end
end

Instance Attribute Details

#certificateObject

Returns the value of attribute certificate


29
30
31
# File 'lib/certificate_depot/certificate.rb', line 29

def certificate
  @certificate
end

Class Method Details

.from_file(path) ⇒ Object

Instantiates a new instance using certificate data read from file.


235
236
237
# File 'lib/certificate_depot/certificate.rb', line 235

def self.from_file(path)
  new(OpenSSL::X509::Certificate.new(File.read(path)))
end

.generate(attributes = {}) ⇒ Object

Shortcut for CertificateDepot::Certificate#generate. See this method for more details.


228
229
230
231
232
# File 'lib/certificate_depot/certificate.rb', line 228

def self.generate(attributes={})
  certificate = new
  certificate.generate(attributes)
  certificate
end

Instance Method Details

#[](key) ⇒ Object

Used to support easy querying of subject attributes. See ATTRIBUTE_MAP for a complete list of supported attributes.

certificate['common_name'] #=> '*.example.com'

220
221
222
223
224
# File 'lib/certificate_depot/certificate.rb', line 220

def [](key)
  @certificate.subject.to_a.each do |name, value, type|
    return value if name == key
  end; nil
end

#generate(attributes = {}) ⇒ Object

Generates a new certificate. Possible and compulsory attributes depend on the type of certificate.

Client certificate

Client certificates are used to authenticate a client to a server. They are generated by setting the :type attribute to :client.

Compulsory

  • :ca_certificate - A CertificateDepot::Certificate instance representing the Certification Authority.

  • :serial_number - The serial number to store in the certificate. This number should be unique for certificates issued by the CA.

  • :public_key - A public key which is the sister key of the private key used by the client.

Options

You can choose to either supply an instance of OpenSSL::X509::Name as the :subject attribute or supply any of the attributes listed in ATTRIBUTE_MAP to generate the subject name. For example:

generate(:common_name => 'John Doe', :email_address => '[email protected]')

Server certificate

Server certificates are used to authenticate a server to a client and set up a secure socket. They are generated by setting the :type attribute to :server.

Compulsory

  • :ca_certificate - A CertificateDepot::Certificate instance representing the Certification Authority.

  • :serial_number - The serial number to store in the certificate. This number should be unique for certificates issued by the CA.

  • :public_key - A public key which is the sister key of the private key used by the client.

  • :common_name - The common name has to match the hostname used for the server. It has to be either a complete match or a wildcard match. So *.example.com will match www.example.com and mail.example.com but example.com will only match example.com.

Options

If you want to supply an instance of OpenSSL::X509::Name as the :subject of the certificate, please make sure you set the CN attribute to the correct value of the certificate will be worthless.

You can choose to set any of the other X509 attributes as found in the ATTRIBUTE_MAP, but none of the are strictly necessary.

Certification Authority certificate

CA certificates are used to sign all certificates issued by the CA. They are generated by setting the :type to :ca.

Options

You can choose to either supply an instance of OpenSSL::X509::Name as the :subject attribute or supply any of the attributes listed in ATTRIBUTE_MAP to generate the subject name. For example:

subject = OpenSSL::X509::Name.new
subject.add_entry('CN', 'Certificate Depot CA')
generate(:subject => subject)

generate(:common_name => 'Certificate Depot CA')

Note that this name will be used for both the subject and issuer field in the certificate because it's a so-called sign-signed certificate.

Raises:

  • (ArgumentError)

114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
# File 'lib/certificate_depot/certificate.rb', line 114

def generate(attributes={})
  from         = Time.now
  to           = Time.now + DEFAULT_VALIDITY_PERIOD
  
  name         =  attributes[:subject] || OpenSSL::X509::Name.new
  ATTRIBUTE_MAP.each do |internal, x509_attribute|
    name.add_entry(x509_attribute, attributes[internal]) if attributes[internal]
  end
  
  case attributes[:type]
  when :client, :server
    issuer = attributes[:ca_certificate].subject
    serial = attributes[:serial_number]
  when :ca
    issuer = name
    serial = 0
  else
    raise ArgumentError, "Unknown certificate type #{attributes[:type]}, please specify either :client, :server, or :ca"
  end
  
  raise ArgumentError, "Please supply a serial number for the certificate to generate" unless serial
  
  @certificate = OpenSSL::X509::Certificate.new
  @certificate.subject    = name
  @certificate.issuer     = issuer
  @certificate.not_before = from
  @certificate.not_after  = to
  @certificate.version    = X509v3
  @certificate.public_key = attributes[:public_key]
  @certificate.serial     = serial
  
  extensions = []
  factory = OpenSSL::X509::ExtensionFactory.new
  factory.subject_certificate = @certificate
  
  case attributes[:type]
  when :server
    factory.issuer_certificate = attributes[:ca_certificate].certificate
    extensions << factory.create_extension('basicConstraints', 'CA:FALSE', true)
    extensions << factory.create_extension('keyUsage', 'digitalSignature,keyEncipherment')
    extensions << factory.create_extension('extendedKeyUsage', 'serverAuth,clientAuth,emailProtection')
  when :client
    factory.issuer_certificate = attributes[:ca_certificate].certificate
    extensions << factory.create_extension('basicConstraints', 'CA:FALSE', true)
    extensions << factory.create_extension('keyUsage', 'nonRepudiation,digitalSignature,keyEncipherment')
    extensions << factory.create_extension('extendedKeyUsage', 'clientAuth')
  when :ca
    factory.issuer_certificate = @certificate
    extensions << factory.create_extension('basicConstraints', 'CA:TRUE', true)
    extensions << factory.create_extension('keyUsage', 'cRLSign,keyCertSign')
  end
  extensions << factory.create_extension('subjectKeyIdentifier', 'hash')
  extensions << factory.create_extension('authorityKeyIdentifier', 'keyid,issuer:always')
  
  @certificate.extensions = extensions
  
  if attributes[:private_key]
    @certificate.sign(attributes[:private_key], OpenSSL::Digest::SHA1.new)
  end
  
  @certificate
end

#issuerObject

Returns the issuer for the certificate.


190
191
192
# File 'lib/certificate_depot/certificate.rb', line 190

def issuer
  @certificate.issuer
end

#public_keyObject

Returns the public key in the certificate.


185
186
187
# File 'lib/certificate_depot/certificate.rb', line 185

def public_key
  @certificate.public_key
end

#serial_numberObject

Returns the serial number of the certificate.


200
201
202
# File 'lib/certificate_depot/certificate.rb', line 200

def serial_number
  @certificate.serial
end

#subjectObject

Returns the subject of the certificate.


195
196
197
# File 'lib/certificate_depot/certificate.rb', line 195

def subject
  @certificate.subject
end

#write_to(path) ⇒ Object

Writes the certificate to file. The path should be a filename pointing to an existing directory. Note that this will overwrite files without asking.


180
181
182
# File 'lib/certificate_depot/certificate.rb', line 180

def write_to(path)
  File.open(path, 'w') { |file| file.write(@certificate.to_pem) }
end