Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve ability to create PKI tier by building off of each step #57

Open
reedloden opened this issue Aug 9, 2013 · 4 comments
Open

Comments

@reedloden
Copy link

It's pretty painful right now to build a full PKI tiered system manually due to problems such as issue #55. I just put together a quick demo showing what I mean. I have a few thoughts to improve this (besides the aforementioned issue), but I'll post them as separate comments.

The following code creates a PKI system looking like this:

  • Foo CA
    • Bar CA
      • Baz Intermediate CA - 2013
        • blah.example.com (not shown)
require 'r509'
require 'active_support/all'

foo_ca_csr = R509::CSR.new(
  :subject => [
     ['C','US'],
     ['ST','Hawaii'],
     ['L','Pie in the Sky'],
     ['O','Moocow, LLC'],
     ['CN','Foo CA']
  ],
  :type => "EC",
  :curve_name => "secp521r1"
)

bar_ca_csr = R509::CSR.new(
  :subject => [
     ['C','US'],
     ['ST','Hawaii'],
     ['L','Pie in the Sky'],
     ['O','Moocow, LLC'],
     ['CN','Bar CA']
  ],
  :type => "EC",
  :curve_name => "secp521r1"
)

baz_2013int_csr = R509::CSR.new(
  :subject => [
     ['C','US'],
     ['ST','Hawaii'],
     ['L','Pie in the Sky'],
     ['O','Moocow, LLC'],
     ['CN','Baz Intermediate CA - 2013']
  ],
  :type => "EC",
  :curve_name => "secp521r1"
)

not_before = Time.now.change(:min => 0).utc.to_i
not_after_20 = Time.now.change(:min => 0).utc.to_i+3600*24*7305
not_after_10 = Time.now.change(:min => 0).utc.to_i+3600*24*3652
not_after_4 = Time.now.change(:min => 0).utc.to_i+3600*24*1461

ext_foo_ca = []
ext_foo_ca << R509::Cert::Extensions::BasicConstraints.new(:ca => true)
ext_foo_ca << R509::Cert::Extensions::SubjectKeyIdentifier.new(:public_key => foo_ca_csr.public_key)
ext_foo_ca << R509::Cert::Extensions::AuthorityKeyIdentifier.new(:public_key => foo_ca_csr.public_key)
ext_foo_ca << R509::Cert::Extensions::KeyUsage.new(
  :key_usage => ["cRLSign", "keyCertSign", "digitalSignature", "keyEncipherment", "keyAgreement"],
  :critical => true
)
foo_ca_cert = R509::CertificateAuthority::Signer.selfsign(
  :csr => foo_ca_csr,
  :not_before => not_before,
  :not_after => not_after_20,
  :extensions => ext_foo_ca,
  :message_digest => "SHA512"
)
File.open('foo_ca.key', 'w') {|f| f.write(foo_ca_csr.key.to_pem) }
File.open('foo_ca.crt', 'w') {|f| f.write(foo_ca_cert.to_pem) }

ext_bar_ca = []
ext_bar_ca << R509::Cert::Extensions::BasicConstraints.new(:ca => true)
ext_bar_ca << R509::Cert::Extensions::SubjectKeyIdentifier.new(:public_key => bar_ca_csr.public_key)
ext_bar_ca << R509::Cert::Extensions::AuthorityKeyIdentifier.new(:public_key => foo_ca_csr.public_key)
ext_bar_ca << R509::Cert::Extensions::KeyUsage.new(
  :key_usage => ["cRLSign", "keyCertSign", "digitalSignature"],
  :critical => true
)
foo_ca = R509::Cert.new(
  :cert => foo_ca_cert.to_pem,
  :key => foo_ca_csr.key.to_pem
)
bar_ca_config = R509::Config::CAConfig.new(
  :ca_cert => foo_ca
)
bar_ca = R509::CertificateAuthority::Signer.new(bar_ca_config)
bar_ca_cert = bar_ca.sign(
  :csr => bar_ca_csr,
  :not_before => not_before,
  :not_after => not_after_10,
  :extensions => ext_bar_ca,
  :message_digest => "SHA512"
)
File.open('bar_ca.key', 'w') {|f| f.write(bar_ca_csr.key.to_pem) }
File.open('bar_ca.crt', 'w') {|f| f.write(bar_ca_cert.to_pem) }

ext_baz_int = []
ext_baz_int << R509::Cert::Extensions::BasicConstraints.new(:ca => true, :path_length => 1)
ext_baz_int << R509::Cert::Extensions::SubjectKeyIdentifier.new(:public_key => baz_2013int_csr.public_key)
ext_baz_int << R509::Cert::Extensions::AuthorityKeyIdentifier.new(:public_key => bar_ca_csr.public_key)
ext_baz_int << R509::Cert::Extensions::KeyUsage.new(
  :key_usage => ["cRLSign", "keyCertSign"],
  :critical => true
)
bar_ca = R509::Cert.new(
  :cert => bar_ca_cert.to_pem,
  :key => bar_ca_csr.key.to_pem
)
baz_2013int_config = R509::Config::CAConfig.new(
  :ca_cert => bar_ca
)
baz_2013int = R509::CertificateAuthority::Signer.new(baz_2013int_config)
baz_2013int_cert = baz_2013int.sign(
  :csr => baz_2013int_csr,
  :not_before => not_before,
  :not_after => not_after_4,
  :extensions => ext_baz_int,
  :message_digest => "SHA512"
)
File.open('baz_2013int.key', 'w') {|f| f.write(baz_2013int_csr.key.to_pem) }
File.open('baz_2013int.crt', 'w') {|f| f.write(baz_2013int_cert.to_pem) }
@reedloden
Copy link
Author

Note I didn't include any OCSP or CRL type stuff in the above PKI... Just trying to keep it simple for the demo.

@reedloden
Copy link
Author

This part is probably the most frustrating, and I feel it could be dramatically simplified:

foo_ca_cert = R509::CertificateAuthority::Signer.selfsign(
  :csr => foo_ca_csr,
  :not_before => not_before,
  :not_after => not_after_20,
  :extensions => ext_foo_ca,
  :message_digest => "SHA512"
)
foo_ca = R509::Cert.new(
  :cert => foo_ca_cert.to_pem,
  :key => foo_ca_csr.key.to_pem
)
bar_ca_config = R509::Config::CAConfig.new(
  :ca_cert => foo_ca
)
bar_ca = R509::CertificateAuthority::Signer.new(bar_ca_config)
bar_ca_cert = bar_ca.sign(
  :csr => bar_ca_csr,
  :not_before => not_before,
  :not_after => not_after_10,
  :extensions => ext_bar_ca,
  :message_digest => "SHA512"
)

Basically, I want to take a CSR, sign it (whether self-signed or by another certificate), and then use that object to take a separate CSR and sign it, repeating ad infinitum. Right now, I have to manually create R509::Cert and R509::Config::CAConfig objects before I can do that, which adds unneeded complexity.

@reedloden
Copy link
Author

Issue #50 causes a lot of useless code for generation of extensions that already exist by default, but yet I have to do manually just to set a critical flag on keyUsage.

@reaperhulk
Copy link
Member

The current trunk definitely requires you to create a new CAConfig and Signer object and since you need the paired cert + key I see why you have to create a new Cert object too. This is far outside use cases I've previously considered so maybe we can work through a sane system together. I can think of two potential approaches...

New Method on Cert

Add a new method on R509::Cert instances (call it build_signer) that takes a hash of options. When invoked it reads the options to obtain the key (if needed and not present in the current Cert object), a basic set of CAConfig options, and then it creates a CAConfig and passes it to a new Signer instance.

New Class

Build a new class (let's call it SignerBuilder) that takes a cert + key and builds a simplistic CAConfig (maybe based on a provided template?) and then instantiates a new Signer object.

My inclination if either of these sound like they'd be useful to you (and hopefully to others in the future!) would be to add the new method to Cert. It feels more Ruby-ish than a factory class like SignerBuilder.

BTW, on #50 I haven't refactored that yet because of the colossal backwards compatibility consequences. I'm still thinking about it.

reaperhulk added a commit that referenced this issue Sep 2, 2013
* This change improves the situation for #57
* Tests to validate the key is being returned (or not) based on the
  three types of objects that can be sent to the signer
** CSR (with key)
** CSR (no key)
** SPKI (can't contain key)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants