Skip to content

Instantly share code, notes, and snippets.

@justinstoller
Created April 17, 2018 15:45
Show Gist options
  • Save justinstoller/ca76cb1168e2933db236f0f5aaf8948d to your computer and use it in GitHub Desktop.
Save justinstoller/ca76cb1168e2933db236f0f5aaf8948d to your computer and use it in GitHub Desktop.
require 'openssl'
PRIVATE_KEY_LENGTH = 2048
NOT_BEFORE = Time.now
NOT_AFTER = Time.now + (5 * 365 * 24 * 60 * 60)
CA_EXTENSIONS = [
["basicConstraints", "CA:TRUE", true],
["keyUsage", "keyCertSign, cRLSign", true],
["subjectKeyIdentifier", "hash", false],
["authorityKeyIdentifier", "keyid:always", false]
]
NODE_EXTENSIONS = [
["keyUsage", "digitalSignature", true],
["subjectKeyIdentifier", "hash", false]
]
DEFAULT_OPTIONS = {
not_before: NOT_BEFORE,
not_after: NOT_AFTER
}
DEFAULT_SIGNING_DIGEST = OpenSSL::Digest::SHA256.new
DEFAULT_REVOCATION_REASON = OpenSSL::OCSP::REVOKED_STATUS_KEYCOMPROMISE
def create_private_key(length = PRIVATE_KEY_LENGTH)
OpenSSL::PKey::RSA.new(length)
end
def self_signed_ca(key, name = "CN=puppet-#{rand(100000)}")
cert = OpenSSL::X509::Certificate.new
cert.public_key = key.public_key
cert.subject = OpenSSL::X509::Name.parse(name)
cert.issuer = cert.subject
cert.version = 2
cert.serial = rand(2**128)
cert.not_before = NOT_BEFORE
cert.not_after = NOT_AFTER
ext_factory = extension_factory_for(cert, cert)
CA_EXTENSIONS.each do |ext|
extension = ext_factory.create_extension(*ext)
cert.add_extension(extension)
end
cert.sign(key, DEFAULT_SIGNING_DIGEST)
cert
end
# A request is from a specific node to a specific ca
def create_csr(key,
name = "CN=puppet-#{rand(100000)}",
digest = DEFAULT_SIGNING_DIGEST)
csr = OpenSSL::X509::Request.new
csr.public_key = key.public_key
csr.subject = OpenSSL::X509::Name.parse(name)
csr.version = 2
csr.sign(key, digest)
csr
end
def sign(ca_key,
ca_cert,
csr,
extensions = NODE_EXTENSIONS,
options = DEFAULT_OPTIONS,
digest = DEFAULT_SIGNING_DIGEST)
cert = OpenSSL::X509::Certificate.new
cert.public_key = csr.public_key
cert.subject = csr.subject
cert.issuer = ca_cert.subject
cert.version = 2
cert.serial = rand(2**128)
cert.not_before = NOT_BEFORE
cert.not_after = NOT_AFTER
ext_factory = extension_factory_for(ca_cert, cert)
extensions.each do |ext|
extension = ext_factory.create_extension(*ext)
cert.add_extension(extension)
end
cert.sign(ca_key, DEFAULT_SIGNING_DIGEST)
cert
end
def create_crl_for(ca_cert, ca_key)
ef = OpenSSL::X509::ExtensionFactory.new(ca_cert)
crl = OpenSSL::X509::CRL.new
crl.issuer = ca_cert.subject
crl.add_extension(ef.create_ext("authorityKeyIdentifier", "keyid:always"))
crl.add_extension(OpenSSL::X509::Extension.new("crlNumber", OpenSSL::ASN1::Integer(0)))
crl.version = 2
crl.last_update = NOT_BEFORE
crl.next_update = NOT_AFTER
crl.sign(ca_key, DEFAULT_SIGNING_DIGEST)
end
def revoke(serial, crl, ca_key, reason = DEFAULT_REVOCATION_REASON)
revoked = OpenSSL::X509::Revoked.new
revoked.serial = serial
revoked.time = Time.now
ext = OpenSSL::X509::Extension.new("CRLReason",
OpenSSL::ASN1::Enumerated(reason))
revoked.add_extension(ext)
crl.add_revoked(revoked)
extensions = crl.extensions.group_by{|e| e.oid == 'crlNumber' }
crl_number = extensions[true].first
unchanged_exts = extensions[false]
next_crl_number = crl_number.value.to_i + 1
new_crl_number_ext = OpenSSL::X509::Extension.new("crlNumber",
OpenSSL::ASN1::Integer(next_crl_number))
crl.extensions = unchanged_exts + [new_crl_number_ext]
crl.sign(ca_key, DEFAULT_SIGNING_DIGEST)
end
def create_store(certs, crls)
store = OpenSSL::X509::Store.new
store.purpose = OpenSSL::X509::PURPOSE_ANY
store.flags = OpenSSL::X509::V_FLAG_CRL_CHECK | OpenSSL::X509::V_FLAG_CRL_CHECK_ALL
certs.each {|cert| store.add_cert cert }
crls.each {|crl| store.add_crl crl }
store
end
def extension_factory_for(ca, cert)
ef = OpenSSL::X509::ExtensionFactory.new
ef.issuer_certificate = ca
ef.subject_certificate = cert
ef
end
@root_key = create_private_key
@root_cert = self_signed_ca(@root_key, "/CN=root-ca")
@root_crl = create_crl_for(@root_cert, @root_key)
@valid_root_node_key = create_private_key
valid_root_node_csr = create_csr(@valid_root_node_key, "/CN=valid-root-node")
@valid_root_node_cert = sign(@root_key, @root_cert, valid_root_node_csr)
@invalid_root_node_key = create_private_key
invalid_root_node_csr = create_csr(@invalid_root_node_key, "/CN=invalid-root-node")
@invalid_root_node_cert = sign(@root_key, @root_cert, invalid_root_node_csr)
revoke(@invalid_root_node_cert.serial, @root_crl, @root_key)
@intermediate_key = create_private_key
intermediate_csr = create_csr(@intermediate_key, "/CN=int-ca")
@intermediate_cert = sign(@root_key, @root_cert, intermediate_csr, CA_EXTENSIONS)
@intermediate_crl = create_crl_for(@intermediate_cert, @intermediate_key)
@valid_int_node_key = create_private_key
valid_int_node_csr = create_csr(@valid_int_node_key, "/CN=valid-int-node")
@valid_int_node_cert = sign(@intermediate_key, @intermediate_cert, valid_int_node_csr)
@invalid_int_node_key = create_private_key
invalid_int_node_csr = create_csr(@invalid_int_node_key, "/CN=invalid-int-node")
@invalid_int_node_cert = sign(@intermediate_key, @intermediate_cert, invalid_int_node_csr)
revoke(@invalid_int_node_cert.serial, @intermediate_crl, @intermediate_key)
@leaf_key = create_private_key
leaf_csr = create_csr(@leaf_key, "/CN=leaf-ca")
@leaf_cert = sign(@intermediate_key, @intermediate_cert, leaf_csr, CA_EXTENSIONS)
@leaf_crl = create_crl_for(@leaf_cert, @leaf_key)
@valid_leaf_node_key = create_private_key
valid_leaf_node_csr = create_csr(@valid_leaf_node_key, "/CN=valid-leaf-node")
@valid_leaf_node_cert = sign(@leaf_key, @leaf_cert, valid_leaf_node_csr)
@invalid_leaf_node_key = create_private_key
invalid_leaf_node_csr = create_csr(@invalid_leaf_node_key, "/CN=invalid-leaf-node")
@invalid_leaf_node_cert = sign(@leaf_key, @leaf_cert, invalid_leaf_node_csr)
revoke(@invalid_leaf_node_cert.serial, @leaf_crl, @leaf_key)
@store = create_store([@root_cert, @intermediate_cert, @leaf_cert],
[@root_crl, @intermediate_crl, @leaf_crl])
fail "invalid leaf node cert verified" unless @store.verify(@invalid_leaf_node_cert) == false
fail "valid leaf node cert unverified" unless @store.verify(@valid_leaf_node_cert) == true
fail "invalid int node cert verified" unless @store.verify(@invalid_int_node_cert) == false
fail "valid int node cert unverified" unless @store.verify(@valid_int_node_cert) == true
fail "invalid root node cert verified" unless @store.verify(@invalid_root_node_cert) == false
fail "valid root node cert unverified" unless @store.verify(@valid_root_node_cert) == true
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment