Class: Ronin::Support::Crypto::Cert

Inherits:
OpenSSL::X509::Certificate
  • Object
show all
Defined in:
lib/ronin/support/crypto/cert.rb

Overview

Represents a X509 or TLS certificate.

Since:

  • 1.0.0

Defined Under Namespace

Classes: Name

Constant Summary collapse

ONE_YEAR =

One year in seconds

Since:

  • 1.0.0

60 * 60 * 24 * 365

Class Method Summary collapse

Instance Method Summary collapse

Class Method Details

.generate(version: 2, serial: 0, not_before: Time.now, not_after: not_before + ONE_YEAR, subject: nil, extensions: nil, key:, ca_cert: nil, ca_key: nil, signing_hash: :sha256) ⇒ Cert

Generates and signs a new certificate.

Examples:

Generate a self-signed certificate for localhost:

key  = Ronin::Support::Crypto::Key::RSA.random
cert = Ronin::Support::Crypto::Cert.generate(
  key: key,
  subject: {
    common_name:         'localhost',
    organization:        'Test Co..',
    organizational_unit: 'Test Dept',
    locality:            'Test City',
    state:               'XX',
    country:             'US'
  },
  extensions: {
    'subjectAltName' => 'DNS: localhost, IP: 127.0.0.1'
  }
)
key.save('cert.key')
cert.save('cert.pem')

Generate a CA certificate:

ca_key  = Ronin::Support::Crypto::Key::RSA.random
ca_cert = Ronin::Support::Crypto::Cert.generate(
  key: ca_key,
  subject: {
    common_name:         'Test CA',
    organization:        'Test CA, Inc..',
    organizational_unit: 'Test Dept',
    locality:            'Test City',
    state:               'XX',
    country:             'US'
  },
  extensions: {
    'basicConstraints' => ['CA:TRUE', true]
  }
)
key.save('ca.key')
cert.save('ca.pem')

Generate a sub-certificate from a CA certificate:

key  = Ronin::Support::Crypto::Key::RSA.random
cert = Ronin::Support::Crypto::Cert.generate(
  key:     key,
  ca_key:  ca_key,
  ca_cert: ca_cert,
  subject: {
    common_name:         'test.com',
    organization:        'Test Co..',
    organizational_unit: 'Test Dept',
    locality:            'Test City',
    state:               'XX',
    country:             'US'
  },
  extensions: {
    'subjectAltName'   => 'DNS: *.test.com',
    'basicConstraints' => ['CA:FALSE', true]
  }
)
key.save('cert.key')
cert.save('cert.pem')

Parameters:

  • version (Integer) (defaults to: 2)

    The version of the encoded certificate. See RFC 5280.

  • serial (Integer) (defaults to: 0)

    The certificate serial number.

  • subject (String, Hash{Symbol => String,nil}, Name, nil) (defaults to: nil)

    The subject field for the certificate. If a Hash is given it will be passed to Ronin::Support::Crypto::Cert::Name.build.

  • not_before (Time) (defaults to: Time.now)

    Beginning time when the certificate is valid.

  • not_after (Time) (defaults to: not_before + ONE_YEAR)

    When the certificate expires and is no longer valid.

  • extensions (Hash{String => Object}) (defaults to: nil)

    Additional extensions to add to the new certificate.

  • key (Key::RSA)

    The public/private key pair used with the certificate.

  • ca_key (Key::RSA, nil) (defaults to: nil)

    The optional Certificate Authority (CA) key to use to sign the new certificate.

  • ca_cert (Cert, nil) (defaults to: nil)

    The optional Certificate Authority (CA) certificate to attach to the new certificate.

  • signing_hash (Symbol) (defaults to: :sha256)

    The hashing algorithm to use to sign the new certificate.

Returns:

  • (Cert)

    The newly generated and signed certificate.

Since:

  • 1.0.0



326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
# File 'lib/ronin/support/crypto/cert.rb', line 326

def self.generate(version:    2,
                  serial:     0,
                  not_before: Time.now,
                  not_after:  not_before + ONE_YEAR,
                  subject:    nil,
                  extensions: nil,
                  # signing arguments
                  key: ,
                  ca_cert: nil,
                  ca_key:  nil,
                  signing_hash: :sha256)
  cert = new

  cert.version = version
  cert.serial  = if ca_cert then ca_cert.serial + 1
                 else            serial
                 end

  cert.not_before = not_before
  cert.not_after  = not_after
  cert.public_key = key.public_key
  cert.subject    = Name(subject) if subject
  cert.issuer     = if ca_cert then ca_cert.subject
                    else            cert.subject
                    end

  if extensions
    extension_factory = OpenSSL::X509::ExtensionFactory.new

    extension_factory.subject_certificate = cert
    extension_factory.issuer_certificate  = ca_cert || cert

    extensions.each do |name,(value,critical)|
      ext = extension_factory.create_extension(name,value,critical)
      cert.add_extension(ext)
    end
  end

  signing_key    = ca_key || key
  signing_digest = OpenSSL::Digest.const_get(signing_hash.upcase).new

  cert.sign(signing_key,signing_digest)
  return cert
end

.load(buffer) ⇒ Cert

Parses the PEM encoded certificate.

Parameters:

  • buffer (String)

    The String containing the certificate.

Returns:

  • (Cert)

    The parsed certificate.

Since:

  • 1.0.0



206
207
208
# File 'lib/ronin/support/crypto/cert.rb', line 206

def self.load(buffer)
  new(buffer)
end

.load_file(path) ⇒ Cert

Loads the certificate from the file.

Parameters:

  • path (String)

    The path to the file.

Returns:

  • (Cert)

    The loaded certificate.

Since:

  • 1.0.0



219
220
221
# File 'lib/ronin/support/crypto/cert.rb', line 219

def self.load_file(path)
  new(File.read(path))
end

.Name(name) ⇒ Cert::Name

Coerces a value into a Name object.

Parameters:

  • name (String, Hash, OpenSSL::X509::Name)

    The name value to coerce.

Returns:

Since:

  • 1.0.0



171
172
173
174
175
176
177
178
179
180
181
182
# File 'lib/ronin/support/crypto/cert.rb', line 171

def self.Name(name)
  case name
  when String              then Name.parse(name)
  when Hash                then Name.build(**name)
  when OpenSSL::X509::Name
    new_name = Name.allocate
    new_name.send(:initialize_copy,name)
    new_name
  else
    raise(ArgumentError,"value must be either a String, Hash, or a OpenSSL::X509::Name object: #{name.inspect}")
  end
end

.parse(string) ⇒ Cert

Parses the PEM encoded certificate string.

Parameters:

  • string (String)

    The certificate string.

Returns:

  • (Cert)

    The parsed certificate.

Since:

  • 1.0.0



193
194
195
# File 'lib/ronin/support/crypto/cert.rb', line 193

def self.parse(string)
  new(string)
end

Instance Method Details

#common_nameString?

The subjects common name (CN) entry.

Returns:

Since:

  • 1.0.0



398
399
400
401
402
# File 'lib/ronin/support/crypto/cert.rb', line 398

def common_name
  if (subject = self.subject)
    subject.common_name
  end
end

#extension_namesArray<String>

The extension OID names.

Returns:

Since:

  • 1.0.0



409
410
411
# File 'lib/ronin/support/crypto/cert.rb', line 409

def extension_names
  extensions.map(&:oid)
end

#extension_value(oid) ⇒ String?

Gets the value for the extension with the matching OID.

Parameters:

  • oid (String)

    The OID to search for.

Returns:

  • (String, nil)

    The value of the matching extension.

Since:

  • 1.0.0



432
433
434
435
436
# File 'lib/ronin/support/crypto/cert.rb', line 432

def extension_value(oid)
  if (ext = find_extension(oid))
    ext.value
  end
end

#extensions_hashHash{String => OpenSSL::X509::Extension}

Converts the certificate's extensions into a Hash.

Returns:

  • (Hash{String => OpenSSL::X509::Extension})

    The Hash of extension OID names and extension objects.

Since:

  • 1.0.0



419
420
421
# File 'lib/ronin/support/crypto/cert.rb', line 419

def extensions_hash
  extensions.to_h { |ext| [ext.oid, ext] }
end

#issuerName?

The issuer of the certificate.

Returns:

Since:

  • 1.0.0



376
377
378
379
380
# File 'lib/ronin/support/crypto/cert.rb', line 376

def issuer
  @issuer ||= if (issuer = super)
                Cert::Name(issuer)
              end
end

#save(path, encoding: :pem) ⇒ Object

Saves the certificate to the given path.

Parameters:

  • path (String)

    The path to write the exported certificate to.

  • encoding (:pem, :der) (defaults to: :pem)

    The desired encoding of the exported key.

    • :pem - PEM encoding.
    • :der - DER encoding.

Raises:

  • (ArgumentError)

    The endcoding: value must be either :pem or :der.

Since:

  • 1.0.0



478
479
480
481
482
483
484
485
486
487
# File 'lib/ronin/support/crypto/cert.rb', line 478

def save(path, encoding: :pem)
  exported = case encoding
             when :pem then to_pem
             when :der then to_der
             else
               raise(ArgumentError,"encoding: keyword argument (#{encoding.inspect}) must be either :pem or :der")
             end

  File.write(path,exported)
end

#subjectName?

The subject of the certificate.

Returns:

Since:

  • 1.0.0



387
388
389
390
391
# File 'lib/ronin/support/crypto/cert.rb', line 387

def subject
  @subject ||= if (subject = super)
                 Cert::Name(subject)
               end
end

#subject_alt_nameString?

Retrieves the subjectAltName extension and parses it's contents.

Returns:

  • (String, nil)

    The subjectAltName value or nil if the certificate does not have the extension.

Since:

  • 1.0.0



445
446
447
# File 'lib/ronin/support/crypto/cert.rb', line 445

def subject_alt_name
  extension_value('subjectAltName')
end

#subject_alt_namesArray<String>?

Retrieves the subjectAltName extension and parses it's value.

Returns:

  • (Array<String>, nil)

    The parsed subjectAltName or nil if the certificate does not have the extension.

Since:

  • 1.0.0



456
457
458
459
460
461
462
# File 'lib/ronin/support/crypto/cert.rb', line 456

def subject_alt_names
  if (value = subject_alt_name)
    value.split(', ').map do |name|
      name.split(':',2).last
    end
  end
end