certificate_authority_sonian 0.1.7

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,135 @@
1
+ module CertificateAuthority
2
+ module KeyMaterial
3
+ def public_key
4
+ raise "Required implementation"
5
+ end
6
+
7
+ def private_key
8
+ raise "Required implementation"
9
+ end
10
+
11
+ def is_in_hardware?
12
+ raise "Required implementation"
13
+ end
14
+
15
+ def is_in_memory?
16
+ raise "Required implementation"
17
+ end
18
+ end
19
+
20
+ class MemoryKeyMaterial
21
+ include KeyMaterial
22
+ include ActiveModel::Validations
23
+
24
+ attr_accessor :keypair
25
+ attr_accessor :private_key
26
+ attr_accessor :public_key
27
+
28
+ def initialize
29
+ end
30
+
31
+ validates_each :private_key do |record, attr, value|
32
+ record.errors.add :private_key, "cannot be blank" if record.private_key.nil?
33
+ end
34
+ validates_each :public_key do |record, attr, value|
35
+ record.errors.add :public_key, "cannot be blank" if record.public_key.nil?
36
+ end
37
+
38
+ # @return [Boolean]
39
+ def is_in_hardware?
40
+ false
41
+ end
42
+
43
+ # @return [Boolean]
44
+ def is_in_memory?
45
+ true
46
+ end
47
+
48
+ # @param modulus_bits [Integer] number of bits to generate the key with
49
+ # @return [OpenSSL::Pkey::RSA]
50
+ def generate_key(modulus_bits=2048)
51
+ self.keypair = OpenSSL::PKey::RSA.new(modulus_bits)
52
+ self.private_key = keypair
53
+ self.public_key = keypair.public_key
54
+ self.keypair
55
+ end
56
+
57
+ # @return [OpenSSL::Pkey::RSA]
58
+ def private_key
59
+ @private_key
60
+ end
61
+
62
+ # @return [OpenSSL::Pkey::RSA]
63
+ def public_key
64
+ @public_key
65
+ end
66
+ end
67
+
68
+ class SigningRequestKeyMaterial
69
+ include KeyMaterial
70
+ include ActiveModel::Validations
71
+
72
+ validates_each :public_key do |record, attr, value|
73
+ record.errors.add :public_key, "cannot be blank" if record.public_key.nil?
74
+ end
75
+
76
+ attr_accessor :public_key
77
+ attr_reader :csr # @return [OpenSSL::X509::Request,OpenSSL::Netscape::SPKI]
78
+ attr_reader :certificate # @return [CertificateAuthority::Certificate]
79
+
80
+ # @param request [OpenSSL::X508::Request,OpenSSL::Netscape::SPKI,String] a signing request
81
+ def initialize(request=nil)
82
+ if request.is_a?(OpenSSL::X509::Request) || request.is_a?(OpenSSL::Netscape::SPKI)
83
+ @csr = request
84
+ raise "Invalid certificate signing request" unless @csr.verify(@csr.public_key)
85
+ self.public_key = @csr.public_key
86
+ end
87
+ end
88
+
89
+ # Given a root certificate and a key, will generate a signed certificate
90
+ # @param root_cert [CertificateAuthority::Certificate] the parent certificate (CA)
91
+ # @param key [OpenSSL::Pkey::RSA] the private key to sign with
92
+ # @param serial_number [Integer] the serial number for the generated certificate
93
+ # @param options [Hash{:dn => CertificateAuthority::DistinguishedName, :algorithm => OpenSSL::Digest, :not_after => Time}] :dn is required for SPKAC signing
94
+ # @return [CertificateAuthority::Certificate] A signed certificate instance
95
+ def sign_and_certify(root_cert, key, serial_number, options = {})
96
+ if csr.is_a? OpenSSL::Netscape::SPKI
97
+ raise "Must pass :dn in options to generate certificates for OpenSSL::Netscape::SPKI requests" unless options[:dn]
98
+ end
99
+ algorithm = options[:algorithm] || OpenSSL::Digest::SHA1.new
100
+ cert = OpenSSL::X509::Certificate.new
101
+ if options[:dn]
102
+ cert.subject = options[:dn].to_x509_name
103
+ else
104
+ cert.subject = csr.subject
105
+ end
106
+ cert.public_key = public_key
107
+ cert.not_before = Time.now
108
+ cert.not_after = options[:not_after] || (Time.now + 100000000)
109
+ cert.issuer = root_cert.subject.to_x509_name
110
+ cert.serial = serial_number
111
+ cert.sign key, algorithm
112
+ @certificate = CertificateAuthority::Certificate.from_openssl cert
113
+ end
114
+
115
+ # @return [Boolean]
116
+ def is_in_hardware?
117
+ false
118
+ end
119
+
120
+ # @return [Boolean]
121
+ def is_in_memory?
122
+ true
123
+ end
124
+
125
+ # @return [NilClass]
126
+ def private_key
127
+ nil
128
+ end
129
+
130
+ # @return [OpenSSL::Pkey::RSA]
131
+ def public_key
132
+ @public_key
133
+ end
134
+ end
135
+ end
@@ -0,0 +1,77 @@
1
+ module CertificateAuthority
2
+ class OCSPHandler
3
+ include ActiveModel::Validations
4
+
5
+ attr_accessor :ocsp_request
6
+ attr_accessor :certificate_ids
7
+
8
+ attr_accessor :certificates
9
+ attr_accessor :parent
10
+
11
+ attr_accessor :ocsp_response_body
12
+
13
+ validate do |crl|
14
+ errors.add :parent, "A parent entity must be set" if parent.nil?
15
+ end
16
+ validate :all_certificates_available
17
+
18
+ def initialize
19
+ self.certificates = {}
20
+ end
21
+
22
+ def <<(cert)
23
+ self.certificates[cert.serial_number.number.to_s] = cert
24
+ end
25
+
26
+ def extract_certificate_serials
27
+ raise "No valid OCSP request was supplied" if self.ocsp_request.nil?
28
+ openssl_request = OpenSSL::OCSP::Request.new(self.ocsp_request)
29
+
30
+ self.certificate_ids = openssl_request.certid.collect do |cert_id|
31
+ cert_id.serial
32
+ end
33
+
34
+ self.certificate_ids
35
+ end
36
+
37
+
38
+ def response
39
+ raise "Invalid response" unless valid?
40
+
41
+ openssl_ocsp_response = OpenSSL::OCSP::BasicResponse.new
42
+ openssl_ocsp_request = OpenSSL::OCSP::Request.new(self.ocsp_request)
43
+ openssl_ocsp_response.copy_nonce(openssl_ocsp_request)
44
+
45
+ openssl_ocsp_request.certid.each do |cert_id|
46
+ certificate = self.certificates[cert_id.serial.to_s]
47
+
48
+ openssl_ocsp_response.add_status(cert_id,
49
+ OpenSSL::OCSP::V_CERTSTATUS_GOOD, 0,
50
+ 0, 0, 30, nil)
51
+ end
52
+
53
+
54
+ openssl_ocsp_response.sign(OpenSSL::X509::Certificate.new(self.parent.to_pem), self.parent.key_material.private_key, nil, nil)
55
+ final_response = OpenSSL::OCSP::Response.create(OpenSSL::OCSP::RESPONSE_STATUS_SUCCESSFUL, openssl_ocsp_response)
56
+ self.ocsp_response_body = final_response
57
+ self.ocsp_response_body
58
+ end
59
+
60
+ def to_der
61
+ raise "No signed OCSP response body available" if self.ocsp_response_body.nil?
62
+ self.ocsp_response_body.to_der
63
+ end
64
+
65
+ private
66
+
67
+ def all_certificates_available
68
+ openssl_ocsp_request = OpenSSL::OCSP::Request.new(self.ocsp_request)
69
+
70
+ openssl_ocsp_request.certid.each do |cert_id|
71
+ certificate = self.certificates[cert_id.serial.to_s]
72
+ errors.add(:base, "Certificate #{cert_id.serial} has not been added yet") if certificate.nil?
73
+ end
74
+ end
75
+
76
+ end
77
+ end
@@ -0,0 +1,65 @@
1
+ module CertificateAuthority
2
+ class Pkcs11KeyMaterial
3
+ include KeyMaterial
4
+ include ActiveModel::Validations
5
+ include ActiveModel::Serialization
6
+
7
+ attr_accessor :engine
8
+ attr_accessor :token_id
9
+ attr_accessor :pkcs11_lib
10
+ attr_accessor :openssl_pkcs11_engine_lib
11
+ attr_accessor :pin
12
+
13
+ def initialize(attributes = {})
14
+ @attributes = attributes
15
+ initialize_engine
16
+ end
17
+
18
+ def is_in_hardware?
19
+ true
20
+ end
21
+
22
+ def is_in_memory?
23
+ false
24
+ end
25
+
26
+ def generate_key(modulus_bits=1024)
27
+ puts "Key generation is not currently supported in hardware"
28
+ nil
29
+ end
30
+
31
+ def private_key
32
+ initialize_engine
33
+ self.engine.load_private_key(self.token_id)
34
+ end
35
+
36
+ def public_key
37
+ initialize_engine
38
+ self.engine.load_public_key(self.token_id)
39
+ end
40
+
41
+ private
42
+
43
+ def initialize_engine
44
+ ## We're going to return early and try again later if params weren't passed in
45
+ ## at initialization. Any attempt at getting a public/private key will try
46
+ ## again.
47
+ return false if self.openssl_pkcs11_engine_lib.nil? or self.pkcs11_lib.nil?
48
+ return self.engine unless self.engine.nil?
49
+ OpenSSL::Engine.load
50
+
51
+ pkcs11 = OpenSSL::Engine.by_id("dynamic") do |e|
52
+ e.ctrl_cmd("SO_PATH",self.openssl_pkcs11_engine_lib)
53
+ e.ctrl_cmd("ID","pkcs11")
54
+ e.ctrl_cmd("LIST_ADD","1")
55
+ e.ctrl_cmd("LOAD")
56
+ e.ctrl_cmd("PIN",self.pin) unless self.pin.nil? or self.pin == ""
57
+ e.ctrl_cmd("MODULE_PATH",self.pkcs11_lib)
58
+ end
59
+
60
+ self.engine = pkcs11
61
+ pkcs11
62
+ end
63
+
64
+ end
65
+ end
@@ -0,0 +1,9 @@
1
+ module CertificateAuthority
2
+ class SerialNumber
3
+ include ActiveModel::Validations
4
+
5
+ attr_accessor :number
6
+
7
+ validates :number, :presence => true, :numericality => {:greater_than => 0}
8
+ end
9
+ end
@@ -0,0 +1,16 @@
1
+ module CertificateAuthority
2
+ module SigningEntity
3
+
4
+ def self.included(mod)
5
+ mod.class_eval do
6
+ attr_accessor :signing_entity
7
+ end
8
+ end
9
+
10
+ def signing_entity=(val)
11
+ raise "invalid param" unless [true,false].include?(val)
12
+ @signing_entity = val
13
+ end
14
+
15
+ end
16
+ end
@@ -0,0 +1,23 @@
1
+ require 'certificate_authority'
2
+
3
+ namespace :certificate_authority do
4
+ desc "Generate a quick self-signed cert"
5
+ task :self_signed do
6
+
7
+ cn = "http://localhost"
8
+ cn = ENV['DOMAIN'] unless ENV['DOMAIN'].nil?
9
+
10
+ root = CertificateAuthority::Certificate.new
11
+ root.subject.common_name= cn
12
+ root.key_material.generate_key
13
+ root.signing_entity = true
14
+ root.valid?
15
+ root.sign!
16
+
17
+ print "Your cert for #{cn}\n"
18
+ print root.to_pem
19
+
20
+ print "Your private key\n"
21
+ print root.key_material.private_key.to_pem
22
+ end
23
+ end
@@ -0,0 +1,4 @@
1
+ require 'rubygems'
2
+ require 'rspec'
3
+
4
+ require File.dirname(__FILE__) + '/../lib/certificate_authority'
@@ -0,0 +1,4 @@
1
+ require File.dirname(__FILE__) + '/units_helper'
2
+
3
+ describe CertificateAuthority do
4
+ end
@@ -0,0 +1,68 @@
1
+ require File.dirname(__FILE__) + '/units_helper'
2
+
3
+ describe CertificateAuthority::CertificateRevocationList do
4
+ before(:each) do
5
+ @crl = CertificateAuthority::CertificateRevocationList.new
6
+
7
+ @root_certificate = CertificateAuthority::Certificate.new
8
+ @root_certificate.signing_entity = true
9
+ @root_certificate.subject.common_name = "CRL Root"
10
+ @root_certificate.key_material.generate_key(1024)
11
+ @root_certificate.serial_number.number = 1
12
+ @root_certificate.sign!
13
+
14
+ @certificate = CertificateAuthority::Certificate.new
15
+ @certificate.key_material.generate_key(1024)
16
+ @certificate.subject.common_name = "http://bogusSite.com"
17
+ @certificate.parent = @root_certificate
18
+ @certificate.serial_number.number = 2
19
+ @certificate.sign!
20
+
21
+ @crl.parent = @root_certificate
22
+ @certificate.revoked_at = Time.now
23
+ end
24
+
25
+ it "should accept a list of certificates" do
26
+ @crl << @certificate
27
+ end
28
+
29
+ it "should complain if you add a certificate without a revocation time" do
30
+ @certificate.revoked_at = nil
31
+ lambda{ @crl << @certificate}.should raise_error
32
+ end
33
+
34
+ it "should have a 'parent' that will be responsible for signing" do
35
+ @crl.parent = @root_certificate
36
+ @crl.parent.should_not be_nil
37
+ end
38
+
39
+ it "should raise an error if you try and sign a CRL without attaching a parent" do
40
+ @crl.parent = nil
41
+ lambda { @crl.sign! }.should raise_error
42
+ end
43
+
44
+ it "should be able to generate a proper CRL" do
45
+ @crl << @certificate
46
+ lambda {@crl.to_pem}.should raise_error
47
+ @crl.parent = @root_certificate
48
+ @crl.sign!
49
+ @crl.to_pem.should_not be_nil
50
+ OpenSSL::X509::CRL.new(@crl.to_pem).should_not be_nil
51
+ end
52
+
53
+ describe "Next update" do
54
+ it "should be able to set a 'next_update' value" do
55
+ @crl.next_update = (60 * 60 * 10) # 10 Hours
56
+ @crl.next_update.should_not be_nil
57
+ end
58
+
59
+ it "should throw an error if we try and sign up with a negative next_update" do
60
+ @crl.sign!
61
+ @crl.next_update = - (60 * 60 * 10)
62
+ lambda{@crl.sign!}.should raise_error
63
+ end
64
+
65
+ end
66
+
67
+
68
+ end
@@ -0,0 +1,428 @@
1
+ require File.dirname(__FILE__) + '/units_helper'
2
+
3
+ describe CertificateAuthority::Certificate do
4
+ before(:each) do
5
+ @certificate = CertificateAuthority::Certificate.new
6
+ end
7
+
8
+ describe CertificateAuthority::SigningEntity do
9
+ it "should behave as a signing entity" do
10
+ @certificate.respond_to?(:is_signing_entity?).should be_true
11
+ end
12
+
13
+ it "should only be a signing entity if it's identified as a CA", :rfc3280 => true do
14
+ @certificate.is_signing_entity?.should be_false
15
+ @certificate.signing_entity = true
16
+ @certificate.is_signing_entity?.should be_true
17
+ end
18
+
19
+ describe "Root certificates" do
20
+ before(:each) do
21
+ @certificate.signing_entity = true
22
+ end
23
+
24
+ it "should be able to be identified as a root certificate" do
25
+ @certificate.is_root_entity?.should be_true
26
+ end
27
+
28
+ it "should only be a root certificate if the parent entity is itself", :rfc3280 => true do
29
+ @certificate.parent.should == @certificate
30
+ end
31
+
32
+ it "should be a root certificate by default" do
33
+ @certificate.is_root_entity?.should be_true
34
+ end
35
+
36
+ it "should be able to self-sign" do
37
+ @certificate.serial_number.number = 1
38
+ @certificate.subject.common_name = "chrischandler.name"
39
+ @certificate.key_material.generate_key(1024)
40
+ @certificate.sign!
41
+ cert = OpenSSL::X509::Certificate.new(@certificate.to_pem)
42
+ cert.subject.to_s.should == cert.issuer.to_s
43
+ end
44
+
45
+ it "should have the basicContraint CA:TRUE" do
46
+ @certificate.serial_number.number = 1
47
+ @certificate.subject.common_name = "chrischandler.name"
48
+ @certificate.key_material.generate_key(1024)
49
+ @certificate.sign!
50
+ cert = OpenSSL::X509::Certificate.new(@certificate.to_pem)
51
+ cert.extensions.map{|i| [i.oid,i.value] }.select{|i| i.first == "basicConstraints"}.first[1].should == "CA:TRUE"
52
+ end
53
+ end
54
+
55
+ describe "Intermediate certificates" do
56
+ before(:each) do
57
+ @different_cert = CertificateAuthority::Certificate.new
58
+ @different_cert.signing_entity = true
59
+ @different_cert.subject.common_name = "chrischandler.name root"
60
+ @different_cert.key_material.generate_key(1024)
61
+ @different_cert.serial_number.number = 2
62
+ @different_cert.sign! #self-signed
63
+ @certificate.parent = @different_cert
64
+ @certificate.signing_entity = true
65
+ end
66
+
67
+ it "should be able to be identified as an intermediate certificate" do
68
+ @certificate.is_intermediate_entity?.should be_true
69
+ end
70
+
71
+ it "should not be identified as a root" do
72
+ @certificate.is_root_entity?.should be_false
73
+ end
74
+
75
+ it "should only be an intermediate certificate if the parent is a different entity" do
76
+ @certificate.parent.should_not == @certificate
77
+ @certificate.parent.should_not be_nil
78
+ end
79
+
80
+ it "should correctly be signed by a parent certificate" do
81
+ @certificate.subject.common_name = "chrischandler.name"
82
+ @certificate.key_material.generate_key(1024)
83
+ @certificate.signing_entity = true
84
+ @certificate.serial_number.number = 1
85
+ @certificate.sign!
86
+ cert = OpenSSL::X509::Certificate.new(@certificate.to_pem)
87
+ cert.subject.to_s.should_not == cert.issuer.to_s
88
+ end
89
+
90
+ it "should have the basicContraint CA:TRUE" do
91
+ @certificate.subject.common_name = "chrischandler.name"
92
+ @certificate.key_material.generate_key(1024)
93
+ @certificate.signing_entity = true
94
+ @certificate.serial_number.number = 3
95
+ @certificate.sign!
96
+ cert = OpenSSL::X509::Certificate.new(@certificate.to_pem)
97
+ cert.extensions.map{|i| [i.oid,i.value] }.select{|i| i.first == "basicConstraints"}.first[1].should == "CA:TRUE"
98
+ end
99
+
100
+ end
101
+
102
+ describe "Terminal certificates" do
103
+ before(:each) do
104
+ @different_cert = CertificateAuthority::Certificate.new
105
+ @different_cert.signing_entity = true
106
+ @different_cert.subject.common_name = "chrischandler.name root"
107
+ @different_cert.key_material.generate_key(1024)
108
+ @different_cert.serial_number.number = 1
109
+ @different_cert.sign! #self-signed
110
+ @certificate.parent = @different_cert
111
+ end
112
+
113
+ it "should not be identified as an intermediate certificate" do
114
+ @certificate.is_intermediate_entity?.should be_false
115
+ end
116
+
117
+ it "should not be identified as a root" do
118
+ @certificate.is_root_entity?.should be_false
119
+ end
120
+
121
+ it "should have the basicContraint CA:FALSE" do
122
+ @certificate.subject.common_name = "chrischandler.name"
123
+ @certificate.key_material.generate_key(1024)
124
+ @certificate.signing_entity = false
125
+ @certificate.serial_number.number = 1
126
+ @certificate.sign!
127
+ cert = OpenSSL::X509::Certificate.new(@certificate.to_pem)
128
+ cert.extensions.map{|i| [i.oid,i.value] }.select{|i| i.first == "basicConstraints"}.first[1].should == "CA:FALSE"
129
+ end
130
+ end
131
+
132
+
133
+ it "should be able to be identified as a root certificate" do
134
+ @certificate.respond_to?(:is_root_entity?).should be_true
135
+ end
136
+ end #End of SigningEntity
137
+
138
+ describe "Signed certificates" do
139
+ before(:each) do
140
+ @certificate = CertificateAuthority::Certificate.new
141
+ @certificate.subject.common_name = "chrischandler.name"
142
+ @certificate.key_material.generate_key(1024)
143
+ @certificate.serial_number.number = 1
144
+ @certificate.sign!
145
+ end
146
+
147
+ it "should have a PEM encoded certificate body available" do
148
+ @certificate.to_pem.should_not be_nil
149
+ OpenSSL::X509::Certificate.new(@certificate.to_pem).should_not be_nil
150
+ end
151
+ end
152
+
153
+ describe "X.509 V3 Extensions on Signed Certificates" do
154
+ before(:each) do
155
+ @certificate = CertificateAuthority::Certificate.new
156
+ @certificate.subject.common_name = "chrischandler.name"
157
+ @certificate.key_material.generate_key(1024)
158
+ @certificate.serial_number.number = 1
159
+ @signing_profile = {
160
+ "extensions" => {
161
+ "subjectAltName" => {"uris" => ["www.chrischandler.name"]},
162
+ "certificatePolicies" => {
163
+ "policy_identifier" => "1.3.5.7",
164
+ "cps_uris" => ["http://my.host.name/", "http://my.your.name/"],
165
+ "user_notice" => {
166
+ "explicit_text" => "Testing!", "organization" => "RSpec Test organization name", "notice_numbers" => "1,2,3,4"
167
+ }
168
+ }
169
+ }
170
+ }
171
+ @certificate.sign!(@signing_profile)
172
+ end
173
+
174
+ describe "SubjectAltName" do
175
+ before(:each) do
176
+ @certificate = CertificateAuthority::Certificate.new
177
+ @certificate.subject.common_name = "chrischandler.name"
178
+ @certificate.key_material.generate_key(1024)
179
+ @certificate.serial_number.number = 1
180
+ end
181
+
182
+ it "should have a subjectAltName if specified" do
183
+ @certificate.sign!({"extensions" => {"subjectAltName" => {"uris" => ["www.chrischandler.name"]}}})
184
+ cert = OpenSSL::X509::Certificate.new(@certificate.to_pem)
185
+ cert.extensions.map(&:oid).include?("subjectAltName").should be_true
186
+ end
187
+
188
+ it "should NOT have a subjectAltName if one was not specified" do
189
+ @certificate.sign!
190
+ cert = OpenSSL::X509::Certificate.new(@certificate.to_pem)
191
+ cert.extensions.map(&:oid).include?("subjectAltName").should be_false
192
+ end
193
+ end
194
+
195
+ describe "AuthorityInfoAccess" do
196
+ before(:each) do
197
+ @certificate = CertificateAuthority::Certificate.new
198
+ @certificate.subject.common_name = "chrischandler.name"
199
+ @certificate.key_material.generate_key(1024)
200
+ @certificate.serial_number.number = 1
201
+ end
202
+
203
+ it "should have an authority info access if specified" do
204
+ @certificate.sign!({"extensions" => {"authorityInfoAccess" => {"ocsp" => ["www.chrischandler.name"]}}})
205
+ cert = OpenSSL::X509::Certificate.new(@certificate.to_pem)
206
+ cert.extensions.map(&:oid).include?("authorityInfoAccess").should be_true
207
+ end
208
+ end
209
+
210
+ describe "CrlDistributionPoints" do
211
+ before(:each) do
212
+ @certificate = CertificateAuthority::Certificate.new
213
+ @certificate.subject.common_name = "chrischandler.name"
214
+ @certificate.key_material.generate_key(1024)
215
+ @certificate.serial_number.number = 1
216
+ end
217
+
218
+ it "should have a crlDistributionPoint if specified" do
219
+ @certificate.sign!({"extensions" => {"crlDistributionPoints" => {"uri" => ["http://crlThingy.com"]}}})
220
+ cert = OpenSSL::X509::Certificate.new(@certificate.to_pem)
221
+ cert.extensions.map(&:oid).include?("crlDistributionPoints").should be_true
222
+ end
223
+
224
+ it "should NOT have a crlDistributionPoint if one was not specified" do
225
+ @certificate.sign!
226
+ cert = OpenSSL::X509::Certificate.new(@certificate.to_pem)
227
+ cert.extensions.map(&:oid).include?("crlDistributionPoints").should be_false
228
+ end
229
+ end
230
+
231
+
232
+ describe "CertificatePolicies" do
233
+ before(:each) do
234
+ @certificate = CertificateAuthority::Certificate.new
235
+ @certificate.subject.common_name = "chrischandler.name"
236
+ @certificate.key_material.generate_key(1024)
237
+ @certificate.serial_number.number = 1
238
+ end
239
+
240
+ it "should have a certificatePolicy if specified" do
241
+ @certificate.sign!({
242
+ "extensions" => {
243
+ "certificatePolicies" => {
244
+ "policy_identifier" => "1.3.5.7",
245
+ "cps_uris" => ["http://my.host.name/", "http://my.your.name/"]
246
+ }
247
+ }
248
+ })
249
+ cert = OpenSSL::X509::Certificate.new(@certificate.to_pem)
250
+ cert.extensions.map(&:oid).include?("certificatePolicies").should be_true
251
+ end
252
+
253
+ it "should contain a nested userNotice if specified" do
254
+ pending
255
+ # @certificate.sign!({
256
+ # "extensions" => {
257
+ # "certificatePolicies" => {
258
+ # "policy_identifier" => "1.3.5.7",
259
+ # "cps_uris" => ["http://my.host.name/", "http://my.your.name/"],
260
+ # "user_notice" => {
261
+ # "explicit_text" => "Testing!", "organization" => "RSpec Test organization name", "notice_numbers" => "1,2,3,4"
262
+ # }
263
+ # }
264
+ # }
265
+ # })
266
+ # cert = OpenSSL::X509::Certificate.new(@certificate.to_pem)
267
+ # cert.extensions.map(&:oid).include?("certificatePolicies").should be_true
268
+ end
269
+
270
+ it "should NOT include a certificatePolicy if not specified" do
271
+ @certificate.sign!
272
+ cert = OpenSSL::X509::Certificate.new(@certificate.to_pem)
273
+ cert.extensions.map(&:oid).include?("certificatePolicies").should be_false
274
+ end
275
+ end
276
+
277
+
278
+ it "should support BasicContraints" do
279
+ cert = OpenSSL::X509::Certificate.new(@certificate.to_pem)
280
+ cert.extensions.map(&:oid).include?("basicConstraints").should be_true
281
+ end
282
+
283
+ it "should support subjectKeyIdentifier" do
284
+ cert = OpenSSL::X509::Certificate.new(@certificate.to_pem)
285
+ cert.extensions.map(&:oid).include?("subjectKeyIdentifier").should be_true
286
+ end
287
+
288
+ it "should support authorityKeyIdentifier" do
289
+ cert = OpenSSL::X509::Certificate.new(@certificate.to_pem)
290
+ cert.extensions.map(&:oid).include?("authorityKeyIdentifier").should be_true
291
+ end
292
+
293
+ it "should order subjectKeyIdentifier before authorityKeyIdentifier" do
294
+ cert = OpenSSL::X509::Certificate.new(@certificate.to_pem)
295
+ cert.extensions.map(&:oid).select do |oid|
296
+ ["subjectKeyIdentifier", "authorityKeyIdentifier"].include?(oid)
297
+ end.should == ["subjectKeyIdentifier", "authorityKeyIdentifier"]
298
+ end
299
+
300
+ it "should support keyUsage" do
301
+ cert = OpenSSL::X509::Certificate.new(@certificate.to_pem)
302
+ cert.extensions.map(&:oid).include?("keyUsage").should be_true
303
+ end
304
+
305
+ it "should support extendedKeyUsage" do
306
+ cert = OpenSSL::X509::Certificate.new(@certificate.to_pem)
307
+ cert.extensions.map(&:oid).include?("extendedKeyUsage").should be_true
308
+ end
309
+ end
310
+
311
+ describe "Signing profile" do
312
+ before(:each) do
313
+ @certificate = CertificateAuthority::Certificate.new
314
+ @certificate.subject.common_name = "chrischandler.name"
315
+ @certificate.key_material.generate_key(1024)
316
+ @certificate.serial_number.number = 1
317
+
318
+ @signing_profile = {
319
+ "extensions" => {
320
+ "basicConstraints" => {"ca" => false},
321
+ "crlDistributionPoints" => {"uri" => "http://notme.com/other.crl" },
322
+ "subjectKeyIdentifier" => {},
323
+ "authorityKeyIdentifier" => {},
324
+ "authorityInfoAccess" => {"ocsp" => ["http://youFillThisOut/ocsp/"] },
325
+ "keyUsage" => {"usage" => ["digitalSignature","nonRepudiation"] },
326
+ "extendedKeyUsage" => {"usage" => [ "serverAuth","clientAuth"]},
327
+ "subjectAltName" => {"uris" => ["http://subdomains.youFillThisOut/"]},
328
+ "certificatePolicies" => {
329
+ "policy_identifier" => "1.3.5.8", "cps_uris" => ["http://my.host.name/", "http://my.your.name/"], "user_notice" => {
330
+ "explicit_text" => "Explicit Text Here", "organization" => "Organization name", "notice_numbers" => "1,2,3,4"
331
+ }
332
+ }
333
+ }
334
+ }
335
+ end
336
+
337
+ it "should be able to sign with an optional policy hash" do
338
+ @certificate.sign!(@signing_profile)
339
+ end
340
+
341
+ it "should support a default signing digest of SHA512" do
342
+ @certificate.sign!(@signing_profile)
343
+ cert = OpenSSL::X509::Certificate.new(@certificate.to_pem)
344
+ cert.signature_algorithm.should == "sha512WithRSAEncryption"
345
+ end
346
+
347
+ it "should support a configurable digest algorithm" do
348
+ @signing_profile.merge!({"digest" => "SHA1"})
349
+ @certificate.sign!(@signing_profile)
350
+ cert = OpenSSL::X509::Certificate.new(@certificate.to_pem)
351
+ cert.signature_algorithm.should == "sha1WithRSAEncryption"
352
+ end
353
+
354
+ end
355
+
356
+ describe "from_openssl" do
357
+ before(:each) do
358
+ @pem_cert=<<CERT
359
+ -----BEGIN CERTIFICATE-----
360
+ MIICFDCCAc6gAwIBAgIJAPDLgMilKuayMA0GCSqGSIb3DQEBBQUAMEgxCzAJBgNV
361
+ BAYTAlVTMRMwEQYDVQQIEwpTb21lLVN0YXRlMQowCAYDVQQKEwEgMRgwFgYDVQQD
362
+ Ew9WZXJ5IFNtYWxsIENlcnQwHhcNMTIwNTAzMDMyODI1WhcNMTMwNTAzMDMyODI1
363
+ WjBIMQswCQYDVQQGEwJVUzETMBEGA1UECBMKU29tZS1TdGF0ZTEKMAgGA1UEChMB
364
+ IDEYMBYGA1UEAxMPVmVyeSBTbWFsbCBDZXJ0MEwwDQYJKoZIhvcNAQEBBQADOwAw
365
+ OAIxAN6+33+WQ3FBMt+vMhshxOj+8W7V64pDKCJ3pVlnSn36imBWqrN0AGWX8qjv
366
+ S+GzGwIDAQABo4GqMIGnMB0GA1UdDgQWBBRMUQ/HpPrAkKOufS5h+xPtEuzyWDB4
367
+ BgNVHSMEcTBvgBRMUQ/HpPrAkKOufS5h+xPtEuzyWKFMpEowSDELMAkGA1UEBhMC
368
+ VVMxEzARBgNVBAgTClNvbWUtU3RhdGUxCjAIBgNVBAoTASAxGDAWBgNVBAMTD1Zl
369
+ cnkgU21hbGwgQ2VydIIJAPDLgMilKuayMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcN
370
+ AQEFBQADMQAq0CsqEChn4uf6MkXYBwaAAmS3JLmagyliJe5zM3y8dZz6Em2Ugb8o
371
+ 1cCaKaHJHSg=
372
+ -----END CERTIFICATE-----
373
+ CERT
374
+ @openssl_cert = OpenSSL::X509::Certificate.new @pem_cert
375
+ @small_cert = CertificateAuthority::Certificate.from_openssl @openssl_cert
376
+ end
377
+
378
+ it "should reject non-Certificate arguments" do
379
+ lambda { CertificateAuthority::Certificate.from_openssl "a string" }.should raise_error
380
+ end
381
+
382
+ it "should only be missing a private key" do
383
+ @small_cert.should_not be_valid
384
+ @small_cert.key_material.private_key = "data"
385
+ @small_cert.should be_valid
386
+ end
387
+ end
388
+
389
+ it "should have a distinguished name" do
390
+ @certificate.distinguished_name.should_not be_nil
391
+ end
392
+
393
+ it "should have a serial number" do
394
+ @certificate.serial_number.should_not be_nil
395
+ end
396
+
397
+ it "should have a subject" do
398
+ @certificate.subject.should_not be_nil
399
+ end
400
+
401
+ it "should be able to have a parent entity" do
402
+ @certificate.respond_to?(:parent).should be_true
403
+ end
404
+
405
+ it "should have key material" do
406
+ @certificate.key_material.should_not be_nil
407
+ end
408
+
409
+ it "should have a not_before field" do
410
+ @certificate.not_before.should_not be_nil
411
+ end
412
+
413
+ it "should have a not_after field" do
414
+ @certificate.not_after.should_not be_nil
415
+ end
416
+
417
+ it "should default to one year validity" do
418
+ @certificate.not_after.should < Time.now + 65 * 60 * 24 * 365 and
419
+ @certificate.not_after.should > Time.now + 55 * 60 * 24 * 365
420
+ end
421
+
422
+ it "should be able to have a revoked at time" do
423
+ @certificate.revoked?.should be_false
424
+ @certificate.revoked_at = Time.now
425
+ @certificate.revoked?.should be_true
426
+ end
427
+
428
+ end