r509 is a Ruby gem built using OpenSSL that is designed to ease management of a public key infrastructure. The r509 API facilitates easy creation of CSRs, signing of certificates, revocation (CRL/OCSP), and much more. Together with projects like r509-ocsp-responder and r509-ca-http it is intended to be a complete RFC 5280-compliant certificate authority for use in production environments.
Certificates are hard, and the Ruby OpenSSL APIs aren't easy to use (because they hew closely to OpenSSL itself). Additionally, as SSL/TLS has aged a variety of best practices and workarounds around certificate issuance have grown up around it that are not easy to discover. r509 is an attempt to build a straightforward API that allows you to do things as simple as parsing a certificate all the way up to operating an entire certificate authority.
r509 requires Ruby 2.0.0+ compiled with OpenSSL and YAML support (this is a typical default). It is recommended that you compile Ruby against OpenSSL 1.0.1+ (with elliptic curve support enabled). Red Hat-derived distributions prior to RHEL/CentOS 6.5 ship with EC disabled in OpenSSL, so if you need EC support you will need to recompile.
You can install via rubygems with gem install r509
To install the gem from your own clone (you will need to satisfy the dependencies via bundle install
or other means):
rake gem:build
rake gem:install
There is documentation available for every method and class in r509 available via yardoc. If you installed via gem it should be pre-generated in the doc directory. If you cloned this repo, just type rake yard
with the yard gem installed. You will also need the redcarpet and github-markup gems to properly parse the README.md.
- Support Rubies compiled against OpenSSL 1.1.0.
You can file bugs to get support from the community.
If you want to run the tests for r509 you'll need rspec. Additionally, you should install simplecov and yard for running the code coverage and documentation tasks in the Rakefile. rake -T
for a complete list of rake tasks available.
We run continuous integration tests (using Travis-CI) against 2.2, 2.3, and 2.4. 1.8.7 is no longer a supported configuration due to issues with its elliptic curve methods. 0.8.1 was the last official r509 release with 1.8.7 support.
r509 ships with a binary named r509
that can generate CSRs, keys, and create self-signed certificates. Type r509 -h
to see a list of options.
This guide provides instructions on building a basic CA using r509, r509-ca-http, and r509-ocsp-responder. In it you will learn how to create a root, set up the configuration profiles, issue certificates, revoke certificates, and see responses from an OCSP responder.
To generate a 2048-bit RSA CSR
csr = R509::CSR.new(
:subject => [
['CN','somedomain.com'],
['O','My Org'],
['L','City'],
['ST','State'],
['C','US']
]
)
# alternately
csr = R509::CSR.new(
:subject => {
:CN => 'somedomain.com',
:O => 'My Org',
:L => 'City',
:ST => 'State',
:C => 'US'
}
)
Another way to build the subject:
subject = R509::Subject.new
subject.CN="somedomain.com"
subject.O="My Org"
subject.L="City"
subject.ST="State"
subject.C="US"
csr = R509::CSR.new( :subject => subject )
To load an existing CSR (without private key)
csr_pem = File.read("/path/to/csr")
csr = R509::CSR.new(:csr => csr_pem)
# or
csr = R509::CSR.load_from_file("/path/to/csr")
To create a new CSR from the subject of a certificate
cert_pem = File.read("/path/to/cert")
csr = R509::CSR.new(:cert => cert_pem)
To create a CSR with SAN names
csr = R509::CSR.new(
:subject => [['CN','something.com']],
:san_names => ["something2.com","something3.com"]
)
To load an existing certificate
cert_pem = File.read("/path/to/cert")
cert = R509::Cert.new(:cert => cert_pem)
# or
cert = R509::Cert.load_from_file("/path/to/cert")
Load a cert and key
cert_pem = File.read("/path/to/cert")
key_pem = File.read("/path/to/key")
cert = R509::Cert.new(
:cert => cert_pem,
:key => key_pem
)
Load an encrypted private key
cert_pem = File.read("/path/to/cert")
key_pem = File.read("/path/to/key")
cert = R509::Cert.new(
:cert => cert_pem,
:key => key_pem,
:password => "private_key_password"
)
Load a PKCS12 file
pkcs12_der = File.read("/path/to/p12")
cert = R509::Cert.new(
:pkcs12 => pkcs12_der,
:password => "password"
)
Generate a 1536-bit RSA key
key = R509::PrivateKey.new(:type => "RSA", :bit_length => 1536)
Encrypt a private key
key = R509::PrivateKey.new(:type => "RSA", :bit_length => 2048)
encrypted_pem = key.to_encrypted_pem("aes256","my-password")
# or write it to disk
key.write_encrypted_pem("/tmp/path","aes256","my-password")
The engine you want to load must already be available to OpenSSL. How to compile/install OpenSSL engines is outside the scope of this document.
engine = R509::Engine.instance.load(:so_path => "/usr/lib64/openssl/engines/libchil.so", :id => "chil")
key = R509::PrivateKey(
:engine => engine,
:key_name => "my_key_name"
)
You can then use this key for signing.
To generate a 2048-bit RSA SPKI
key = R509::PrivateKey.new(:type => "RSA", :bit_length => 1024)
spki = R509::SPKI.new(:key => key)
To create a self-signed certificate
not_before = Time.now.to_i
not_after = Time.now.to_i+3600*24*7300
csr = R509::CSR.new(
:subject => [['C','US'],['O','r509 LLC'],['CN','r509 Self-Signed CA Test']]
)
# if you do not pass :extensions it will add basic constraints CA:TRUE, a SubjectKeyIdentifier, and an AuthorityKeyIdentifier
cert = R509::CertificateAuthority::Signer.selfsign(
:csr => csr,
:not_before => not_before,
:not_after => not_after
)
Create a basic CAConfig object
cert_pem = File.read("/path/to/cert")
key_pem = File.read("/path/to/key")
cert = R509::Cert.new(
:cert => cert_pem,
:key => key_pem
)
config = R509::Config::CAConfig.new(
:ca_cert => cert
)
Subject Item Policy allows you to define what subject fields are allowed in a certificate. Required means that field must be supplied, optional means it will be encoded if provided, and match means the field must be present and must match the value specified. The keys must match OpenSSL's short names.
sip = 509::Config::SubjectItemPolicy.new(
"CN" => {:policy => "required"},
"O" => {:policy => "optional"},
"OU" => {:policy => "match", :value => "Engineering" }
)
Certificate profiles hold extensions you want to put in a certificate, allowed/default message digests, and subject item policies. You can build them programmatically or load them via YAML. When building programmatically you can also serialize to YAML for future use. This is the preferred way to build the YAML.
The CertProfile object can either take objects or the hash that would build those objects.
Objects:
profile = R509::Config::CertProfile.new(
:basic_constraints => R509::Cert::Extensions::BasicConstraints.new(
:ca => false
),
:key_usage => R509::Cert::Extensions::KeyUsage.new(
:value => ['digitalSignature','keyEncipherment']
),
:extended_key_usage => R509::Cert::Extensions::ExtendedKeyUsage.new(
:value => ['serverAuth','clientAuth']
),
:authority_info_access => R509::Cert::Extensions::AuthorityInfoAccess.new(
:ocsp_location => [{:type => 'URI', :value => 'http://ocsp.myca.net'}]
),
:certificate_policies => R509::Cert::Extensions::CertificatePolicies.new(
:value => [{:policy_identifier => '1.23.3.4.4.5.56'}]
),
:crl_distribution_points => R509::Cert::Extensions::CRLDistributionPoints.new(
:value => [{:type => 'URI', :value => 'http://crl.myca.net/ca.crl'}]
),
:inhibit_any_policy => R509::Cert::Extensions::InhibitAnyPolicy.new(
:value => 0
),
:name_constraints => R509::Cert::Extensions::NameConstraints.new(
:permitted => [{:type => 'dirName', :value => { :CN => 'test' } }]
),
:ocsp_no_check => R509::Cert::Extensions::OCSPNoCheck.new(:value => true),
:policy_constraints => R509::Cert::Extensions::PolicyConstraints.new(
:require_explicit_policy=> 1
),
:subject_item_policy => R509::Config::SubjectItemPolicy.new(
"CN" => {:policy => "required"},
"O" => {:policy => "optional"},
"OU" => {:policy => "match", :value => "Engineering" }
),
:default_md => "SHA256",
:allowed_mds => ["SHA256","SHA512"]
)
Hashes:
profile = R509::Config::CertProfile.new(
:basic_constraints => {:ca => false},
:key_usage => { :value => ["digitalSignature","keyEncipherment"] },
:extended_key_usage => { :value => ["serverAuth"] },
:certificate_policies => [
{ :policy_identifier => "2.16.840.1.99999.21.234",
:cps_uris => ["http://example.com/cps","http://haha.com"],
:user_notices => [ { :explicit_text => "this is a great thing", :organization => "my org", :notice_numbers => [1,2,3] } ]
}
],
:subject_item_policy => nil,
:crl_distribution_points => {:value => [{ :type => "URI", :value => "http://crl.myca.net/ca.crl" }] },
:authority_info_access => {
:ocsp_location => [{ :type => "URI", :value => "http://ocsp.myca.net" }],
:ca_issuers_location => [{ :type => "URI", :value => "http://www.myca.net/some_ca.cer" }]
}
)
# CAConfig object from above assumed
config.set_profile("server",profile)
Multiple CAConfigs can be loaded via CAConfigPool
# from objects
pool = R509::Config::CAConfigPool.new("my_ca" => config, "another_ca" => another_config)
# from yaml
pool = R509::Config::CAConfigPool.from_yaml("certificate_authorities", "config_pool.yaml")
Example (Minimal) Config Pool YAML
certificate_authorities:
test_ca:
ca_cert:
cert: test_ca.cer
key: test_ca.key
second_ca:
ca_cert:
cert: second_ca.cer
key: second_ca.key
You can serialize a CAConfig (or CAConfigPool) via #to_yaml
. The output of the YAML will vary depending upon what data you have supplied to the object, but the output does require the following manual configuration:
- Add paths to the requested files where you see add_path (or change the options entirely. See the YAML config section below)
- Define a name for your config and put the YAML inside it. In the example below the config has been named example_ca
example_ca:
# the following is the output of #to_yaml
ca_cert:
cert: <add_path>
key: <add_path>
ocsp_start_skew_seconds: 3600
ocsp_validity_hours: 168
crl_md: SHA256
profiles:
profile:
subject_item_policy:
CN:
:policy: required
O:
:policy: required
L:
:policy: required
OU:
:policy: optional
default_md: SHA512
Sign a CSR
csr = R509::CSR.new(
:subject => {
:CN => 'somedomain.com',
:O => 'My Org',
:L => 'City',
:ST => 'State',
:C => 'US'
}
)
# assume config from yaml load above
ca = R509::CertificateAuthority::Signer.new(config)
ext = []
# you can add extensions in an array. See R509::Cert::Extensions::*
ext << R509::Cert::Extensions::BasicConstraints.new(:ca => false)
cert = ca.sign(
:csr => csr,
:extensions => ext
)
Override a CSR's subject or SAN names when signing
csr = R509::CSR.new(
:subject => {
:CN => 'somedomain.com',
:O => 'My Org',
:L => 'City',
:ST => 'State',
:C => 'US'
}
)
subject = csr.subject.dup
san_names = [{:type=> 'DNS', :value => "domain2.com"},{:type => 'IP', :value => "128.128.128.128"}]
subject.common_name = "newdomain.com"
subject.organization = "Org 2.0"
ext = []
ext << R509::Cert::Extensions::BasicConstraints.new(:ca => false)
ext << R509::Cert::Extensions::SubjectAlternativeName.new(:value => san_names)
# assume config from yaml load above
ca = R509::CertificateAuthority::Signer.new(config)
cert = ca.sign(
:csr => csr,
:subject => subject,
:extensions => ext
)
Sign an SPKI/SPKAC object
key = R509::PrivateKey.new(:type => "RSA", :bit_length => 2048)
spki = R509::SPKI.new(:key => key)
# SPKI objects do not contain subject or san name data so it must be specified
subject = R509::Subject.new
subject.CN = "mydomain.com"
subject.L = "Locality"
subject.ST = "State"
subject.C = "US"
san_names = [{:type=> 'DNS', :value => "domain2.com"},{:type => 'IP', :value => "128.128.128.128"}]
ext = []
ext << R509::Cert::Extensions::BasicConstraints.new(:ca => false)
ext << R509::Cert::Extensions::SubjectAlternativeName.new(:value => san_names)
# assume config from yaml load above
ca = R509::CertificateAuthority::Signer.new(config)
cert = ca.sign(
:spki => spki,
:subject => subject,
:extensions => ext
)
The OptionsBuilder takes in a CAConfig with CertProfiles. You then call #build_and_enforce
to have it create a hash that can be passed to R509::CertificateAuthority::Signer#sign
. The OptionsBuilder is responsible for enforcing restrictions on subject DN (via SubjectItemPolicy), determing allowed message digest, and adding a profile's extensions.
# assume config from yaml load above
csr = R509::CSR.new(
:subject => {
:CN => 'somedomain.com',
:O => 'My Org',
:L => 'City',
:ST => 'State',
:C => 'US'
}
)
builder = R509::CertificateAuthority::OptionsBuilder.new(config)
scrubbed_data = builder.build_and_enforce(
:csr => csr,
:profile_name => "server",
:subject => {:CN => 'rewritten.com'},
:san_names => ['r509.org'],
:message_digest => 'SHA256'
)
# this returns a hash with keys :csr/:pki, :subject, :extensions, and :message_digest
signer = R509::CertificateAuthority::Signer.new(config)
cert = signer.sign(scrubbed_data)
You can optionally supply an array of R509::Cert::Extensions::* objects to the builder via the :extensions
key. These will be merged with the extensions from the profile. If an extension in this array is also present in the profile, the supplied extension will override the profile.
# assume pre-existing config and csr from above
builder = R509::CertificateAuthority::OptionsBuilder.new(config)
scrubbed_data = builder.build_and_enforce(
:csr => csr,
:profile_name => "server",
:subject => {:CN => 'rewritten.com'},
:san_names => ['r509.org'],
:message_digest => 'SHA256',
:extensions => [R509::Cert::Extensions::BasicConstraints.new(:ca => true)]
)
The CRL administrator object takes an R509::Config::CAConfig
and an optional R509::CRL::ReaderWriter
subclass. By default it will use an R509::CRL::FileReaderWriter
class that assumes the presence of crl_number_file
and crl_list_file
in the CAConfig.
admin = R509::CRL::Administrator.new(config)
To revoke a certificate and generate a new CRL
admin.revoke_cert(serial)
crl = admin.generate_crl
This revokes on the root configured by the CAConfig that was passed into the Administrator constructor.
Register one
R509::OIDMapper.register("1.3.5.6.7.8.3.23.3","short_name","optional_long_name")
Register in batch
R509::OIDMapper.batch_register([
{:oid => "1.3.5.6.7.8.3.23.3", :short_name => "short_name", :long_name => "optional_long_name"},
{:oid => "1.3.5.6.7.8.3.23.5", :short_name => "another_name"}
])
In addition to the default RSA objects that are created above, r509 supports DSA and elliptic curve (EC). EC support is present only if Ruby has been linked against a version of OpenSSL compiled with EC enabled. This excludes Red Hat-based distributions at this time (unless you build it yourself). Take a look at the documentation for R509::PrivateKey, R509::Cert, and R509::CSR to see how to create DSA and EC types. You can test if elliptic curve support is available in your Ruby with:
R509.ec_supported?
These curves are set via :curve_name
. The system defaults to using secp384r1
- secp224r1 -- NIST/SECG curve over a 224 bit prime field
- secp384r1 -- NIST/SECG curve over a 384 bit prime field
- secp521r1 -- NIST/SECG curve over a 521 bit prime field
- prime192v1 -- NIST/X9.62/SECG curve over a 192 bit prime field
- sect163k1 -- NIST/SECG/WTLS curve over a 163 bit binary field
- sect163r2 -- NIST/SECG curve over a 163 bit binary field
- sect233k1 -- NIST/SECG/WTLS curve over a 233 bit binary field
- sect233r1 -- NIST/SECG/WTLS curve over a 233 bit binary field
- sect283k1 -- NIST/SECG curve over a 283 bit binary field
- sect283r1 -- NIST/SECG curve over a 283 bit binary field
- sect409k1 -- NIST/SECG curve over a 409 bit binary field
- sect409r1 -- NIST/SECG curve over a 409 bit binary field
- sect571k1 -- NIST/SECG curve over a 571 bit binary field
- sect571r1 -- NIST/SECG curve over a 571 bit binary field
Paul Kehrer (Twitter | GitHub)
See the LICENSE file. Licensed under the Apache 2.0 License.