[DRE-commits] [ruby-certificate-authority] 02/07: New upstream version 0.2.0~6dd483bf

Micah Anderson micah at moszumanska.debian.org
Mon Dec 5 16:01:57 UTC 2016


This is an automated email from the git hooks/post-receive script.

micah pushed a commit to branch master
in repository ruby-certificate-authority.

commit db8d471673b1e641577febd29bd2c69377bf5015
Author: Micah Anderson <micah at riseup.net>
Date:   Thu Dec 1 23:07:52 2016 -0500

    New upstream version 0.2.0~6dd483bf
---
 .gitignore                                         |   6 +
 .travis.yml                                        |   3 +
 Gemfile                                            |  13 +-
 Gemfile.lock                                       |  61 +--
 README.rdoc                                        |  93 ++++-
 Rakefile                                           |   6 +-
 VERSION.yml                                        |   4 +-
 certificate_authority.gemspec                      |  39 +-
 lib/certificate_authority.rb                       |   5 +-
 lib/certificate_authority/certificate.rb           | 127 ++++--
 .../certificate_revocation_list.rb                 |  48 ++-
 lib/certificate_authority/core_extensions.rb       |  46 +++
 lib/certificate_authority/distinguished_name.rb    |  80 +++-
 lib/certificate_authority/extensions.rb            | 460 +++++++++++++++++++--
 lib/certificate_authority/key_material.rb          |  39 +-
 lib/certificate_authority/ocsp_handler.rb          |  77 +++-
 lib/certificate_authority/pkcs11_key_material.rb   |   2 -
 lib/certificate_authority/revocable.rb             |  14 +
 lib/certificate_authority/serial_number.rb         |  17 +-
 lib/certificate_authority/signing_request.rb       |  91 ++++
 lib/certificate_authority/validations.rb           |  31 ++
 metadata.yml                                       | 111 -----
 spec/samples/certs/DigiCertHighAssuranceEVCA-1.pem | 115 ++++++
 spec/samples/certs/apple_wwdr_issued_cert.pem      | 112 +++++
 spec/samples/certs/apple_wwdr_issuer.pem           |  89 ++++
 spec/samples/certs/ca.crt                          | 130 ++++++
 spec/samples/certs/ca.key                          |  51 +++
 spec/samples/certs/client.crt                      |  78 ++++
 spec/samples/certs/client.csr                      |  11 +
 spec/samples/certs/client.key                      |  15 +
 spec/samples/certs/github.com.pem                  | 122 ++++++
 spec/samples/certs/server.crt                      |  79 ++++
 spec/samples/certs/server.csr                      |  13 +
 spec/samples/certs/server.key                      |  15 +
 spec/spec_helper.rb                                |  12 +-
 spec/units/certificate_revocation_list_spec.rb     |  67 ++-
 spec/units/certificate_spec.rb                     | 134 ++++--
 spec/units/distinguished_name_spec.rb              |  17 +-
 spec/units/extensions_spec.rb                      |  80 +++-
 spec/units/key_material_spec.rb                    |  89 +++-
 spec/units/ocsp_handler_spec.rb                    | 121 +++++-
 spec/units/signing_entity_spec.rb                  |   2 +-
 spec/units/signing_request_spec.rb                 | 154 +++++++
 spec/units/working_with_openssl_spec.rb            | 136 ++++++
 44 files changed, 2671 insertions(+), 344 deletions(-)

diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..5533fdb
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,6 @@
+pkg
+development
+.bundle
+.rvmrc
+coverage
+doc
diff --git a/.travis.yml b/.travis.yml
new file mode 100644
index 0000000..11a115a
--- /dev/null
+++ b/.travis.yml
@@ -0,0 +1,3 @@
+language: ruby
+rvm:
+  - 1.9.3
diff --git a/Gemfile b/Gemfile
index 3f97a03..a1c7893 100644
--- a/Gemfile
+++ b/Gemfile
@@ -1,9 +1,12 @@
-source 'http://rubygems.org'
-
-gem 'activemodel', ">= 3.0.6"
+source 'https://rubygems.org'
 
 group :development do
-	gem 'rspec'
+  gem 'rspec'
   gem "jeweler", ">= 1.5.2"
-  #gem "rcov", ">= 0"
 end
+
+group :test do
+ gem 'rake'
+ gem 'coveralls', require: false
+end
+
diff --git a/Gemfile.lock b/Gemfile.lock
index 0439385..caa748b 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -1,39 +1,54 @@
 GEM
-  remote: http://rubygems.org/
+  remote: https://rubygems.org/
   specs:
-    activemodel (3.2.8)
-      activesupport (= 3.2.8)
-      builder (~> 3.0.0)
-    activesupport (3.2.8)
-      i18n (~> 0.6)
-      multi_json (~> 1.0)
-    builder (3.0.0)
-    diff-lcs (1.1.3)
+    coveralls (0.7.0)
+      multi_json (~> 1.3)
+      rest-client
+      simplecov (>= 0.7)
+      term-ansicolor
+      thor
+    diff-lcs (1.2.4)
+    docile (1.1.2)
     git (1.2.5)
-    i18n (0.6.0)
     jeweler (1.8.4)
       bundler (~> 1.0)
       git (>= 1.2.5)
       rake
       rdoc
-    json (1.7.4)
-    multi_json (1.3.6)
-    rake (0.9.2.2)
-    rdoc (3.12)
+    json (1.8.0)
+    mime-types (2.0)
+    multi_json (1.7.4)
+    rake (10.0.4)
+    rdoc (4.0.1)
       json (~> 1.4)
-    rspec (2.11.0)
-      rspec-core (~> 2.11.0)
-      rspec-expectations (~> 2.11.0)
-      rspec-mocks (~> 2.11.0)
-    rspec-core (2.11.1)
-    rspec-expectations (2.11.2)
-      diff-lcs (~> 1.1.3)
-    rspec-mocks (2.11.2)
+    rest-client (1.6.7)
+      mime-types (>= 1.16)
+    rspec (2.13.0)
+      rspec-core (~> 2.13.0)
+      rspec-expectations (~> 2.13.0)
+      rspec-mocks (~> 2.13.0)
+    rspec-core (2.13.1)
+    rspec-expectations (2.13.0)
+      diff-lcs (>= 1.1.3, < 2.0)
+    rspec-mocks (2.13.1)
+    simplecov (0.8.2)
+      docile (~> 1.1.0)
+      multi_json
+      simplecov-html (~> 0.8.0)
+    simplecov-html (0.8.0)
+    term-ansicolor (1.2.2)
+      tins (~> 0.8)
+    thor (0.18.1)
+    tins (0.13.1)
 
 PLATFORMS
   ruby
 
 DEPENDENCIES
-  activemodel (>= 3.0.6)
+  coveralls
   jeweler (>= 1.5.2)
+  rake
   rspec
+
+BUNDLED WITH
+   1.11.2
diff --git a/README.rdoc b/README.rdoc
index 979a2b1..7a44122 100644
--- a/README.rdoc
+++ b/README.rdoc
@@ -1,5 +1,9 @@
 = CertificateAuthority - Because it shouldn't be this damned complicated
 
+{<img src="https://travis-ci.org/cchandler/certificate_authority.png?branch=master" alt="Build Status" />}[https://travis-ci.org/cchandler/certificate_authority]
+{<img src="https://codeclimate.com/github/cchandler/certificate_authority.png" alt="Code Climate" />}[https://codeclimate.com/github/cchandler/certificate_authority]
+{<img src="https://coveralls.io/repos/cchandler/certificate_authority/badge.png?branch=master" alt="Code Coverage" />}[https://coveralls.io/r/cchandler/certificate_authority]
+
 This is meant to provide a (more) programmer-friendly implementation of all the basic functionality contained in RFC-3280 to implement your own certificate authority.
 
 You can generate root certificates, intermediate certificates, and terminal certificates.  You can also generate/manage Certificate Revocation Lists (CRLs) and Online Certificate Status Protocol (OCSP) messages.
@@ -60,7 +64,7 @@ Maybe you don't want to actually sign certificates with your super-secret root c
   signing_profile = {"extensions" => {"keyUsage" => {"usage" => ["critical", "keyCertSign"] }} }
   intermediate.sign!(signing_profile)
 
-All we have to do is create another certificate like we did with the root. In this example we gave it the next available serial number which for us, was 2.  We then generate (and save!) key material for this new entity.  Even the +signing_entity+ is set to true so this certificate can sign other certificates.  The difference here is that the +parent+ field is set to the root. Going forward, whatever entity you want to sign a certificate, you set that entity to be the parent. In this case [...]
+All we have to do is create another certificate like we did with the root. In this example we gave it the next available serial number, which for us, was 2.  We then generate (and save!) key material for this new entity.  Even the +signing_entity+ is set to true so this certificate can sign other certificates.  The difference here is that the +parent+ field is set to the root. Going forward, whatever entity you want to sign a certificate, you set that entity to be the parent. In this cas [...]
 
 = Creating new certificates (in general)
 
@@ -193,6 +197,78 @@ These CPSs define what vetting criteria and maintenance practices are required t
 
 [user_notice] This is a nested field containing explicit human readable text if you want to embed a notice in the certificate body related to certification practices. It contains nested attributes of +explicit_text+ for the notice, +organization+ and +notice_numbers+. Refer to the RFC for specific implications of how these are set, but whether or not browsers implement the correct specified behavior for their presence is another issue.
 
+= Certificate Signing Requests (CSRs)
+
+If you want certificate requestors to be able to request certificates without moving the private key you'll need to generate a CSR and submit it to the certificate authority.
+
+Here's an example of using +certificate_authority+ to generate a CSR.
+
+  csr = CertificateAuthority::SigningRequest.new
+  dn = CertificateAuthority::DistinguishedName.new
+  dn.common_name = "localhost"
+  csr.distinguished_name = dn
+  k = CertificateAuthority::MemoryKeyMaterial.new
+  k.generate_key(2048)
+  csr.key_material = k
+  csr.digest = "SHA256"
+  csr.to_x509_csr.to_pem
+
+Similarly, reading a CSR in is as simple as providing the PEM formatted version to +SigningRequest.from_x509_csr+.
+
+  csr = CertificateAuthority::SigningRequest.from_x509_csr(@pem_csr)
+
+Once you have the CSR in the form of a +SigningRequest+ you can transform it to a +Certificate+ with +to_cert+. At this point it works just like any other certificate. You'll have to provide a serial number to actually sign it.
+
+= Certificate Revocation Lists (CRLs)
+
+Revocation lists let clients know when a certificate in the wild should no longer be trusted.
+
+Like end-user certificates, CRLs have to be signed by a signing authority to be valid. Additionally, you will need to furnish a +nextUpdate+ value that indicates to the client when it should look for updates to the CRL and how long it should consider a cached value valid.
+
+Ideally you would place the result CRL somewhere generally accessible on the Internet and reference the URI in the +crlDistributionPoints+ extension on issued certificates.
+
+  crl = CertificateAuthority::CertificateRevocationList.new
+  crl << certificate # Some CertificateAuthority::Certificate
+  crl << serial_number # Also works with plain CertificateAuthority::SerialNumber
+  crl.parent = root_certificate # A valid root
+  crl.next_update = (60 * 60 * 10) # 10 Hours
+  crl.sign!
+  crl.to_pem
+
+= OCSP Support
+
+OCSP is the Online Certificate Status Protocol. It provides a mechanism to query an authority to see if a certificate is still valid without downloading an entire CRL. To use this mechanism you provide a URI in the Authority Information Access extension.
+If a client wishes to check the validity of a certificate they can query this endpoint.
+This request will only contain serial numbers, so you'll need to uniquely identify your authority in the AIA path.
+
+If a client sends you a DER encoded OCSP request you can read it out via +OCSPRequestReader+
+
+  ocsp_request_reader = CertificateAuthority::OCSPRequestReader.from_der(@ocsp_request.to_der)
+  ocsp_request_reader.serial_numbers
+
+Then, you can construct a response like this
+
+  response_builder = CertificateAuthority::OCSPResponseBuilder.from_request_reader(ocsp_request_reader)
+  response_builder.parent = root
+  response = response_builder.build_response # Returns OpenSSL::OCSP::Response
+  response.to_der
+
+The response builder will copy a (possible) nonce from the request. By default, the +OCSPResponseBuilder+ will say that every certificate is GOOD.
+You should definitely override this if you plan on revoking certificates.
+If you want to override this you'll need to supply a proc/lambda that takes a serial number and returns an array of status and reason.
+
+  response_builder = CertificateAuthority::OCSPResponseBuilder.from_request_reader(ocsp_request_reader)
+  response_builder.verification_mechanism = lambda {|certid|
+    [CertificateAuthority::OCSPResponseBuilder::REVOKED,CertificateAuthority::OCSPResponseBuilder::UNSPECIFIED]
+  }
+  response_builder.parent = root
+  response = response_builder.build_response # Response will say everything is revoked for unspecified reasons
+
+Lastly, you can configure a nextUpdate time in the response. This is the length of time for which a client may consider this response valid.
+The default is 15 minutes.
+
+  response_builder.next_update = 30 * 60 # 30 minutes
+
 = PKCS#11 Support
 
 If you happen to have a PKCS#11 compliant hardware token you can use +certificate_authority+ to maintain private key materials in hardware security modules. At this point the scope of operating that hardware is out of scope of this README but it's there and it is supported.
@@ -223,15 +299,28 @@ Your current version of OpenSSL _must_ include dynamic engine support and you wi
 
 Also of note, I have gotten these to work with 32-bit copies of Ubuntu 10.10 and pre-Snow Leopard versions of OS X. If you are running Snow Leopard you're out of luck since none of the companies I've contacted make a 64 bit driver.
 
-= Coming Soon
+= Hopefully in the future
 
 * More PKCS#11 hardware (I need driver support from the manufacturers)
+
+= Todone
+
 * Support for working with CSRs to request & issue certificates
+* OCSP support
 
 = Misc notes
 
 * Firefox will complain about root/intermediate certificates unless both digitalSignature and keyEncipherment are specified as keyUsage attributes. Thanks diogomonica
 
+= Special thanks and Contributions
+
+* Diogo Monica @diogo
+* Justin Cummins @sul3n3t
+* @databus23
+* Colin Jones @trptcolin
+* Eric Monti @emonti
+* TJ Vanderpoel @bougyman
+
 == Meta
 
 Written by Chris Chandler(http://chrischandler.name)
diff --git a/Rakefile b/Rakefile
index bdcfc1e..8f4dd26 100644
--- a/Rakefile
+++ b/Rakefile
@@ -20,14 +20,12 @@ require 'jeweler'
 Jeweler::Tasks.new do |gem|
   # gem is a Gem::Specification... see http://docs.rubygems.org/read/chapter/20 for more options
   gem.name = "certificate_authority"
-  gem.homepage = "http://github.com/cchandler/certificate_authority"
+  gem.homepage = "https://github.com/cchandler/certificate_authority"
   gem.license = "MIT"
   gem.summary = 'Ruby gem for managing the core functions outlined in RFC-3280 for PKI'
   # gem.description = ''
-  gem.email = "chris at flatterline.com"
+  gem.email = "squanderingtime at gmail.com"
   gem.authors = ["Chris Chandler"]
-  
-  # gem.add_dependency('activemodel', '3.0.6')
 end
 Jeweler::RubygemsDotOrgTasks.new
 
diff --git a/VERSION.yml b/VERSION.yml
index 295ba34..437bf68 100644
--- a/VERSION.yml
+++ b/VERSION.yml
@@ -1,5 +1,5 @@
 ---
 :major: 0
-:minor: 1
-:patch: 6
+:minor: 2
+:patch: 0
 :build: 
diff --git a/certificate_authority.gemspec b/certificate_authority.gemspec
index c8c45e2..4768a5f 100644
--- a/certificate_authority.gemspec
+++ b/certificate_authority.gemspec
@@ -2,19 +2,22 @@
 # DO NOT EDIT THIS FILE DIRECTLY
 # Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec'
 # -*- encoding: utf-8 -*-
+# stub: certificate_authority 0.2.0 ruby lib
 
 Gem::Specification.new do |s|
   s.name = "certificate_authority"
-  s.version = "0.1.6"
+  s.version = "0.2.0"
 
   s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
+  s.require_paths = ["lib"]
   s.authors = ["Chris Chandler"]
-  s.date = "2012-08-12"
-  s.email = "chris at flatterline.com"
+  s.date = "2016-06-21"
+  s.email = "squanderingtime at gmail.com"
   s.extra_rdoc_files = [
     "README.rdoc"
   ]
   s.files = [
+    ".travis.yml",
     "Gemfile",
     "Gemfile.lock",
     "README.rdoc",
@@ -24,14 +27,30 @@ Gem::Specification.new do |s|
     "lib/certificate_authority.rb",
     "lib/certificate_authority/certificate.rb",
     "lib/certificate_authority/certificate_revocation_list.rb",
+    "lib/certificate_authority/core_extensions.rb",
     "lib/certificate_authority/distinguished_name.rb",
     "lib/certificate_authority/extensions.rb",
     "lib/certificate_authority/key_material.rb",
     "lib/certificate_authority/ocsp_handler.rb",
     "lib/certificate_authority/pkcs11_key_material.rb",
+    "lib/certificate_authority/revocable.rb",
     "lib/certificate_authority/serial_number.rb",
     "lib/certificate_authority/signing_entity.rb",
+    "lib/certificate_authority/signing_request.rb",
+    "lib/certificate_authority/validations.rb",
     "lib/tasks/certificate_authority.rake",
+    "spec/samples/certs/DigiCertHighAssuranceEVCA-1.pem",
+    "spec/samples/certs/apple_wwdr_issued_cert.pem",
+    "spec/samples/certs/apple_wwdr_issuer.pem",
+    "spec/samples/certs/ca.crt",
+    "spec/samples/certs/ca.key",
+    "spec/samples/certs/client.crt",
+    "spec/samples/certs/client.csr",
+    "spec/samples/certs/client.key",
+    "spec/samples/certs/github.com.pem",
+    "spec/samples/certs/server.crt",
+    "spec/samples/certs/server.csr",
+    "spec/samples/certs/server.key",
     "spec/spec_helper.rb",
     "spec/units/certificate_authority_spec.rb",
     "spec/units/certificate_revocation_list_spec.rb",
@@ -43,28 +62,26 @@ Gem::Specification.new do |s|
     "spec/units/pkcs11_key_material_spec.rb",
     "spec/units/serial_number_spec.rb",
     "spec/units/signing_entity_spec.rb",
-    "spec/units/units_helper.rb"
+    "spec/units/signing_request_spec.rb",
+    "spec/units/units_helper.rb",
+    "spec/units/working_with_openssl_spec.rb"
   ]
-  s.homepage = "http://github.com/cchandler/certificate_authority"
+  s.homepage = "https://github.com/cchandler/certificate_authority"
   s.licenses = ["MIT"]
-  s.require_paths = ["lib"]
-  s.rubygems_version = "1.8.15"
+  s.rubygems_version = "2.2.2"
   s.summary = "Ruby gem for managing the core functions outlined in RFC-3280 for PKI"
 
   if s.respond_to? :specification_version then
-    s.specification_version = 3
+    s.specification_version = 4
 
     if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
-      s.add_runtime_dependency(%q<activemodel>, [">= 3.0.6"])
       s.add_development_dependency(%q<rspec>, [">= 0"])
       s.add_development_dependency(%q<jeweler>, [">= 1.5.2"])
     else
-      s.add_dependency(%q<activemodel>, [">= 3.0.6"])
       s.add_dependency(%q<rspec>, [">= 0"])
       s.add_dependency(%q<jeweler>, [">= 1.5.2"])
     end
   else
-    s.add_dependency(%q<activemodel>, [">= 3.0.6"])
     s.add_dependency(%q<rspec>, [">= 0"])
     s.add_dependency(%q<jeweler>, [">= 1.5.2"])
   end
diff --git a/lib/certificate_authority.rb b/lib/certificate_authority.rb
index bada423..c52e4b6 100644
--- a/lib/certificate_authority.rb
+++ b/lib/certificate_authority.rb
@@ -2,10 +2,12 @@ $:.unshift(File.dirname(__FILE__)) unless $:.include?(File.dirname(__FILE__)) ||
 
 #Exterior requirements
 require 'openssl'
-require 'active_model'
 
 #Internal modules
+require 'certificate_authority/core_extensions'
 require 'certificate_authority/signing_entity'
+require 'certificate_authority/revocable'
+require 'certificate_authority/validations'
 require 'certificate_authority/distinguished_name'
 require 'certificate_authority/serial_number'
 require 'certificate_authority/key_material'
@@ -14,6 +16,7 @@ require 'certificate_authority/extensions'
 require 'certificate_authority/certificate'
 require 'certificate_authority/certificate_revocation_list'
 require 'certificate_authority/ocsp_handler'
+require 'certificate_authority/signing_request'
 
 module CertificateAuthority
 end
diff --git a/lib/certificate_authority/certificate.rb b/lib/certificate_authority/certificate.rb
index 412398d..cdf432c 100644
--- a/lib/certificate_authority/certificate.rb
+++ b/lib/certificate_authority/certificate.rb
@@ -1,14 +1,13 @@
 module CertificateAuthority
   class Certificate
-    # include SigningEntity
-    include ActiveModel::Validations
+    include Validations
+    include Revocable
 
     attr_accessor :distinguished_name
     attr_accessor :serial_number
     attr_accessor :key_material
     attr_accessor :not_before
     attr_accessor :not_after
-    attr_accessor :revoked_at
     attr_accessor :extensions
     attr_accessor :openssl_body
 
@@ -16,7 +15,7 @@ module CertificateAuthority
 
     attr_accessor :parent
 
-    validate do |certificate|
+    def validate
       errors.add :base, "Distinguished name must be valid" unless distinguished_name.valid?
       errors.add :base, "Key material must be valid" unless key_material.valid?
       errors.add :base, "Serial number must be valid" unless serial_number.valid?
@@ -33,8 +32,8 @@ module CertificateAuthority
       self.distinguished_name = DistinguishedName.new
       self.serial_number = SerialNumber.new
       self.key_material = MemoryKeyMaterial.new
-      self.not_before = Time.now
-      self.not_after = Time.now + 60 * 60 * 24 * 365 #One year
+      self.not_before = Date.today.utc
+      self.not_after = Date.today.advance(:years => 1).utc
       self.parent = self
       self.extensions = load_extensions()
 
@@ -42,12 +41,31 @@ module CertificateAuthority
 
     end
 
+=begin
+    def self.from_openssl openssl_cert
+      unless openssl_cert.is_a? OpenSSL::X509::Certificate
+        raise "Can only construct from an OpenSSL::X509::Certificate"
+      end
+
+      certificate = Certificate.new
+      # Only subject, key_material, and body are used for signing
+      certificate.distinguished_name = DistinguishedName.from_openssl openssl_cert.subject
+      certificate.key_material.public_key = openssl_cert.public_key
+      certificate.openssl_body = openssl_cert
+      certificate.serial_number.number = openssl_cert.serial.to_i
+      certificate.not_before = openssl_cert.not_before
+      certificate.not_after = openssl_cert.not_after
+      # TODO extensions
+      certificate
+    end
+=end
+
     def sign!(signing_profile={})
       raise "Invalid certificate #{self.errors.full_messages}" unless valid?
       merge_profile_with_extensions(signing_profile)
 
       openssl_cert = OpenSSL::X509::Certificate.new
-      openssl_cert.version    = 2
+      openssl_cert.version = 2
       openssl_cert.not_before = self.not_before
       openssl_cert.not_after = self.not_after
       openssl_cert.public_key = self.key_material.public_key
@@ -59,7 +77,6 @@ module CertificateAuthority
 
       require 'tempfile'
       t = Tempfile.new("bullshit_conf")
-      # t = File.new("/tmp/openssl.cnf")
       ## The config requires a file even though we won't use it
       openssl_config = OpenSSL::Config.new(t.path)
 
@@ -86,18 +103,19 @@ module CertificateAuthority
       self.extensions.keys.sort{|a,b| b<=>a}.each do |k|
         e = extensions[k]
         next if e.to_s.nil? or e.to_s == "" ## If the extension returns an empty string we won't include it
-        ext = factory.create_ext(e.openssl_identifier, e.to_s)
+        ext = factory.create_ext(e.openssl_identifier, e.to_s, e.critical)
         openssl_cert.add_extension(ext)
       end
 
       if signing_profile["digest"].nil?
-        digest = OpenSSL::Digest::Digest.new("SHA512")
+        digest = OpenSSL::Digest.new("SHA512")
       else
-        digest = OpenSSL::Digest::Digest.new(signing_profile["digest"])
+        digest = OpenSSL::Digest.new(signing_profile["digest"])
       end
-      self.openssl_body = openssl_cert.sign(parent.key_material.private_key,digest)
-      t.close! if t.is_a?(Tempfile)# We can get rid of the ridiculous temp file
-      self.openssl_body
+
+      self.openssl_body = openssl_cert.sign(parent.key_material.private_key, digest)
+    ensure
+      t.close! if t # We can get rid of the ridiculous temp file
     end
 
     def is_signing_entity?
@@ -117,6 +135,34 @@ module CertificateAuthority
       self.openssl_body.to_pem
     end
 
+    def to_csr
+      csr = SigningRequest.new
+      csr.distinguished_name = self.distinguished_name
+      csr.key_material = self.key_material
+      factory = OpenSSL::X509::ExtensionFactory.new
+      exts = []
+      self.extensions.keys.each do |k|
+        ## Don't copy over key identifiers for CSRs
+        next if k == "subjectKeyIdentifier" || k == "authorityKeyIdentifier"
+        e = extensions[k]
+        ## If the extension returns an empty string we won't include it
+        next if e.to_s.nil? or e.to_s == ""
+        exts << factory.create_ext(e.openssl_identifier, e.to_s, e.critical)
+      end
+      attrval = OpenSSL::ASN1::Set([OpenSSL::ASN1::Sequence(exts)])
+      attrs = [
+        OpenSSL::X509::Attribute.new("extReq", attrval),
+        OpenSSL::X509::Attribute.new("msExtReq", attrval)
+      ]
+      csr.attributes = attrs
+      csr
+    end
+
+    def self.from_x509_cert(raw_cert)
+      openssl_cert = OpenSSL::X509::Certificate.new(raw_cert)
+      Certificate.from_openssl(openssl_cert)
+    end
+
     def is_root_entity?
       self.parent == self && is_signing_entity?
     end
@@ -135,6 +181,16 @@ module CertificateAuthority
         items = signing_config[k]
         items.keys.each do |profile_item_key|
           if extension.respond_to?("#{profile_item_key}=".to_sym)
+            if k == 'subjectAltName' && profile_item_key == 'emails'
+              items[profile_item_key].map do |email|
+                if email == 'email:copy'
+                  fail "no email address provided for subject: #{subject.to_x509_name}" unless subject.email_address
+                  "email:#{subject.email_address}"
+                else
+                  email
+                end
+              end
+            end
             extension.send("#{profile_item_key}=".to_sym, items[profile_item_key] )
           else
             p "Tried applying '#{profile_item_key}' to #{extension.class} but it doesn't respond!"
@@ -143,30 +199,25 @@ module CertificateAuthority
       end
     end
 
+    # Enumeration of the extensions. Not the worst option since
+    # the likelihood of these needing to be updated is low at best.
+    EXTENSIONS = [
+        CertificateAuthority::Extensions::BasicConstraints,
+        CertificateAuthority::Extensions::CrlDistributionPoints,
+        CertificateAuthority::Extensions::SubjectKeyIdentifier,
+        CertificateAuthority::Extensions::AuthorityKeyIdentifier,
+        CertificateAuthority::Extensions::AuthorityInfoAccess,
+        CertificateAuthority::Extensions::KeyUsage,
+        CertificateAuthority::Extensions::ExtendedKeyUsage,
+        CertificateAuthority::Extensions::SubjectAlternativeName,
+        CertificateAuthority::Extensions::CertificatePolicies
+    ]
+
     def load_extensions
       extension_hash = {}
 
-      temp_extensions = []
-      basic_constraints = CertificateAuthority::Extensions::BasicContraints.new
-      temp_extensions << basic_constraints
-      crl_distribution_points = CertificateAuthority::Extensions::CrlDistributionPoints.new
-      temp_extensions << crl_distribution_points
-      subject_key_identifier = CertificateAuthority::Extensions::SubjectKeyIdentifier.new
-      temp_extensions << subject_key_identifier
-      authority_key_identifier = CertificateAuthority::Extensions::AuthorityKeyIdentifier.new
-      temp_extensions << authority_key_identifier
-      authority_info_access = CertificateAuthority::Extensions::AuthorityInfoAccess.new
-      temp_extensions << authority_info_access
-      key_usage = CertificateAuthority::Extensions::KeyUsage.new
-      temp_extensions << key_usage
-      extended_key_usage = CertificateAuthority::Extensions::ExtendedKeyUsage.new
-      temp_extensions << extended_key_usage
-      subject_alternative_name = CertificateAuthority::Extensions::SubjectAlternativeName.new
-      temp_extensions << subject_alternative_name
-      certificate_policies = CertificateAuthority::Extensions::CertificatePolicies.new
-      temp_extensions << certificate_policies
-
-      temp_extensions.each do |extension|
+      EXTENSIONS.each do |klass|
+        extension = klass.new
         extension_hash[extension.openssl_identifier] = extension
       end
 
@@ -193,7 +244,11 @@ module CertificateAuthority
       certificate.serial_number.number = openssl_cert.serial.to_i
       certificate.not_before = openssl_cert.not_before
       certificate.not_after = openssl_cert.not_after
-      # TODO extensions
+      EXTENSIONS.each do |klass|
+        _,v,c = (openssl_cert.extensions.detect { |e| e.to_a.first == klass::OPENSSL_IDENTIFIER } || []).to_a
+        certificate.extensions[klass::OPENSSL_IDENTIFIER] = klass.parse(v, c) if v
+      end
+
       certificate
     end
 
diff --git a/lib/certificate_authority/certificate_revocation_list.rb b/lib/certificate_authority/certificate_revocation_list.rb
index 4118123..cb3aaf7 100644
--- a/lib/certificate_authority/certificate_revocation_list.rb
+++ b/lib/certificate_authority/certificate_revocation_list.rb
@@ -1,36 +1,52 @@
 module CertificateAuthority
   class CertificateRevocationList
-    include ActiveModel::Validations
+    include Validations
 
     attr_accessor :certificates
     attr_accessor :parent
     attr_accessor :crl_body
     attr_accessor :next_update
+    attr_accessor :last_update_skew_seconds
 
-    validate do |crl|
-      errors.add :next_update, "Next update must be a positive value" if crl.next_update < 0
-      errors.add :parent, "A parent entity must be set" if crl.parent.nil?
+    def validate
+      errors.add :next_update, "Next update must be a positive value" if self.next_update < 0
+      errors.add :parent, "A parent entity must be set" if self.parent.nil?
     end
 
     def initialize
       self.certificates = []
       self.next_update = 60 * 60 * 4 # 4 hour default
+      self.last_update_skew_seconds = 0
     end
 
-    def <<(cert)
-      raise "Only revoked certificates can be added to a CRL" unless cert.revoked?
-      self.certificates << cert
+    def <<(revocable)
+      case revocable
+      when Revocable
+        raise "Only revoked entities can be added to a CRL" unless revocable.revoked?
+        self.certificates << revocable
+      when OpenSSL::X509::Certificate
+        raise "Not implemented yet"
+      else
+        raise "#{revocable.class} cannot be included in a CRL"
+      end
     end
 
-    def sign!
+    def sign!(signing_profile={})
       raise "No parent entity has been set!" if self.parent.nil?
       raise "Invalid CRL" unless self.valid?
 
-      revocations = self.certificates.collect do |certificate|
+      revocations = self.certificates.collect do |revocable|
         revocation = OpenSSL::X509::Revoked.new
-        x509_cert = OpenSSL::X509::Certificate.new(certificate.to_pem)
-        revocation.serial = x509_cert.serial
-        revocation.time = certificate.revoked_at
+
+        ## We really just need a serial number, now we have to dig it out
+        case revocable
+        when Certificate
+          x509_cert = OpenSSL::X509::Certificate.new(revocable.to_pem)
+          revocation.serial = x509_cert.serial
+        when SerialNumber
+          revocation.serial = revocable.number
+        end
+        revocation.time = revocable.revoked_at
         revocation
       end
 
@@ -40,11 +56,15 @@ module CertificateAuthority
       end
 
       crl.version = 1
-      crl.last_update = Time.now
+      crl.last_update = Time.now - self.last_update_skew_seconds
       crl.next_update = Time.now + self.next_update
 
       signing_cert = OpenSSL::X509::Certificate.new(self.parent.to_pem)
-      digest = OpenSSL::Digest::Digest.new("SHA512")
+      if signing_profile["digest"].nil?
+        digest = OpenSSL::Digest.new("SHA512")
+      else
+        digest = OpenSSL::Digest.new(signing_profile["digest"])
+      end
       crl.issuer = signing_cert.subject
       self.crl_body = crl.sign(self.parent.key_material.private_key, digest)
 
diff --git a/lib/certificate_authority/core_extensions.rb b/lib/certificate_authority/core_extensions.rb
new file mode 100644
index 0000000..0508f9a
--- /dev/null
+++ b/lib/certificate_authority/core_extensions.rb
@@ -0,0 +1,46 @@
+#
+# ActiveSupport has these modifications. Now that we don't use ActiveSupport,
+# these are added here as a kindness.
+#
+
+require 'date'
+
+unless nil.respond_to?(:blank?)
+  class NilClass
+    def blank?
+      true
+    end
+  end
+end
+
+unless String.respond_to?(:blank?)
+  class String
+    def blank?
+      self.empty?
+    end
+  end
+end
+
+class Date
+
+  def today
+    t = Time.now.utc
+    Date.new(t.year, t.month, t.day)
+  end
+
+  def utc
+    self.to_datetime.to_time.utc
+  end
+
+  unless Date.respond_to?(:advance)
+    def advance(options)
+      options = options.dup
+      d = self
+      d = d >> options.delete(:years) * 12 if options[:years]
+      d = d >> options.delete(:months)     if options[:months]
+      d = d +  options.delete(:weeks) * 7  if options[:weeks]
+      d = d +  options.delete(:days)       if options[:days]
+      d
+    end
+  end
+end
diff --git a/lib/certificate_authority/distinguished_name.rb b/lib/certificate_authority/distinguished_name.rb
index 71111c2..3b83582 100644
--- a/lib/certificate_authority/distinguished_name.rb
+++ b/lib/certificate_authority/distinguished_name.rb
@@ -1,58 +1,106 @@
 module CertificateAuthority
   class DistinguishedName
-    include ActiveModel::Validations
+    include Validations
 
-    validates_presence_of :common_name
+    def validate
+      if self.common_name.nil? || self.common_name.empty?
+        errors.add :common_name, 'cannot be blank'
+      end
+    end
 
     attr_accessor :common_name
     alias :cn :common_name
+    alias :cn= :common_name=
 
     attr_accessor :locality
     alias :l :locality
+    alias :l= :locality=
 
     attr_accessor :state
     alias :s :state
+    alias :st= :state=
 
     attr_accessor :country
     alias :c :country
+    alias :c= :country=
 
     attr_accessor :organization
     alias :o :organization
+    alias :o= :organization=
 
     attr_accessor :organizational_unit
     alias :ou :organizational_unit
+    alias :ou= :organizational_unit=
+
+    attr_accessor :email_address
+    alias :emailAddress :email_address
+    alias :emailAddress= :email_address=
+
+    attr_accessor :serial_number
+    alias :serialNumber :serial_number
+    alias :serialNumber= :serial_number=
 
     def to_x509_name
       raise "Invalid Distinguished Name" unless valid?
 
       # NB: the capitalization in the strings counts
       name = OpenSSL::X509::Name.new
-      name.add_entry("CN", common_name)
-      name.add_entry("O", organization) unless organization.blank?
-      name.add_entry("OU", organizational_unit) unless organizational_unit.blank?
+      name.add_entry("serialNumber", serial_number) unless serial_number.blank?
+      name.add_entry("C", country) unless country.blank?
       name.add_entry("ST", state) unless state.blank?
       name.add_entry("L", locality) unless locality.blank?
-      name.add_entry("C", country) unless country.blank?
+      name.add_entry("O", organization) unless organization.blank?
+      name.add_entry("OU", organizational_unit) unless organizational_unit.blank?
+      name.add_entry("CN", common_name)
+      name.add_entry("emailAddress", email_address) unless email_address.blank?
       name
     end
 
+    def ==(other)
+      # Use the established OpenSSL comparison
+      self.to_x509_name() == other.to_x509_name()
+    end
+
     def self.from_openssl openssl_name
       unless openssl_name.is_a? OpenSSL::X509::Name
         raise "Argument must be a OpenSSL::X509::Name"
       end
 
-      name = DistinguishedName.new
-      openssl_name.to_a.each do |k,v|
-        case k
-        when "CN" then name.common_name = v
-        when "L" then name.locality = v
-        when "ST" then name.state = v
-        when "C" then name.country = v
-        when "O" then name.organization = v
-        when "OU" then name.organizational_unit = v
+      WrappedDistinguishedName.new(openssl_name)
+    end
+  end
+
+  ## This is a significantly more complicated case. It's possible that
+  ## generically handled certificates will include custom OIDs in the
+  ## subject.
+  class WrappedDistinguishedName < DistinguishedName
+    attr_accessor :x509_name
+
+    def initialize(x509_name)
+      @x509_name = x509_name
+
+      subject = @x509_name.to_a
+      subject.each do |element|
+        field = element[0].downcase
+        value = element[1]
+        #type = element[2] ## -not used
+        method_sym = "#{field}=".to_sym
+        if self.respond_to?(method_sym)
+          self.send("#{field}=",value)
+        else
+          ## Custom OID
+          @custom_oids = true
         end
       end
-      name
+
+    end
+
+    def to_x509_name
+      @x509_name
+    end
+
+    def custom_oids?
+      @custom_oids
     end
   end
 end
diff --git a/lib/certificate_authority/extensions.rb b/lib/certificate_authority/extensions.rb
index 0c51552..2b9478b 100644
--- a/lib/certificate_authority/extensions.rb
+++ b/lib/certificate_authority/extensions.rb
@@ -5,6 +5,10 @@ module CertificateAuthority
         raise "Implementation required"
       end
 
+      def self.parse(value, critical)
+        raise "Implementation required"
+      end
+
       def config_extensions
         {}
       end
@@ -12,21 +16,47 @@ module CertificateAuthority
       def openssl_identifier
         raise "Implementation required"
       end
+
+      def ==(value)
+        raise "Implementation required"
+      end
     end
 
-    class BasicContraints
+    # Specifies whether an X.509v3 certificate can act as a CA, signing other
+    # certificates to be verified. If set, a path length constraint can also be
+    # specified.
+    # Reference: Section 4.2.1.10 of RFC3280
+    # http://tools.ietf.org/html/rfc3280#section-4.2.1.10
+    class BasicConstraints
+      OPENSSL_IDENTIFIER = "basicConstraints"
+
       include ExtensionAPI
-      include ActiveModel::Validations
+      include Validations
+
+      attr_accessor :critical
       attr_accessor :ca
       attr_accessor :path_len
-      validates :ca, :inclusion => [true,false]
+
+      def validate
+        unless [true, false].include? self.critical
+          errors.add :critical, 'must be true or false'
+        end
+        unless [true, false].include? self.ca
+          errors.add :ca, 'must be true or false'
+        end
+      end
 
       def initialize
-        self.ca = false
+        @critical = false
+        @ca = false
+      end
+
+      def openssl_identifier
+        OPENSSL_IDENTIFIER
       end
 
       def is_ca?
-        self.ca
+        @ca
       end
 
       def path_len=(value)
@@ -34,29 +64,54 @@ module CertificateAuthority
         @path_len = value
       end
 
-      def openssl_identifier
-        "basicConstraints"
+      def to_s
+        res = []
+        res << "CA:#{@ca}"
+        res << "pathlen:#{@path_len}" unless @path_len.nil?
+        res.join(',')
       end
 
-      def to_s
-        result = ""
-        result += "CA:#{self.ca}"
-        result += ",pathlen:#{self.path_len}" unless self.path_len.nil?
-        result
+      def ==(o)
+        o.class == self.class && o.state == state
+      end
+
+      def self.parse(value, critical)
+        obj = self.new
+        return obj if value.nil?
+        obj.critical = critical
+        value.split(/,\s*/).each do |v|
+          c = v.split(':', 2)
+          obj.ca = (c.last.upcase == "TRUE") if c.first == "CA"
+          obj.path_len = c.last.to_i if c.first == "pathlen"
+        end
+        obj
+      end
+
+      protected
+      def state
+        [@critical, at ca, at path_len]
       end
     end
 
+    # Specifies where CRL information be be retrieved. This extension isn't
+    # critical, but is recommended for proper CAs.
+    # Reference: Section 4.2.1.14 of RFC3280
+    # http://tools.ietf.org/html/rfc3280#section-4.2.1.14
     class CrlDistributionPoints
+      OPENSSL_IDENTIFIER = "crlDistributionPoints"
+
       include ExtensionAPI
 
-      attr_accessor :uri
+      attr_accessor :critical
+      attr_accessor :uris
 
       def initialize
-        # self.uri = "http://moo.crlendPoint.example.com/something.crl"
+        @critical = false
+        @uris = []
       end
 
       def openssl_identifier
-        "crlDistributionPoints"
+        OPENSSL_IDENTIFIER
       end
 
       ## NB: At this time it seems OpenSSL's extension handlers don't support
@@ -69,99 +124,302 @@ module CertificateAuthority
         }
       end
 
+      # This is for legacy support. Technically it can (and probably should)
+      # be an array. But if someone is calling the old accessor we shouldn't
+      # necessarily break it.
+      def uri=(value)
+        @uris << value
+      end
+
       def to_s
-        return "" if self.uri.nil?
-        "URI:#{self.uri}"
+        res = []
+        @uris.each do |uri|
+          res << "URI:#{uri}"
+        end
+        res.join(',')
+      end
+
+      def ==(o)
+        o.class == self.class && o.state == state
+      end
+
+      def self.parse(value, critical)
+        obj = self.new
+        return obj if value.nil?
+        obj.critical = critical
+        value.split(/,\s*/).each do |v|
+          c = v.split(':', 2)
+          obj.uris << c.last if c.first == "URI"
+        end
+        obj
+      end
+
+      protected
+      def state
+        [@critical, at uri]
       end
     end
 
+    # Identifies the public key associated with a given certificate.
+    # Should be required for "CA" certificates.
+    # Reference: Section 4.2.1.2 of RFC3280
+    # http://tools.ietf.org/html/rfc3280#section-4.2.1.2
     class SubjectKeyIdentifier
+      OPENSSL_IDENTIFIER = "subjectKeyIdentifier"
+
       include ExtensionAPI
+
+      attr_accessor :critical
+      attr_accessor :identifier
+
+      def initialize
+        @critical = false
+        @identifier = "hash"
+      end
+
       def openssl_identifier
-        "subjectKeyIdentifier"
+        OPENSSL_IDENTIFIER
       end
 
       def to_s
-        "hash"
+        res = []
+        res << @identifier
+        res.join(',')
+      end
+
+      def ==(o)
+        o.class == self.class && o.state == state
+      end
+
+      def self.parse(value, critical)
+        obj = self.new
+        return obj if value.nil?
+        obj.critical = critical
+        obj.identifier = value
+        obj
+      end
+
+      protected
+      def state
+        [@critical, at identifier]
       end
     end
 
+    # Identifies the public key associated with a given private key.
+    # Reference: Section 4.2.1.1 of RFC3280
+    # http://tools.ietf.org/html/rfc3280#section-4.2.1.1
     class AuthorityKeyIdentifier
+      OPENSSL_IDENTIFIER = "authorityKeyIdentifier"
+
       include ExtensionAPI
 
+      attr_accessor :critical
+      attr_accessor :identifier
+
+      def initialize
+        @critical = false
+        @identifier = ["keyid", "issuer"]
+      end
+
       def openssl_identifier
-        "authorityKeyIdentifier"
+        OPENSSL_IDENTIFIER
       end
 
       def to_s
-        "keyid,issuer"
+        res = []
+        res += @identifier
+        res.join(',')
+      end
+
+      def ==(o)
+        o.class == self.class && o.state == state
+      end
+
+      def self.parse(value, critical)
+        obj = self.new
+        return obj if value.nil?
+        obj.critical = critical
+        obj.identifier = value.split(/,\s*/).last.chomp
+        obj
+      end
+
+      protected
+      def state
+        [@critical, at identifier]
       end
     end
 
+    # Specifies how to access CA information and services for the CA that
+    # issued this certificate.
+    # Generally used to specify OCSP servers.
+    # Reference: Section 4.2.2.1 of RFC3280
+    # http://tools.ietf.org/html/rfc3280#section-4.2.2.1
     class AuthorityInfoAccess
+      OPENSSL_IDENTIFIER = "authorityInfoAccess"
+
       include ExtensionAPI
 
+      attr_accessor :critical
       attr_accessor :ocsp
+      attr_accessor :ca_issuers
 
       def initialize
-        self.ocsp = []
+        @critical = false
+        @ocsp = []
+        @ca_issuers = []
       end
 
       def openssl_identifier
-        "authorityInfoAccess"
+        OPENSSL_IDENTIFIER
       end
 
       def to_s
-        return "" if self.ocsp.empty?
-        "OCSP;URI:#{self.ocsp}"
+        res = []
+        res += @ocsp.map {|o| "OCSP;URI:#{o}" }
+        res += @ca_issuers.map {|c| "caIssuers;URI:#{c}" }
+        res.join(',')
+      end
+
+      def ==(o)
+        o.class == self.class && o.state == state
+      end
+
+      def self.parse(value, critical)
+        obj = self.new
+        return obj if value.nil?
+        obj.critical = critical
+        value.split("\n").each do |v|
+          if v =~ /^OCSP/
+            obj.ocsp << v.split.last
+          end
+
+          if v =~ /^CA Issuers/
+            obj.ca_issuers << v.split.last
+          end
+        end
+        obj
+      end
+
+      protected
+      def state
+        [@critical, at ocsp, at ca_issuers]
       end
     end
 
+    # Specifies the allowed usage purposes of the keypair specified in this certificate.
+    # Reference: Section 4.2.1.3 of RFC3280
+    # http://tools.ietf.org/html/rfc3280#section-4.2.1.3
+    #
+    # Note: OpenSSL when parsing an extension will return results in the form
+    # 'Digital Signature', but on signing you have to set it to 'digitalSignature'.
+    # So copying an extension from an imported cert isn't going to work yet.
     class KeyUsage
+      OPENSSL_IDENTIFIER = "keyUsage"
+
       include ExtensionAPI
 
+      attr_accessor :critical
       attr_accessor :usage
 
       def initialize
-        self.usage = ["digitalSignature", "nonRepudiation"]
+        @critical = false
+        @usage = ["digitalSignature", "nonRepudiation"]
       end
 
       def openssl_identifier
-        "keyUsage"
+        OPENSSL_IDENTIFIER
       end
 
       def to_s
-        "#{self.usage.join(',')}"
+        res = []
+        res += @usage
+        res.join(',')
+      end
+
+      def ==(o)
+        o.class == self.class && o.state == state
+      end
+
+      def self.parse(value, critical)
+        obj = self.new
+        return obj if value.nil?
+        obj.critical = critical
+        obj.usage = value.split(/,\s*/)
+        obj
+      end
+
+      protected
+      def state
+        [@critical, at usage]
       end
     end
 
+    # Specifies even more allowed usages in addition to what is specified in
+    # the Key Usage extension.
+    # Reference: Section 4.2.1.13 of RFC3280
+    # http://tools.ietf.org/html/rfc3280#section-4.2.1.13
     class ExtendedKeyUsage
+      OPENSSL_IDENTIFIER = "extendedKeyUsage"
+
       include ExtensionAPI
 
+      attr_accessor :critical
       attr_accessor :usage
 
       def initialize
-        self.usage = ["serverAuth","clientAuth"]
+        @critical = false
+        @usage = ["serverAuth"]
       end
 
       def openssl_identifier
-        "extendedKeyUsage"
+        OPENSSL_IDENTIFIER
       end
 
       def to_s
-        "#{self.usage.join(',')}"
+        res = []
+        res += @usage
+        res.join(',')
+      end
+
+      def ==(o)
+        o.class == self.class && o.state == state
+      end
+
+      def self.parse(value, critical)
+        obj = self.new
+        return obj if value.nil?
+        obj.critical = critical
+        obj.usage = value.split(/,\s*/)
+        obj
+      end
+
+      protected
+      def state
+        [@critical, at usage]
       end
     end
 
+    # Specifies additional "names" for which this certificate is valid.
+    # Reference: Section 4.2.1.7 of RFC3280
+    # http://tools.ietf.org/html/rfc3280#section-4.2.1.7
     class SubjectAlternativeName
+      OPENSSL_IDENTIFIER = "subjectAltName"
+
       include ExtensionAPI
 
-      attr_accessor :uris, :dns_names, :ips
+      attr_accessor :critical
+      attr_accessor :uris, :dns_names, :ips, :emails
 
       def initialize
-        self.uris = []
-        self.dns_names = []
-        self.ips = []
+        @critical = false
+        @uris = []
+        @dns_names = []
+        @ips = []
+        @emails = []
+      end
+
+      def openssl_identifier
+        OPENSSL_IDENTIFIER
       end
 
       def uris=(value)
@@ -179,22 +437,50 @@ module CertificateAuthority
         @ips = value
       end
 
-      def openssl_identifier
-        "subjectAltName"
+      def emails=(value)
+        raise "Emails must be an array" unless value.is_a?(Array)
+        @emails = value
       end
 
       def to_s
-        res =  self.uris.map {|u| "URI:#{u}" }
-        res += self.dns_names.map {|d| "DNS:#{d}" }
-        res += self.ips.map {|i| "IP:#{i}" }
+        res = []
+        res += @uris.map {|u| "URI:#{u}" }
+        res += @dns_names.map {|d| "DNS:#{d}" }
+        res += @ips.map {|i| "IP:#{i}" }
+        res += @emails.map {|i| "email:#{i}" }
+        res.join(',')
+      end
+
+      def ==(o)
+        o.class == self.class && o.state == state
+      end
+
+      def self.parse(value, critical)
+        obj = self.new
+        return obj if value.nil?
+        obj.critical = critical
+        value.split(/,\s*/).each do |v|
+          c = v.split(':', 2)
+          obj.uris << c.last if c.first == "URI"
+          obj.dns_names << c.last if c.first == "DNS"
+          obj.ips << c.last if c.first == "IP"
+          obj.emails << c.last if c.first == "EMAIL"
+        end
+        obj
+      end
 
-        return res.join(',')
+      protected
+      def state
+        [@critical, at uris, at dns_names, at ips, at emails]
       end
     end
 
     class CertificatePolicies
+      OPENSSL_IDENTIFIER = "certificatePolicies"
+
       include ExtensionAPI
 
+      attr_accessor :critical
       attr_accessor :policy_identifier
       attr_accessor :cps_uris
       ##User notice
@@ -203,12 +489,12 @@ module CertificateAuthority
       attr_accessor :notice_numbers
 
       def initialize
+        self.critical = false
         @contains_data = false
       end
 
-
       def openssl_identifier
-        "certificatePolicies"
+        OPENSSL_IDENTIFIER
       end
 
       def user_notice=(value={})
@@ -258,7 +544,93 @@ module CertificateAuthority
 
       def to_s
         return "" unless @contains_data
-        "ia5org, at custom_policies"
+        res = []
+        res << "ia5org"
+        res += @config_extensions["custom_policies"] unless @config_extensions.nil?
+        res.join(',')
+      end
+
+      def self.parse(value, critical)
+        obj = self.new
+        return obj if value.nil?
+        obj.critical = critical
+        value.split(/,\s*/).each do |v|
+          c = v.split(':', 2)
+          obj.policy_identifier = c.last if c.first == "policyIdentifier"
+          obj.cps_uris << c.last if c.first =~ %r{CPS.\d+}
+          # TODO: explicit_text, organization, notice_numbers
+        end
+        obj
+      end
+    end
+
+    # DEPRECATED
+    # Specifics the purposes for which a certificate can be used.
+    # The basicConstraints, keyUsage, and extendedKeyUsage extensions are now used instead.
+    # https://www.openssl.org/docs/apps/x509v3_config.html#Netscape_Certificate_Type
+    class NetscapeCertificateType
+      OPENSSL_IDENTIFIER = "nsCertType"
+
+      include ExtensionAPI
+
+      attr_accessor :critical
+      attr_accessor :flags
+
+      def initialize
+        self.critical = false
+        self.flags = []
+      end
+
+      def openssl_identifier
+        OPENSSL_IDENTIFIER
+      end
+
+      def to_s
+        res = []
+        res += self.flags
+        res.join(',')
+      end
+
+      def self.parse(value, critical)
+        obj = self.new
+        return obj if value.nil?
+        obj.critical = critical
+        obj.flags = value.split(/,\s*/)
+        obj
+      end
+    end
+
+    # DEPRECATED
+    # Contains a comment which will be displayed when the certificate is viewed in some browsers.
+    # https://www.openssl.org/docs/apps/x509v3_config.html#Netscape_String_extensions_
+    class NetscapeComment
+      OPENSSL_IDENTIFIER = "nsComment"
+
+      include ExtensionAPI
+
+      attr_accessor :critical
+      attr_accessor :comment
+
+      def initialize
+        self.critical = false
+      end
+
+      def openssl_identifier
+        OPENSSL_IDENTIFIER
+      end
+
+      def to_s
+        res = []
+        res << self.comment if self.comment
+        res.join(',')
+      end
+
+      def self.parse(value, critical)
+        obj = self.new
+        return obj if value.nil?
+        obj.critical = critical
+        obj.comment = value
+        obj
       end
     end
 
diff --git a/lib/certificate_authority/key_material.rb b/lib/certificate_authority/key_material.rb
index 13d0997..ae3a530 100644
--- a/lib/certificate_authority/key_material.rb
+++ b/lib/certificate_authority/key_material.rb
@@ -15,11 +15,30 @@ module CertificateAuthority
     def is_in_memory?
       raise "Required implementation"
     end
+
+    def self.from_x509_key_pair(pair,password=nil)
+      if password.nil?
+        key = OpenSSL::PKey::RSA.new(pair)
+      else
+        key = OpenSSL::PKey::RSA.new(pair,password)
+      end
+      mem_key = MemoryKeyMaterial.new
+      mem_key.public_key = key.public_key
+      mem_key.private_key = key
+      mem_key
+    end
+
+    def self.from_x509_public_key(public_key_pem)
+      key = OpenSSL::PKey::RSA.new(public_key_pem)
+      signing_request_key = SigningRequestKeyMaterial.new
+      signing_request_key.public_key = key.public_key
+      signing_request_key
+    end
   end
 
   class MemoryKeyMaterial
     include KeyMaterial
-    include ActiveModel::Validations
+    include Validations
 
     attr_accessor :keypair
     attr_accessor :private_key
@@ -28,11 +47,13 @@ module CertificateAuthority
     def initialize
     end
 
-    validates_each :private_key do |record, attr, value|
-        record.errors.add :private_key, "cannot be blank" if record.private_key.nil?
-    end
-    validates_each :public_key do |record, attr, value|
-      record.errors.add :public_key, "cannot be blank" if record.public_key.nil?
+    def validate
+      if private_key.nil?
+        errors.add :private_key, "cannot be blank"
+      end
+      if public_key.nil?
+        errors.add :public_key, "cannot be blank"
+      end
     end
 
     def is_in_hardware?
@@ -61,10 +82,10 @@ module CertificateAuthority
 
   class SigningRequestKeyMaterial
     include KeyMaterial
-    include ActiveModel::Validations
+    include Validations
 
-    validates_each :public_key do |record, attr, value|
-      record.errors.add :public_key, "cannot be blank" if record.public_key.nil?
+    def validate
+      errors.add :public_key, "cannot be blank" if public_key.nil?
     end
 
     attr_accessor :public_key
diff --git a/lib/certificate_authority/ocsp_handler.rb b/lib/certificate_authority/ocsp_handler.rb
index 776a714..0f2661c 100644
--- a/lib/certificate_authority/ocsp_handler.rb
+++ b/lib/certificate_authority/ocsp_handler.rb
@@ -1,6 +1,74 @@
 module CertificateAuthority
+  class OCSPResponseBuilder
+    attr_accessor :ocsp_response
+    attr_accessor :verification_mechanism
+    attr_accessor :ocsp_request_reader
+    attr_accessor :parent
+    attr_accessor :next_update
+
+    GOOD = OpenSSL::OCSP::V_CERTSTATUS_GOOD
+    REVOKED = OpenSSL::OCSP::V_CERTSTATUS_REVOKED
+
+    NO_REASON=0
+    KEY_COMPROMISED=OpenSSL::OCSP::REVOKED_STATUS_KEYCOMPROMISE
+    UNSPECIFIED=OpenSSL::OCSP::REVOKED_STATUS_UNSPECIFIED
+
+    def build_response()
+      raise "Requires a parent for signing" if @parent.nil?
+      if @verification_mechanism.nil?
+        ## If no verification callback is provided we're marking it GOOD
+        @verification_mechanism = lambda {|cert_id| [GOOD,NO_REASON] }
+      end
+
+      @ocsp_request_reader.ocsp_request.certid.each do |cert_id|
+        result,reason = verification_mechanism.call(cert_id.serial)
+
+        ## cert_id, status, reason, rev_time, this update, next update, ext
+        ## - unit of time is seconds
+        ## - rev_time is currently set to "now"
+        @ocsp_response.add_status(cert_id,
+        result, reason,
+          0, 0, @next_update, nil)
+      end
+
+      @ocsp_response.sign(OpenSSL::X509::Certificate.new(@parent.to_pem), @parent.key_material.private_key, nil, nil)
+      OpenSSL::OCSP::Response.create(OpenSSL::OCSP::RESPONSE_STATUS_SUCCESSFUL, @ocsp_response)
+    end
+
+    def self.from_request_reader(request_reader,verification_mechanism=nil)
+      response_builder = OCSPResponseBuilder.new
+      response_builder.ocsp_request_reader = request_reader
+
+      ocsp_response = OpenSSL::OCSP::BasicResponse.new
+      ocsp_response.copy_nonce(request_reader.ocsp_request)
+      response_builder.ocsp_response = ocsp_response
+      response_builder.next_update = 60*15 #Default of 15 minutes
+      response_builder
+    end
+  end
+
+  class OCSPRequestReader
+    attr_accessor :raw_ocsp_request
+    attr_accessor :ocsp_request
+
+    def serial_numbers
+      @ocsp_request.certid.collect do |cert_id|
+        cert_id.serial
+      end
+    end
+
+    def self.from_der(request_body)
+      reader = OCSPRequestReader.new
+      reader.raw_ocsp_request = request_body
+      reader.ocsp_request = OpenSSL::OCSP::Request.new(request_body)
+
+      reader
+    end
+  end
+
+  ## DEPRECATED
   class OCSPHandler
-    include ActiveModel::Validations
+    include Validations
 
     attr_accessor :ocsp_request
     attr_accessor :certificate_ids
@@ -10,10 +78,10 @@ module CertificateAuthority
 
     attr_accessor :ocsp_response_body
 
-    validate do |crl|
+    def validate
       errors.add :parent, "A parent entity must be set" if parent.nil?
+      all_certificates_available
     end
-    validate :all_certificates_available
 
     def initialize
       self.certificates = {}
@@ -24,8 +92,7 @@ module CertificateAuthority
     end
 
     def extract_certificate_serials
-      raise "No valid OCSP request was supplied" if self.ocsp_request.nil?
-      openssl_request = OpenSSL::OCSP::Request.new(self.ocsp_request)
+      openssl_request = OpenSSL::OCSP::Request.new(@ocsp_request)
 
       self.certificate_ids = openssl_request.certid.collect do |cert_id|
         cert_id.serial
diff --git a/lib/certificate_authority/pkcs11_key_material.rb b/lib/certificate_authority/pkcs11_key_material.rb
index d4ebc47..8a83f0e 100644
--- a/lib/certificate_authority/pkcs11_key_material.rb
+++ b/lib/certificate_authority/pkcs11_key_material.rb
@@ -1,8 +1,6 @@
 module CertificateAuthority
   class Pkcs11KeyMaterial
     include KeyMaterial
-    include ActiveModel::Validations
-    include ActiveModel::Serialization
 
     attr_accessor :engine
     attr_accessor :token_id
diff --git a/lib/certificate_authority/revocable.rb b/lib/certificate_authority/revocable.rb
new file mode 100644
index 0000000..eba5d98
--- /dev/null
+++ b/lib/certificate_authority/revocable.rb
@@ -0,0 +1,14 @@
+module CertificateAuthority
+  module Revocable
+    attr_accessor :revoked_at
+
+    def revoke!(time=Time.now)
+      @revoked_at = time
+    end
+
+    def revoked?
+      # If we have a time, then we're revoked
+      !@revoked_at.nil?
+    end
+  end
+end
diff --git a/lib/certificate_authority/serial_number.rb b/lib/certificate_authority/serial_number.rb
index c00fa21..99f3002 100644
--- a/lib/certificate_authority/serial_number.rb
+++ b/lib/certificate_authority/serial_number.rb
@@ -1,9 +1,22 @@
+require 'securerandom'
+
 module CertificateAuthority
   class SerialNumber
-    include ActiveModel::Validations
+    include Validations
+    include Revocable
 
     attr_accessor :number
 
-    validates :number, :presence => true, :numericality => {:greater_than => 0}
+    def validate
+      if self.number.nil?
+        errors.add :number, "must not be empty"
+      elsif self.number.to_i <= 0
+        errors.add :number, "must be greater than zero"
+      end
+    end
+
+    def initialize
+      self.number = SecureRandom.random_number(2**128-1)
+    end
   end
 end
diff --git a/lib/certificate_authority/signing_request.rb b/lib/certificate_authority/signing_request.rb
new file mode 100644
index 0000000..3584dac
--- /dev/null
+++ b/lib/certificate_authority/signing_request.rb
@@ -0,0 +1,91 @@
+module CertificateAuthority
+  class SigningRequest
+    attr_accessor :distinguished_name
+    attr_accessor :key_material
+    attr_accessor :raw_body
+    attr_accessor :openssl_csr
+    attr_accessor :digest
+    attr_accessor :attributes
+
+    def initialize()
+      @attributes = []
+    end
+
+    # Fake attribute for convenience because adding
+    # alternative names on a CSR is remarkably non-trivial.
+    def subject_alternative_names=(alt_names)
+      raise "alt_names must be an Array" unless alt_names.is_a?(Array)
+
+      factory = OpenSSL::X509::ExtensionFactory.new
+      name_list = alt_names.map{|m| "DNS:#{m}"}.join(",")
+      ext = factory.create_ext("subjectAltName",name_list,false)
+      ext_set = OpenSSL::ASN1::Set([OpenSSL::ASN1::Sequence([ext])])
+      attr = OpenSSL::X509::Attribute.new("extReq", ext_set)
+      @attributes << attr
+    end
+
+    def read_attributes_by_oid(*oids)
+      attributes.detect { |a| oids.include?(a.oid) }
+    end
+    protected :read_attributes_by_oid
+
+    def to_cert
+      cert = Certificate.new
+      if !@distinguished_name.nil?
+        cert.distinguished_name = @distinguished_name
+      end
+      cert.key_material = @key_material
+      if attribute = read_attributes_by_oid('extReq', 'msExtReq')
+        set = OpenSSL::ASN1.decode(attribute.value)
+        seq = set.value.first
+        seq.value.collect { |asn1ext| OpenSSL::X509::Extension.new(asn1ext).to_a }.each do |o, v, c|
+         Certificate::EXTENSIONS.each do |klass|
+            cert.extensions[klass::OPENSSL_IDENTIFIER] = klass.parse(v, c) if v && klass::OPENSSL_IDENTIFIER == o
+          end
+        end
+      end
+      cert
+    end
+
+    def to_pem
+      to_x509_csr.to_pem
+    end
+
+    def to_x509_csr
+      raise "Must specify a DN/subject on csr" if @distinguished_name.nil?
+      raise "Invalid DN in request" unless @distinguished_name.valid?
+      raise "CSR must have key material" if @key_material.nil?
+      raise "CSR must include a public key on key material" if @key_material.public_key.nil?
+      raise "Need a private key on key material for CSR generation" if @key_material.private_key.nil?
+
+      opensslcsr = OpenSSL::X509::Request.new
+      opensslcsr.subject = @distinguished_name.to_x509_name
+      opensslcsr.public_key = @key_material.public_key
+      opensslcsr.attributes = @attributes unless @attributes.nil?
+      opensslcsr.sign @key_material.private_key, OpenSSL::Digest.new(@digest || "SHA512")
+      opensslcsr
+    end
+
+    def self.from_x509_csr(raw_csr)
+      csr = SigningRequest.new
+      openssl_csr = OpenSSL::X509::Request.new(raw_csr)
+      csr.distinguished_name = DistinguishedName.from_openssl openssl_csr.subject
+      csr.raw_body = raw_csr
+      csr.openssl_csr = openssl_csr
+      csr.attributes = openssl_csr.attributes
+      key_material = SigningRequestKeyMaterial.new
+      key_material.public_key = openssl_csr.public_key
+      csr.key_material = key_material
+      csr
+    end
+
+    def self.from_netscape_spkac(raw_spkac)
+      openssl_spkac = OpenSSL::Netscape::SPKI.new raw_spkac
+      csr = SigningRequest.new
+      csr.raw_body = raw_spkac
+      key_material = SigningRequestKeyMaterial.new
+      key_material.public_key = openssl_spkac.public_key
+      csr
+    end
+  end
+end
diff --git a/lib/certificate_authority/validations.rb b/lib/certificate_authority/validations.rb
new file mode 100644
index 0000000..a429c96
--- /dev/null
+++ b/lib/certificate_authority/validations.rb
@@ -0,0 +1,31 @@
+#
+# This is a super simple replacement for ActiveSupport::Validations
+#
+
+module CertificateAuthority
+  class Errors < Array
+    def add(symbol, msg)
+      self.push([symbol, msg])
+    end
+    def full_messages
+      self.map {|i| i[0].to_s + ": " + i[1]}.join("\n")
+    end
+  end
+
+  module Validations
+    def valid?
+      @errors = Errors.new
+      validate
+      errors.empty?
+    end
+
+    # must be overridden
+    def validate
+      raise NotImplementedError
+    end
+
+    def errors
+      @errors ||= Errors.new
+    end
+  end
+end
diff --git a/metadata.yml b/metadata.yml
deleted file mode 100644
index 12b460a..0000000
--- a/metadata.yml
+++ /dev/null
@@ -1,111 +0,0 @@
---- !ruby/object:Gem::Specification
-name: certificate_authority
-version: !ruby/object:Gem::Version
-  version: 0.1.6
-  prerelease: 
-platform: ruby
-authors:
-- Chris Chandler
-autorequire: 
-bindir: bin
-cert_chain: []
-date: 2012-08-12 00:00:00.000000000 Z
-dependencies:
-- !ruby/object:Gem::Dependency
-  name: activemodel
-  requirement: &70182567433260 !ruby/object:Gem::Requirement
-    none: false
-    requirements:
-    - - ! '>='
-      - !ruby/object:Gem::Version
-        version: 3.0.6
-  type: :runtime
-  prerelease: false
-  version_requirements: *70182567433260
-- !ruby/object:Gem::Dependency
-  name: rspec
-  requirement: &70182567432240 !ruby/object:Gem::Requirement
-    none: false
-    requirements:
-    - - ! '>='
-      - !ruby/object:Gem::Version
-        version: '0'
-  type: :development
-  prerelease: false
-  version_requirements: *70182567432240
-- !ruby/object:Gem::Dependency
-  name: jeweler
-  requirement: &70182567430960 !ruby/object:Gem::Requirement
-    none: false
-    requirements:
-    - - ! '>='
-      - !ruby/object:Gem::Version
-        version: 1.5.2
-  type: :development
-  prerelease: false
-  version_requirements: *70182567430960
-description: 
-email: chris at flatterline.com
-executables: []
-extensions: []
-extra_rdoc_files:
-- README.rdoc
-files:
-- Gemfile
-- Gemfile.lock
-- README.rdoc
-- Rakefile
-- VERSION.yml
-- certificate_authority.gemspec
-- lib/certificate_authority.rb
-- lib/certificate_authority/certificate.rb
-- lib/certificate_authority/certificate_revocation_list.rb
-- lib/certificate_authority/distinguished_name.rb
-- lib/certificate_authority/extensions.rb
-- lib/certificate_authority/key_material.rb
-- lib/certificate_authority/ocsp_handler.rb
-- lib/certificate_authority/pkcs11_key_material.rb
-- lib/certificate_authority/serial_number.rb
-- lib/certificate_authority/signing_entity.rb
-- lib/tasks/certificate_authority.rake
-- spec/spec_helper.rb
-- spec/units/certificate_authority_spec.rb
-- spec/units/certificate_revocation_list_spec.rb
-- spec/units/certificate_spec.rb
-- spec/units/distinguished_name_spec.rb
-- spec/units/extensions_spec.rb
-- spec/units/key_material_spec.rb
-- spec/units/ocsp_handler_spec.rb
-- spec/units/pkcs11_key_material_spec.rb
-- spec/units/serial_number_spec.rb
-- spec/units/signing_entity_spec.rb
-- spec/units/units_helper.rb
-homepage: http://github.com/cchandler/certificate_authority
-licenses:
-- MIT
-post_install_message: 
-rdoc_options: []
-require_paths:
-- lib
-required_ruby_version: !ruby/object:Gem::Requirement
-  none: false
-  requirements:
-  - - ! '>='
-    - !ruby/object:Gem::Version
-      version: '0'
-      segments:
-      - 0
-      hash: -2366790866817530073
-required_rubygems_version: !ruby/object:Gem::Requirement
-  none: false
-  requirements:
-  - - ! '>='
-    - !ruby/object:Gem::Version
-      version: '0'
-requirements: []
-rubyforge_project: 
-rubygems_version: 1.8.15
-signing_key: 
-specification_version: 3
-summary: Ruby gem for managing the core functions outlined in RFC-3280 for PKI
-test_files: []
diff --git a/spec/samples/certs/DigiCertHighAssuranceEVCA-1.pem b/spec/samples/certs/DigiCertHighAssuranceEVCA-1.pem
new file mode 100644
index 0000000..5122b60
--- /dev/null
+++ b/spec/samples/certs/DigiCertHighAssuranceEVCA-1.pem
@@ -0,0 +1,115 @@
+Certificate:
+    Data:
+        Version: 3 (0x2)
+        Serial Number:
+            08:bb:b0:25:47:13:4b:c9:b1:10:d7:c1:a2:12:59:c5
+        Signature Algorithm: sha1WithRSAEncryption
+        Issuer: C=US, O=DigiCert Inc, OU=www.digicert.com, CN=DigiCert High Assurance EV Root CA
+        Validity
+            Not Before: Nov 10 00:00:00 2006 GMT
+            Not After : Nov 10 00:00:00 2021 GMT
+        Subject: C=US, O=DigiCert Inc, OU=www.digicert.com, CN=DigiCert High Assurance EV CA-1
+        Subject Public Key Info:
+            Public Key Algorithm: rsaEncryption
+            RSA Public Key: (2048 bit)
+                Modulus (2048 bit):
+                    00:f3:96:62:d8:75:6e:19:ff:3f:34:7c:49:4f:31:
+                    7e:0d:04:4e:99:81:e2:b3:85:55:91:30:b1:c0:af:
+                    70:bb:2c:a8:e7:18:aa:3f:78:f7:90:68:52:86:01:
+                    88:97:e2:3b:06:65:90:aa:bd:65:76:c2:ec:be:10:
+                    5b:37:78:83:60:75:45:c6:bd:74:aa:b6:9f:a4:3a:
+                    01:50:17:c4:39:69:b9:f1:4f:ef:82:c1:ca:f3:4a:
+                    db:cc:9e:50:4f:4d:40:a3:3a:90:e7:86:66:bc:f0:
+                    3e:76:28:4c:d1:75:80:9e:6a:35:14:35:03:9e:db:
+                    0c:8c:c2:28:ad:50:b2:ce:f6:91:a3:c3:a5:0a:58:
+                    49:f6:75:44:6c:ba:f9:ce:e9:ab:3a:02:e0:4d:f3:
+                    ac:e2:7a:e0:60:22:05:3c:82:d3:52:e2:f3:9c:47:
+                    f8:3b:d8:b2:4b:93:56:4a:bf:70:ab:3e:e9:68:c8:
+                    1d:8f:58:1d:2a:4d:5e:27:3d:ad:0a:59:2f:5a:11:
+                    20:40:d9:68:04:68:2d:f4:c0:84:0b:0a:1b:78:df:
+                    ed:1a:58:dc:fb:41:5a:6d:6b:f2:ed:1c:ee:5c:32:
+                    b6:5c:ec:d7:a6:03:32:a6:e8:de:b7:28:27:59:88:
+                    80:ff:7b:ad:89:58:d5:1e:14:a4:f2:b0:70:d4:a0:
+                    3e:a7
+                Exponent: 65537 (0x10001)
+        X509v3 extensions:
+            X509v3 Key Usage: critical
+                Digital Signature, Certificate Sign, CRL Sign
+            X509v3 Extended Key Usage: 
+                TLS Web Server Authentication, TLS Web Client Authentication, Code Signing, E-mail Protection, Time Stamping
+            X509v3 Certificate Policies: 
+                Policy: 2.16.840.1.114412.2.1
+                  CPS: http://www.digicert.com/ssl-cps-repository.htm
+                  User Notice:
+                    Explicit Text: 
+
+            X509v3 Basic Constraints: critical
+                CA:TRUE
+            Authority Information Access: 
+                OCSP - URI:http://ocsp.digicert.com
+                CA Issuers - URI:http://www.digicert.com/CACerts/DigiCertHighAssuranceEVRootCA.crt
+
+            X509v3 CRL Distribution Points: 
+                URI:http://crl3.digicert.com/DigiCertHighAssuranceEVRootCA.crl
+                URI:http://crl4.digicert.com/DigiCertHighAssuranceEVRootCA.crl
+
+            X509v3 Subject Key Identifier: 
+                4C:58:CB:25:F0:41:4F:52:F4:28:C8:81:43:9B:A6:A8:A0:E6:92:E5
+            X509v3 Authority Key Identifier: 
+                keyid:B1:3E:C3:69:03:F8:BF:47:01:D4:98:26:1A:08:02:EF:63:64:2B:C3
+
+    Signature Algorithm: sha1WithRSAEncryption
+        50:1e:43:b0:f7:4d:29:96:5b:bb:a7:d3:0a:b5:b5:d5:d0:27:
+        aa:f9:af:c7:25:d1:95:d5:2f:5a:53:bd:42:07:7e:78:49:ca:
+        0b:eb:4c:55:e2:ea:2f:7f:49:ad:c7:ff:d1:2d:3e:9c:a0:64:
+        2b:51:9e:91:26:28:bb:87:bb:75:7c:bc:a1:fd:66:68:2e:4c:
+        4a:16:cc:fe:06:cf:31:ea:80:6e:e4:bd:e8:03:72:f6:25:b5:
+        41:83:61:d0:97:0a:27:1d:b3:f7:2b:32:84:8f:5b:e7:cc:3f:
+        e2:2c:67:86:94:f4:b2:2b:6c:52:3b:67:2a:8d:58:95:00:14:
+        46:24:ac:0b:fa:c9:8e:c7:26:80:df:d1:e1:97:e3:f8:bb:68:
+        c6:9c:bd:be:08:54:3b:10:32:7c:81:1f:2b:28:95:a8:41:0a:
+        c6:d0:30:66:b4:e9:f2:a2:00:69:20:07:ca:82:4c:1e:cf:a7:
+        98:b8:0c:ee:cd:16:1c:be:1a:63:d4:c0:99:f6:67:b2:f0:8e:
+        17:2d:58:c2:80:aa:5d:96:c7:b3:28:ed:f0:da:8e:b6:47:1b:
+        8f:4e:15:f1:97:4c:0b:4b:af:81:d4:46:94:62:2c:43:a7:3c:
+        25:48:19:63:f2:5c:aa:15:89:76:84:85:73:91:7d:28:3c:09:
+        83:82:bc:f7
+-----BEGIN CERTIFICATE-----
+MIIG4zCCBcugAwIBAgIQCLuwJUcTS8mxENfBohJZxTANBgkqhkiG9w0BAQUFADBs
+MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3
+d3cuZGlnaWNlcnQuY29tMSswKQYDVQQDEyJEaWdpQ2VydCBIaWdoIEFzc3VyYW5j
+ZSBFViBSb290IENBMB4XDTA2MTExMDAwMDAwMFoXDTIxMTExMDAwMDAwMFowaTEL
+MAkGA1UEBhMCVVMxFTATBgNVBAoTDERpZ2lDZXJ0IEluYzEZMBcGA1UECxMQd3d3
+LmRpZ2ljZXJ0LmNvbTEoMCYGA1UEAxMfRGlnaUNlcnQgSGlnaCBBc3N1cmFuY2Ug
+RVYgQ0EtMTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAPOWYth1bhn/
+PzR8SU8xfg0ETpmB4rOFVZEwscCvcLssqOcYqj9495BoUoYBiJfiOwZlkKq9ZXbC
+7L4QWzd4g2B1Rca9dKq2n6Q6AVAXxDlpufFP74LByvNK28yeUE9NQKM6kOeGZrzw
+PnYoTNF1gJ5qNRQ1A57bDIzCKK1Qss72kaPDpQpYSfZ1RGy6+c7pqzoC4E3zrOJ6
+4GAiBTyC01Li85xH+DvYskuTVkq/cKs+6WjIHY9YHSpNXic9rQpZL1oRIEDZaARo
+LfTAhAsKG3jf7RpY3PtBWm1r8u0c7lwytlzs16YDMqbo3rcoJ1mIgP97rYlY1R4U
+pPKwcNSgPqcCAwEAAaOCA4IwggN+MA4GA1UdDwEB/wQEAwIBhjA7BgNVHSUENDAy
+BggrBgEFBQcDAQYIKwYBBQUHAwIGCCsGAQUFBwMDBggrBgEFBQcDBAYIKwYBBQUH
+AwgwggHEBgNVHSAEggG7MIIBtzCCAbMGCWCGSAGG/WwCATCCAaQwOgYIKwYBBQUH
+AgEWLmh0dHA6Ly93d3cuZGlnaWNlcnQuY29tL3NzbC1jcHMtcmVwb3NpdG9yeS5o
+dG0wggFkBggrBgEFBQcCAjCCAVYeggFSAEEAbgB5ACAAdQBzAGUAIABvAGYAIAB0
+AGgAaQBzACAAQwBlAHIAdABpAGYAaQBjAGEAdABlACAAYwBvAG4AcwB0AGkAdAB1
+AHQAZQBzACAAYQBjAGMAZQBwAHQAYQBuAGMAZQAgAG8AZgAgAHQAaABlACAARABp
+AGcAaQBDAGUAcgB0ACAARQBWACAAQwBQAFMAIABhAG4AZAAgAHQAaABlACAAUgBl
+AGwAeQBpAG4AZwAgAFAAYQByAHQAeQAgAEEAZwByAGUAZQBtAGUAbgB0ACAAdwBo
+AGkAYwBoACAAbABpAG0AaQB0ACAAbABpAGEAYgBpAGwAaQB0AHkAIABhAG4AZAAg
+AGEAcgBlACAAaQBuAGMAbwByAHAAbwByAGEAdABlAGQAIABoAGUAcgBlAGkAbgAg
+AGIAeQAgAHIAZQBmAGUAcgBlAG4AYwBlAC4wDwYDVR0TAQH/BAUwAwEB/zCBgwYI
+KwYBBQUHAQEEdzB1MCQGCCsGAQUFBzABhhhodHRwOi8vb2NzcC5kaWdpY2VydC5j
+b20wTQYIKwYBBQUHMAKGQWh0dHA6Ly93d3cuZGlnaWNlcnQuY29tL0NBQ2VydHMv
+RGlnaUNlcnRIaWdoQXNzdXJhbmNlRVZSb290Q0EuY3J0MIGPBgNVHR8EgYcwgYQw
+QKA+oDyGOmh0dHA6Ly9jcmwzLmRpZ2ljZXJ0LmNvbS9EaWdpQ2VydEhpZ2hBc3N1
+cmFuY2VFVlJvb3RDQS5jcmwwQKA+oDyGOmh0dHA6Ly9jcmw0LmRpZ2ljZXJ0LmNv
+bS9EaWdpQ2VydEhpZ2hBc3N1cmFuY2VFVlJvb3RDQS5jcmwwHQYDVR0OBBYEFExY
+yyXwQU9S9CjIgUObpqig5pLlMB8GA1UdIwQYMBaAFLE+w2kD+L9HAdSYJhoIAu9j
+ZCvDMA0GCSqGSIb3DQEBBQUAA4IBAQBQHkOw900pllu7p9MKtbXV0Ceq+a/HJdGV
+1S9aU71CB354ScoL60xV4uovf0mtx//RLT6coGQrUZ6RJii7h7t1fLyh/WZoLkxK
+Fsz+Bs8x6oBu5L3oA3L2JbVBg2HQlwonHbP3KzKEj1vnzD/iLGeGlPSyK2xSO2cq
+jViVABRGJKwL+smOxyaA39Hhl+P4u2jGnL2+CFQ7EDJ8gR8rKJWoQQrG0DBmtOny
+ogBpIAfKgkwez6eYuAzuzRYcvhpj1MCZ9mey8I4XLVjCgKpdlsezKO3w2o62RxuP
+ThXxl0wLS6+B1EaUYixDpzwlSBlj8lyqFYl2hIVzkX0oPAmDgrz3
+-----END CERTIFICATE-----
diff --git a/spec/samples/certs/apple_wwdr_issued_cert.pem b/spec/samples/certs/apple_wwdr_issued_cert.pem
new file mode 100644
index 0000000..20eb993
--- /dev/null
+++ b/spec/samples/certs/apple_wwdr_issued_cert.pem
@@ -0,0 +1,112 @@
+Certificate:
+    Data:
+        Version: 3 (0x2)
+        Serial Number:
+            0f:50:11:d8:8f:07:09:bf
+        Signature Algorithm: sha1WithRSAEncryption
+        Issuer: C=US, O=Apple Inc., OU=Apple Worldwide Developer Relations, CN=Apple Worldwide Developer Relations Certification Authority
+        Validity
+            Not Before: Jun 26 21:18:40 2012 GMT
+            Not After : Jun 26 21:18:40 2013 GMT
+        Subject: UID=pass.void-star.com.meepedoo, CN=Pass Type ID: pass.void-star.com.meepedoo, OU=UL736KYQR9, O=Eric Monti, C=US
+        Subject Public Key Info:
+            Public Key Algorithm: rsaEncryption
+            RSA Public Key: (2048 bit)
+                Modulus (2048 bit):
+                    00:af:f9:a6:9f:c0:8b:a5:56:f2:b6:97:0a:86:42:
+                    d0:f1:54:01:98:95:9f:d9:69:2b:9c:be:b0:b5:f4:
+                    a4:ad:9e:e6:ef:8e:a5:dc:50:d0:ce:2a:89:a9:41:
+                    ce:44:36:af:90:33:e7:56:76:9e:68:91:df:c6:e7:
+                    b8:21:f2:d5:75:d2:2a:17:3a:9d:4a:e0:cc:d2:94:
+                    90:e7:f2:36:2f:1c:41:00:02:76:45:fe:c2:6a:fc:
+                    36:96:e7:7e:59:00:f2:85:9e:31:ff:a3:9b:a0:b8:
+                    6d:95:9e:e4:f1:c4:d0:e9:7c:70:61:52:03:39:5c:
+                    b8:8a:34:69:22:82:c5:44:f9:cd:a1:25:57:26:86:
+                    e4:31:d5:08:c9:9d:5f:73:44:10:21:6d:99:90:74:
+                    f6:69:fb:20:de:a9:46:49:a3:a9:96:ab:66:44:e6:
+                    bd:56:65:8e:7d:dd:07:7e:71:bd:13:0f:b1:50:07:
+                    af:eb:71:78:af:46:d5:71:39:da:ed:f2:d1:db:8d:
+                    81:64:7f:56:c6:87:f1:7e:a5:a3:f4:9f:02:01:b9:
+                    d0:36:7f:87:f2:0d:d6:93:4e:cb:d8:32:9c:5a:ea:
+                    44:07:64:6a:9f:1c:14:8e:9a:3a:0e:a3:86:2c:e8:
+                    20:2a:f1:32:e0:11:2a:13:8a:d3:4c:73:5e:ab:70:
+                    fa:4d
+                Exponent: 65537 (0x10001)
+        X509v3 extensions:
+            Authority Information Access: 
+                OCSP - URI:http://ocsp.apple.com/ocsp-wwdr03
+
+            X509v3 Subject Key Identifier: 
+                02:87:24:00:CA:53:38:8F:C3:4D:5C:80:98:E1:65:95:38:D5:2B:69
+            X509v3 Basic Constraints: 
+                CA:FALSE
+            X509v3 Authority Key Identifier: 
+                keyid:88:27:17:09:A9:B6:18:60:8B:EC:EB:BA:F6:47:59:C5:52:54:A3:B7
+
+            X509v3 Certificate Policies: 
+                Policy: 1.2.840.113635.100.5.1
+                  CPS: http://www.apple.com/appleca/
+                  User Notice:
+                    Explicit Text: Reliance on this certificate by any party assumes acceptance of the then applicable standard terms and conditions of use, certificate policy and certification practice statements.
+
+            X509v3 CRL Distribution Points: 
+                URI:http://crl.apple.com/wwdrca.crl
+
+            X509v3 Key Usage: 
+                Digital Signature
+            X509v3 Extended Key Usage: 
+                TLS Web Client Authentication, 1.2.840.113635.100.4.14
+            1.2.840.113635.100.6.3.2: 
+                ..
+            1.2.840.113635.100.6.1.16: 
+                ..pass.void-star.com.meepedoo
+    Signature Algorithm: sha1WithRSAEncryption
+        5d:e8:2d:5a:d7:65:9e:92:dd:bf:66:1c:04:99:08:a9:40:b7:
+        92:dc:fa:d4:c8:8c:cf:ad:3a:99:22:34:0c:0f:72:c9:4f:7f:
+        c5:90:dc:8a:5d:47:c0:dd:ee:47:f7:01:81:6a:06:61:66:c3:
+        44:a1:0b:96:e5:70:a2:2f:c3:bb:98:d0:bf:07:0a:3d:56:d1:
+        95:01:08:16:6e:9a:5e:6b:45:ce:d9:b5:78:09:0f:eb:ff:11:
+        a6:9a:eb:65:f3:b3:b1:14:a5:6f:97:a1:53:31:65:a6:e0:ea:
+        e6:70:2f:df:5a:f9:b5:e4:59:2b:33:d4:a0:a3:4c:c6:61:c8:
+        56:5a:ca:be:4e:ac:12:c7:d3:1e:e5:b6:e3:de:04:c4:63:e5:
+        0e:33:4d:b9:33:92:7e:11:a4:ee:85:2b:65:00:7f:a5:dc:f6:
+        19:5b:69:37:fe:61:a7:e6:45:27:c5:1c:3a:b6:46:76:fb:f3:
+        56:93:00:2a:4f:b4:1e:d3:ed:75:8c:32:e5:09:c8:28:84:46:
+        29:4d:db:8f:e9:6e:1c:9e:bb:76:74:6a:8f:63:d5:04:1a:b3:
+        16:42:cc:70:4c:b1:88:e2:6d:58:bd:52:d2:3c:dc:52:9d:de:
+        37:94:20:00:07:6e:06:48:e7:17:86:44:a2:3a:23:07:c1:74:
+        ef:6f:2a:a5
+-----BEGIN CERTIFICATE-----
+MIIF8zCCBNugAwIBAgIID1AR2I8HCb8wDQYJKoZIhvcNAQEFBQAwgZYxCzAJBgNV
+BAYTAlVTMRMwEQYDVQQKDApBcHBsZSBJbmMuMSwwKgYDVQQLDCNBcHBsZSBXb3Js
+ZHdpZGUgRGV2ZWxvcGVyIFJlbGF0aW9uczFEMEIGA1UEAww7QXBwbGUgV29ybGR3
+aWRlIERldmVsb3BlciBSZWxhdGlvbnMgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkw
+HhcNMTIwNjI2MjExODQwWhcNMTMwNjI2MjExODQwWjCBmDErMCkGCgmSJomT8ixk
+AQEMG3Bhc3Mudm9pZC1zdGFyLmNvbS5tZWVwZWRvbzEyMDAGA1UEAwwpUGFzcyBU
+eXBlIElEOiBwYXNzLnZvaWQtc3Rhci5jb20ubWVlcGVkb28xEzARBgNVBAsMClVM
+NzM2S1lRUjkxEzARBgNVBAoMCkVyaWMgTW9udGkxCzAJBgNVBAYTAlVTMIIBIjAN
+BgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAr/mmn8CLpVbytpcKhkLQ8VQBmJWf
+2WkrnL6wtfSkrZ7m746l3FDQziqJqUHORDavkDPnVnaeaJHfxue4IfLVddIqFzqd
+SuDM0pSQ5/I2LxxBAAJ2Rf7Cavw2lud+WQDyhZ4x/6OboLhtlZ7k8cTQ6XxwYVID
+OVy4ijRpIoLFRPnNoSVXJobkMdUIyZ1fc0QQIW2ZkHT2afsg3qlGSaOplqtmROa9
+VmWOfd0HfnG9Ew+xUAev63F4r0bVcTna7fLR242BZH9WxofxfqWj9J8CAbnQNn+H
+8g3Wk07L2DKcWupEB2RqnxwUjpo6DqOGLOggKvEy4BEqE4rTTHNeq3D6TQIDAQAB
+o4ICPzCCAjswPQYIKwYBBQUHAQEEMTAvMC0GCCsGAQUFBzABhiFodHRwOi8vb2Nz
+cC5hcHBsZS5jb20vb2NzcC13d2RyMDMwHQYDVR0OBBYEFAKHJADKUziPw01cgJjh
+ZZU41StpMAkGA1UdEwQCMAAwHwYDVR0jBBgwFoAUiCcXCam2GGCL7Ou69kdZxVJU
+o7cwggEPBgNVHSAEggEGMIIBAjCB/wYJKoZIhvdjZAUBMIHxMCkGCCsGAQUFBwIB
+Fh1odHRwOi8vd3d3LmFwcGxlLmNvbS9hcHBsZWNhLzCBwwYIKwYBBQUHAgIwgbYM
+gbNSZWxpYW5jZSBvbiB0aGlzIGNlcnRpZmljYXRlIGJ5IGFueSBwYXJ0eSBhc3N1
+bWVzIGFjY2VwdGFuY2Ugb2YgdGhlIHRoZW4gYXBwbGljYWJsZSBzdGFuZGFyZCB0
+ZXJtcyBhbmQgY29uZGl0aW9ucyBvZiB1c2UsIGNlcnRpZmljYXRlIHBvbGljeSBh
+bmQgY2VydGlmaWNhdGlvbiBwcmFjdGljZSBzdGF0ZW1lbnRzLjAwBgNVHR8EKTAn
+MCWgI6Ahhh9odHRwOi8vY3JsLmFwcGxlLmNvbS93d2RyY2EuY3JsMAsGA1UdDwQE
+AwIHgDAeBgNVHSUEFzAVBggrBgEFBQcDAgYJKoZIhvdjZAQOMBAGCiqGSIb3Y2QG
+AwIEAgUAMCsGCiqGSIb3Y2QGARAEHQwbcGFzcy52b2lkLXN0YXIuY29tLm1lZXBl
+ZG9vMA0GCSqGSIb3DQEBBQUAA4IBAQBd6C1a12Wekt2/ZhwEmQipQLeS3PrUyIzP
+rTqZIjQMD3LJT3/FkNyKXUfA3e5H9wGBagZhZsNEoQuW5XCiL8O7mNC/Bwo9VtGV
+AQgWbppea0XO2bV4CQ/r/xGmmutl87OxFKVvl6FTMWWm4OrmcC/fWvm15FkrM9Sg
+o0zGYchWWsq+TqwSx9Me5bbj3gTEY+UOM025M5J+EaTuhStlAH+l3PYZW2k3/mGn
+5kUnxRw6tkZ2+/NWkwAqT7Qe0+11jDLlCcgohEYpTduP6W4cnrt2dGqPY9UEGrMW
+QsxwTLGI4m1YvVLSPNxSnd43lCAAB24GSOcXhkSiOiMHwXTvbyql
+-----END CERTIFICATE-----
diff --git a/spec/samples/certs/apple_wwdr_issuer.pem b/spec/samples/certs/apple_wwdr_issuer.pem
new file mode 100644
index 0000000..20fa897
--- /dev/null
+++ b/spec/samples/certs/apple_wwdr_issuer.pem
@@ -0,0 +1,89 @@
+Certificate:
+    Data:
+        Version: 3 (0x2)
+        Serial Number: 25 (0x19)
+        Signature Algorithm: sha1WithRSAEncryption
+        Issuer: C=US, O=Apple Inc., OU=Apple Certification Authority, CN=Apple Root CA
+        Validity
+            Not Before: Feb 14 18:56:35 2008 GMT
+            Not After : Feb 14 18:56:35 2016 GMT
+        Subject: C=US, O=Apple Inc., OU=Apple Worldwide Developer Relations, CN=Apple Worldwide Developer Relations Certification Authority
+        Subject Public Key Info:
+            Public Key Algorithm: rsaEncryption
+            RSA Public Key: (2048 bit)
+                Modulus (2048 bit):
+                    00:ca:38:54:a6:cb:56:aa:c8:24:39:48:e9:8c:ee:
+                    ec:5f:b8:7f:26:91:bc:34:53:7a:ce:7c:63:80:61:
+                    77:64:5e:a5:07:23:b6:39:fe:50:2d:15:56:58:70:
+                    2d:7e:c4:6e:c1:4a:85:3e:2f:f0:de:84:1a:a1:57:
+                    c9:af:7b:18:ff:6a:fa:15:12:49:15:08:19:ac:aa:
+                    db:2a:32:ed:96:63:68:52:15:3d:8c:8a:ec:bf:6b:
+                    18:95:e0:03:ac:01:7d:97:05:67:ce:0e:85:95:37:
+                    6a:ed:09:b6:ae:67:cd:51:64:9f:c6:5c:d1:bc:57:
+                    6e:67:35:80:76:36:a4:87:81:6e:38:8f:d8:2b:15:
+                    4e:7b:25:d8:5a:bf:4e:83:c1:8d:d2:93:d5:1a:71:
+                    b5:60:9c:9d:33:4e:55:f9:12:58:0c:86:b8:16:0d:
+                    c1:e5:77:45:8d:50:48:ba:2b:2d:e4:94:85:e1:e8:
+                    c4:9d:c6:68:a5:b0:a3:fc:67:7e:70:ba:02:59:4b:
+                    77:42:91:39:b9:f5:cd:e1:4c:ef:c0:3b:48:8c:a6:
+                    e5:21:5d:fd:6a:6a:bb:a7:16:35:60:d2:e6:ad:f3:
+                    46:29:c9:e8:c3:8b:e9:79:c0:6a:61:67:15:b2:f0:
+                    fd:e5:68:bc:62:5f:6e:cf:99:dd:ef:1b:63:fe:92:
+                    65:ab
+                Exponent: 65537 (0x10001)
+        X509v3 extensions:
+            X509v3 Key Usage: critical
+                Digital Signature, Certificate Sign, CRL Sign
+            X509v3 Basic Constraints: critical
+                CA:TRUE
+            X509v3 Subject Key Identifier: 
+                88:27:17:09:A9:B6:18:60:8B:EC:EB:BA:F6:47:59:C5:52:54:A3:B7
+            X509v3 Authority Key Identifier: 
+                keyid:2B:D0:69:47:94:76:09:FE:F4:6B:8D:2E:40:A6:F7:47:4D:7F:08:5E
+
+            X509v3 CRL Distribution Points: 
+                URI:http://www.apple.com/appleca/root.crl
+
+            1.2.840.113635.100.6.2.1: 
+                ..
+    Signature Algorithm: sha1WithRSAEncryption
+        da:32:00:96:c5:54:94:d3:3b:82:37:66:7d:2e:68:d5:c3:c6:
+        b8:cb:26:8c:48:90:cf:13:24:6a:46:8e:63:d4:f0:d0:13:06:
+        dd:d8:c4:c1:37:15:f2:33:13:39:26:2d:ce:2e:55:40:e3:0b:
+        03:af:fa:12:c2:e7:0d:21:b8:d5:80:cf:ac:28:2f:ce:2d:b3:
+        4e:af:86:19:04:c6:e9:50:dd:4c:29:47:10:23:fc:6c:bb:1b:
+        98:6b:48:89:e1:5b:9d:de:46:db:35:85:35:ef:3e:d0:e2:58:
+        4b:38:f4:ed:75:5a:1f:5c:70:1d:56:39:12:e5:e1:0d:11:e4:
+        89:25:06:bd:d5:b4:15:8e:5e:d0:59:97:90:e9:4b:81:e2:df:
+        18:af:44:74:1e:19:a0:3a:47:cc:91:1d:3a:eb:23:5a:fe:a5:
+        2d:97:f7:7b:bb:d6:87:46:42:85:eb:52:3d:26:b2:63:a8:b4:
+        b1:ca:8f:f4:cc:e2:b3:c8:47:e0:bf:9a:59:83:fa:da:98:53:
+        2a:82:f5:7c:65:2e:95:d9:33:5d:f5:ed:65:cc:31:37:c5:5a:
+        04:e8:6b:e1:e7:88:03:4a:75:9e:9b:28:cb:4a:40:88:65:43:
+        75:dd:cb:3a:25:23:c5:9e:57:f8:2e:ce:d2:a9:92:5e:73:2e:
+        2f:25:75:15
+-----BEGIN CERTIFICATE-----
+MIIEIzCCAwugAwIBAgIBGTANBgkqhkiG9w0BAQUFADBiMQswCQYDVQQGEwJVUzET
+MBEGA1UEChMKQXBwbGUgSW5jLjEmMCQGA1UECxMdQXBwbGUgQ2VydGlmaWNhdGlv
+biBBdXRob3JpdHkxFjAUBgNVBAMTDUFwcGxlIFJvb3QgQ0EwHhcNMDgwMjE0MTg1
+NjM1WhcNMTYwMjE0MTg1NjM1WjCBljELMAkGA1UEBhMCVVMxEzARBgNVBAoMCkFw
+cGxlIEluYy4xLDAqBgNVBAsMI0FwcGxlIFdvcmxkd2lkZSBEZXZlbG9wZXIgUmVs
+YXRpb25zMUQwQgYDVQQDDDtBcHBsZSBXb3JsZHdpZGUgRGV2ZWxvcGVyIFJlbGF0
+aW9ucyBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTCCASIwDQYJKoZIhvcNAQEBBQAD
+ggEPADCCAQoCggEBAMo4VKbLVqrIJDlI6Yzu7F+4fyaRvDRTes58Y4Bhd2RepQcj
+tjn+UC0VVlhwLX7EbsFKhT4v8N6EGqFXya97GP9q+hUSSRUIGayq2yoy7ZZjaFIV
+PYyK7L9rGJXgA6wBfZcFZ84OhZU3au0Jtq5nzVFkn8Zc0bxXbmc1gHY2pIeBbjiP
+2CsVTnsl2Fq/ToPBjdKT1RpxtWCcnTNOVfkSWAyGuBYNweV3RY1QSLorLeSUheHo
+xJ3GaKWwo/xnfnC6AllLd0KRObn1zeFM78A7SIym5SFd/Wpqu6cWNWDS5q3zRinJ
+6MOL6XnAamFnFbLw/eVovGJfbs+Z3e8bY/6SZasCAwEAAaOBrjCBqzAOBgNVHQ8B
+Af8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUiCcXCam2GGCL7Ou6
+9kdZxVJUo7cwHwYDVR0jBBgwFoAUK9BpR5R2Cf70a40uQKb3R01/CF4wNgYDVR0f
+BC8wLTAroCmgJ4YlaHR0cDovL3d3dy5hcHBsZS5jb20vYXBwbGVjYS9yb290LmNy
+bDAQBgoqhkiG92NkBgIBBAIFADANBgkqhkiG9w0BAQUFAAOCAQEA2jIAlsVUlNM7
+gjdmfS5o1cPGuMsmjEiQzxMkakaOY9Tw0BMG3djEwTcV8jMTOSYtzi5VQOMLA6/6
+EsLnDSG41YDPrCgvzi2zTq+GGQTG6VDdTClHECP8bLsbmGtIieFbnd5G2zWFNe8+
+0OJYSzj07XVaH1xwHVY5EuXhDRHkiSUGvdW0FY5e0FmXkOlLgeLfGK9EdB4ZoDpH
+zJEdOusjWv6lLZf3e7vWh0ZChetSPSayY6i0scqP9Mzis8hH4L+aWYP62phTKoL1
+fGUuldkzXfXtZcwxN8VaBOhr4eeIA0p1npsoy0pAiGVDdd3LOiUjxZ5X+C7O0qmS
+XnMuLyV1FQ==
+-----END CERTIFICATE-----
diff --git a/spec/samples/certs/ca.crt b/spec/samples/certs/ca.crt
new file mode 100644
index 0000000..7d0820a
--- /dev/null
+++ b/spec/samples/certs/ca.crt
@@ -0,0 +1,130 @@
+Certificate:
+    Data:
+        Version: 3 (0x2)
+        Serial Number:
+            e7:13:70:5d:01:cc:39:d2
+        Signature Algorithm: sha1WithRSAEncryption
+        Issuer: C=AU, ST=Some-State, L=locality, O=Internet Widgits Pty Ltd, OU=section, CN=example ca/emailAddress=emailaddr at foo.com
+        Validity
+            Not Before: Sep 14 18:42:25 2012 GMT
+            Not After : Sep 14 18:42:25 2013 GMT
+        Subject: C=AU, ST=Some-State, L=locality, O=Internet Widgits Pty Ltd, OU=section, CN=example ca/emailAddress=emailaddr at foo.com
+        Subject Public Key Info:
+            Public Key Algorithm: rsaEncryption
+            RSA Public Key: (4096 bit)
+                Modulus (4096 bit):
+                    00:c0:b4:be:5d:80:c2:19:5f:5c:f4:64:72:2f:3c:
+                    0f:d1:6f:f2:69:74:de:ee:86:d2:a9:4e:02:85:d1:
+                    e2:b0:84:c8:73:1f:e0:0d:a9:9f:60:e0:53:7f:b3:
+                    4e:ed:42:63:3d:19:09:bc:67:a0:13:32:dc:a0:25:
+                    ac:09:e1:7e:ae:72:73:4f:a1:9c:76:8c:80:35:e2:
+                    75:2e:b2:4a:68:5f:03:83:bc:2e:63:1f:4a:7b:29:
+                    80:2c:3e:fe:72:7f:43:ee:7b:59:6f:fc:51:8b:42:
+                    0a:1b:b9:bb:5f:14:cd:9e:16:36:dd:05:ef:2c:21:
+                    3e:de:39:bc:27:b1:0c:06:6a:49:f8:01:ff:3a:cc:
+                    2c:5a:d9:56:0f:81:bc:6b:88:ee:83:72:d9:14:0c:
+                    34:08:98:aa:1d:35:c2:34:fd:0c:3b:5d:5a:72:c1:
+                    9b:28:ac:04:e1:30:3b:b8:ea:04:a2:48:34:a2:87:
+                    3b:1c:b8:fc:89:f6:91:35:42:e9:6e:c7:af:ab:3b:
+                    f6:b7:0a:2e:e3:7f:31:45:1d:04:a5:a4:5a:f6:cd:
+                    bb:62:f9:49:e0:83:b3:77:df:ee:ba:84:bf:07:fa:
+                    29:72:6d:2d:b8:e0:42:10:bd:f9:f3:e4:c6:7e:8a:
+                    e8:d7:11:b8:67:14:b4:8c:f5:c4:a9:fa:d9:04:bd:
+                    17:7f:b4:d0:a3:b9:c7:df:44:2d:50:dd:ba:7c:01:
+                    11:02:23:d9:9c:4c:9a:f0:92:2f:4a:b7:0a:72:3f:
+                    fc:61:6d:08:3b:cc:99:3c:65:78:92:76:63:a3:c4:
+                    cb:1f:b8:ca:4a:7a:b0:c4:22:b2:9a:13:b6:ae:19:
+                    60:7a:bd:37:1b:93:0e:02:9f:22:e4:dc:21:24:11:
+                    4d:9d:9e:30:7f:7b:92:48:2d:4d:b9:1a:2a:58:c6:
+                    77:4b:70:cf:c1:a6:6d:06:f9:54:83:bf:27:73:8f:
+                    cc:8b:5c:7a:d2:79:56:75:41:d4:77:5f:9c:39:30:
+                    02:0c:c5:0b:0c:32:6b:23:39:68:26:d5:f7:ef:b1:
+                    7f:1e:c6:1d:a9:39:64:15:dd:b0:df:f5:d6:cf:c0:
+                    73:b2:d0:f2:2a:f4:6e:9e:d8:9c:5b:9f:bf:ce:e7:
+                    69:80:bf:69:d2:20:3b:aa:55:49:3e:fa:50:fc:63:
+                    7e:8d:90:46:9a:7d:eb:9c:ca:17:bc:36:d7:88:e0:
+                    d9:c4:29:8c:2f:bc:ce:9d:e5:35:d5:ec:d7:a6:93:
+                    49:2a:84:3b:b6:37:1e:0c:57:f1:04:bb:2f:ea:75:
+                    75:6a:de:0c:71:42:e6:e9:14:5e:ee:55:1a:1f:a4:
+                    87:21:2b:12:98:8b:17:63:c8:84:88:43:46:f4:8f:
+                    b2:b7:5b
+                Exponent: 65537 (0x10001)
+        X509v3 extensions:
+            X509v3 Subject Key Identifier: 
+                28:88:9A:FA:67:E6:16:C3:67:92:46:15:0B:46:12:08:54:45:AC:3A
+            X509v3 Authority Key Identifier: 
+                keyid:28:88:9A:FA:67:E6:16:C3:67:92:46:15:0B:46:12:08:54:45:AC:3A
+                DirName:/C=AU/ST=Some-State/L=locality/O=Internet Widgits Pty Ltd/OU=section/CN=example ca/emailAddress=emailaddr at foo.com
+                serial:E7:13:70:5D:01:CC:39:D2
+
+            X509v3 Basic Constraints: 
+                CA:TRUE
+    Signature Algorithm: sha1WithRSAEncryption
+        81:b5:c6:b6:4a:10:f5:8a:98:30:96:07:46:8e:bb:6b:b3:c1:
+        2e:fa:f3:59:ca:e1:c6:85:70:b6:bc:fe:9f:77:0a:70:1a:56:
+        fb:46:71:d6:ee:90:cf:f5:a6:1d:4c:3c:24:76:32:fe:ec:55:
+        d6:c4:cd:3a:ee:dd:2d:97:b6:3b:d3:3b:3f:02:21:1c:ba:03:
+        48:b8:8d:03:a7:b8:70:81:b6:43:82:82:5f:34:eb:b1:a7:01:
+        cf:e4:f8:f7:d0:d8:5a:b6:c4:a6:0b:4e:e2:cc:4d:be:d4:99:
+        f4:37:54:6f:a7:7d:63:7c:7f:25:34:bb:e0:f4:e2:cf:31:c3:
+        99:4e:9e:a0:d1:f0:3b:15:8e:95:2e:76:a7:14:f0:3b:0c:e7:
+        42:8d:12:53:f9:24:d7:f9:90:93:71:56:9a:5f:35:8e:f3:29:
+        1e:d8:d2:71:fe:d8:d0:eb:d9:61:d5:3e:dd:d8:8f:6a:10:0d:
+        7c:f8:2a:1d:ef:24:d0:c1:13:d7:d6:9e:f7:cc:20:62:ea:78:
+        71:d1:7a:5f:2e:20:0d:64:38:07:1b:c9:a0:0f:3c:c9:4e:3f:
+        b1:84:8c:ae:86:ea:60:c7:84:2f:b6:d6:93:95:8d:36:9f:d2:
+        6a:bf:5e:98:1f:f9:da:fd:97:f3:e0:a7:0b:a7:52:30:e5:10:
+        89:c1:2c:85:75:07:a4:c7:d4:dc:b5:96:2a:c3:d5:eb:3b:7c:
+        fb:a0:55:4c:c1:65:6c:f8:29:74:8b:78:54:7d:a0:d0:9c:32:
+        8a:87:48:90:ad:5c:41:fe:ab:16:5a:ad:44:06:1b:c6:4d:e4:
+        01:f0:d8:c7:b1:db:bf:ca:fd:31:75:0a:01:70:00:45:e1:2d:
+        98:42:d5:36:53:2c:f1:57:93:7d:cd:e6:24:f3:4d:c9:31:ce:
+        a6:59:fe:2c:4a:61:56:9d:ad:55:7b:43:c5:48:ee:95:1e:b4:
+        0b:8d:4d:53:c7:a9:af:03:01:12:99:06:e8:fe:3a:63:8d:53:
+        30:8e:5a:95:64:d4:28:04:d8:bf:72:30:a1:fb:10:a7:84:15:
+        1b:13:a0:82:f3:38:5b:95:57:79:16:24:49:1a:1c:c5:43:90:
+        04:dd:5f:7b:d6:67:36:e1:41:fe:9d:20:a4:08:9c:14:9d:e3:
+        30:5c:1f:8e:46:f7:89:6b:f3:04:b9:75:b6:b6:33:49:54:99:
+        8e:9d:b0:f5:2b:51:61:3a:48:fc:f5:73:32:cd:a2:f6:9d:2a:
+        52:92:66:ae:a9:1f:06:b0:4d:63:29:83:e8:80:61:af:99:76:
+        bc:fd:97:d4:98:0b:49:d5:94:24:b1:34:85:31:0a:4e:6c:c3:
+        0d:de:be:85:1d:49:41:6c
+-----BEGIN CERTIFICATE-----
+MIIG0zCCBLugAwIBAgIJAOcTcF0BzDnSMA0GCSqGSIb3DQEBBQUAMIGhMQswCQYD
+VQQGEwJBVTETMBEGA1UECBMKU29tZS1TdGF0ZTERMA8GA1UEBxMIbG9jYWxpdHkx
+ITAfBgNVBAoTGEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDEQMA4GA1UECxMHc2Vj
+dGlvbjETMBEGA1UEAxMKZXhhbXBsZSBjYTEgMB4GCSqGSIb3DQEJARYRZW1haWxh
+ZGRyQGZvby5jb20wHhcNMTIwOTE0MTg0MjI1WhcNMTMwOTE0MTg0MjI1WjCBoTEL
+MAkGA1UEBhMCQVUxEzARBgNVBAgTClNvbWUtU3RhdGUxETAPBgNVBAcTCGxvY2Fs
+aXR5MSEwHwYDVQQKExhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQxEDAOBgNVBAsT
+B3NlY3Rpb24xEzARBgNVBAMTCmV4YW1wbGUgY2ExIDAeBgkqhkiG9w0BCQEWEWVt
+YWlsYWRkckBmb28uY29tMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA
+wLS+XYDCGV9c9GRyLzwP0W/yaXTe7obSqU4ChdHisITIcx/gDamfYOBTf7NO7UJj
+PRkJvGegEzLcoCWsCeF+rnJzT6GcdoyANeJ1LrJKaF8Dg7wuYx9KeymALD7+cn9D
+7ntZb/xRi0IKG7m7XxTNnhY23QXvLCE+3jm8J7EMBmpJ+AH/OswsWtlWD4G8a4ju
+g3LZFAw0CJiqHTXCNP0MO11acsGbKKwE4TA7uOoEokg0ooc7HLj8ifaRNULpbsev
+qzv2twou438xRR0EpaRa9s27YvlJ4IOzd9/uuoS/B/opcm0tuOBCEL358+TGforo
+1xG4ZxS0jPXEqfrZBL0Xf7TQo7nH30QtUN26fAERAiPZnEya8JIvSrcKcj/8YW0I
+O8yZPGV4knZjo8TLH7jKSnqwxCKymhO2rhlger03G5MOAp8i5NwhJBFNnZ4wf3uS
+SC1NuRoqWMZ3S3DPwaZtBvlUg78nc4/Mi1x60nlWdUHUd1+cOTACDMULDDJrIzlo
+JtX377F/HsYdqTlkFd2w3/XWz8BzstDyKvRunticW5+/zudpgL9p0iA7qlVJPvpQ
+/GN+jZBGmn3rnMoXvDbXiODZxCmML7zOneU11ezXppNJKoQ7tjceDFfxBLsv6nV1
+at4McULm6RRe7lUaH6SHISsSmIsXY8iEiENG9I+yt1sCAwEAAaOCAQowggEGMB0G
+A1UdDgQWBBQoiJr6Z+YWw2eSRhULRhIIVEWsOjCB1gYDVR0jBIHOMIHLgBQoiJr6
+Z+YWw2eSRhULRhIIVEWsOqGBp6SBpDCBoTELMAkGA1UEBhMCQVUxEzARBgNVBAgT
+ClNvbWUtU3RhdGUxETAPBgNVBAcTCGxvY2FsaXR5MSEwHwYDVQQKExhJbnRlcm5l
+dCBXaWRnaXRzIFB0eSBMdGQxEDAOBgNVBAsTB3NlY3Rpb24xEzARBgNVBAMTCmV4
+YW1wbGUgY2ExIDAeBgkqhkiG9w0BCQEWEWVtYWlsYWRkckBmb28uY29tggkA5xNw
+XQHMOdIwDAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQUFAAOCAgEAgbXGtkoQ9YqY
+MJYHRo67a7PBLvrzWcrhxoVwtrz+n3cKcBpW+0Zx1u6Qz/WmHUw8JHYy/uxV1sTN
+Ou7dLZe2O9M7PwIhHLoDSLiNA6e4cIG2Q4KCXzTrsacBz+T499DYWrbEpgtO4sxN
+vtSZ9DdUb6d9Y3x/JTS74PTizzHDmU6eoNHwOxWOlS52pxTwOwznQo0SU/kk1/mQ
+k3FWml81jvMpHtjScf7Y0OvZYdU+3diPahANfPgqHe8k0MET19ae98wgYup4cdF6
+Xy4gDWQ4BxvJoA88yU4/sYSMrobqYMeEL7bWk5WNNp/Sar9emB/52v2X8+CnC6dS
+MOUQicEshXUHpMfU3LWWKsPV6zt8+6BVTMFlbPgpdIt4VH2g0JwyiodIkK1cQf6r
+FlqtRAYbxk3kAfDYx7Hbv8r9MXUKAXAAReEtmELVNlMs8VeTfc3mJPNNyTHOpln+
+LEphVp2tVXtDxUjulR60C41NU8eprwMBEpkG6P46Y41TMI5alWTUKATYv3IwofsQ
+p4QVGxOggvM4W5VXeRYkSRocxUOQBN1fe9ZnNuFB/p0gpAicFJ3jMFwfjkb3iWvz
+BLl1trYzSVSZjp2w9StRYTpI/PVzMs2i9p0qUpJmrqkfBrBNYymD6IBhr5l2vP2X
+1JgLSdWUJLE0hTEKTmzDDd6+hR1JQWw=
+-----END CERTIFICATE-----
diff --git a/spec/samples/certs/ca.key b/spec/samples/certs/ca.key
new file mode 100644
index 0000000..3c6d1ee
--- /dev/null
+++ b/spec/samples/certs/ca.key
@@ -0,0 +1,51 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIJKQIBAAKCAgEAwLS+XYDCGV9c9GRyLzwP0W/yaXTe7obSqU4ChdHisITIcx/g
+DamfYOBTf7NO7UJjPRkJvGegEzLcoCWsCeF+rnJzT6GcdoyANeJ1LrJKaF8Dg7wu
+Yx9KeymALD7+cn9D7ntZb/xRi0IKG7m7XxTNnhY23QXvLCE+3jm8J7EMBmpJ+AH/
+OswsWtlWD4G8a4jug3LZFAw0CJiqHTXCNP0MO11acsGbKKwE4TA7uOoEokg0ooc7
+HLj8ifaRNULpbsevqzv2twou438xRR0EpaRa9s27YvlJ4IOzd9/uuoS/B/opcm0t
+uOBCEL358+TGforo1xG4ZxS0jPXEqfrZBL0Xf7TQo7nH30QtUN26fAERAiPZnEya
+8JIvSrcKcj/8YW0IO8yZPGV4knZjo8TLH7jKSnqwxCKymhO2rhlger03G5MOAp8i
+5NwhJBFNnZ4wf3uSSC1NuRoqWMZ3S3DPwaZtBvlUg78nc4/Mi1x60nlWdUHUd1+c
+OTACDMULDDJrIzloJtX377F/HsYdqTlkFd2w3/XWz8BzstDyKvRunticW5+/zudp
+gL9p0iA7qlVJPvpQ/GN+jZBGmn3rnMoXvDbXiODZxCmML7zOneU11ezXppNJKoQ7
+tjceDFfxBLsv6nV1at4McULm6RRe7lUaH6SHISsSmIsXY8iEiENG9I+yt1sCAwEA
+AQKCAgAo+AQkwtQBKuoLNzOjYSSHxUIHM4aVtWoh/mjA5H9KQeCPwS4UGYS9xtNZ
+qdhUzrFkcudD+8/nZP/MuFWcACm7kq97NYObHIHBcvSwyczR5alMn3xJLITcLFWI
+kpfr5ayejfDUwxLfBVo6zMDOFREl09k0IifX/PVtr16WHajN0FkLdfk6GeAwLFaE
+k3NodUMoBQmrnnCNh09bSGuScl3gXRd2oDyJaBDdgzCfPnlfuvQdvZxOnfFqr1Xt
+ud0A5UkuoV/xSCPxz7+8zs+HG6sPH2wAPbl8FuPXz27kjoZCfufC4P1AecTx5EG4
+nWGp04Ru/OB0Yc2EzldSP/dVb5IIAEeIzeqOC1dKh+m5BL6+KCMW4RA2VRmtVcSO
+Xu8/uLNw3coWj8OdUg4JJLWIuY17puKyhmMuZqXSwF5qcSCD2kGMtgdIFevHGLDy
+NCXIxAhnp/mWGJJvkRPxe7NVZSKGh7nfGGFbr2GAsTX5XZetk6ye/A+oNrUM4UP+
+WQNtD5m2R1IPIp7f8NdTiSpAsKrgzDVEKSdXW/DObRm64DdRINuxGkrjDTOR6B+M
+HmcKQdMZPVyzWeFHF+JyR4+GVx2OZ/5lzEPTHO8thiA1nn1ov/RYZwuwrl8EKDt6
+jgU26C+1TD3lL9smBdekOB8EtQT2EcuE7hwS6ca4Sryj5RYCmQKCAQEA/DTBSczZ
+HtPcHs75IiHfUXGoJB943LIR4cqrWKO2xy16MpamNQy8yBzet7pgXmrxixtar13B
+SpPozQNwlKD43rLSG77soY9eZcn2SQCOlcuekdRczR2jp4cnIo3bTXOm5OTz55EK
+WTLGkTirlcGCPFHv866qrmVQF+cIo3qtszrwuJGkOWTfU8X9zSN+AU+KwAqam5C5
+ZbkcXFNwI2FOud5nQsOrjJ9Aivrw1CbHvHqAiZzZXjZmMNurH/cLX9jregxoiTAv
+wXsS1Apvvq0kvWJ9QrRBNyRsZ6qEt0ABgf2tl6+4t+S9bCmS2L6RzqjrxbkVPoM2
+ChHX+8XkwuVUHwKCAQEAw5rao4MU4itSeaWLrny5oo57pICyY3zF5c5HySpaWyA8
+JksxYCvNDR1aFZjCdOG9zOf5vLieniyTK+Tl3FF5P6OxSJOcQ9UOXaoTKny1Kw9W
+BMTbR0COLXsJSNvjNyZSyoLgOOLTZj11S+X/W1vb+Xi0kkx4SS6Tg3i5N+0Fl2Wa
+Mgucy/pgq+JBTlgKZ1Rjpna1T5ibUn0pT6fxgZ2U+FAD0cYNyaCjVmzMbiIkLUId
+jJEDGYXenyoIFI0rrJvF/0nl98BZjsT9+GNs0ZlejS0rfn2+DWlrLur3aqG2i3Yr
+L9nWZ51bSB7UhHALN2t2qd9etKWmz4YbuFHPvHOVRQKCAQEAgmdMoboXcYcdw5hE
+3M8ixtu7kqHrPkGcWWEPY4+SzD3JdyrJ2ZgybE3xIpJtjaRCLCkCpLYXYVZFAuwK
+Y+8vfwZ6+PmpJIgayQq6G1j8YJud680gBraSjeal54ntoIhx/Nwc+NjXvvMwFJp2
+rcIWctXy+c6QVgfwd8tvfgfKlGefW++COGLdzlULO+xkFI1qMo3JDzKviddCwMIr
+sz93E0fZoH3Hz7fwCWxi5W7/y4aTu5OsGLiL8itCug5khTSF3N9ZlcWii6n1PEoB
+KVghLQMlvT2ykq50ls1mPdIMdYgTH+Et43eUMb71PLicb3yMG/ns8Bur71z2jinu
+dI9bBQKCAQBk30XjTuUFIbw9mX6oNA/zYbEni2rzXVQdB70DY8EG/1+li99hrhTn
+r3xWaNnXNtcPhY3HohnCjlAzMa7MaIOzqvHw8JaEcKog6WVK4tb25sjAWtiOLR9l
+Gu8V0LejKDNH1ihVjbvhHM6RnoGKlpuhUnskeyUI8GkIQsiZq7TXd4EGT/DDTFJw
+MZTmFwb+dImTPeKQsq1e48bbGku0QRSi3XiqxI01ro6tMhxWq2qmoFLmu52ymtPM
+lvtlxcuBDzATUAO1OU+2Daa/Yl6q1IHrIiEs8SGCfxvULT38knq1/vGUkq077+00
+CxojVjiiktu2DMglNswIdytyaVZM4/pVAoIBAQDmXzA7dptH9tOrp5/pYO4C8Fk0
+4NB7OgKrhtE8YQtfghQn1O0FjvNT/o1+Gf9mf40Np7OtVulhZ56fQupmkk56TAJu
+P1YDVDRPLOvMkYeIsqqv6HnXGQaS6Bv91wrqwWPjLDD3pH+AdjP27SnObzARjRUq
+/s5LmI+u87rCldkGLG4qO4sjptWRlU8pIsLWQ3NckXINT7i8wwXzEfOcu7IzKWg6
+sdzMWxc7Vt/4WE5m+9DLkpk3sD9dqELHry5lGWbpLHhfoVXoRdoFNVmyi2ckdr0J
+FJ6hkWkgQ4ee+yHyL3FfYRbGIolqaVSCd8/CvvDZ/24eS3/7TpCzf8SUqtL6
+-----END RSA PRIVATE KEY-----
diff --git a/spec/samples/certs/client.crt b/spec/samples/certs/client.crt
new file mode 100644
index 0000000..b481376
--- /dev/null
+++ b/spec/samples/certs/client.crt
@@ -0,0 +1,78 @@
+Certificate:
+    Data:
+        Version: 1 (0x0)
+        Serial Number: 2 (0x2)
+        Signature Algorithm: sha1WithRSAEncryption
+        Issuer: C=AU, ST=Some-State, L=locality, O=Internet Widgits Pty Ltd, OU=section, CN=example ca/emailAddress=emailaddr at foo.com
+        Validity
+            Not Before: Sep 14 18:43:15 2012 GMT
+            Not After : Sep 14 18:43:15 2013 GMT
+        Subject: C=AU, ST=Some-State, O=Internet Widgits Pty Ltd, CN=client.example.com
+        Subject Public Key Info:
+            Public Key Algorithm: rsaEncryption
+            RSA Public Key: (1024 bit)
+                Modulus (1024 bit):
+                    00:dd:81:af:cb:67:4f:9d:2b:37:9f:bb:52:6a:17:
+                    f3:25:ba:ca:6d:23:45:94:dc:08:9b:cd:82:56:79:
+                    da:0b:47:c9:d0:3f:a8:16:bb:56:77:3a:85:a9:ab:
+                    20:f3:1e:2d:5c:fa:b2:23:90:86:81:bf:a9:f6:10:
+                    85:55:3f:c6:aa:7e:fa:43:96:08:c0:fa:e8:2f:cb:
+                    e5:2d:01:d1:12:22:a2:3f:7a:c5:75:4e:6f:47:d3:
+                    2e:7a:fe:17:6e:4f:a3:20:60:5b:46:57:51:69:ce:
+                    8f:33:87:05:df:1e:cb:fb:32:43:2a:71:6c:44:d1:
+                    a6:2d:98:41:e4:88:36:9a:f1
+                Exponent: 65537 (0x10001)
+    Signature Algorithm: sha1WithRSAEncryption
+        9e:7f:e1:05:a7:9a:86:6c:0d:6e:37:76:ae:2e:5b:78:e5:22:
+        6f:52:40:36:e8:7e:cf:20:77:4f:3b:b4:7b:7c:72:46:68:df:
+        fe:04:87:16:9f:cd:6e:0f:79:01:25:cf:ca:5e:b8:47:31:9e:
+        f1:20:44:26:78:79:40:00:57:cc:a6:a8:39:67:ef:01:d7:65:
+        1c:dd:5d:8f:6e:48:43:f4:03:48:46:8f:08:95:cf:27:3f:30:
+        da:82:a7:33:82:5f:82:cb:e6:2c:f0:25:e8:87:3b:e5:bf:82:
+        79:72:a9:10:45:69:3c:7f:f0:dd:c9:50:6e:02:c9:05:16:cf:
+        c3:58:15:3f:a6:32:ec:80:4f:88:b4:72:d2:5f:70:62:24:98:
+        e5:99:c8:a7:d9:dd:0c:b0:cb:9c:70:d1:6a:44:21:d9:d7:65:
+        a6:71:6f:60:64:7e:28:de:5d:98:42:6d:aa:fc:32:f9:1c:d6:
+        5c:d7:b6:15:18:79:09:80:7e:d7:9e:74:16:a5:80:39:6d:93:
+        8e:8e:4e:c5:8c:f5:4c:ea:d3:fd:12:bc:fa:fc:b8:e2:2c:30:
+        52:f4:eb:ad:d9:56:e9:84:e9:a8:df:a3:16:fa:d2:1e:74:49:
+        5d:d1:24:10:f2:2e:c4:b9:4a:a9:2d:3d:a4:70:6d:24:00:26:
+        46:bf:e2:98:16:4d:c8:55:40:a7:ab:76:b3:c6:a7:72:46:2d:
+        9b:fd:a2:ca:b8:62:9c:59:53:cc:64:ef:60:76:10:c8:c9:e7:
+        51:11:82:d4:81:04:73:e9:af:df:2d:c4:c7:2d:e4:17:d4:e2:
+        10:82:68:56:ae:7a:f2:3c:60:b7:59:29:39:6a:56:86:94:fc:
+        93:2b:5b:f0:ac:80:1d:c7:c5:b7:27:36:94:1c:ad:e9:1c:6b:
+        f3:8a:2a:6c:c8:ce:69:52:b2:42:d9:b9:e7:8e:a3:d4:18:07:
+        a1:db:bf:54:3c:ec:2e:68:7f:cf:d6:71:8f:3e:99:88:e4:ea:
+        7f:98:22:3a:31:68:24:a5:47:23:e2:d6:21:8f:1f:5f:a7:9a:
+        12:10:ba:6d:ac:22:e7:97:95:93:a2:b5:1c:f8:c8:86:1a:ad:
+        32:ff:64:4f:25:8d:d5:25:29:46:85:30:bc:c4:86:41:1b:6b:
+        24:7e:04:b6:eb:46:39:55:9c:4d:84:86:2f:bf:11:26:a9:40:
+        3d:2d:f4:90:22:05:7f:27:3b:13:d1:86:17:70:05:e1:68:be:
+        12:ce:c5:30:7b:0a:1b:7b:8a:89:e6:e7:9a:9c:b0:8b:c1:f8:
+        c3:b6:0a:4c:41:da:fe:cd:ea:ce:89:3b:a5:8d:30:90:93:93:
+        9b:85:b6:61:46:8d:69:f2
+-----BEGIN CERTIFICATE-----
+MIID9DCCAdwCAQIwDQYJKoZIhvcNAQEFBQAwgaExCzAJBgNVBAYTAkFVMRMwEQYD
+VQQIEwpTb21lLVN0YXRlMREwDwYDVQQHEwhsb2NhbGl0eTEhMB8GA1UEChMYSW50
+ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMRAwDgYDVQQLEwdzZWN0aW9uMRMwEQYDVQQD
+EwpleGFtcGxlIGNhMSAwHgYJKoZIhvcNAQkBFhFlbWFpbGFkZHJAZm9vLmNvbTAe
+Fw0xMjA5MTQxODQzMTVaFw0xMzA5MTQxODQzMTVaMGIxCzAJBgNVBAYTAkFVMRMw
+EQYDVQQIEwpTb21lLVN0YXRlMSEwHwYDVQQKExhJbnRlcm5ldCBXaWRnaXRzIFB0
+eSBMdGQxGzAZBgNVBAMTEmNsaWVudC5leGFtcGxlLmNvbTCBnzANBgkqhkiG9w0B
+AQEFAAOBjQAwgYkCgYEA3YGvy2dPnSs3n7tSahfzJbrKbSNFlNwIm82CVnnaC0fJ
+0D+oFrtWdzqFqasg8x4tXPqyI5CGgb+p9hCFVT/Gqn76Q5YIwProL8vlLQHREiKi
+P3rFdU5vR9Muev4Xbk+jIGBbRldRac6PM4cF3x7L+zJDKnFsRNGmLZhB5Ig2mvEC
+AwEAATANBgkqhkiG9w0BAQUFAAOCAgEAnn/hBaeahmwNbjd2ri5beOUib1JANuh+
+zyB3Tzu0e3xyRmjf/gSHFp/Nbg95ASXPyl64RzGe8SBEJnh5QABXzKaoOWfvAddl
+HN1dj25IQ/QDSEaPCJXPJz8w2oKnM4JfgsvmLPAl6Ic75b+CeXKpEEVpPH/w3clQ
+bgLJBRbPw1gVP6Yy7IBPiLRy0l9wYiSY5ZnIp9ndDLDLnHDRakQh2ddlpnFvYGR+
+KN5dmEJtqvwy+RzWXNe2FRh5CYB+1550FqWAOW2Tjo5OxYz1TOrT/RK8+vy44iww
+UvTrrdlW6YTpqN+jFvrSHnRJXdEkEPIuxLlKqS09pHBtJAAmRr/imBZNyFVAp6t2
+s8anckYtm/2iyrhinFlTzGTvYHYQyMnnURGC1IEEc+mv3y3Exy3kF9TiEIJoVq56
+8jxgt1kpOWpWhpT8kytb8KyAHcfFtyc2lByt6Rxr84oqbMjOaVKyQtm5546j1BgH
+odu/VDzsLmh/z9Zxjz6ZiOTqf5giOjFoJKVHI+LWIY8fX6eaEhC6bawi55eVk6K1
+HPjIhhqtMv9kTyWN1SUpRoUwvMSGQRtrJH4EtutGOVWcTYSGL78RJqlAPS30kCIF
+fyc7E9GGF3AF4Wi+Es7FMHsKG3uKiebnmpywi8H4w7YKTEHa/s3qzok7pY0wkJOT
+m4W2YUaNafI=
+-----END CERTIFICATE-----
diff --git a/spec/samples/certs/client.csr b/spec/samples/certs/client.csr
new file mode 100644
index 0000000..68856e1
--- /dev/null
+++ b/spec/samples/certs/client.csr
@@ -0,0 +1,11 @@
+-----BEGIN CERTIFICATE REQUEST-----
+MIIBojCCAQsCAQAwYjELMAkGA1UEBhMCQVUxEzARBgNVBAgTClNvbWUtU3RhdGUx
+ITAfBgNVBAoTGEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDEbMBkGA1UEAxMSY2xp
+ZW50LmV4YW1wbGUuY29tMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDdga/L
+Z0+dKzefu1JqF/MlusptI0WU3AibzYJWedoLR8nQP6gWu1Z3OoWpqyDzHi1c+rIj
+kIaBv6n2EIVVP8aqfvpDlgjA+ugvy+UtAdESIqI/esV1Tm9H0y56/hduT6MgYFtG
+V1Fpzo8zhwXfHsv7MkMqcWxE0aYtmEHkiDaa8QIDAQABoAAwDQYJKoZIhvcNAQEF
+BQADgYEARSbIBSvvhvqX7zMBap+RcQfMdXbSQTI3iNVSEOoUtfuGEJOmkHrWwsz0
+ZfKv/qC9LBeWD+yqDeKbuRNJEla2oIInUfs3FINYLsm3jufsuBpVPY1OOglq91VD
+v1zBodLbTvbHIHKStsMfNaS9lKYZ/PycWNXxhQhpZZAeGBqk3mY=
+-----END CERTIFICATE REQUEST-----
diff --git a/spec/samples/certs/client.key b/spec/samples/certs/client.key
new file mode 100644
index 0000000..5a87945
--- /dev/null
+++ b/spec/samples/certs/client.key
@@ -0,0 +1,15 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIICXgIBAAKBgQDdga/LZ0+dKzefu1JqF/MlusptI0WU3AibzYJWedoLR8nQP6gW
+u1Z3OoWpqyDzHi1c+rIjkIaBv6n2EIVVP8aqfvpDlgjA+ugvy+UtAdESIqI/esV1
+Tm9H0y56/hduT6MgYFtGV1Fpzo8zhwXfHsv7MkMqcWxE0aYtmEHkiDaa8QIDAQAB
+AoGBAIdkD4mqWhVdJyCxJMzIWsyDAdv3pT45x+FDmhk1XbtrY8WwQxOx6kXyNWTh
+vsAbf+rHKT9nxW9lMYO/0V+sHcdRtG0NEXPfB8pX7LEsaHpRPHkVoiWpRlCL/maM
+ci85RPsATlDkiOn1luysfk2PHy5aSKG0RkLS7lSkahTQOn9JAkEA8goUnDdh5hVm
+7o2npeZG66Zb2mnm1l8aO9LJ76u6L/jmTx5aSRXP0aBiDb1PXJZedqhd/MdPcYEI
+QsFCIoT/iwJBAOpIbAK15eWsubUme/UoCbfNpM4H3jQXbSODgJ83nwmqr2slrX9m
+soz/+2nZl6/TL344xxTmDChFGivHdh7JXvMCQBoHi3/hVN3xn0g4Y7crtKTTFz29
+9d1IDQIyARWNWlCea+ZGVV9WwSrCHMlteoNyiGYqZTEyHhEO11yWfA5KT1ECQQCY
+bFHJWaqeyMdxsf4Hu+rGqIZGfRv17B/XcSDndXqFAYVrQnIkZx5XWduqPCTSAaXu
+iuYLFLhoIr0qKnURBpY9AkEAy9JOf5tqc1jFndLIgVtXzM5KptIFHr1ZA7VSfrdY
+ozt9adbCTXfNTTLqAK0N7A2F7T3APEbpPuR2a7TpzawRWA==
+-----END RSA PRIVATE KEY-----
diff --git a/spec/samples/certs/github.com.pem b/spec/samples/certs/github.com.pem
new file mode 100644
index 0000000..74dda62
--- /dev/null
+++ b/spec/samples/certs/github.com.pem
@@ -0,0 +1,122 @@
+Certificate:
+    Data:
+        Version: 3 (0x2)
+        Serial Number:
+            0e:77:76:8a:5d:07:f0:e5:79:59:ca:2a:9d:50:82:b5
+        Signature Algorithm: sha1WithRSAEncryption
+        Issuer: C=US, O=DigiCert Inc, OU=www.digicert.com, CN=DigiCert High Assurance EV CA-1
+        Validity
+            Not Before: May 27 00:00:00 2011 GMT
+            Not After : Jul 29 12:00:00 2013 GMT
+        Subject: businessCategory=Private Organization/1.3.6.1.4.1.311.60.2.1.3=US/1.3.6.1.4.1.311.60.2.1.2=California/serialNumber=C3268102, C=US, ST=California, L=San Francisco, O=GitHub, Inc., CN=github.com
+        Subject Public Key Info:
+            Public Key Algorithm: rsaEncryption
+            RSA Public Key: (2048 bit)
+                Modulus (2048 bit):
+                    00:ed:d3:89:c3:5d:70:72:09:f3:33:4f:1a:72:74:
+                    d9:b6:5a:95:50:bb:68:61:9f:f7:fb:1f:19:e1:da:
+                    04:31:af:15:7c:1a:7f:f9:73:af:1d:e5:43:2b:56:
+                    09:00:45:69:4a:e8:c4:5b:df:c2:77:52:51:19:5b:
+                    d1:2b:d9:39:65:36:a0:32:19:1c:41:73:fb:32:b2:
+                    3d:9f:98:ec:82:5b:0b:37:64:39:2c:b7:10:83:72:
+                    cd:f0:ea:24:4b:fa:d9:94:2e:c3:85:15:39:a9:3a:
+                    f6:88:da:f4:27:89:a6:95:4f:84:a2:37:4e:7c:25:
+                    78:3a:c9:83:6d:02:17:95:78:7d:47:a8:55:83:ee:
+                    13:c8:19:1a:b3:3c:f1:5f:fe:3b:02:e1:85:fb:11:
+                    66:ab:09:5d:9f:4c:43:f0:c7:24:5e:29:72:28:ce:
+                    d4:75:68:4f:24:72:29:ae:39:28:fc:df:8d:4f:4d:
+                    83:73:74:0c:6f:11:9b:a7:dd:62:de:ff:e2:eb:17:
+                    e6:ff:0c:bf:c0:2d:31:3b:d6:59:a2:f2:dd:87:4a:
+                    48:7b:6d:33:11:14:4d:34:9f:32:38:f6:c8:19:9d:
+                    f1:b6:3d:c5:46:ef:51:0b:8a:c6:33:ed:48:61:c4:
+                    1d:17:1b:bd:7c:b6:67:e9:39:cf:a5:52:80:0a:f4:
+                    ea:cd
+                Exponent: 65537 (0x10001)
+        X509v3 extensions:
+            X509v3 Authority Key Identifier: 
+                keyid:4C:58:CB:25:F0:41:4F:52:F4:28:C8:81:43:9B:A6:A8:A0:E6:92:E5
+
+            X509v3 Subject Key Identifier: 
+                87:D1:8F:19:6E:E4:87:6F:53:8C:77:91:07:50:DF:A3:BF:55:47:20
+            X509v3 Subject Alternative Name: 
+                DNS:github.com, DNS:www.github.com
+            Authority Information Access: 
+                OCSP - URI:http://ocsp.digicert.com
+                CA Issuers - URI:http://www.digicert.com/CACerts/DigiCertHighAssuranceEVCA-1.crt
+
+            X509v3 Basic Constraints: critical
+                CA:FALSE
+            X509v3 CRL Distribution Points: 
+                URI:http://crl3.digicert.com/ev2009a.crl
+                URI:http://crl4.digicert.com/ev2009a.crl
+
+            X509v3 Certificate Policies: 
+                Policy: 2.16.840.1.114412.2.1
+                  CPS: http://www.digicert.com/ssl-cps-repository.htm
+                  User Notice:
+                    Explicit Text: 
+
+            X509v3 Extended Key Usage: 
+                TLS Web Server Authentication, TLS Web Client Authentication
+            Netscape Cert Type: 
+                SSL Client, SSL Server
+            X509v3 Key Usage: critical
+                Digital Signature, Key Encipherment
+    Signature Algorithm: sha1WithRSAEncryption
+        14:52:71:1f:86:9d:6d:35:3e:86:bb:66:1a:8b:85:98:b9:00:
+        4c:cb:42:b5:46:fc:06:e7:44:39:c8:e8:52:d8:11:14:23:b3:
+        72:96:e9:14:94:9e:2f:00:28:f7:d5:04:45:40:00:c6:f4:57:
+        42:42:de:09:89:97:11:0d:14:5c:6b:bd:0b:f7:18:a3:5f:67:
+        02:f3:09:38:63:bf:c1:12:9d:30:ba:8e:a5:54:74:59:53:67:
+        a1:1b:50:5b:26:da:fd:13:7e:59:17:bf:49:ef:94:7e:45:a4:
+        fd:3a:49:32:f0:6a:ff:89:8d:a9:61:a9:aa:9b:96:46:c8:1c:
+        e0:18:1c:e6:fb:82:f4:0a:ab:52:a6:ca:e8:54:22:d9:db:2a:
+        3d:5a:22:7b:80:ea:07:05:d4:f9:c7:f0:53:59:5f:bb:77:7e:
+        de:93:70:41:4e:23:cb:78:79:79:c4:2e:ea:d7:66:2a:18:f7:
+        d1:c5:7c:e2:12:78:82:8d:1d:ec:82:9e:01:a2:e5:02:be:78:
+        a1:b9:59:58:c5:4c:6f:4f:a5:31:b4:49:5b:5e:98:1e:2e:38:
+        f6:19:c4:39:a2:4a:fb:79:05:b8:f2:59:e5:26:12:70:ad:c0:
+        e8:75:23:1f:18:d1:0b:e0:9f:65:e4:c3:d7:49:87:5b:72:6c:
+        b1:2f:ac:6f
+-----BEGIN CERTIFICATE-----
+MIIHKjCCBhKgAwIBAgIQDnd2il0H8OV5WcoqnVCCtTANBgkqhkiG9w0BAQUFADBp
+MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3
+d3cuZGlnaWNlcnQuY29tMSgwJgYDVQQDEx9EaWdpQ2VydCBIaWdoIEFzc3VyYW5j
+ZSBFViBDQS0xMB4XDTExMDUyNzAwMDAwMFoXDTEzMDcyOTEyMDAwMFowgcoxHTAb
+BgNVBA8MFFByaXZhdGUgT3JnYW5pemF0aW9uMRMwEQYLKwYBBAGCNzwCAQMTAlVT
+MRswGQYLKwYBBAGCNzwCAQITCkNhbGlmb3JuaWExETAPBgNVBAUTCEMzMjY4MTAy
+MQswCQYDVQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMNU2Fu
+IEZyYW5jaXNjbzEVMBMGA1UEChMMR2l0SHViLCBJbmMuMRMwEQYDVQQDEwpnaXRo
+dWIuY29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA7dOJw11wcgnz
+M08acnTZtlqVULtoYZ/3+x8Z4doEMa8VfBp/+XOvHeVDK1YJAEVpSujEW9/Cd1JR
+GVvRK9k5ZTagMhkcQXP7MrI9n5jsglsLN2Q5LLcQg3LN8OokS/rZlC7DhRU5qTr2
+iNr0J4mmlU+EojdOfCV4OsmDbQIXlXh9R6hVg+4TyBkaszzxX/47AuGF+xFmqwld
+n0xD8MckXilyKM7UdWhPJHIprjko/N+NT02Dc3QMbxGbp91i3v/i6xfm/wy/wC0x
+O9ZZovLdh0pIe20zERRNNJ8yOPbIGZ3xtj3FRu9RC4rGM+1IYcQdFxu9fLZn6TnP
+pVKACvTqzQIDAQABo4IDajCCA2YwHwYDVR0jBBgwFoAUTFjLJfBBT1L0KMiBQ5um
+qKDmkuUwHQYDVR0OBBYEFIfRjxlu5IdvU4x3kQdQ36O/VUcgMCUGA1UdEQQeMByC
+CmdpdGh1Yi5jb22CDnd3dy5naXRodWIuY29tMIGBBggrBgEFBQcBAQR1MHMwJAYI
+KwYBBQUHMAGGGGh0dHA6Ly9vY3NwLmRpZ2ljZXJ0LmNvbTBLBggrBgEFBQcwAoY/
+aHR0cDovL3d3dy5kaWdpY2VydC5jb20vQ0FDZXJ0cy9EaWdpQ2VydEhpZ2hBc3N1
+cmFuY2VFVkNBLTEuY3J0MAwGA1UdEwEB/wQCMAAwYQYDVR0fBFowWDAqoCigJoYk
+aHR0cDovL2NybDMuZGlnaWNlcnQuY29tL2V2MjAwOWEuY3JsMCqgKKAmhiRodHRw
+Oi8vY3JsNC5kaWdpY2VydC5jb20vZXYyMDA5YS5jcmwwggHEBgNVHSAEggG7MIIB
+tzCCAbMGCWCGSAGG/WwCATCCAaQwOgYIKwYBBQUHAgEWLmh0dHA6Ly93d3cuZGln
+aWNlcnQuY29tL3NzbC1jcHMtcmVwb3NpdG9yeS5odG0wggFkBggrBgEFBQcCAjCC
+AVYeggFSAEEAbgB5ACAAdQBzAGUAIABvAGYAIAB0AGgAaQBzACAAQwBlAHIAdABp
+AGYAaQBjAGEAdABlACAAYwBvAG4AcwB0AGkAdAB1AHQAZQBzACAAYQBjAGMAZQBw
+AHQAYQBuAGMAZQAgAG8AZgAgAHQAaABlACAARABpAGcAaQBDAGUAcgB0ACAAQwBQ
+AC8AQwBQAFMAIABhAG4AZAAgAHQAaABlACAAUgBlAGwAeQBpAG4AZwAgAFAAYQBy
+AHQAeQAgAEEAZwByAGUAZQBtAGUAbgB0ACAAdwBoAGkAYwBoACAAbABpAG0AaQB0
+ACAAbABpAGEAYgBpAGwAaQB0AHkAIABhAG4AZAAgAGEAcgBlACAAaQBuAGMAbwBy
+AHAAbwByAGEAdABlAGQAIABoAGUAcgBlAGkAbgAgAGIAeQAgAHIAZQBmAGUAcgBl
+AG4AYwBlAC4wHQYDVR0lBBYwFAYIKwYBBQUHAwEGCCsGAQUFBwMCMBEGCWCGSAGG
++EIBAQQEAwIGwDAOBgNVHQ8BAf8EBAMCBaAwDQYJKoZIhvcNAQEFBQADggEBABRS
+cR+GnW01Poa7ZhqLhZi5AEzLQrVG/AbnRDnI6FLYERQjs3KW6RSUni8AKPfVBEVA
+AMb0V0JC3gmJlxENFFxrvQv3GKNfZwLzCThjv8ESnTC6jqVUdFlTZ6EbUFsm2v0T
+flkXv0nvlH5FpP06STLwav+JjalhqaqblkbIHOAYHOb7gvQKq1KmyuhUItnbKj1a
+InuA6gcF1PnH8FNZX7t3ft6TcEFOI8t4eXnELurXZioY99HFfOISeIKNHeyCngGi
+5QK+eKG5WVjFTG9PpTG0SVtemB4uOPYZxDmiSvt5BbjyWeUmEnCtwOh1Ix8Y0Qvg
+n2Xkw9dJh1tybLEvrG8=
+-----END CERTIFICATE-----
+
diff --git a/spec/samples/certs/server.crt b/spec/samples/certs/server.crt
new file mode 100644
index 0000000..f986ab3
--- /dev/null
+++ b/spec/samples/certs/server.crt
@@ -0,0 +1,79 @@
+Certificate:
+    Data:
+        Version: 1 (0x0)
+        Serial Number: 1 (0x1)
+        Signature Algorithm: sha1WithRSAEncryption
+        Issuer: C=AU, ST=Some-State, L=locality, O=Internet Widgits Pty Ltd, OU=section, CN=example ca/emailAddress=emailaddr at foo.com
+        Validity
+            Not Before: Sep 14 18:43:03 2012 GMT
+            Not After : Sep 14 18:43:03 2013 GMT
+        Subject: C=AU, ST=Some-State, L=locality, O=Internet Widgits Pty Ltd, OU=ouname, CN=server.example.com/emailAddress=email at example.com
+        Subject Public Key Info:
+            Public Key Algorithm: rsaEncryption
+            RSA Public Key: (1024 bit)
+                Modulus (1024 bit):
+                    00:c2:6b:ec:81:d7:3f:94:09:f0:a6:f1:78:65:79:
+                    a7:28:26:5c:b7:96:86:5b:77:9a:be:67:7a:16:e0:
+                    a2:c8:8d:9c:ec:5d:9a:56:0e:c7:f4:8b:11:01:df:
+                    d2:94:80:ba:87:2b:cf:d4:33:4a:06:21:3b:c6:31:
+                    b5:fc:b4:30:f6:fd:28:58:5f:18:11:46:08:fa:12:
+                    75:82:c2:e9:e6:65:9a:e4:fe:8d:f0:36:63:91:06:
+                    bb:43:f6:72:50:5c:8b:00:e3:53:2a:09:34:06:e6:
+                    05:16:30:d0:f8:96:3b:b0:7d:b6:8d:ef:5f:b0:c1:
+                    31:bc:77:d4:a9:93:31:d7:f1
+                Exponent: 65537 (0x10001)
+    Signature Algorithm: sha1WithRSAEncryption
+        aa:c7:e4:d5:c5:33:e8:75:43:01:23:9f:ae:91:c3:17:dc:54:
+        d5:34:65:e3:76:ec:00:e4:71:06:70:84:10:2d:ae:ea:21:90:
+        70:27:ec:86:15:97:e5:e4:10:62:19:08:b5:56:86:4f:84:ef:
+        43:c7:86:06:33:8a:61:bb:71:ac:f9:e1:d2:f0:08:83:32:bb:
+        73:a0:fd:39:66:8f:a3:d9:bb:59:bb:c2:cc:5e:8f:56:fc:72:
+        b2:42:da:d2:31:1b:98:be:0d:0c:8d:1a:8e:12:fe:7f:ef:5b:
+        cf:93:b7:e1:ba:c8:a0:c4:de:60:4f:74:ea:12:9d:3a:ea:81:
+        28:b3:ed:14:6e:22:00:23:56:b0:ef:d4:7e:6b:7d:4a:fb:7e:
+        3b:c2:a8:9e:84:42:43:ad:6d:b1:41:78:75:a6:32:46:5c:98:
+        c2:e5:3b:d0:dd:cc:17:35:7b:f9:54:25:ef:38:07:82:dc:a3:
+        32:69:bb:15:28:71:a9:c7:a3:8e:55:29:61:04:eb:ee:05:e9:
+        9f:4b:f6:c7:6e:9b:02:19:e9:0c:5e:66:a1:65:fe:ae:6e:25:
+        a4:a4:31:3c:40:0d:b9:f4:c2:44:40:23:65:85:58:33:5a:0d:
+        84:3d:24:71:43:0f:65:69:28:75:de:ae:b3:b7:82:a4:09:f1:
+        b7:21:8f:5d:76:66:4c:d7:08:19:80:68:a0:9f:33:df:46:a7:
+        ab:7f:45:4d:1f:d1:45:54:8f:53:b9:da:77:86:b3:e2:b2:7e:
+        72:a2:6e:ad:08:01:2a:05:79:d7:ba:a2:17:c7:82:72:58:7c:
+        4d:fb:b9:0e:09:54:24:1e:34:e0:ae:32:d7:0a:00:1b:23:e4:
+        95:1b:8e:28:6c:7b:31:55:ad:6b:bb:e0:76:d3:2e:d2:14:0c:
+        02:9a:b5:65:ce:54:c7:28:08:7b:85:3c:43:00:09:c3:90:4e:
+        b0:9c:57:f0:66:d2:18:95:ce:4e:18:f0:81:f1:16:a1:b0:ca:
+        a8:85:33:c4:8c:b5:06:9c:eb:e6:5b:b9:13:31:14:53:83:3c:
+        8f:0e:02:56:f9:b7:07:d5:a0:66:8a:8a:06:ee:c7:46:a2:e6:
+        b2:f5:ef:be:f5:ac:e0:a3:fa:9e:1d:03:b1:40:0f:b8:8c:bb:
+        ab:f0:13:55:db:05:65:eb:8a:f7:03:86:e0:a5:bb:f2:ae:ab:
+        f4:32:7b:2c:28:56:fb:7d:30:b3:c1:71:c7:52:3c:a9:ee:a0:
+        82:6f:b6:90:54:b7:5b:f2:74:11:4d:40:89:e4:cb:c3:50:b8:
+        f7:e6:98:bd:95:7e:1c:8f:b9:e8:9f:ef:d6:ca:3b:77:40:6a:
+        dc:1e:51:74:c0:32:53:61
+-----BEGIN CERTIFICATE-----
+MIIEOzCCAiMCAQEwDQYJKoZIhvcNAQEFBQAwgaExCzAJBgNVBAYTAkFVMRMwEQYD
+VQQIEwpTb21lLVN0YXRlMREwDwYDVQQHEwhsb2NhbGl0eTEhMB8GA1UEChMYSW50
+ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMRAwDgYDVQQLEwdzZWN0aW9uMRMwEQYDVQQD
+EwpleGFtcGxlIGNhMSAwHgYJKoZIhvcNAQkBFhFlbWFpbGFkZHJAZm9vLmNvbTAe
+Fw0xMjA5MTQxODQzMDNaFw0xMzA5MTQxODQzMDNaMIGoMQswCQYDVQQGEwJBVTET
+MBEGA1UECBMKU29tZS1TdGF0ZTERMA8GA1UEBxMIbG9jYWxpdHkxITAfBgNVBAoT
+GEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDEPMA0GA1UECxMGb3VuYW1lMRswGQYD
+VQQDExJzZXJ2ZXIuZXhhbXBsZS5jb20xIDAeBgkqhkiG9w0BCQEWEWVtYWlsQGV4
+YW1wbGUuY29tMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDCa+yB1z+UCfCm
+8XhleacoJly3loZbd5q+Z3oW4KLIjZzsXZpWDsf0ixEB39KUgLqHK8/UM0oGITvG
+MbX8tDD2/ShYXxgRRgj6EnWCwunmZZrk/o3wNmORBrtD9nJQXIsA41MqCTQG5gUW
+MND4ljuwfbaN71+wwTG8d9SpkzHX8QIDAQABMA0GCSqGSIb3DQEBBQUAA4ICAQCq
+x+TVxTPodUMBI5+ukcMX3FTVNGXjduwA5HEGcIQQLa7qIZBwJ+yGFZfl5BBiGQi1
+VoZPhO9Dx4YGM4phu3Gs+eHS8AiDMrtzoP05Zo+j2btZu8LMXo9W/HKyQtrSMRuY
+vg0MjRqOEv5/71vPk7fhusigxN5gT3TqEp066oEos+0UbiIAI1aw79R+a31K+347
+wqiehEJDrW2xQXh1pjJGXJjC5TvQ3cwXNXv5VCXvOAeC3KMyabsVKHGpx6OOVSlh
+BOvuBemfS/bHbpsCGekMXmahZf6ubiWkpDE8QA259MJEQCNlhVgzWg2EPSRxQw9l
+aSh13q6zt4KkCfG3IY9ddmZM1wgZgGignzPfRqerf0VNH9FFVI9Tudp3hrPisn5y
+om6tCAEqBXnXuqIXx4JyWHxN+7kOCVQkHjTgrjLXCgAbI+SVG44obHsxVa1ru+B2
+0y7SFAwCmrVlzlTHKAh7hTxDAAnDkE6wnFfwZtIYlc5OGPCB8RahsMqohTPEjLUG
+nOvmW7kTMRRTgzyPDgJW+bcH1aBmiooG7sdGouay9e++9azgo/qeHQOxQA+4jLur
+8BNV2wVl64r3A4bgpbvyrqv0MnssKFb7fTCzwXHHUjyp7qCCb7aQVLdb8nQRTUCJ
+5MvDULj35pi9lX4cj7non+/Wyjt3QGrcHlF0wDJTYQ==
+-----END CERTIFICATE-----
diff --git a/spec/samples/certs/server.csr b/spec/samples/certs/server.csr
new file mode 100644
index 0000000..c12c9b1
--- /dev/null
+++ b/spec/samples/certs/server.csr
@@ -0,0 +1,13 @@
+-----BEGIN CERTIFICATE REQUEST-----
+MIIB6TCCAVICAQAwgagxCzAJBgNVBAYTAkFVMRMwEQYDVQQIEwpTb21lLVN0YXRl
+MREwDwYDVQQHEwhsb2NhbGl0eTEhMB8GA1UEChMYSW50ZXJuZXQgV2lkZ2l0cyBQ
+dHkgTHRkMQ8wDQYDVQQLEwZvdW5hbWUxGzAZBgNVBAMTEnNlcnZlci5leGFtcGxl
+LmNvbTEgMB4GCSqGSIb3DQEJARYRZW1haWxAZXhhbXBsZS5jb20wgZ8wDQYJKoZI
+hvcNAQEBBQADgY0AMIGJAoGBAMJr7IHXP5QJ8KbxeGV5pygmXLeWhlt3mr5nehbg
+osiNnOxdmlYOx/SLEQHf0pSAuocrz9QzSgYhO8Yxtfy0MPb9KFhfGBFGCPoSdYLC
+6eZlmuT+jfA2Y5EGu0P2clBciwDjUyoJNAbmBRYw0PiWO7B9to3vX7DBMbx31KmT
+MdfxAgMBAAGgADANBgkqhkiG9w0BAQUFAAOBgQAyoSojahrYbGK+6QbxOO7i5Ufm
+VHlhVBbPFfmYDrpWjoRlKVwk1iRNi/3ijQi3oPONk19wRh/A0gD0DOiKi3fz2m5K
+gaFLIRcBy25EYVeBic39A6b69SiXQoHv00f5CBHNSHLk4hc30vGIWifexU8ehwJJ
+TlHmdHkECni6w0eDmg==
+-----END CERTIFICATE REQUEST-----
diff --git a/spec/samples/certs/server.key b/spec/samples/certs/server.key
new file mode 100644
index 0000000..bded07a
--- /dev/null
+++ b/spec/samples/certs/server.key
@@ -0,0 +1,15 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIICXAIBAAKBgQDCa+yB1z+UCfCm8XhleacoJly3loZbd5q+Z3oW4KLIjZzsXZpW
+Dsf0ixEB39KUgLqHK8/UM0oGITvGMbX8tDD2/ShYXxgRRgj6EnWCwunmZZrk/o3w
+NmORBrtD9nJQXIsA41MqCTQG5gUWMND4ljuwfbaN71+wwTG8d9SpkzHX8QIDAQAB
+AoGBALX9FBvOAsNuMofyjEJgh6m7jxqCmi3QXVdRwSTdDUMx2+wdCjT4DQ/JhRf+
+DT3Y6cFRr27wu5/VSACT08hCW3mVgE5WcsmxvxPeaWMQCAUoHW4I9bmpvf/1AksZ
+x36N4GwdEvFjDGlM8B1ndW3qLUp6e5iDFUFB7veQGoA6WR/hAkEA39yivn0ThKH/
+9a8glJMBDVmnzNXQveBX+Y0aiJSNTT+rBfDRgWxR8A60z1Xl1iHtGG1j1tRXvDL3
+2u2w4d1lnQJBAN5VTMFkf7ojTvPh0+QHkc7b3d0IRruojEIG3A1ZdUj4WaOqnOBs
+xk6EWsF5YbCBTWH42qWAq/EBXaJcPbyIdWUCQF25882LcpOSfCcyJpLuJX+gbPf/
+AYGuH0dVg6lxgOO553H6TM1CO+AlWCCC11LbK3iRvD5i80TRliJsaCV426UCQEAD
+RS8lNVUtV00GhxBPUZ7CVPWPrXXYSFG2UeMSD5+ryXtC4xoGl24B03OC9CpygAom
+MSWXj2m7X+8gKbI/g7UCQEc0Ne6+4T0NsCz9Dw2TWqIvi+WbK65veyFC212OJ5Wh
+qEF4SY3WXkxJk4Y0ElQARMz6DojpwHI5PtAaYswe8wI=
+-----END RSA PRIVATE KEY-----
diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb
index 4d2e02d..25daaab 100644
--- a/spec/spec_helper.rb
+++ b/spec/spec_helper.rb
@@ -1,4 +1,14 @@
 require 'rubygems'
 require 'rspec'
+require 'pathname'
+
+require 'coveralls'
+Coveralls.wear!
+
+SPECDIR = Pathname(__FILE__).dirname
+require SPECDIR.join('..', 'lib', 'certificate_authority').to_s
+
+def sample_file(name)
+  SPECDIR.join("samples", name)
+end
 
-require File.dirname(__FILE__) + '/../lib/certificate_authority'
\ No newline at end of file
diff --git a/spec/units/certificate_revocation_list_spec.rb b/spec/units/certificate_revocation_list_spec.rb
index 0c3e8ed..af69b38 100644
--- a/spec/units/certificate_revocation_list_spec.rb
+++ b/spec/units/certificate_revocation_list_spec.rb
@@ -7,17 +7,21 @@ describe CertificateAuthority::CertificateRevocationList do
     @root_certificate = CertificateAuthority::Certificate.new
     @root_certificate.signing_entity = true
     @root_certificate.subject.common_name = "CRL Root"
-    @root_certificate.key_material.generate_key(1024)
+    @root_certificate.key_material.generate_key(768)
     @root_certificate.serial_number.number = 1
     @root_certificate.sign!
 
     @certificate = CertificateAuthority::Certificate.new
-    @certificate.key_material.generate_key(1024)
+    @certificate.key_material.generate_key(768)
     @certificate.subject.common_name = "http://bogusSite.com"
     @certificate.parent = @root_certificate
     @certificate.serial_number.number = 2
     @certificate.sign!
 
+    @serial_number = CertificateAuthority::SerialNumber.new
+    @serial_number.revoked_at = Time.now
+    @serial_number.number = 5
+
     @crl.parent = @root_certificate
     @certificate.revoked_at = Time.now
   end
@@ -50,6 +54,62 @@ describe CertificateAuthority::CertificateRevocationList do
     OpenSSL::X509::CRL.new(@crl.to_pem).should_not be_nil
   end
 
+  it "should be able to mix Certificates and SerialNumbers for convenience" do
+    @crl << @certificate
+    @crl << @serial_number
+    @crl.parent = @root_certificate
+    @crl.sign!
+    openssl_csr = OpenSSL::X509::CRL.new(@crl.to_pem)
+    openssl_csr.revoked.size.should == 2
+  end
+
+  it "should have the correct number of entities" do
+    @crl << @certificate
+    @crl.parent = @root_certificate
+    @crl.sign!
+    openssl_clr = OpenSSL::X509::CRL.new(@crl.to_pem)
+    openssl_clr.revoked.should be_a(Array)
+    openssl_clr.revoked.size.should == 1
+  end
+
+  it "should have the serial numbers of revoked entities" do
+    @crl << @certificate
+    @crl << @serial_number
+    @crl.parent = @root_certificate
+    @crl.sign!
+    openssl_clr = OpenSSL::X509::CRL.new(@crl.to_pem)
+    openssl_clr.revoked.should be_a(Array)
+    openssl_clr.revoked.first.serial.should == @certificate.serial_number.number
+    openssl_clr.revoked.last.serial.should == @serial_number.number
+  end
+
+  it "should be valid according to OpenSSL and signer" do
+    @crl << @certificate
+    @crl.parent = @root_certificate
+    @crl.sign!
+    openssl_clr = OpenSSL::X509::CRL.new(@crl.to_pem)
+    openssl_root = OpenSSL::X509::Certificate.new(@root_certificate.to_pem)
+    openssl_clr.verify(openssl_root.public_key).should be_true
+  end
+
+  describe "Digests" do
+    it "should use SHA512 by default" do
+      @crl << @certificate
+      @crl.parent = @root_certificate
+      @crl.sign!
+      openssl_clr = OpenSSL::X509::CRL.new(@crl.to_pem)
+      openssl_clr.signature_algorithm.should == "sha512WithRSAEncryption"
+    end
+
+    it "should support alternate digests supported by OpenSSL" do
+      @crl << @certificate
+      @crl.parent = @root_certificate
+      @crl.sign!({"digest" => "SHA1"})
+      openssl_clr = OpenSSL::X509::CRL.new(@crl.to_pem)
+      openssl_clr.signature_algorithm.should == "sha1WithRSAEncryption"
+    end
+  end
+
   describe "Next update" do
     it "should be able to set a 'next_update' value" do
       @crl.next_update = (60 * 60 * 10) # 10 Hours
@@ -61,8 +121,5 @@ describe CertificateAuthority::CertificateRevocationList do
       @crl.next_update = - (60 * 60 * 10)
       lambda{@crl.sign!}.should raise_error
     end
-
   end
-
-
 end
diff --git a/spec/units/certificate_spec.rb b/spec/units/certificate_spec.rb
index 8158e54..d07abd6 100644
--- a/spec/units/certificate_spec.rb
+++ b/spec/units/certificate_spec.rb
@@ -36,7 +36,7 @@ describe CertificateAuthority::Certificate do
       it "should be able to self-sign" do
         @certificate.serial_number.number = 1
         @certificate.subject.common_name = "chrischandler.name"
-        @certificate.key_material.generate_key(1024)
+        @certificate.key_material.generate_key(768)
         @certificate.sign!
         cert = OpenSSL::X509::Certificate.new(@certificate.to_pem)
         cert.subject.to_s.should == cert.issuer.to_s
@@ -45,7 +45,7 @@ describe CertificateAuthority::Certificate do
       it "should have the basicContraint CA:TRUE" do
         @certificate.serial_number.number = 1
         @certificate.subject.common_name = "chrischandler.name"
-        @certificate.key_material.generate_key(1024)
+        @certificate.key_material.generate_key(768)
         @certificate.sign!
         cert = OpenSSL::X509::Certificate.new(@certificate.to_pem)
         cert.extensions.map{|i| [i.oid,i.value] }.select{|i| i.first == "basicConstraints"}.first[1].should == "CA:TRUE"
@@ -57,7 +57,7 @@ describe CertificateAuthority::Certificate do
         @different_cert = CertificateAuthority::Certificate.new
         @different_cert.signing_entity = true
         @different_cert.subject.common_name = "chrischandler.name root"
-        @different_cert.key_material.generate_key(1024)
+        @different_cert.key_material.generate_key(768)
         @different_cert.serial_number.number = 2
         @different_cert.sign! #self-signed
         @certificate.parent = @different_cert
@@ -79,7 +79,7 @@ describe CertificateAuthority::Certificate do
 
       it "should correctly be signed by a parent certificate" do
         @certificate.subject.common_name = "chrischandler.name"
-        @certificate.key_material.generate_key(1024)
+        @certificate.key_material.generate_key(768)
         @certificate.signing_entity = true
         @certificate.serial_number.number = 1
         @certificate.sign!
@@ -89,7 +89,7 @@ describe CertificateAuthority::Certificate do
 
       it "should have the basicContraint CA:TRUE" do
         @certificate.subject.common_name = "chrischandler.name"
-        @certificate.key_material.generate_key(1024)
+        @certificate.key_material.generate_key(768)
         @certificate.signing_entity = true
         @certificate.serial_number.number = 3
         @certificate.sign!
@@ -104,7 +104,7 @@ describe CertificateAuthority::Certificate do
         @different_cert = CertificateAuthority::Certificate.new
         @different_cert.signing_entity = true
         @different_cert.subject.common_name = "chrischandler.name root"
-        @different_cert.key_material.generate_key(1024)
+        @different_cert.key_material.generate_key(768)
         @different_cert.serial_number.number = 1
         @different_cert.sign! #self-signed
         @certificate.parent = @different_cert
@@ -120,7 +120,7 @@ describe CertificateAuthority::Certificate do
 
       it "should have the basicContraint CA:FALSE" do
         @certificate.subject.common_name = "chrischandler.name"
-        @certificate.key_material.generate_key(1024)
+        @certificate.key_material.generate_key(768)
         @certificate.signing_entity = false
         @certificate.serial_number.number = 1
         @certificate.sign!
@@ -139,7 +139,7 @@ describe CertificateAuthority::Certificate do
     before(:each) do
       @certificate = CertificateAuthority::Certificate.new
       @certificate.subject.common_name = "chrischandler.name"
-      @certificate.key_material.generate_key(1024)
+      @certificate.key_material.generate_key(768)
       @certificate.serial_number.number = 1
       @certificate.sign!
     end
@@ -154,7 +154,7 @@ describe CertificateAuthority::Certificate do
     before(:each) do
       @certificate = CertificateAuthority::Certificate.new
       @certificate.subject.common_name = "chrischandler.name"
-      @certificate.key_material.generate_key(1024)
+      @certificate.key_material.generate_key(768)
       @certificate.serial_number.number = 1
       @signing_profile = {
         "extensions" => {
@@ -175,7 +175,7 @@ describe CertificateAuthority::Certificate do
       before(:each) do
         @certificate = CertificateAuthority::Certificate.new
         @certificate.subject.common_name = "chrischandler.name"
-        @certificate.key_material.generate_key(1024)
+        @certificate.key_material.generate_key(768)
         @certificate.serial_number.number = 1
       end
 
@@ -190,13 +190,23 @@ describe CertificateAuthority::Certificate do
         cert = OpenSSL::X509::Certificate.new(@certificate.to_pem)
         cert.extensions.map(&:oid).include?("subjectAltName").should be_false
       end
+
+      it 'should replace email:copy with email address' do
+        @certificate.subject.email_address = 'foo at bar.com'
+        @certificate.sign!(
+            { "extensions" => { "subjectAltName" => { 'emails' => %w[copy fubar at bar.com] } } }
+        )
+        cert = OpenSSL::X509::Certificate.new(@certificate.to_pem)
+        alt = cert.extensions.select { |e| e.oid == 'subjectAltName' }.first
+        alt.value.should == 'email:foo at bar.com, email:fubar at bar.com'
+      end
     end
 
     describe "AuthorityInfoAccess" do
       before(:each) do
         @certificate = CertificateAuthority::Certificate.new
         @certificate.subject.common_name = "chrischandler.name"
-        @certificate.key_material.generate_key(1024)
+        @certificate.key_material.generate_key(768)
         @certificate.serial_number.number = 1
       end
 
@@ -211,12 +221,12 @@ describe CertificateAuthority::Certificate do
       before(:each) do
         @certificate = CertificateAuthority::Certificate.new
         @certificate.subject.common_name = "chrischandler.name"
-        @certificate.key_material.generate_key(1024)
+        @certificate.key_material.generate_key(768)
         @certificate.serial_number.number = 1
       end
 
       it "should have a crlDistributionPoint if specified" do
-        @certificate.sign!({"extensions" => {"crlDistributionPoints" => {"uri" => ["http://crlThingy.com"]}}})
+        @certificate.sign!({"extensions" => {"crlDistributionPoints" => {"uris" => ["http://crlThingy.com"]}}})
         cert = OpenSSL::X509::Certificate.new(@certificate.to_pem)
         cert.extensions.map(&:oid).include?("crlDistributionPoints").should be_true
       end
@@ -233,7 +243,7 @@ describe CertificateAuthority::Certificate do
       before(:each) do
         @certificate = CertificateAuthority::Certificate.new
         @certificate.subject.common_name = "chrischandler.name"
-        @certificate.key_material.generate_key(1024)
+        @certificate.key_material.generate_key(768)
         @certificate.serial_number.number = 1
       end
 
@@ -250,21 +260,28 @@ describe CertificateAuthority::Certificate do
         cert.extensions.map(&:oid).include?("certificatePolicies").should be_true
       end
 
-      it "should contain a nested userNotice if specified" do
-        pending
-        # @certificate.sign!({
-        #   "extensions" => {
-        #     "certificatePolicies" => {
-        #       "policy_identifier" => "1.3.5.7",
-        #       "cps_uris" => ["http://my.host.name/", "http://my.your.name/"],
-        #       "user_notice" => {
-        #        "explicit_text" => "Testing!", "organization" => "RSpec Test organization name", "notice_numbers" => "1,2,3,4"
-        #       }
-        #     }
-        #   }
-        # })
-        # cert = OpenSSL::X509::Certificate.new(@certificate.to_pem)
-        # cert.extensions.map(&:oid).include?("certificatePolicies").should be_true
+      pending "should contain a nested userNotice if specified" do
+        #pending
+         @certificate.sign!({
+           "extensions" => {
+             "certificatePolicies" => {
+               "policy_identifier" => "1.3.5.7",
+               "cps_uris" => ["http://my.host.name/", "http://my.your.name/"],
+               "user_notice" => {
+                "explicit_text" => "Testing explicit text!", "organization" => "RSpec Test organization name", "notice_numbers" => "1,2,3,4"
+               }
+             }
+           }
+         })
+         cert = OpenSSL::X509::Certificate.new(@certificate.to_pem)
+         cert.extensions.map(&:oid).include?("certificatePolicies").should be_true
+         ## Checking OIDs after they've run through OpenSSL is a pain...
+         ## The nicely structured data will be flattened to a single String
+         cert.extensions.each do |ext|
+           if ext.oid == "certificatePolicies"
+             ext.to_a[1].should include("Testing explicit text!")
+           end
+         end
       end
 
       it "should NOT include a certificatePolicy if not specified" do
@@ -275,7 +292,7 @@ describe CertificateAuthority::Certificate do
     end
 
 
-    it "should support BasicContraints" do
+    it "should support BasicConstraints" do
       cert = OpenSSL::X509::Certificate.new(@certificate.to_pem)
       cert.extensions.map(&:oid).include?("basicConstraints").should be_true
     end
@@ -312,7 +329,7 @@ describe CertificateAuthority::Certificate do
     before(:each) do
       @certificate = CertificateAuthority::Certificate.new
       @certificate.subject.common_name = "chrischandler.name"
-      @certificate.key_material.generate_key(1024)
+      @certificate.key_material.generate_key(768)
       @certificate.serial_number.number = 1
 
       @signing_profile = {
@@ -321,7 +338,7 @@ describe CertificateAuthority::Certificate do
           "crlDistributionPoints" => {"uri" => "http://notme.com/other.crl" },
           "subjectKeyIdentifier" => {},
           "authorityKeyIdentifier" => {},
-          "authorityInfoAccess" => {"ocsp" => ["http://youFillThisOut/ocsp/"] },
+          "authorityInfoAccess" => {"ocsp" => ["http://youFillThisOut/ocsp/"], "ca_issuers" => ["http://me.com/other.crt"] },
           "keyUsage" => {"usage" => ["digitalSignature","nonRepudiation"] },
           "extendedKeyUsage" => {"usage" => [ "serverAuth","clientAuth"]},
           "subjectAltName" => {"uris" => ["http://subdomains.youFillThisOut/"]},
@@ -384,6 +401,51 @@ CERT
       @small_cert.key_material.private_key = "data"
       @small_cert.should be_valid
     end
+
+    it "should check to make sure that if a certificate had extensions they were imported" do
+      cert_path = File.join(File.dirname(__FILE__),"..","samples","certs","github.com.pem")
+      openssl_cert = OpenSSL::X509::Certificate.new(File.read(cert_path))
+      @cert_with_extensions = CertificateAuthority::Certificate.from_openssl(openssl_cert)
+
+      expected_basicConstraints = CertificateAuthority::Extensions::BasicConstraints.new
+      expected_basicConstraints.critical = true
+      expected_basicConstraints.ca = false
+      @cert_with_extensions.extensions["basicConstraints"].should == expected_basicConstraints
+
+      expected_crlDistributionPoints = CertificateAuthority::Extensions::CrlDistributionPoints.new
+      expected_crlDistributionPoints.uris = ["http://crl3.digicert.com/ev2009a.crl","http://crl4.digicert.com/ev2009a.crl"]
+      @cert_with_extensions.extensions["crlDistributionPoints"].should == expected_crlDistributionPoints
+
+      expected_subjectAlt = CertificateAuthority::Extensions::SubjectAlternativeName.new
+      expected_subjectAlt.dns_names =["github.com", "www.github.com"]
+      @cert_with_extensions.extensions["subjectAltName"].should == expected_subjectAlt
+
+      expected_subjectKeyIdentifier = CertificateAuthority::Extensions::SubjectKeyIdentifier.new
+      expected_subjectKeyIdentifier.identifier = "87:D1:8F:19:6E:E4:87:6F:53:8C:77:91:07:50:DF:A3:BF:55:47:20"
+      @cert_with_extensions.extensions["subjectKeyIdentifier"].should == expected_subjectKeyIdentifier
+
+      expected_authorityKeyIdentifier = CertificateAuthority::Extensions::AuthorityKeyIdentifier.new
+      expected_authorityKeyIdentifier.identifier = "keyid:4C:58:CB:25:F0:41:4F:52:F4:28:C8:81:43:9B:A6:A8:A0:E6:92:E5"
+      @cert_with_extensions.extensions["authorityKeyIdentifier"].should == expected_authorityKeyIdentifier
+
+      expected_authorityInfoAccess = CertificateAuthority::Extensions::AuthorityInfoAccess.new
+      expected_authorityInfoAccess.ocsp << "URI:http://ocsp.digicert.com"
+      expected_authorityInfoAccess.ca_issuers << "URI:http://www.digicert.com/CACerts/DigiCertHighAssuranceEVCA-1.crt"
+      @cert_with_extensions.extensions["authorityInfoAccess"].should == expected_authorityInfoAccess
+
+      expected_keyUsage = CertificateAuthority::Extensions::KeyUsage.new
+      expected_keyUsage.critical = true
+      # This one is goofy. Though you have to tell openssl 'digitalSignature'
+      # it will parse and return 'Digital Signature' even though those should
+      # be identical.
+      expected_keyUsage.usage = ["Digital Signature", "Key Encipherment"]
+      @cert_with_extensions.extensions["keyUsage"].should == expected_keyUsage
+
+      expected_extendedKeyUsage = CertificateAuthority::Extensions::ExtendedKeyUsage.new
+      # Same asymmetric specify vs parse as above
+      expected_extendedKeyUsage.usage = ["TLS Web Server Authentication", "TLS Web Client Authentication"]
+      @cert_with_extensions.extensions["extendedKeyUsage"].should == expected_extendedKeyUsage
+    end
   end
 
   it "should have a distinguished name" do
@@ -415,13 +477,15 @@ CERT
   end
 
   it "should default to one year validity" do
-    @certificate.not_after.should < Time.now + 65 * 60 * 24 * 365 and
-    @certificate.not_after.should > Time.now + 55 * 60 * 24 * 365
+    day  = 60 * 60 * 24
+    year = day * 365
+    @certificate.not_after.should < Time.now + year + day and
+    @certificate.not_after.should > Time.now + year - day
   end
 
   it "should be able to have a revoked at time" do
     @certificate.revoked?.should be_false
-    @certificate.revoked_at = Time.now
+    @certificate.revoked_at = Time.now.utc
     @certificate.revoked?.should be_true
   end
 
diff --git a/spec/units/distinguished_name_spec.rb b/spec/units/distinguished_name_spec.rb
index 136a007..0ea5706 100644
--- a/spec/units/distinguished_name_spec.rb
+++ b/spec/units/distinguished_name_spec.rb
@@ -12,6 +12,8 @@ describe CertificateAuthority::DistinguishedName do
     @distinguished_name.respond_to?(:o).should be_true
     @distinguished_name.respond_to?(:ou).should be_true
     @distinguished_name.respond_to?(:c).should be_true
+    @distinguished_name.respond_to?(:emailAddress).should be_true
+    @distinguished_name.respond_to?(:serialNumber).should be_true
   end
 
   it "should provide human-readable equivalents to the distinguished name common attributes" do
@@ -21,6 +23,8 @@ describe CertificateAuthority::DistinguishedName do
     @distinguished_name.respond_to?(:organization).should be_true
     @distinguished_name.respond_to?(:organizational_unit).should be_true
     @distinguished_name.respond_to?(:country).should be_true
+    @distinguished_name.respond_to?(:email_address).should be_true
+    @distinguished_name.respond_to?(:serial_number).should be_true
   end
 
   it "should require a common name" do
@@ -55,5 +59,16 @@ describe CertificateAuthority::DistinguishedName do
     it "should create an equivalent object" do
       @dn.to_x509_name.to_s.split('/').should =~ @name.to_s.split('/')
     end
+
+  end
+
+  describe CertificateAuthority::WrappedDistinguishedName do
+    it "should mark the DN as having custom OIDs if there's an unknown subject element" do
+      OpenSSL::ASN1::ObjectId.register("2.3.4.5","testing","testingCustomOIDs")
+      subject = "/testingCustomOIDs=custom/CN=justincummins.name/L=on my laptop/ST=relaxed/C=as/O=programmer/OU=using this code"
+      @name = OpenSSL::X509::Name.parse subject
+      @dn = CertificateAuthority::DistinguishedName.from_openssl @name
+      @dn.custom_oids?.should be_true
+    end
   end
-end
\ No newline at end of file
+end
diff --git a/spec/units/extensions_spec.rb b/spec/units/extensions_spec.rb
index 72e6e92..a4618ec 100644
--- a/spec/units/extensions_spec.rb
+++ b/spec/units/extensions_spec.rb
@@ -1,32 +1,39 @@
 require File.dirname(__FILE__) + '/units_helper'
 
 describe CertificateAuthority::Extensions do
-  describe CertificateAuthority::Extensions::BasicContraints do
+  describe CertificateAuthority::Extensions::BasicConstraints do
     it "should only allow true/false" do
-      basic_constraints = CertificateAuthority::Extensions::BasicContraints.new
+      basic_constraints = CertificateAuthority::Extensions::BasicConstraints.new
       basic_constraints.valid?.should be_true
       basic_constraints.ca = "moo"
       basic_constraints.valid?.should be_false
     end
 
     it "should respond to :path_len" do
-      basic_constraints = CertificateAuthority::Extensions::BasicContraints.new
+      basic_constraints = CertificateAuthority::Extensions::BasicConstraints.new
       basic_constraints.respond_to?(:path_len).should be_true
     end
 
     it "should raise an error if :path_len isn't a non-negative integer" do
-      basic_constraints = CertificateAuthority::Extensions::BasicContraints.new
+      basic_constraints = CertificateAuthority::Extensions::BasicConstraints.new
       lambda {basic_constraints.path_len = "moo"}.should raise_error
       lambda {basic_constraints.path_len = -1}.should raise_error
       lambda {basic_constraints.path_len = 1.5}.should raise_error
     end
 
     it "should generate a proper OpenSSL extension string" do
-      basic_constraints = CertificateAuthority::Extensions::BasicContraints.new
+      basic_constraints = CertificateAuthority::Extensions::BasicConstraints.new
       basic_constraints.ca = true
       basic_constraints.path_len = 2
       basic_constraints.to_s.should == "CA:true,pathlen:2"
     end
+
+    it "should parse values from a proper OpenSSL extension string" do
+      basic_constraints = CertificateAuthority::Extensions::BasicConstraints.parse("CA:true,pathlen:2", true)
+      basic_constraints.critical.should be_true
+      basic_constraints.ca.should be_true
+      basic_constraints.path_len.should == 2
+    end
   end
 
   describe CertificateAuthority::Extensions::SubjectAlternativeName do
@@ -49,6 +56,13 @@ describe CertificateAuthority::Extensions do
       subjectAltName.to_s.should == "URI:http://localhost.altname.example.com,URI:http://other.example.com"
     end
 
+    it "should parse URIs from a proper OpenSSL extension string" do
+      subjectAltName = CertificateAuthority::Extensions::SubjectAlternativeName.parse("URI:http://localhost.altname.example.com", false)
+      subjectAltName.uris.should == ["http://localhost.altname.example.com"]
+
+      subjectAltName = CertificateAuthority::Extensions::SubjectAlternativeName.parse("URI:http://localhost.altname.example.com,URI:http://other.example.com", false)
+      subjectAltName.uris.should == ["http://localhost.altname.example.com", "http://other.example.com"]
+    end
 
     it "should respond to :dns_names" do
       subjectAltName = CertificateAuthority::Extensions::SubjectAlternativeName.new
@@ -69,6 +83,14 @@ describe CertificateAuthority::Extensions do
       subjectAltName.to_s.should == "DNS:localhost.altname.example.com,DNS:other.example.com"
     end
 
+    it "should parse DNS names from a proper OpenSSL extension string" do
+      subjectAltName = CertificateAuthority::Extensions::SubjectAlternativeName.parse("DNS:localhost.altname.example.com", false)
+      subjectAltName.dns_names.should == ["localhost.altname.example.com"]
+
+      subjectAltName = CertificateAuthority::Extensions::SubjectAlternativeName.parse("DNS:localhost.altname.example.com,DNS:other.example.com", false)
+      subjectAltName.dns_names.should == ["localhost.altname.example.com", "other.example.com"]
+    end
+
     it "should respond to :ips" do
       subjectAltName = CertificateAuthority::Extensions::SubjectAlternativeName.new
       subjectAltName.respond_to?(:ips).should be_true
@@ -88,6 +110,32 @@ describe CertificateAuthority::Extensions do
       subjectAltName.to_s.should == "IP:1.2.3.4,IP:5.6.7.8"
     end
 
+    it "should parse IPs from a proper OpenSSL extension string" do
+      subjectAltName = CertificateAuthority::Extensions::SubjectAlternativeName.parse("IP:1.2.3.4", false)
+      subjectAltName.ips.should == ["1.2.3.4"]
+
+      subjectAltName = CertificateAuthority::Extensions::SubjectAlternativeName.parse("IP:1.2.3.4,IP:5.6.7.8", false)
+      subjectAltName.ips.should == ["1.2.3.4", "5.6.7.8"]
+    end
+
+    describe 'emails' do
+      let(:subject) { CertificateAuthority::Extensions::SubjectAlternativeName.new }
+
+      it "should require 'emails' to be an Array" do
+        expect {
+          subject.emails = "not an array"
+        }.to raise_error "Emails must be an array"
+      end
+
+      it "should generate a proper OpenSSL extension string for emails" do
+        subject.emails = ["copy"]
+        subject.to_s.should == "email:copy"
+
+        subject.emails = ["copy", "foo at bar.com"]
+        subject.to_s.should == "email:copy,email:foo at bar.com"
+      end
+    end
+
     it "should generate a proper OpenSSL extension string for URIs IPs and DNS names together" do
       subjectAltName = CertificateAuthority::Extensions::SubjectAlternativeName.new
       subjectAltName.ips = ["1.2.3.4"]
@@ -107,9 +155,29 @@ describe CertificateAuthority::Extensions do
 
       subjectAltName.uris = ["http://localhost.altname.example.com", "http://other.altname.example.com"]
       subjectAltName.to_s.should == "URI:http://localhost.altname.example.com,URI:http://other.altname.example.com,DNS:localhost.altname.example.com,DNS:other.example.com,IP:1.2.3.4,IP:5.6.7.8"
-
     end
 
+    it "should parse URIs IPs and DNS names together from a proper OpenSSL extension string" do
+      subjectAltName = CertificateAuthority::Extensions::SubjectAlternativeName.parse("IP:1.2.3.4", false)
+      subjectAltName.ips.should == ["1.2.3.4"]
+
+      subjectAltName = CertificateAuthority::Extensions::SubjectAlternativeName.parse("DNS:localhost.altname.example.com,IP:1.2.3.4", false)
+      subjectAltName.dns_names.should == ["localhost.altname.example.com"]
 
+      subjectAltName = CertificateAuthority::Extensions::SubjectAlternativeName.parse("DNS:localhost.altname.example.com,DNS:other.example.com,IP:1.2.3.4", false)
+      subjectAltName.dns_names.should == ["localhost.altname.example.com", "other.example.com"]
+
+      subjectAltName = CertificateAuthority::Extensions::SubjectAlternativeName.parse("DNS:localhost.altname.example.com,DNS:other.example.com,IP:1.2.3.4,IP:5.6.7.8", false)
+      subjectAltName.ips.should == ["1.2.3.4", "5.6.7.8"]
+
+      subjectAltName = CertificateAuthority::Extensions::SubjectAlternativeName.parse("URI:http://localhost.altname.example.com,DNS:localhost.altname.example.com,DNS:other.example.com,IP:1.2.3.4,IP:5.6.7.8", false)
+      subjectAltName.uris.should == ["http://localhost.altname.example.com"]
+
+      subjectAltName = CertificateAuthority::Extensions::SubjectAlternativeName.parse("URI:http://localhost.altname.example.com,URI:http://other.altname.example.com,DNS:localhost.altname.example.com,DNS:other.example.com,IP:1.2.3.4,IP:5.6.7.8", false)
+      subjectAltName.uris.should == ["http://localhost.altname.example.com", "http://other.altname.example.com"]
+
+      subjectAltName.emails= ["copy", "foo at bar.com"]
+      subjectAltName.to_s.should == "URI:http://localhost.altname.example.com,URI:http://other.altname.example.com,DNS:localhost.altname.example.com,DNS:other.example.com,IP:1.2.3.4,IP:5.6.7.8,email:copy,email:foo@bar.com"
+    end
   end
 end
diff --git a/spec/units/key_material_spec.rb b/spec/units/key_material_spec.rb
index 4f4e64a..8398f03 100644
--- a/spec/units/key_material_spec.rb
+++ b/spec/units/key_material_spec.rb
@@ -15,6 +15,85 @@ describe CertificateAuthority::KeyMaterial do
       @key_material.is_in_memory?.should be_true
     end
   end
+
+  describe "reading keys from PEM" do
+    before(:each) do
+      @key_pair=<<EOF
+-----BEGIN RSA PRIVATE KEY-----
+MIICXAIBAAKBgQCxiGVfRrf90CHmvXa+XYWE4m7LZ1slc6cxIYyIgZuQ5T8AeqUa
+kbyYY4wMUR2gZ4pDPs/WGs8fW66q23qmHSr1bQ6HaL8znbD7UL/IiiyiW8I11orb
+rhimIx1A606qi8/0gQc+H851gzUusd5xgKP2X+oPxYx3VG3dpksLnNK1IwIDAQAB
+AoGAfrNNRbX+0dGcoERPXoT4KWJAmEHnNs9XXyUGWtXE5J/3Wqws8M1Zv5gr9w5d
+CoFal6tYQQFZGJQiECYbXjoq0VT8ApWfuO/mCXyXfmnLFEU8EJjmXtXzn2yyPfoY
+At7O8QwvG0bwtw1SqNf7cRtlOEIqoLMtdyaVv4C5ffyheIECQQDVdVf2Sk113Kke
+PREzEb6XZ0n2ugSG8fWJh2QKUI4RXhg7bDzHhSpexeKsJdoet8NJOUEsXMoqLSzK
+bBnSD63RAkEA1OogtDCkpwkvqC63a7hyDP7qRVHFuVeSA1fu+6BFS0xblkgvcPXT
+J7WbWYcP+lqcLjXWeFsqe5qS6sDCsAhsswJAIumZZHgMqU1Y/9AfIwow8RR8vXT5
+TpT+gur5CtLYGbEZJ4bxffSi1HNrOprKTSHjN/O8XCQlELboz4bUxk24MQJAcsaX
+xKsoR4dTMoWkiSRQDyNoJOA1B3nmk3jWsryuPi42fSgCsxFBt/lVeoitm1c3NE3/
+hLgYibNFGdm52e1gswJBAMwYuImbl6AVLv0Y41smxIkvfAzlyNfTAsp7GqLoMhYN
+q/0KoyI2Ge3+NnmJI/eaiYs8qC2HjrgdX9ZDSUCWfpQ=
+-----END RSA PRIVATE KEY-----
+EOF
+    @public_key=<<EOF
+-----BEGIN PUBLIC KEY-----
+MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCxiGVfRrf90CHmvXa+XYWE4m7L
+Z1slc6cxIYyIgZuQ5T8AeqUakbyYY4wMUR2gZ4pDPs/WGs8fW66q23qmHSr1bQ6H
+aL8znbD7UL/IiiyiW8I11orbrhimIx1A606qi8/0gQc+H851gzUusd5xgKP2X+oP
+xYx3VG3dpksLnNK1IwIDAQAB
+-----END PUBLIC KEY-----
+EOF
+
+      @encrypted_key_pair=<<EOF
+-----BEGIN RSA PRIVATE KEY-----
+Proc-Type: 4,ENCRYPTED
+DEK-Info: AES-256-CBC,EF5CCB3A64C0A6DB57FB924A3ED5B9A6
+
+qziIbWkeBVYS8Lhs3DqW6BeL6/rLDOyLcei604qx/SsmQZfNPb6SfRnq5EWqWk8V
+kHHA9cPb8x8bRNdGiajg4NPwXHlkEBUJkeM8C24MCvPEztM7jooPtmrRR8ilz3m+
+62LyvyzO6LIHonoqOq/jQwfYFOInPTqpwW/drzkFxuh2Mc7enVnU9e+Z5Pnk6I8g
+Sv7Nmh1kMEArzu4vSFBFalBFJkFOXjysOUMhQ7SJwKHmTbKEqcI5Le5NKT708BMa
+xnDzWQQgvKjiOSKz71Gq2QT3hwqV1bgwV015G0xlpWpBhemey4pr6L3AxysWFujO
+z1fHe0MaV6dbQNwymljwdE5R+9jzU8uEmdo8QrrRnzUnetr01uUcWgOUqnKmuinq
+WMl7Odvep6hoBbqSMfESJZJA9+XPBXfs4FHE1Ri8riAPoGLl484Lj91cVqBqVXZG
+tht39kXNYROOqBZ7pU9fNuTFhDHvAoH9ydFD6YpTRZALHTgSdyZ5IbhmOiJAlW3b
+nhAZtMwwcTYJIF8xyR+8GAdAG/dIDFM+tRmeB1aRkSd6IgxLMh52SFv/vSglEwYj
+PYHvhcpId93yYO34vCGT4tGsZ2j2VpZqAyu5cl8K6Hq8dTYzH2jkfSdGQ9a8vCKG
+jSlK4vK91ZFdO8sTCTlQd6+jUXH3B4wTUsA4key7yUGLXMr1jmapRQJIuhCUnQQn
+B9Py5RdGlGW2ycVaGh08n4LmG/OTJuLb+xTStm5w4iLmB+ynDjIxpfQjvX98hzMh
+G35pgQ2GdKs+NByYXZKz0OHT2NAHkKpoO7rPlzTpMLgUtAGmH7rLEeOUz6TscUDc
+-----END RSA PRIVATE KEY-----
+EOF
+    end
+
+    it "should include a means of reading an RSA keypair" do
+      key = CertificateAuthority::KeyMaterial.from_x509_key_pair(@key_pair)
+      key.public_key.should_not be_nil
+      key.public_key.should be_a(OpenSSL::PKey::RSA)
+      key.private_key.should_not be_nil
+      key.private_key.should be_a(OpenSSL::PKey::RSA)
+    end
+
+    it "should include a means of reading encrypted RSA keypairs" do
+      key = CertificateAuthority::KeyMaterial.from_x509_key_pair(@encrypted_key_pair,"meow")
+      key.public_key.should_not be_nil
+      key.public_key.should be_a(OpenSSL::PKey::RSA)
+      key.private_key.should_not be_nil
+      key.private_key.should be_a(OpenSSL::PKey::RSA)
+    end
+
+    it "should raise an exception if you read an encrypted keypair w/ bad password" do
+      lambda {
+        key = CertificateAuthority::KeyMaterial.from_x509_key_pair(@encrypted_key_pair,"wrong")
+      }.should raise_error
+    end
+
+    it "should include a means of reading a public-only PEM formatted key" do
+      key = CertificateAuthority::KeyMaterial.from_x509_public_key(@public_key)
+      key.public_key.should_not be_nil
+      key.public_key.should be_a(OpenSSL::PKey::RSA)
+    end
+  end
 end
 
 describe CertificateAuthority::MemoryKeyMaterial do
@@ -23,21 +102,21 @@ describe CertificateAuthority::MemoryKeyMaterial do
   end
 
   it "should be able to generate an RSA key" do
-    @key_material.generate_key(1024).should_not be_nil
+    @key_material.generate_key(768).should_not be_nil
   end
 
   it "should generate a proper OpenSSL::PKey::RSA" do
-    @key_material.generate_key(1024).class.should == OpenSSL::PKey::RSA
+    @key_material.generate_key(768).class.should == OpenSSL::PKey::RSA
   end
 
   it "should be able to specify the size of the modulus to generate" do
-    @key_material.generate_key(1024).should_not be_nil
+    @key_material.generate_key(768).should_not be_nil
   end
 
   describe "with generated key" do
     before(:all) do
       @key_material_in_memory = CertificateAuthority::MemoryKeyMaterial.new
-      @key_material_in_memory.generate_key(1024)
+      @key_material_in_memory.generate_key(768)
     end
 
     it "should be able to retrieve the private key" do
@@ -51,7 +130,7 @@ describe CertificateAuthority::MemoryKeyMaterial do
 
   it "should not validate without public and private keys" do
     @key_material.valid?.should be_false
-    @key_material.generate_key(1024)
+    @key_material.generate_key(768)
     @key_material.valid?.should be_true
     pub = @key_material.public_key
     @key_material.public_key = nil
diff --git a/spec/units/ocsp_handler_spec.rb b/spec/units/ocsp_handler_spec.rb
index 067fa0d..fd6663d 100644
--- a/spec/units/ocsp_handler_spec.rb
+++ b/spec/units/ocsp_handler_spec.rb
@@ -1,18 +1,132 @@
 require File.dirname(__FILE__) + '/units_helper'
 
+describe CertificateAuthority::OCSPRequestReader do
+  before(:each) do
+    @root_certificate = CertificateAuthority::Certificate.new
+    @root_certificate.signing_entity = true
+    @root_certificate.subject.common_name = "OCSP Root"
+    @root_certificate.key_material.generate_key(768)
+    @root_certificate.serial_number.number = 1
+    @root_certificate.sign!
+
+    @certificate = CertificateAuthority::Certificate.new
+    @certificate.key_material.generate_key(768)
+    @certificate.subject.common_name = "http://questionablesite.com"
+    @certificate.parent = @root_certificate
+    @certificate.serial_number.number = 2
+    @certificate.sign!
+
+    @ocsp_request = OpenSSL::OCSP::Request.new
+    openssl_cert_issuer = OpenSSL::X509::Certificate.new(@root_certificate.to_pem)
+    openssl_cert_subject = OpenSSL::X509::Certificate.new(@certificate.to_pem)
+
+    cert_id = OpenSSL::OCSP::CertificateId.new(openssl_cert_subject, openssl_cert_issuer)
+    @ocsp_request.add_certid(cert_id)
+    @ocsp_request_reader = CertificateAuthority::OCSPRequestReader.from_der(@ocsp_request.to_der)
+  end
+
+  it "should read in the DER encoded body" do
+    @ocsp_request_reader.should_not be_nil
+  end
+
+  it "should read out certificate serial numbers" do
+    @ocsp_request_reader.serial_numbers.should == [2]
+  end
+end
+
+describe CertificateAuthority::OCSPResponseBuilder do
+  before(:each) do
+    @root_certificate = CertificateAuthority::Certificate.new
+    @root_certificate.signing_entity = true
+    @root_certificate.subject.common_name = "OCSP Root"
+    @root_certificate.key_material.generate_key(768)
+    @root_certificate.serial_number.number = 1
+    @root_certificate.sign!({"extensions" => {"keyUsage" => {"usage" => ["critical", "keyCertSign"] }} })
+
+    @certificate = CertificateAuthority::Certificate.new
+    @certificate.key_material.generate_key(768)
+    @certificate.subject.common_name = "http://questionablesite.com"
+    @certificate.parent = @root_certificate
+    @certificate.serial_number.number = 2
+    @certificate.sign!
+
+    @ocsp_request = OpenSSL::OCSP::Request.new
+    @ocsp_request.add_nonce
+    openssl_cert_issuer = OpenSSL::X509::Certificate.new(@root_certificate.to_pem)
+    openssl_cert_subject = OpenSSL::X509::Certificate.new(@certificate.to_pem)
+
+    cert_id = OpenSSL::OCSP::CertificateId.new(openssl_cert_subject, openssl_cert_issuer)
+    @ocsp_request.add_certid(cert_id)
+    @ocsp_request_reader = CertificateAuthority::OCSPRequestReader.from_der(@ocsp_request.to_der)
+
+    @response_builder = CertificateAuthority::OCSPResponseBuilder.from_request_reader(@ocsp_request_reader)
+    @response_builder.parent = @root_certificate
+  end
+
+  it "should build from a OCSPRequestReader" do
+    @response_builder.should_not be_nil
+    @response_builder.should be_a(CertificateAuthority::OCSPResponseBuilder)
+  end
+
+  it "should build a response" do
+    response = @response_builder.build_response
+    response.should be_a(OpenSSL::OCSP::Response)
+  end
+
+  it "should verify against the root" do
+    response = @response_builder.build_response
+    root_cert = OpenSSL::X509::Certificate.new(@root_certificate.to_pem)
+    store = OpenSSL::X509::Store.new
+    store.add_cert(root_cert)
+    response.basic.verify([root_cert],store).should be_true
+  end
+
+  it "should have a configurable nextUpdate" do
+    time = 30 * 60 # 30 minutes
+    @response_builder.next_update=time
+    response = @response_builder.build_response
+    response.basic.status.each do |status|
+      ## 3 seconds of wabble is OK
+      status[5].should be_within(3).of(status[4] + time)
+    end
+  end
+
+  describe "verification mechanisms" do
+    it "should support an everything's OK default (though somewhat useless)" do
+      response = @response_builder.build_response
+      response.basic.status.each do |status|
+        status[1].should == OpenSSL::OCSP::V_CERTSTATUS_GOOD
+      end
+    end
+
+    it "should support an overridable verification mechanism callback" do
+      verification = lambda {|serial_number|
+        [CertificateAuthority::OCSPResponseBuilder::REVOKED,CertificateAuthority::OCSPResponseBuilder::UNSPECIFIED]
+      }
+      @response_builder.verification_mechanism = verification
+      response = @response_builder.build_response
+
+      response.basic.status.each do |status|
+        status[1].should == OpenSSL::OCSP::V_CERTSTATUS_REVOKED
+      end
+    end
+  end
+end
+
+
+## DEPRECATED
 describe CertificateAuthority::OCSPHandler do
   before(:each) do
     @ocsp_handler = CertificateAuthority::OCSPHandler.new
-
     @root_certificate = CertificateAuthority::Certificate.new
     @root_certificate.signing_entity = true
     @root_certificate.subject.common_name = "OCSP Root"
-    @root_certificate.key_material.generate_key(1024)
+    @root_certificate.key_material.generate_key(768)
     @root_certificate.serial_number.number = 1
     @root_certificate.sign!
 
     @certificate = CertificateAuthority::Certificate.new
-    @certificate.key_material.generate_key(1024)
+    @certificate.key_material.generate_key(768)
     @certificate.subject.common_name = "http://questionablesite.com"
     @certificate.parent = @root_certificate
     @certificate.serial_number.number = 2
@@ -24,6 +138,7 @@ describe CertificateAuthority::OCSPHandler do
 
     cert_id = OpenSSL::OCSP::CertificateId.new(openssl_cert_subject, openssl_cert_issuer)
     @ocsp_request.add_certid(cert_id)
+
     @ocsp_handler.ocsp_request = @ocsp_request.to_der
   end
 
diff --git a/spec/units/signing_entity_spec.rb b/spec/units/signing_entity_spec.rb
index 8951c22..a38e78c 100644
--- a/spec/units/signing_entity_spec.rb
+++ b/spec/units/signing_entity_spec.rb
@@ -1,4 +1,4 @@
 require File.dirname(__FILE__) + '/units_helper'
 
 describe CertificateAuthority::SigningEntity do
-end
\ No newline at end of file
+end
diff --git a/spec/units/signing_request_spec.rb b/spec/units/signing_request_spec.rb
new file mode 100644
index 0000000..4b770c7
--- /dev/null
+++ b/spec/units/signing_request_spec.rb
@@ -0,0 +1,154 @@
+require File.dirname(__FILE__) + '/units_helper'
+
+describe CertificateAuthority::SigningRequest do
+  before(:each) do
+    @pem_csr =<<EOF
+-----BEGIN CERTIFICATE REQUEST-----
+MIICwDCCAagCAQAwezELMAkGA1UEBhMCVVMxEzARBgNVBAgTCkNhbGlmb3JuaWEx
+FjAUBgNVBAcTDVNhbiBGcmFuY2lzY28xHjAcBgNVBAoTFUNlcnRpZmljYXRlIEF1
+dGhvcml0eTEfMB0GA1UEAxMWd3d3LmNocmlzY2hhbmRsZXIubmFtZTCCASIwDQYJ
+KoZIhvcNAQEBBQADggEPADCCAQoCggEBAMCYdPHqyAafolv+tDmCPVj8R11aZSqG
+h44W2AF7OOhTYiiyJaudBU4uYryJHeWVMnL2I9uxyvzDqBSfjwIU3bQAAvoqWdlS
+qa/V5kLa5CM4nSYvdBErvpxEyd6neAEgtPepPqKWGve8WRziuL0it/TopBTl4eOl
+yqHrXTa3I98qBWS28Iifxpz9SXcCaXXHmMmK9KN0Y+BVCJZFVHtPoTLNIxF+nu6S
+SieWtGXVe71pDDumndjuYsn3vw+q0Oc8v79AYb0ltdU/lc6ptoSQ5dG0NRG9OA0v
+hKntu8TvzgOD6IunJ2ttuLLZ3OqWIwZHi6KrahxOjwoMHVpGdzVLrxkCAwEAAaAA
+MA0GCSqGSIb3DQEBBQUAA4IBAQA9Vc/WPbGWieetcaz6uDToFSJZhXyhRKuiMDwJ
+cBYWiDlzpNXPTsrWnaDr2kySHpLvlrl3GPpMTvTOO1QYfYmX+adgHmZAwezBsI4a
+NBnaAcI4Qv8p+v7ZxHa3yr78Mxj08Yoihd0/f7Ls5XFUppYpwNoYiKYroOMNPEuu
+TJC3u6zMEQH8wtHUy3Ii3Ho+MlXlz/DynlOAPmq6EpnMAwh8fMSbMtwTJeVcU34d
+m7FwfCvp/120RQLdKaB7zYffcwJUBLTSRKIYkWl9lAC4MlhLUfLmYnJi19Gj/SJZ
+jX2pfrub2mscWVhEw+kxYakXh31KnroCYN9I3WGWNYi9ysbi
+-----END CERTIFICATE REQUEST-----
+EOF
+  end
+
+  it "should generate from a PEM CSR" do
+    csr = CertificateAuthority::SigningRequest.from_x509_csr(@pem_csr)
+    csr.should_not be_nil
+    csr.should be_a(CertificateAuthority::SigningRequest)
+  end
+
+  it "should generate a proper DN from the CSR" do
+    csr = CertificateAuthority::SigningRequest.from_x509_csr(@pem_csr)
+    expected_dn = CertificateAuthority::DistinguishedName.new
+    expected_dn.country = "US"
+    expected_dn.organization = "Certificate Authority"
+    expected_dn.common_name = "www.chrischandler.name"
+    expected_dn.locality = "San Francisco"
+    expected_dn.state = "California"
+    csr.distinguished_name.should == expected_dn
+  end
+
+  it "should expose the underlying OpenSSL CSR" do
+    csr = CertificateAuthority::SigningRequest.from_x509_csr(@pem_csr)
+    csr.openssl_csr.should be_a(OpenSSL::X509::Request)
+  end
+
+  it "should expose the PEM encoded original CSR" do
+    csr = CertificateAuthority::SigningRequest.from_x509_csr(@pem_csr)
+    csr.raw_body.should == @pem_csr
+    csr.raw_body.should be_a(String)
+  end
+
+  describe "transforming to a certificate" do
+    before(:each) do
+      @csr = CertificateAuthority::SigningRequest.from_x509_csr(@pem_csr)
+      @cert = @csr.to_cert
+    end
+
+    it "should allow transformation to a certificate" do
+      cert = @csr.to_cert
+      cert.should_not be_nil
+      cert.should be_a(CertificateAuthority::Certificate)
+    end
+
+    it "should be signable w/ a serial number" do
+      root = CertificateAuthority::Certificate.new
+      root.signing_entity = true
+      root.subject.common_name = "chrischandler.name root"
+      root.key_material.generate_key(1024)
+      root.serial_number.number = 2
+      root.sign!
+      @cert.serial_number.number = 5
+      @cert.parent = root
+      result_cert = @cert.sign!
+      result_cert.should be_a(OpenSSL::X509::Certificate)
+      ## Verify the subjects and public key match
+      @csr.distinguished_name.to_x509_name.should == result_cert.subject
+      @csr.key_material.public_key.to_pem.should == result_cert.public_key.to_pem
+    end
+  end
+
+  describe "Netscape SPKAC" do
+    before(:each) do
+      @spkac =<<EOF
+MIICQDCCASgwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDVDEqzj21++aMWvN6zzwDXKKpR9g5hIeAPYqbUdaPFePhtz7R73l7fVxDeQZDQpQxTqts0/w0wEa/A1ehHCtAkDoTYzjwX8G0Gkb90poA156I8b4Cl1Q2veKbLsaOsMWItlXSU6HULQ5McfYfvEaPmIKiIr0UIFdzMDcy9TnY854w9TcQVvLJZcQkaM3dy/p9W4gg0a9hBJwwFUUR2UV/nEEi+++HbsOE46Z7Y3qoQhLrL4DNUrXUPDVeqac1SmNfKTA71QADbezWDfKi9habHHGXqk18i2Pl6uA2mpNPuSWnEHbQONgnfeoWZBvMWkwlolaBeWhGSmgcL/HqaRLlFAgMBAAEWADANBgkqhkiG9w0BAQQFAAOCAQEAp8wOvrl2QG9p1PS19dnrh4l0JWNAPB+d1kc64xUG6FAfGCKnOHzdDndTJfEE [...]
+EOF
+    end
+
+    it "should process a netscape SPKAC" do
+      @csr = CertificateAuthority::SigningRequest.from_netscape_spkac(@spkac)
+      @csr.should be_a(CertificateAuthority::SigningRequest)
+    end
+  end
+
+  describe "Generating CSRs" do
+    before(:each) do
+      @key_pair =<<EOF
+-----BEGIN RSA PRIVATE KEY-----
+MIICXAIBAAKBgQCxiGVfRrf90CHmvXa+XYWE4m7LZ1slc6cxIYyIgZuQ5T8AeqUa
+kbyYY4wMUR2gZ4pDPs/WGs8fW66q23qmHSr1bQ6HaL8znbD7UL/IiiyiW8I11orb
+rhimIx1A606qi8/0gQc+H851gzUusd5xgKP2X+oPxYx3VG3dpksLnNK1IwIDAQAB
+AoGAfrNNRbX+0dGcoERPXoT4KWJAmEHnNs9XXyUGWtXE5J/3Wqws8M1Zv5gr9w5d
+CoFal6tYQQFZGJQiECYbXjoq0VT8ApWfuO/mCXyXfmnLFEU8EJjmXtXzn2yyPfoY
+At7O8QwvG0bwtw1SqNf7cRtlOEIqoLMtdyaVv4C5ffyheIECQQDVdVf2Sk113Kke
+PREzEb6XZ0n2ugSG8fWJh2QKUI4RXhg7bDzHhSpexeKsJdoet8NJOUEsXMoqLSzK
+bBnSD63RAkEA1OogtDCkpwkvqC63a7hyDP7qRVHFuVeSA1fu+6BFS0xblkgvcPXT
+J7WbWYcP+lqcLjXWeFsqe5qS6sDCsAhsswJAIumZZHgMqU1Y/9AfIwow8RR8vXT5
+TpT+gur5CtLYGbEZJ4bxffSi1HNrOprKTSHjN/O8XCQlELboz4bUxk24MQJAcsaX
+xKsoR4dTMoWkiSRQDyNoJOA1B3nmk3jWsryuPi42fSgCsxFBt/lVeoitm1c3NE3/
+hLgYibNFGdm52e1gswJBAMwYuImbl6AVLv0Y41smxIkvfAzlyNfTAsp7GqLoMhYN
+q/0KoyI2Ge3+NnmJI/eaiYs8qC2HjrgdX9ZDSUCWfpQ=
+-----END RSA PRIVATE KEY-----
+EOF
+      @csr = CertificateAuthority::SigningRequest.new
+      dn = CertificateAuthority::DistinguishedName.new
+      dn.common_name = "localhost"
+      @csr.distinguished_name = dn
+
+      k = CertificateAuthority::KeyMaterial.from_x509_key_pair(@key_pair)
+      @csr.key_material = k
+    end
+
+    it "should generate a CSR" do
+      expected =<<EOF
+-----BEGIN CERTIFICATE REQUEST-----
+MIIBUDCBugIAMBQxEjAQBgNVBAMMCWxvY2FsaG9zdDCBnzANBgkqhkiG9w0BAQEF
+AAOBjQAwgYkCgYEAsYhlX0a3/dAh5r12vl2FhOJuy2dbJXOnMSGMiIGbkOU/AHql
+GpG8mGOMDFEdoGeKQz7P1hrPH1uuqtt6ph0q9W0Oh2i/M52w+1C/yIosolvCNdaK
+264YpiMdQOtOqovP9IEHPh/OdYM1LrHecYCj9l/qD8WMd1Rt3aZLC5zStSMCAwEA
+ATANBgkqhkiG9w0BAQ0FAAOBgQACWe6Eyl5XDKgvUS9PUBnATpag2joaRDgc1qxU
+FTkA7VX3GcnDFqnu1bj8kE7Ej7KBUybSJoSlfZrTxT1GsZ1tubzBeWsYdY1LctU2
+5a/fyqvMg/m2DQaMK5oupJNuAvihmVCM0I1qjmDregeAqz94iki8YgAbG6q/NnyT
+YK3KbQ==
+-----END CERTIFICATE REQUEST-----
+EOF
+      @csr.to_pem.should == expected
+    end
+
+    it "should generate a signed CSR" do
+      @csr.digest = "SHA256"
+      @csr.to_x509_csr.signature_algorithm.should == "sha256WithRSAEncryption"
+    end
+
+    it "should generate a CSR w/ a subjectAlternativeName extension" do
+      alt_names = ["abc.com","somethingelse.com"]
+      @csr.subject_alternative_names = alt_names
+
+      expected_subjectAlt = CertificateAuthority::Extensions::SubjectAlternativeName.new
+      expected_subjectAlt.dns_names =["abc.com", "somethingelse.com"]
+      @csr.to_cert.extensions["subjectAltName"] == expected_subjectAlt
+    end
+  end
+end
diff --git a/spec/units/working_with_openssl_spec.rb b/spec/units/working_with_openssl_spec.rb
new file mode 100644
index 0000000..3af4fbf
--- /dev/null
+++ b/spec/units/working_with_openssl_spec.rb
@@ -0,0 +1,136 @@
+require File.dirname(__FILE__) + '/units_helper'
+
+describe "Using OpenSSL" do
+
+  shared_examples_for "an ossl issuer and its signed cert" do
+    it "should issue a certificate verified by the issuer" do
+      @signed.verify(@issuer.public_key ).should be_true
+    end
+
+    it "should issue a certificate with a matching issuer subject string" do
+      @signed.issuer.to_s.should == @issuer.subject.to_s
+    end
+
+    it "should issue a certificate with a matching issuer subject openssl name" do
+      @signed.issuer.should == @issuer.subject
+    end
+
+  end
+
+  context "Signing CSRs" do
+    shared_examples_for "a csr operation" do
+      before :all do
+        @ca = sample_file("certs/ca.crt").read
+        @ca_key = sample_file("certs/ca.key").read
+
+        @issuer = OpenSSL::X509::Certificate.new(@ca)
+        issuer_ca = CertificateAuthority::Certificate.from_openssl(@issuer)
+        issuer_ca.key_material.private_key = OpenSSL::PKey::RSA.new(@ca_key)
+
+        @our_csr = CertificateAuthority::SigningRequest.from_x509_csr(@csr_pem)
+        signed = @our_csr.to_cert
+        signed.parent = issuer_ca
+        signed.serial_number.number = 2
+
+        signed.sign!
+
+        @cert = @signed = OpenSSL::X509::Certificate.new(signed.to_pem)
+      end
+      it_should_behave_like "an ossl issuer and its signed cert"
+    end
+
+    context "With a server CSR" do
+      before :all do
+        @csr_pem = sample_file("certs/server.csr").read
+      end
+
+      it_should_behave_like "a csr operation"
+    end
+
+    context "With a client CSR" do
+      before :all do
+        @csr_pem = sample_file("certs/client.csr").read
+      end
+
+      it_should_behave_like "a csr operation"
+    end
+  end
+
+  context "Handling externally supplied CAs and certs" do
+    shared_examples_for "comparing a pair of openssl certs" do
+      context "using openssl" do
+        before :all do
+          @issuer = @ca
+          @signed = @cert
+        end
+        it_should_behave_like "an ossl issuer and its signed cert"
+      end
+
+      context "using certificate_authority" do
+        before :all do
+          # from openssl
+          @our_ca = CertificateAuthority::Certificate.from_openssl(@ca)
+          @our_cert = CertificateAuthority::Certificate.from_openssl(@cert)
+
+          # and back
+          @issuer = OpenSSL::X509::Certificate.new(@our_ca.to_pem)
+          @signed = OpenSSL::X509::Certificate.new(@our_cert.to_pem)
+        end
+
+        it "should match the original ca's distinguished name" do
+          @our_ca.distinguished_name.to_x509_name.should == @ca.subject
+        end
+
+        it "should match the original openssl ca" do
+          back = OpenSSL::X509::Certificate.new(@our_ca.to_pem)
+          back.subject.should == @ca.subject
+        end
+
+        it "should match the original cert's distinguished name" do
+          @our_cert.distinguished_name.to_x509_name.should == @cert.subject
+        end
+
+        it "should match the original openssl cert" do
+          back = OpenSSL::X509::Certificate.new(@our_cert.to_pem)
+          back.subject.should == @cert.subject
+        end
+
+        it_should_behave_like "an ossl issuer and its signed cert"
+      end
+    end
+
+    context "A custom CA signing a client cert" do
+      before :all do
+        @ca = OpenSSL::X509::Certificate.new(sample_file("certs/ca.crt").read)
+        @cert = OpenSSL::X509::Certificate.new(sample_file("certs/client.crt").read)
+      end
+
+      it_should_behave_like "comparing a pair of openssl certs"
+    end
+
+    context "A custom CA signing a server cert" do
+      before :all do
+        @ca = OpenSSL::X509::Certificate.new(sample_file("certs/ca.crt").read)
+        @cert = OpenSSL::X509::Certificate.new(sample_file("certs/server.crt").read)
+      end
+
+      it_should_behave_like "comparing a pair of openssl certs"
+    end
+
+    context "Github's signer" do
+      before :all do
+        @ca = OpenSSL::X509::Certificate.new(sample_file("certs/DigiCertHighAssuranceEVCA-1.pem").read)
+        @cert = OpenSSL::X509::Certificate.new(sample_file("certs/github.com.pem").read)
+      end
+      it_should_behave_like "comparing a pair of openssl certs"
+    end
+
+    context "Apple's WWDR signer" do
+      before :all do
+        @ca = OpenSSL::X509::Certificate.new(sample_file("certs/apple_wwdr_issuer.pem").read)
+        @cert = OpenSSL::X509::Certificate.new(sample_file("certs/apple_wwdr_issued_cert.pem").read)
+      end
+      it_should_behave_like "comparing a pair of openssl certs"
+    end
+  end
+end

-- 
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/pkg-ruby-extras/ruby-certificate-authority.git



More information about the Pkg-ruby-extras-commits mailing list