certificate-authority-0.1.6/0000755000175000017500000000000012665402716015267 5ustar sbadiasbadiacertificate-authority-0.1.6/Gemfile0000644000175000017500000000022612665402716016562 0ustar sbadiasbadiasource 'http://rubygems.org' gem 'activemodel', ">= 3.0.6" group :development do gem 'rspec' gem "jeweler", ">= 1.5.2" #gem "rcov", ">= 0" end certificate-authority-0.1.6/README.rdoc0000644000175000017500000003563512665402716017111 0ustar sbadiasbadia= CertificateAuthority - Because it shouldn't be this damned complicated 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. Because this library is built using the native Ruby bindings for OpenSSL it also supports PKCS#11 cryptographic hardware for secure maintenance of private key materials. = So you want to maintain a certificate authority root Let's suppose hypothetically you want to be able to issue and manage your own certificates. This section is meant to outline the basic functions you'll need(optionally want) to support. Not everyone is out to be in total compliance with WebTrust[link:http://www.webtrust.org/] or {Mozilla's rules for CA inclusion}[link:https://wiki.mozilla.org/CA:How_to_apply]. The three primary elements to be aware of are: [Certificate Authority] These are the functions primarily related to the signing, issuance, and revocation of certificates. [Registration Authority] These are the functions primarily related to registering and requesting certificates and vetting of the entities requesting certification. [Validation Authority] These are the functions related to verifying the status of certificates out in the wild. Mostly CRLs and OCSP related functions. = Establishing a new root in software Let's look at a complete example for generating a new root certificate. Assuming that you don't have a PKCS#11 hardware token available (lists coming...) we'll have to store this safe. Generating a self-signed root certificate is fairly easy: require 'certificate_authority' root = CertificateAuthority::Certificate.new root.subject.common_name= "http://mydomain.com" root.serial_number.number=1 root.key_material.generate_key root.signing_entity = true signing_profile = {"extensions" => {"keyUsage" => {"usage" => ["critical", "keyCertSign"] }} } root.sign!(signing_profile) The required elements for the gem at this time are a common name for the subject and a serial number for the certificate. Since this is our self-signed root we're going to give it the first serial available of 1. Because certificate_authority is not designed to manage the issuance lifecycle you'll be expected to store serial numbers yourself. Next, after taking care of required fields, we will require key material for the new certificate. There's a convenience method made available on the key_material object for generating new keys. The private key will be available in: root.key_material.private_key and the public key: root.key_material.public_key Make sure to save the private key somewhere safe! Lastly, we declare that the certificate we're about to sign is itself a signing entity so we can continue on and sign other certificates. == Creating a new intermediate Maybe you don't want to actually sign certificates with your super-secret root certificate. This is actually how a good number of most public certificate authorities do it. Rather than sign with the primary root, they generate an intermediate root that is then responsible for signing the final certificates. If you wanted to create an intermediate root certificate you would do something like the following: intermediate = CertificateAuthority::Certificate.new intermediate.subject.common_name= "My snazzy intermediate!" intermediate.serial_number.number=2 intermediate.key_material.generate_key intermediate.signing_entity = true intermediate.parent = root 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, our root will be responsible for signing this intermediate when we call +sign!+. = Creating new certificates (in general) Now that we have a root certificate (and possibly an intermediate) we can sign end-user certificates. It is, perhaps unsurprisingly, similar to all the others: plain_cert = CertificateAuthority::Certificate.new plain_cert.subject.common_name= "http://mydomain.com" plain_cert.serial_number.number=4 plain_cert.key_material.generate_key plain_cert.parent = root # or intermediate plain_cert.sign! That's all there is to it! In this example we generate the key material ourselves, but it's possible for the end-user to generate certificate signing request (CSR) that we can then parse and consume automatically (coming soon). To get the PEM formatted certificate for the user you would need to call: plain_cert.to_pem to get the certificate body. = Signing Profiles Creating basic certificates is all well and good, but maybe you want _more_ signing control. +certificate_authority+ supports the idea of signing profiles. These are hashes containing values that +sign!+ will use to merge in additional control options for setting extensions on the certificate. Here's an example of a full signing profile for most of the common V3 extensions: signing_profile = { "extensions" => { "basicConstraints" => {"ca" => false}, "crlDistributionPoints" => {"uri" => "http://notme.com/other.crl" }, "subjectKeyIdentifier" => {}, "authorityKeyIdentifier" => {}, "authorityInfoAccess" => {"ocsp" => ["http://youFillThisOut/ocsp/"] }, "keyUsage" => {"usage" => ["digitalSignature","nonRepudiation"] }, "extendedKeyUsage" => {"usage" => [ "serverAuth","clientAuth"]}, "subjectAltName" => {"uris" => ["http://subdomains.youFillThisOut/"]}, "certificatePolicies" => { "policy_identifier" => "1.3.5.8", "cps_uris" => ["http://my.host.name/", "http://my.your.name/"], "user_notice" => { "explicit_text" => "Explicit Text Here", "organization" => "Organization name", "notice_numbers" => "1,2,3,4" } } } } Using a signing profile is done this way: certificate.sign!(signing_profile) At that point all the configuration options will be merged into the extensions. == Basic Constraints The basic constraints extension allows you to control whether or not a certificate can sign other certificates. [CA] If this value is true then this certificate has the authority to sign additional certificates. [pathlen] This is the maximum length of the chain-of-trust. For instance, if an intermediate certificate has a pathlen of 1 then it can sign additional certificates, but it cannot create another signing entity because the total chain-of-trust would have a length greater than 1. == CRL Distribution Points This extension controls where a conformant client can go to obtain a list of certificate revocation information. At this point +certificate_authority+ only supports a list of URIs. The formal RFC however provides for the ability to provide a URI and an issuer identifier that allows a different signing entity to generate/sign the CRL. [uri] The URI in subject alternative name format of the URI endpoint. Example: "http://ca.chrischandler.name/some_identifier.crl" == Subject Key Identifier This extension is required to be present, but doesn't offer any configurable parameters. Directly from the RFC: The subject key identifier extension provides a means of identifying certificates that contain a particular public key. To facilitate certification path construction, this extension MUST appear in all conforming CA certificates, that is, all certificates including the basic constraints extension (section 4.2.1.10) where the value of cA is TRUE. The value of the subject key identifier MUST be the value placed in the key identifier field of the Authority Key Identifier extension (section 4.2.1.1) of certificates issued by the subject of this certificate. == Authority Key Identifier Just like the subject key identifier, this is required under most circumstances and doesn't contain any meaningful configuration options. From the RFC: The keyIdentifier field of the authorityKeyIdentifier extension MUST be included in all certificates generated by conforming CAs to facilitate certification path construction. There is one exception; where a CA distributes its public key in the form of a "self-signed" certificate, the authority key identifier MAY be omitted. The signature on a self-signed certificate is generated with the private key associated with the certificate's subject public key. (This proves that the issuer possesses both the public and private keys.) In this case, the subject and authority key identifiers would be identical, but only the subject key identifier is needed for certification path building. == Authority Info Access The authority info access extension allows a CA to sign a certificate with information a client can use to get up-to-the-minute status information on a signed certificate. This takes the form of an OCSP[link:http://en.wikipedia.org/wiki/Online_Certificate_Status_Protocol] (Online Certificate Status Protocol) endpoints. [ocsp] This is an array of URIs specifying possible endpoints that will be able to provide a signed response. +certificate_authority+ has an OCSP message handler for parsing OCSP requests and generating OCSP signed responses. == Key Usage This extension contains a list of the functions this certificate is allowed to participate in. [usage] An array of OIDs in string format. The acceptable values are specified by OpenSSL and are: +digitalSignature+, +nonRepudiation+, +keyEncipherment+, +dataEncipherment+, +keyAgreement+, +keyCertSign+, +cRLSign+, +encipherOnly+ and +decipherOnly+. == Extended Key Usage This one is like key usage, but allows for certain application specific purposes. It's generally only present in end-user certificates. [usage] An array of OIDs in string format. The only ones with practical significance at this point are: +serverAuth+, +clientAuth+, and +codeSigning+. == Subject Alternative Name If the certificate needs to work for multiple domains then you can specify the others for which it is valid in the subject alternative name field. [uris] An array of full URIs for other common names this certificate should be valid for. For instance, if you want http://ca.chrischandler.name and http://www.ca.chrischandler.name to share the same cert you would place both in the +uris+ attribute of the subject alternative name. == Certificate Policies This is one of the most esoteric of the extensions. This allows a conformant certificate authority to embed signing policy information into the certificate body. Public certificate authorities are required to maintain a Certificate Practice Statement in accordance with {RFC 2527}[link:http://www.ietf.org/rfc/rfc2527.txt]. These CPSs define what vetting criteria and maintenance practices are required to issue, maintain, and revoke a certificate. While it might be overkill for private certificates, if you wanted to make an actual public CA you would need to put together a practice statement and embed it in certificates you issue. [policy_identifier] This is an arbitrary OID (that you make up!) that uniquely represents the policy you are enforcing for whatever kind of certificate this is meant to be. [cps_uris] This is an array of URIs where a client or human can go to get information related to your certification practice. [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. = 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. To configure a certificate to utilize PKCS#11 instead of in memory keys all you need to do is: root = CertificateAuthority::Certificate.new root.subject.common_name= "http://mydomain.com" root.serial_number.number=1 root.signing_entity = true key_material_in_hardware = CertificateAuthority::Pkcs11KeyMaterial.new key_material_in_hardware.token_id = "46" key_material_in_hardware.pkcs11_lib = "/usr/lib/libeTPkcs11.so" key_material_in_hardware.openssl_pkcs11_engine_lib = "/usr/lib/engines/engine_pkcs11.so" key_material_in_hardware.pin = "11111111" root.key_material = key_material_in_hardware root.sign! Your current version of OpenSSL _must_ include dynamic engine support and you will need to have OpenSSL PKCS#11 engine support. You will also require the actual PKCS#11 driver from the hardware manufacturer. As of today the only tokens I've gotten to work are: [eTokenPro] Released by Aladdin (now SafeNet Inc.). I have only had success with the version 4 and 5 (32 bit only) copy of the driver. The newer authentication client released by SafeNet appears to be completely broken for interacting with the tokens outside of SafeNet's own tools. If anyone has a different experience I'd like to hear from you. [ACS CryptoMate] Also a 32-bit only driver. You'll have to jump through some hoops to get the Linux PKCS#11 driver but it works surprisingly well. It also appears to support symmetric key operations in hardware. [Your company] Do you make a PKCS#11 device? I'd love to get it working but I probably can't afford your device. Get in touch with me and if you're willing to loan me one for a week I can get it listed. 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 * More PKCS#11 hardware (I need driver support from the manufacturers) * Support for working with CSRs to request & issue certificates = Misc notes * Firefox will complain about root/intermediate certificates unless both digitalSignature and keyEncipherment are specified as keyUsage attributes. Thanks diogomonica == Meta Written by Chris Chandler(http://chrischandler.name) Released under the MIT License: http://www.opensource.org/licenses/mit-license.php Main page: http://github.com/cchandler/certificate_authority Issue tracking: https://github.com/cchandler/certificate_authority/issues certificate-authority-0.1.6/Gemfile.lock0000644000175000017500000000136612665402716017517 0ustar sbadiasbadiaGEM remote: http://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) 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.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) PLATFORMS ruby DEPENDENCIES activemodel (>= 3.0.6) jeweler (>= 1.5.2) rspec certificate-authority-0.1.6/spec/0000755000175000017500000000000012665402716016221 5ustar sbadiasbadiacertificate-authority-0.1.6/spec/units/0000755000175000017500000000000012665402716017363 5ustar sbadiasbadiacertificate-authority-0.1.6/spec/units/units_helper.rb0000644000175000017500000000006212665402716022407 0ustar sbadiasbadiarequire File.dirname(__FILE__) + '/../spec_helper'certificate-authority-0.1.6/spec/units/ocsp_handler_spec.rb0000644000175000017500000000750112665402716023366 0ustar sbadiasbadiarequire File.dirname(__FILE__) + '/units_helper' 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.serial_number.number = 1 @root_certificate.sign! @certificate = CertificateAuthority::Certificate.new @certificate.key_material.generate_key(1024) @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_handler.ocsp_request = @ocsp_request.to_der end it "should be able to accept an OCSP Request" do @ocsp_handler.ocsp_request = @ocsp_request @ocsp_handler.ocsp_request.should_not be_nil end it "should raise an error if you try and extract certificates without a raw request" do @ocsp_handler.extract_certificate_serials @ocsp_handler.ocsp_request = nil lambda {@ocsp_handler.extract_certificate_serials}.should raise_error end it "should return a hash of extracted certificates from OCSP requests" do result = @ocsp_handler.extract_certificate_serials result.size.should == 1 end it "should be able to generate an OCSP response" do @ocsp_handler.extract_certificate_serials @ocsp_handler << @certificate @ocsp_handler.parent = @root_certificate @ocsp_handler.response end it "should require a 'parent' entity for signing" do @ocsp_handler.parent = @root_certificate @ocsp_handler.parent.should_not be_nil end it "should raise an error if you ask for the signed OCSP response without generating it" do @ocsp_handler.extract_certificate_serials @ocsp_handler << @certificate @ocsp_handler.parent = @root_certificate lambda { @ocsp_handler.to_der }.should raise_error @ocsp_handler.response @ocsp_handler.to_der.should_not be_nil end it "should raise an error if you generate a response without adding all certificates in request" do @ocsp_handler.extract_certificate_serials @ocsp_handler.parent = @root_certificate lambda { @ocsp_handler.response }.should raise_error end it "should raise an error if you generate a response without adding a parent signing entity" do @ocsp_handler.extract_certificate_serials @ocsp_handler << @certificate lambda { @ocsp_handler.response }.should raise_error end describe "Response" do before(:each) do @ocsp_handler.extract_certificate_serials @ocsp_handler << @certificate @ocsp_handler.parent = @root_certificate @ocsp_handler.response @openssl_ocsp_response = OpenSSL::OCSP::Response.new(@ocsp_handler.to_der) end it "should have a correct status/status string" do @openssl_ocsp_response.status_string.should == "successful" @openssl_ocsp_response.status.should == 0 end it "should have an embedded BasicResponse with certificate statuses" do # [#, 0, 1, nil, 2011-04-15 23:29:47 UTC, 2011-04-15 23:30:17 UTC, []] @openssl_ocsp_response.basic.status.first[1].should == 0 # Everything is OK end it "should have a next_update time" do @openssl_ocsp_response.basic.status.first[5].should_not be_nil @openssl_ocsp_response.basic.status.first[5].class.should == Time end end end certificate-authority-0.1.6/spec/units/certificate_revocation_list_spec.rb0000644000175000017500000000401612665402716026471 0ustar sbadiasbadiarequire File.dirname(__FILE__) + '/units_helper' describe CertificateAuthority::CertificateRevocationList do before(:each) do @crl = CertificateAuthority::CertificateRevocationList.new @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.serial_number.number = 1 @root_certificate.sign! @certificate = CertificateAuthority::Certificate.new @certificate.key_material.generate_key(1024) @certificate.subject.common_name = "http://bogusSite.com" @certificate.parent = @root_certificate @certificate.serial_number.number = 2 @certificate.sign! @crl.parent = @root_certificate @certificate.revoked_at = Time.now end it "should accept a list of certificates" do @crl << @certificate end it "should complain if you add a certificate without a revocation time" do @certificate.revoked_at = nil lambda{ @crl << @certificate}.should raise_error end it "should have a 'parent' that will be responsible for signing" do @crl.parent = @root_certificate @crl.parent.should_not be_nil end it "should raise an error if you try and sign a CRL without attaching a parent" do @crl.parent = nil lambda { @crl.sign! }.should raise_error end it "should be able to generate a proper CRL" do @crl << @certificate lambda {@crl.to_pem}.should raise_error @crl.parent = @root_certificate @crl.sign! @crl.to_pem.should_not be_nil OpenSSL::X509::CRL.new(@crl.to_pem).should_not be_nil end describe "Next update" do it "should be able to set a 'next_update' value" do @crl.next_update = (60 * 60 * 10) # 10 Hours @crl.next_update.should_not be_nil end it "should throw an error if we try and sign up with a negative next_update" do @crl.sign! @crl.next_update = - (60 * 60 * 10) lambda{@crl.sign!}.should raise_error end end end certificate-authority-0.1.6/spec/units/pkcs11_key_material_spec.rb0000644000175000017500000000324212665402716024553 0ustar sbadiasbadiarequire File.dirname(__FILE__) + '/units_helper' ## Anything that requires crypto hardware needs to be tagged as 'pkcs11' describe CertificateAuthority::Pkcs11KeyMaterial, :pkcs11 => true do before(:each) do @key_material_in_hardware = CertificateAuthority::Pkcs11KeyMaterial.new @key_material_in_hardware.token_id = "46" @key_material_in_hardware.pkcs11_lib = "/usr/lib/libeTPkcs11.so" @key_material_in_hardware.openssl_pkcs11_engine_lib = "/usr/lib/engines/engine_pkcs11.so" @key_material_in_hardware.pin = "11111111" end it "should identify as being in hardware", :pkcs11 => true do @key_material_in_hardware.is_in_hardware?.should be_true end it "should return a Pkey ref if the private key is requested", :pkcs11 => true do @key_material_in_hardware.private_key.class.should == OpenSSL::PKey::RSA end it "should return a Pkey ref if the public key is requested", :pkcs11 => true do @key_material_in_hardware.public_key.class.should == OpenSSL::PKey::RSA end it "should accept an ID for on-token objects", :pkcs11 => true do @key_material_in_hardware.respond_to?(:token_id).should be_true end it "should accept a path to a shared library for a PKCS11 driver", :pkcs11 => true do @key_material_in_hardware.respond_to?(:pkcs11_lib).should be_true end it "should accept a path to OpenSSL's dynamic PKCS11 engine (provided by libengine-pkcs11-openssl)", :pkcs11 => true do @key_material_in_hardware.respond_to?(:openssl_pkcs11_engine_lib).should be_true end it "should accept an optional PIN to authenticate to the token", :pkcs11 => true do @key_material_in_hardware.respond_to?(:pin).should be_true end end certificate-authority-0.1.6/spec/units/extensions_spec.rb0000644000175000017500000001217212665402716023124 0ustar sbadiasbadiarequire File.dirname(__FILE__) + '/units_helper' describe CertificateAuthority::Extensions do describe CertificateAuthority::Extensions::BasicContraints do it "should only allow true/false" do basic_constraints = CertificateAuthority::Extensions::BasicContraints.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.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 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.ca = true basic_constraints.path_len = 2 basic_constraints.to_s.should == "CA:true,pathlen:2" end end describe CertificateAuthority::Extensions::SubjectAlternativeName do it "should respond to :uris" do subjectAltName = CertificateAuthority::Extensions::SubjectAlternativeName.new subjectAltName.respond_to?(:uris).should be_true end it "should require 'uris' to be an Array" do subjectAltName = CertificateAuthority::Extensions::SubjectAlternativeName.new lambda {subjectAltName.uris = "not an array"}.should raise_error end it "should generate a proper OpenSSL extension string for URIs" do subjectAltName = CertificateAuthority::Extensions::SubjectAlternativeName.new subjectAltName.uris = ["http://localhost.altname.example.com"] subjectAltName.to_s.should == "URI:http://localhost.altname.example.com" subjectAltName.uris = ["http://localhost.altname.example.com", "http://other.example.com"] subjectAltName.to_s.should == "URI:http://localhost.altname.example.com,URI:http://other.example.com" end it "should respond to :dns_names" do subjectAltName = CertificateAuthority::Extensions::SubjectAlternativeName.new subjectAltName.respond_to?(:dns_names).should be_true end it "should require 'dns_names' to be an Array" do subjectAltName = CertificateAuthority::Extensions::SubjectAlternativeName.new lambda {subjectAltName.dns_names = "not an array"}.should raise_error end it "should generate a proper OpenSSL extension string for DNS names" do subjectAltName = CertificateAuthority::Extensions::SubjectAlternativeName.new subjectAltName.dns_names = ["localhost.altname.example.com"] subjectAltName.to_s.should == "DNS:localhost.altname.example.com" subjectAltName.dns_names = ["localhost.altname.example.com", "other.example.com"] subjectAltName.to_s.should == "DNS:localhost.altname.example.com,DNS:other.example.com" end it "should respond to :ips" do subjectAltName = CertificateAuthority::Extensions::SubjectAlternativeName.new subjectAltName.respond_to?(:ips).should be_true end it "should require 'ips' to be an Array" do subjectAltName = CertificateAuthority::Extensions::SubjectAlternativeName.new lambda {subjectAltName.ips = "not an array"}.should raise_error end it "should generate a proper OpenSSL extension string for IPs" do subjectAltName = CertificateAuthority::Extensions::SubjectAlternativeName.new subjectAltName.ips = ["1.2.3.4"] subjectAltName.to_s.should == "IP:1.2.3.4" subjectAltName.ips = ["1.2.3.4", "5.6.7.8"] subjectAltName.to_s.should == "IP:1.2.3.4,IP:5.6.7.8" 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"] subjectAltName.to_s.should == "IP:1.2.3.4" subjectAltName.dns_names = ["localhost.altname.example.com"] subjectAltName.to_s.should == "DNS:localhost.altname.example.com,IP:1.2.3.4" subjectAltName.dns_names = ["localhost.altname.example.com", "other.example.com"] subjectAltName.to_s.should == "DNS:localhost.altname.example.com,DNS:other.example.com,IP:1.2.3.4" subjectAltName.ips = ["1.2.3.4", "5.6.7.8"] subjectAltName.to_s.should == "DNS:localhost.altname.example.com,DNS:other.example.com,IP:1.2.3.4,IP:5.6.7.8" subjectAltName.uris = ["http://localhost.altname.example.com"] subjectAltName.to_s.should == "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" 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 end end certificate-authority-0.1.6/spec/units/certificate_authority_spec.rb0000644000175000017500000000012612665402716025313 0ustar sbadiasbadiarequire File.dirname(__FILE__) + '/units_helper' describe CertificateAuthority do endcertificate-authority-0.1.6/spec/units/key_material_spec.rb0000644000175000017500000000635012665402716023374 0ustar sbadiasbadiarequire File.dirname(__FILE__) + '/units_helper' describe CertificateAuthority::KeyMaterial do [CertificateAuthority::MemoryKeyMaterial, CertificateAuthority::SigningRequestKeyMaterial].each do |key_material_class| before do @key_material = key_material_class.new end it "#{key_material_class} should know if a key is in memory or hardware" do @key_material.is_in_hardware?.should_not be_nil @key_material.is_in_memory?.should_not be_nil end it "should use memory by default" do @key_material.is_in_memory?.should be_true end end end describe CertificateAuthority::MemoryKeyMaterial do before(:each) do @key_material = CertificateAuthority::MemoryKeyMaterial.new end it "should be able to generate an RSA key" do @key_material.generate_key(1024).should_not be_nil end it "should generate a proper OpenSSL::PKey::RSA" do @key_material.generate_key(1024).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 end describe "with generated key" do before(:all) do @key_material_in_memory = CertificateAuthority::MemoryKeyMaterial.new @key_material_in_memory.generate_key(1024) end it "should be able to retrieve the private key" do @key_material_in_memory.private_key.should_not be_nil end it "should be able to retrieve the public key" do @key_material_in_memory.public_key.should_not be_nil end end it "should not validate without public and private keys" do @key_material.valid?.should be_false @key_material.generate_key(1024) @key_material.valid?.should be_true pub = @key_material.public_key @key_material.public_key = nil @key_material.valid?.should be_false @key_material.public_key = pub @key_material.private_key = nil @key_material.valid?.should be_false end end describe CertificateAuthority::SigningRequestKeyMaterial do before(:each) do @request = OpenSSL::X509::Request.new < true do @certificate.is_signing_entity?.should be_false @certificate.signing_entity = true @certificate.is_signing_entity?.should be_true end describe "Root certificates" do before(:each) do @certificate.signing_entity = true end it "should be able to be identified as a root certificate" do @certificate.is_root_entity?.should be_true end it "should only be a root certificate if the parent entity is itself", :rfc3280 => true do @certificate.parent.should == @certificate end it "should be a root certificate by default" do @certificate.is_root_entity?.should be_true end 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.sign! cert = OpenSSL::X509::Certificate.new(@certificate.to_pem) cert.subject.to_s.should == cert.issuer.to_s end 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.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" end end describe "Intermediate certificates" do before(:each) 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.serial_number.number = 2 @different_cert.sign! #self-signed @certificate.parent = @different_cert @certificate.signing_entity = true end it "should be able to be identified as an intermediate certificate" do @certificate.is_intermediate_entity?.should be_true end it "should not be identified as a root" do @certificate.is_root_entity?.should be_false end it "should only be an intermediate certificate if the parent is a different entity" do @certificate.parent.should_not == @certificate @certificate.parent.should_not be_nil end it "should correctly be signed by a parent certificate" do @certificate.subject.common_name = "chrischandler.name" @certificate.key_material.generate_key(1024) @certificate.signing_entity = true @certificate.serial_number.number = 1 @certificate.sign! cert = OpenSSL::X509::Certificate.new(@certificate.to_pem) cert.subject.to_s.should_not == cert.issuer.to_s end it "should have the basicContraint CA:TRUE" do @certificate.subject.common_name = "chrischandler.name" @certificate.key_material.generate_key(1024) @certificate.signing_entity = true @certificate.serial_number.number = 3 @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" end end describe "Terminal certificates" do before(:each) 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.serial_number.number = 1 @different_cert.sign! #self-signed @certificate.parent = @different_cert end it "should not be identified as an intermediate certificate" do @certificate.is_intermediate_entity?.should be_false end it "should not be identified as a root" do @certificate.is_root_entity?.should be_false end it "should have the basicContraint CA:FALSE" do @certificate.subject.common_name = "chrischandler.name" @certificate.key_material.generate_key(1024) @certificate.signing_entity = false @certificate.serial_number.number = 1 @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:FALSE" end end it "should be able to be identified as a root certificate" do @certificate.respond_to?(:is_root_entity?).should be_true end end #End of SigningEntity describe "Signed certificates" do before(:each) do @certificate = CertificateAuthority::Certificate.new @certificate.subject.common_name = "chrischandler.name" @certificate.key_material.generate_key(1024) @certificate.serial_number.number = 1 @certificate.sign! end it "should have a PEM encoded certificate body available" do @certificate.to_pem.should_not be_nil OpenSSL::X509::Certificate.new(@certificate.to_pem).should_not be_nil end end describe "X.509 V3 Extensions on Signed Certificates" do before(:each) do @certificate = CertificateAuthority::Certificate.new @certificate.subject.common_name = "chrischandler.name" @certificate.key_material.generate_key(1024) @certificate.serial_number.number = 1 @signing_profile = { "extensions" => { "subjectAltName" => {"uris" => ["www.chrischandler.name"]}, "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" } } } } @certificate.sign!(@signing_profile) end describe "SubjectAltName" do before(:each) do @certificate = CertificateAuthority::Certificate.new @certificate.subject.common_name = "chrischandler.name" @certificate.key_material.generate_key(1024) @certificate.serial_number.number = 1 end it "should have a subjectAltName if specified" do @certificate.sign!({"extensions" => {"subjectAltName" => {"uris" => ["www.chrischandler.name"]}}}) cert = OpenSSL::X509::Certificate.new(@certificate.to_pem) cert.extensions.map(&:oid).include?("subjectAltName").should be_true end it "should NOT have a subjectAltName if one was not specified" do @certificate.sign! cert = OpenSSL::X509::Certificate.new(@certificate.to_pem) cert.extensions.map(&:oid).include?("subjectAltName").should be_false 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.serial_number.number = 1 end it "should have an authority info access if specified" do @certificate.sign!({"extensions" => {"authorityInfoAccess" => {"ocsp" => ["www.chrischandler.name"]}}}) cert = OpenSSL::X509::Certificate.new(@certificate.to_pem) cert.extensions.map(&:oid).include?("authorityInfoAccess").should be_true end end describe "CrlDistributionPoints" do before(:each) do @certificate = CertificateAuthority::Certificate.new @certificate.subject.common_name = "chrischandler.name" @certificate.key_material.generate_key(1024) @certificate.serial_number.number = 1 end it "should have a crlDistributionPoint if specified" do @certificate.sign!({"extensions" => {"crlDistributionPoints" => {"uri" => ["http://crlThingy.com"]}}}) cert = OpenSSL::X509::Certificate.new(@certificate.to_pem) cert.extensions.map(&:oid).include?("crlDistributionPoints").should be_true end it "should NOT have a crlDistributionPoint if one was not specified" do @certificate.sign! cert = OpenSSL::X509::Certificate.new(@certificate.to_pem) cert.extensions.map(&:oid).include?("crlDistributionPoints").should be_false end end describe "CertificatePolicies" do before(:each) do @certificate = CertificateAuthority::Certificate.new @certificate.subject.common_name = "chrischandler.name" @certificate.key_material.generate_key(1024) @certificate.serial_number.number = 1 end it "should have a certificatePolicy if specified" do @certificate.sign!({ "extensions" => { "certificatePolicies" => { "policy_identifier" => "1.3.5.7", "cps_uris" => ["http://my.host.name/", "http://my.your.name/"] } } }) cert = OpenSSL::X509::Certificate.new(@certificate.to_pem) 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 end it "should NOT include a certificatePolicy if not specified" do @certificate.sign! cert = OpenSSL::X509::Certificate.new(@certificate.to_pem) cert.extensions.map(&:oid).include?("certificatePolicies").should be_false end end it "should support BasicContraints" do cert = OpenSSL::X509::Certificate.new(@certificate.to_pem) cert.extensions.map(&:oid).include?("basicConstraints").should be_true end it "should support subjectKeyIdentifier" do cert = OpenSSL::X509::Certificate.new(@certificate.to_pem) cert.extensions.map(&:oid).include?("subjectKeyIdentifier").should be_true end it "should support authorityKeyIdentifier" do cert = OpenSSL::X509::Certificate.new(@certificate.to_pem) cert.extensions.map(&:oid).include?("authorityKeyIdentifier").should be_true end it "should order subjectKeyIdentifier before authorityKeyIdentifier" do cert = OpenSSL::X509::Certificate.new(@certificate.to_pem) cert.extensions.map(&:oid).select do |oid| ["subjectKeyIdentifier", "authorityKeyIdentifier"].include?(oid) end.should == ["subjectKeyIdentifier", "authorityKeyIdentifier"] end it "should support keyUsage" do cert = OpenSSL::X509::Certificate.new(@certificate.to_pem) cert.extensions.map(&:oid).include?("keyUsage").should be_true end it "should support extendedKeyUsage" do cert = OpenSSL::X509::Certificate.new(@certificate.to_pem) cert.extensions.map(&:oid).include?("extendedKeyUsage").should be_true end end describe "Signing profile" do before(:each) do @certificate = CertificateAuthority::Certificate.new @certificate.subject.common_name = "chrischandler.name" @certificate.key_material.generate_key(1024) @certificate.serial_number.number = 1 @signing_profile = { "extensions" => { "basicConstraints" => {"ca" => false}, "crlDistributionPoints" => {"uri" => "http://notme.com/other.crl" }, "subjectKeyIdentifier" => {}, "authorityKeyIdentifier" => {}, "authorityInfoAccess" => {"ocsp" => ["http://youFillThisOut/ocsp/"] }, "keyUsage" => {"usage" => ["digitalSignature","nonRepudiation"] }, "extendedKeyUsage" => {"usage" => [ "serverAuth","clientAuth"]}, "subjectAltName" => {"uris" => ["http://subdomains.youFillThisOut/"]}, "certificatePolicies" => { "policy_identifier" => "1.3.5.8", "cps_uris" => ["http://my.host.name/", "http://my.your.name/"], "user_notice" => { "explicit_text" => "Explicit Text Here", "organization" => "Organization name", "notice_numbers" => "1,2,3,4" } } } } end it "should be able to sign with an optional policy hash" do @certificate.sign!(@signing_profile) end it "should support a default signing digest of SHA512" do @certificate.sign!(@signing_profile) cert = OpenSSL::X509::Certificate.new(@certificate.to_pem) cert.signature_algorithm.should == "sha512WithRSAEncryption" end it "should support a configurable digest algorithm" do @signing_profile.merge!({"digest" => "SHA1"}) @certificate.sign!(@signing_profile) cert = OpenSSL::X509::Certificate.new(@certificate.to_pem) cert.signature_algorithm.should == "sha1WithRSAEncryption" end end describe "from_openssl" do before(:each) do @pem_cert=< Time.now + 55 * 60 * 24 * 365 end it "should be able to have a revoked at time" do @certificate.revoked?.should be_false @certificate.revoked_at = Time.now @certificate.revoked?.should be_true end end certificate-authority-0.1.6/spec/units/serial_number_spec.rb0000644000175000017500000000104712665402716023553 0ustar sbadiasbadiarequire File.dirname(__FILE__) + '/units_helper' describe CertificateAuthority::SerialNumber do before(:each) do @serial_number = CertificateAuthority::SerialNumber.new end it "should support basic integer serial numbers", :rfc3280 => true do @serial_number.number = 25 @serial_number.should be_valid @serial_number.number = "abc" @serial_number.should_not be_valid end it "should not allow negative serial numbers", :rfc3280 => true do @serial_number.number = -5 @serial_number.should_not be_valid end end certificate-authority-0.1.6/spec/spec_helper.rb0000644000175000017500000000014412665402716021036 0ustar sbadiasbadiarequire 'rubygems' require 'rspec' require File.dirname(__FILE__) + '/../lib/certificate_authority'certificate-authority-0.1.6/Rakefile0000644000175000017500000000246212665402716016740 0ustar sbadiasbadiarequire 'rubygems' require 'bundler' require 'rspec' require 'rspec/core/rake_task' begin Bundler.setup(:default, :development) rescue Bundler::BundlerError => e $stderr.puts e.message $stderr.puts "Run `bundle install` to install missing gems" exit e.status_code end require 'rake' desc 'Default: run specs.' task :default => :spec 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.license = "MIT" gem.summary = 'Ruby gem for managing the core functions outlined in RFC-3280 for PKI' # gem.description = '' gem.email = "chris@flatterline.com" gem.authors = ["Chris Chandler"] # gem.add_dependency('activemodel', '3.0.6') end Jeweler::RubygemsDotOrgTasks.new task :spec do Rake::Task["spec:units"].invoke end namespace :spec do desc "Run unit specs." RSpec::Core::RakeTask.new(:units) do |t| t.rspec_opts = ['--colour --format progress --tag ~pkcs11'] end desc "Run integration specs." RSpec::Core::RakeTask.new(:integrations) do |t| t.rspec_opts = ['--colour --format progress'] end end RSpec::Core::RakeTask.new(:doc) do |t| t.rspec_opts = ['--format specdoc '] end certificate-authority-0.1.6/VERSION.yml0000644000175000017500000000005312665402716017135 0ustar sbadiasbadia--- :major: 0 :minor: 1 :patch: 6 :build: certificate-authority-0.1.6/metadata.yml0000644000175000017500000000562612665402716017603 0ustar sbadiasbadia--- !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@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: [] certificate-authority-0.1.6/lib/0000755000175000017500000000000012665402716016035 5ustar sbadiasbadiacertificate-authority-0.1.6/lib/tasks/0000755000175000017500000000000012665402716017162 5ustar sbadiasbadiacertificate-authority-0.1.6/lib/tasks/certificate_authority.rake0000644000175000017500000000103712665402716024421 0ustar sbadiasbadiarequire 'certificate_authority' namespace :certificate_authority do desc "Generate a quick self-signed cert" task :self_signed do cn = "http://localhost" cn = ENV['DOMAIN'] unless ENV['DOMAIN'].nil? root = CertificateAuthority::Certificate.new root.subject.common_name= cn root.key_material.generate_key root.signing_entity = true root.valid? root.sign! print "Your cert for #{cn}\n" print root.to_pem print "Your private key\n" print root.key_material.private_key.to_pem end end certificate-authority-0.1.6/lib/certificate_authority.rb0000644000175000017500000000125512665402716022757 0ustar sbadiasbadia$:.unshift(File.dirname(__FILE__)) unless $:.include?(File.dirname(__FILE__)) || $:.include?(File.expand_path(File.dirname(__FILE__))) #Exterior requirements require 'openssl' require 'active_model' #Internal modules require 'certificate_authority/signing_entity' require 'certificate_authority/distinguished_name' require 'certificate_authority/serial_number' require 'certificate_authority/key_material' require 'certificate_authority/pkcs11_key_material' require 'certificate_authority/extensions' require 'certificate_authority/certificate' require 'certificate_authority/certificate_revocation_list' require 'certificate_authority/ocsp_handler' module CertificateAuthority end certificate-authority-0.1.6/lib/certificate_authority/0000755000175000017500000000000012665402716022427 5ustar sbadiasbadiacertificate-authority-0.1.6/lib/certificate_authority/ocsp_handler.rb0000644000175000017500000000426512665402716025424 0ustar sbadiasbadiamodule CertificateAuthority class OCSPHandler include ActiveModel::Validations attr_accessor :ocsp_request attr_accessor :certificate_ids attr_accessor :certificates attr_accessor :parent attr_accessor :ocsp_response_body validate do |crl| errors.add :parent, "A parent entity must be set" if parent.nil? end validate :all_certificates_available def initialize self.certificates = {} end def <<(cert) self.certificates[cert.serial_number.number.to_s] = cert 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) self.certificate_ids = openssl_request.certid.collect do |cert_id| cert_id.serial end self.certificate_ids end def response raise "Invalid response" unless valid? openssl_ocsp_response = OpenSSL::OCSP::BasicResponse.new openssl_ocsp_request = OpenSSL::OCSP::Request.new(self.ocsp_request) openssl_ocsp_response.copy_nonce(openssl_ocsp_request) openssl_ocsp_request.certid.each do |cert_id| certificate = self.certificates[cert_id.serial.to_s] openssl_ocsp_response.add_status(cert_id, OpenSSL::OCSP::V_CERTSTATUS_GOOD, 0, 0, 0, 30, nil) end openssl_ocsp_response.sign(OpenSSL::X509::Certificate.new(self.parent.to_pem), self.parent.key_material.private_key, nil, nil) final_response = OpenSSL::OCSP::Response.create(OpenSSL::OCSP::RESPONSE_STATUS_SUCCESSFUL, openssl_ocsp_response) self.ocsp_response_body = final_response self.ocsp_response_body end def to_der raise "No signed OCSP response body available" if self.ocsp_response_body.nil? self.ocsp_response_body.to_der end private def all_certificates_available openssl_ocsp_request = OpenSSL::OCSP::Request.new(self.ocsp_request) openssl_ocsp_request.certid.each do |cert_id| certificate = self.certificates[cert_id.serial.to_s] errors.add(:base, "Certificate #{cert_id.serial} has not been added yet") if certificate.nil? end end end end certificate-authority-0.1.6/lib/certificate_authority/pkcs11_key_material.rb0000644000175000017500000000311412665402716026603 0ustar sbadiasbadiamodule CertificateAuthority class Pkcs11KeyMaterial include KeyMaterial include ActiveModel::Validations include ActiveModel::Serialization attr_accessor :engine attr_accessor :token_id attr_accessor :pkcs11_lib attr_accessor :openssl_pkcs11_engine_lib attr_accessor :pin def initialize(attributes = {}) @attributes = attributes initialize_engine end def is_in_hardware? true end def is_in_memory? false end def generate_key(modulus_bits=1024) puts "Key generation is not currently supported in hardware" nil end def private_key initialize_engine self.engine.load_private_key(self.token_id) end def public_key initialize_engine self.engine.load_public_key(self.token_id) end private def initialize_engine ## We're going to return early and try again later if params weren't passed in ## at initialization. Any attempt at getting a public/private key will try ## again. return false if self.openssl_pkcs11_engine_lib.nil? or self.pkcs11_lib.nil? return self.engine unless self.engine.nil? OpenSSL::Engine.load pkcs11 = OpenSSL::Engine.by_id("dynamic") do |e| e.ctrl_cmd("SO_PATH",self.openssl_pkcs11_engine_lib) e.ctrl_cmd("ID","pkcs11") e.ctrl_cmd("LIST_ADD","1") e.ctrl_cmd("LOAD") e.ctrl_cmd("PIN",self.pin) unless self.pin.nil? or self.pin == "" e.ctrl_cmd("MODULE_PATH",self.pkcs11_lib) end self.engine = pkcs11 pkcs11 end end end certificate-authority-0.1.6/lib/certificate_authority/key_material.rb0000644000175000017500000000356312665402716025431 0ustar sbadiasbadiamodule CertificateAuthority module KeyMaterial def public_key raise "Required implementation" end def private_key raise "Required implementation" end def is_in_hardware? raise "Required implementation" end def is_in_memory? raise "Required implementation" end end class MemoryKeyMaterial include KeyMaterial include ActiveModel::Validations attr_accessor :keypair attr_accessor :private_key attr_accessor :public_key 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? end def is_in_hardware? false end def is_in_memory? true end def generate_key(modulus_bits=2048) self.keypair = OpenSSL::PKey::RSA.new(modulus_bits) self.private_key = keypair self.public_key = keypair.public_key self.keypair end def private_key @private_key end def public_key @public_key end end class SigningRequestKeyMaterial include KeyMaterial include ActiveModel::Validations validates_each :public_key do |record, attr, value| record.errors.add :public_key, "cannot be blank" if record.public_key.nil? end attr_accessor :public_key def initialize(request=nil) if request.is_a? OpenSSL::X509::Request raise "Invalid certificate signing request" unless request.verify request.public_key self.public_key = request.public_key end end def is_in_hardware? false end def is_in_memory? true end def private_key nil end def public_key @public_key end end end certificate-authority-0.1.6/lib/certificate_authority/certificate_revocation_list.rb0000644000175000017500000000331612665402716030525 0ustar sbadiasbadiamodule CertificateAuthority class CertificateRevocationList include ActiveModel::Validations attr_accessor :certificates attr_accessor :parent attr_accessor :crl_body attr_accessor :next_update 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? end def initialize self.certificates = [] self.next_update = 60 * 60 * 4 # 4 hour default end def <<(cert) raise "Only revoked certificates can be added to a CRL" unless cert.revoked? self.certificates << cert end def sign! raise "No parent entity has been set!" if self.parent.nil? raise "Invalid CRL" unless self.valid? revocations = self.certificates.collect do |certificate| revocation = OpenSSL::X509::Revoked.new x509_cert = OpenSSL::X509::Certificate.new(certificate.to_pem) revocation.serial = x509_cert.serial revocation.time = certificate.revoked_at revocation end crl = OpenSSL::X509::CRL.new revocations.each do |revocation| crl.add_revoked(revocation) end crl.version = 1 crl.last_update = Time.now crl.next_update = Time.now + self.next_update signing_cert = OpenSSL::X509::Certificate.new(self.parent.to_pem) digest = OpenSSL::Digest::Digest.new("SHA512") crl.issuer = signing_cert.subject self.crl_body = crl.sign(self.parent.key_material.private_key, digest) self.crl_body end def to_pem raise "No signed CRL body" if self.crl_body.nil? self.crl_body.to_pem end end#CertificateRevocationList end certificate-authority-0.1.6/lib/certificate_authority/certificate.rb0000644000175000017500000001551212665402716025242 0ustar sbadiasbadiamodule CertificateAuthority class Certificate # include SigningEntity include ActiveModel::Validations 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 alias :subject :distinguished_name #Same thing as the DN attr_accessor :parent validate do |certificate| 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? errors.add :base, "Extensions must be valid" unless extensions.each do |item| unless item.respond_to?(:valid?) true else item.valid? end end end def initialize 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.parent = self self.extensions = load_extensions() self.signing_entity = false 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.not_before = self.not_before openssl_cert.not_after = self.not_after openssl_cert.public_key = self.key_material.public_key openssl_cert.serial = self.serial_number.number openssl_cert.subject = self.distinguished_name.to_x509_name openssl_cert.issuer = parent.distinguished_name.to_x509_name 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) factory = OpenSSL::X509::ExtensionFactory.new factory.subject_certificate = openssl_cert #NB: If the parent doesn't have an SSL body we're making this a self-signed cert if parent.openssl_body.nil? factory.issuer_certificate = openssl_cert else factory.issuer_certificate = parent.openssl_body end self.extensions.keys.each do |k| config_extensions = extensions[k].config_extensions openssl_config = merge_options(openssl_config,config_extensions) end # p openssl_config.sections factory.config = openssl_config # Order matters: e.g. for self-signed, subjectKeyIdentifier must come before authorityKeyIdentifier 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) openssl_cert.add_extension(ext) end if signing_profile["digest"].nil? digest = OpenSSL::Digest::Digest.new("SHA512") else digest = OpenSSL::Digest::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 end def is_signing_entity? self.extensions["basicConstraints"].ca end def signing_entity=(signing) self.extensions["basicConstraints"].ca = signing end def revoked? !self.revoked_at.nil? end def to_pem raise "Certificate has no signed body" if self.openssl_body.nil? self.openssl_body.to_pem end def is_root_entity? self.parent == self && is_signing_entity? end def is_intermediate_entity? (self.parent != self) && is_signing_entity? end private def merge_profile_with_extensions(signing_profile={}) return self.extensions if signing_profile["extensions"].nil? signing_config = signing_profile["extensions"] signing_config.keys.each do |k| extension = self.extensions[k] items = signing_config[k] items.keys.each do |profile_item_key| if extension.respond_to?("#{profile_item_key}=".to_sym) 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!" end end end end 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| extension_hash[extension.openssl_identifier] = extension end extension_hash end def merge_options(config,hash) hash.keys.each do |k| config[k] = hash[k] end config end 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 end certificate-authority-0.1.6/lib/certificate_authority/signing_entity.rb0000644000175000017500000000045212665402716026007 0ustar sbadiasbadiamodule CertificateAuthority module SigningEntity def self.included(mod) mod.class_eval do attr_accessor :signing_entity end end def signing_entity=(val) raise "invalid param" unless [true,false].include?(val) @signing_entity = val end end end certificate-authority-0.1.6/lib/certificate_authority/distinguished_name.rb0000644000175000017500000000304512665402716026621 0ustar sbadiasbadiamodule CertificateAuthority class DistinguishedName include ActiveModel::Validations validates_presence_of :common_name attr_accessor :common_name alias :cn :common_name attr_accessor :locality alias :l :locality attr_accessor :state alias :s :state attr_accessor :country alias :c :country attr_accessor :organization alias :o :organization attr_accessor :organizational_unit alias :ou :organizational_unit 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("ST", state) unless state.blank? name.add_entry("L", locality) unless locality.blank? name.add_entry("C", country) unless country.blank? 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 end end name end end end certificate-authority-0.1.6/lib/certificate_authority/serial_number.rb0000644000175000017500000000031412665402716025601 0ustar sbadiasbadiamodule CertificateAuthority class SerialNumber include ActiveModel::Validations attr_accessor :number validates :number, :presence => true, :numericality => {:greater_than => 0} end end certificate-authority-0.1.6/lib/certificate_authority/extensions.rb0000644000175000017500000001277212665402716025164 0ustar sbadiasbadiamodule CertificateAuthority module Extensions module ExtensionAPI def to_s raise "Implementation required" end def config_extensions {} end def openssl_identifier raise "Implementation required" end end class BasicContraints include ExtensionAPI include ActiveModel::Validations attr_accessor :ca attr_accessor :path_len validates :ca, :inclusion => [true,false] def initialize self.ca = false end def is_ca? self.ca end def path_len=(value) raise "path_len must be a non-negative integer" if value < 0 or !value.is_a?(Fixnum) @path_len = value end def openssl_identifier "basicConstraints" end def to_s result = "" result += "CA:#{self.ca}" result += ",pathlen:#{self.path_len}" unless self.path_len.nil? result end end class CrlDistributionPoints include ExtensionAPI attr_accessor :uri def initialize # self.uri = "http://moo.crlendPoint.example.com/something.crl" end def openssl_identifier "crlDistributionPoints" end ## NB: At this time it seems OpenSSL's extension handlers don't support ## any of the config options the docs claim to support... everything comes back ## "missing value" on GENERAL NAME. Even if copied verbatim def config_extensions { # "custom_crl_fields" => {"fullname" => "URI:#{fullname}"}, # "issuer_sect" => {"CN" => "crlissuer.com", "C" => "US", "O" => "shudder"} } end def to_s return "" if self.uri.nil? "URI:#{self.uri}" end end class SubjectKeyIdentifier include ExtensionAPI def openssl_identifier "subjectKeyIdentifier" end def to_s "hash" end end class AuthorityKeyIdentifier include ExtensionAPI def openssl_identifier "authorityKeyIdentifier" end def to_s "keyid,issuer" end end class AuthorityInfoAccess include ExtensionAPI attr_accessor :ocsp def initialize self.ocsp = [] end def openssl_identifier "authorityInfoAccess" end def to_s return "" if self.ocsp.empty? "OCSP;URI:#{self.ocsp}" end end class KeyUsage include ExtensionAPI attr_accessor :usage def initialize self.usage = ["digitalSignature", "nonRepudiation"] end def openssl_identifier "keyUsage" end def to_s "#{self.usage.join(',')}" end end class ExtendedKeyUsage include ExtensionAPI attr_accessor :usage def initialize self.usage = ["serverAuth","clientAuth"] end def openssl_identifier "extendedKeyUsage" end def to_s "#{self.usage.join(',')}" end end class SubjectAlternativeName include ExtensionAPI attr_accessor :uris, :dns_names, :ips def initialize self.uris = [] self.dns_names = [] self.ips = [] end def uris=(value) raise "URIs must be an array" unless value.is_a?(Array) @uris = value end def dns_names=(value) raise "DNS names must be an array" unless value.is_a?(Array) @dns_names = value end def ips=(value) raise "IPs must be an array" unless value.is_a?(Array) @ips = value end def openssl_identifier "subjectAltName" 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}" } return res.join(',') end end class CertificatePolicies include ExtensionAPI attr_accessor :policy_identifier attr_accessor :cps_uris ##User notice attr_accessor :explicit_text attr_accessor :organization attr_accessor :notice_numbers def initialize @contains_data = false end def openssl_identifier "certificatePolicies" end def user_notice=(value={}) value.keys.each do |key| self.send("#{key}=".to_sym, value[key]) end end def config_extensions config_extension = {} custom_policies = {} notice = {} unless self.policy_identifier.nil? custom_policies["policyIdentifier"] = self.policy_identifier end if !self.cps_uris.nil? and self.cps_uris.is_a?(Array) self.cps_uris.each_with_index do |cps_uri,i| custom_policies["CPS.#{i}"] = cps_uri end end unless self.explicit_text.nil? notice["explicitText"] = self.explicit_text end unless self.organization.nil? notice["organization"] = self.organization end unless self.notice_numbers.nil? notice["noticeNumbers"] = self.notice_numbers end if notice.keys.size > 0 custom_policies["userNotice.1"] = "@notice" config_extension["notice"] = notice end if custom_policies.keys.size > 0 config_extension["custom_policies"] = custom_policies @contains_data = true end config_extension end def to_s return "" unless @contains_data "ia5org,@custom_policies" end end end end certificate-authority-0.1.6/certificate_authority.gemspec0000644000175000017500000000477612665402716023244 0ustar sbadiasbadia# Generated by jeweler # DO NOT EDIT THIS FILE DIRECTLY # Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec' # -*- encoding: utf-8 -*- Gem::Specification.new do |s| s.name = "certificate_authority" s.version = "0.1.6" s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version= s.authors = ["Chris Chandler"] s.date = "2012-08-12" s.email = "chris@flatterline.com" s.extra_rdoc_files = [ "README.rdoc" ] s.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" ] s.homepage = "http://github.com/cchandler/certificate_authority" s.licenses = ["MIT"] s.require_paths = ["lib"] s.rubygems_version = "1.8.15" 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 if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then s.add_runtime_dependency(%q, [">= 3.0.6"]) s.add_development_dependency(%q, [">= 0"]) s.add_development_dependency(%q, [">= 1.5.2"]) else s.add_dependency(%q, [">= 3.0.6"]) s.add_dependency(%q, [">= 0"]) s.add_dependency(%q, [">= 1.5.2"]) end else s.add_dependency(%q, [">= 3.0.6"]) s.add_dependency(%q, [">= 0"]) s.add_dependency(%q, [">= 1.5.2"]) end end