certificate_authority 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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,18 @@
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
+
16
+
17
+ end
18
+ end
@@ -0,0 +1,19 @@
1
+ $:.unshift(File.dirname(__FILE__)) unless $:.include?(File.dirname(__FILE__)) || $:.include?(File.expand_path(File.dirname(__FILE__)))
2
+
3
+ #Exterior requirements
4
+ require 'openssl'
5
+ require 'active_model'
6
+
7
+ #Internal modules
8
+ require 'certificate_authority/signing_entity'
9
+ require 'certificate_authority/distinguished_name'
10
+ require 'certificate_authority/serial_number'
11
+ require 'certificate_authority/key_material'
12
+ require 'certificate_authority/pkcs11_key_material'
13
+ require 'certificate_authority/extensions'
14
+ require 'certificate_authority/certificate'
15
+ require 'certificate_authority/certificate_revocation_list'
16
+ require 'certificate_authority/ocsp_handler'
17
+
18
+ module CertificateAuthority
19
+ 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
11
+ @root_certificate.serial_number.number = 1
12
+ @root_certificate.sign!
13
+
14
+ @certificate = CertificateAuthority::Certificate.new
15
+ @certificate.key_material.generate_key
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,351 @@
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
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
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
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
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
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.first.value.should == "CA:TRUE"
98
+ cert.extensions.map{|i| [i.oid,i.value] }.select{|i| i.first == "basicConstraints"}.first[1].should == "CA:TRUE"
99
+ end
100
+
101
+ end
102
+
103
+ describe "Terminal certificates" do
104
+ before(:each) do
105
+ @different_cert = CertificateAuthority::Certificate.new
106
+ @different_cert.signing_entity = true
107
+ @different_cert.subject.common_name = "chrischandler.name root"
108
+ @different_cert.key_material.generate_key
109
+ @different_cert.serial_number.number = 1
110
+ @different_cert.sign! #self-signed
111
+ @certificate.parent = @different_cert
112
+ end
113
+
114
+ it "should not be identified as an intermediate certificate" do
115
+ @certificate.is_intermediate_entity?.should be_false
116
+ end
117
+
118
+ it "should not be identified as a root" do
119
+ @certificate.is_root_entity?.should be_false
120
+ end
121
+
122
+ it "should have the basicContraint CA:FALSE" do
123
+ @certificate.subject.common_name = "chrischandler.name"
124
+ @certificate.key_material.generate_key
125
+ @certificate.signing_entity = false
126
+ @certificate.serial_number.number = 1
127
+ @certificate.sign!
128
+ cert = OpenSSL::X509::Certificate.new(@certificate.to_pem)
129
+ # cert.extensions.first.value.should == "CA:FALSE"
130
+ cert.extensions.map{|i| [i.oid,i.value] }.select{|i| i.first == "basicConstraints"}.first[1].should == "CA:FALSE"
131
+ end
132
+ end
133
+
134
+
135
+ it "should be able to be identified as a root certificate" do
136
+ @certificate.respond_to?(:is_root_entity?).should be_true
137
+ end
138
+ end #End of SigningEntity
139
+
140
+ describe "Signed certificates" do
141
+ before(:each) do
142
+ @certificate = CertificateAuthority::Certificate.new
143
+ @certificate.subject.common_name = "chrischandler.name"
144
+ @certificate.key_material.generate_key
145
+ @certificate.serial_number.number = 1
146
+ @certificate.sign!
147
+ end
148
+
149
+ it "should have a PEM encoded certificate body available" do
150
+ @certificate.to_pem.should_not be_nil
151
+ OpenSSL::X509::Certificate.new(@certificate.to_pem).should_not be_nil
152
+ end
153
+ end
154
+
155
+ describe "X.509 V3 Extensions on Signed Certificates" do
156
+ before(:each) do
157
+ @certificate = CertificateAuthority::Certificate.new
158
+ @certificate.subject.common_name = "chrischandler.name"
159
+ @certificate.key_material.generate_key
160
+ @certificate.serial_number.number = 1
161
+ @signing_profile = {
162
+ "extensions" => {
163
+ "subjectAltName" => {"uris" => ["www.chrischandler.name"]},
164
+ "certificatePolicies" => {
165
+ "policy_identifier" => "1.3.5.7",
166
+ "cps_uris" => ["http://my.host.name/", "http://my.your.name/"],
167
+ "user_notice" => {
168
+ "explicit_text" => "Testing!", "organization" => "RSpec Test organization name", "notice_numbers" => "1,2,3,4"
169
+ }
170
+ }
171
+ }
172
+ }
173
+ @certificate.sign!(@signing_profile)
174
+ end
175
+
176
+ describe "SubjectAltName" do
177
+ before(:each) do
178
+ @certificate = CertificateAuthority::Certificate.new
179
+ @certificate.subject.common_name = "chrischandler.name"
180
+ @certificate.key_material.generate_key
181
+ @certificate.serial_number.number = 1
182
+ end
183
+
184
+ it "should have a subjectAltName if specified" do
185
+ @certificate.sign!({"extensions" => {"subjectAltName" => {"uris" => ["www.chrischandler.name"]}}})
186
+ cert = OpenSSL::X509::Certificate.new(@certificate.to_pem)
187
+ cert.extensions.map(&:oid).include?("subjectAltName").should be_true
188
+ end
189
+
190
+ it "should NOT have a subjectAltName if one was not specified" do
191
+ @certificate.sign!
192
+ cert = OpenSSL::X509::Certificate.new(@certificate.to_pem)
193
+ cert.extensions.map(&:oid).include?("subjectAltName").should be_false
194
+ end
195
+ end
196
+
197
+ describe "CertificatePolicies" do
198
+ before(:each) do
199
+ @certificate = CertificateAuthority::Certificate.new
200
+ @certificate.subject.common_name = "chrischandler.name"
201
+ @certificate.key_material.generate_key
202
+ @certificate.serial_number.number = 1
203
+ end
204
+
205
+ it "should have a certificatePolicy if specified" do
206
+ @certificate.sign!({
207
+ "extensions" => {
208
+ "certificatePolicies" => {
209
+ "policy_identifier" => "1.3.5.7",
210
+ "cps_uris" => ["http://my.host.name/", "http://my.your.name/"]
211
+ }
212
+ }
213
+ })
214
+ cert = OpenSSL::X509::Certificate.new(@certificate.to_pem)
215
+ cert.extensions.map(&:oid).include?("certificatePolicies").should be_true
216
+ end
217
+
218
+ it "should contain a nested userNotice if specified" do
219
+ pending
220
+ # @certificate.sign!({
221
+ # "extensions" => {
222
+ # "certificatePolicies" => {
223
+ # "policy_identifier" => "1.3.5.7",
224
+ # "cps_uris" => ["http://my.host.name/", "http://my.your.name/"],
225
+ # "user_notice" => {
226
+ # "explicit_text" => "Testing!", "organization" => "RSpec Test organization name", "notice_numbers" => "1,2,3,4"
227
+ # }
228
+ # }
229
+ # }
230
+ # })
231
+ # cert = OpenSSL::X509::Certificate.new(@certificate.to_pem)
232
+ # cert.extensions.map(&:oid).include?("certificatePolicies").should be_true
233
+ end
234
+
235
+ it "should NOT include a certificatePolicy if not specified" do
236
+ @certificate.sign!
237
+ cert = OpenSSL::X509::Certificate.new(@certificate.to_pem)
238
+ cert.extensions.map(&:oid).include?("certificatePolicies").should be_false
239
+ end
240
+ end
241
+
242
+
243
+ it "should support BasicContraints" do
244
+ cert = OpenSSL::X509::Certificate.new(@certificate.to_pem)
245
+ cert.extensions.map(&:oid).include?("basicConstraints").should be_true
246
+ end
247
+
248
+ it "should support crlDistributionPoints" do
249
+ cert = OpenSSL::X509::Certificate.new(@certificate.to_pem)
250
+ cert.extensions.map(&:oid).include?("crlDistributionPoints").should be_true
251
+ end
252
+
253
+ it "should support subjectKeyIdentifier" do
254
+ cert = OpenSSL::X509::Certificate.new(@certificate.to_pem)
255
+ cert.extensions.map(&:oid).include?("subjectKeyIdentifier").should be_true
256
+ end
257
+
258
+ it "should support authorityKeyIdentifier" do
259
+ cert = OpenSSL::X509::Certificate.new(@certificate.to_pem)
260
+ cert.extensions.map(&:oid).include?("authorityKeyIdentifier").should be_true
261
+ end
262
+
263
+ it "should support authorityInfoAccess" do
264
+ cert = OpenSSL::X509::Certificate.new(@certificate.to_pem)
265
+ cert.extensions.map(&:oid).include?("authorityInfoAccess").should be_true
266
+ end
267
+
268
+ it "should support keyUsage" do
269
+ cert = OpenSSL::X509::Certificate.new(@certificate.to_pem)
270
+ cert.extensions.map(&:oid).include?("keyUsage").should be_true
271
+ end
272
+
273
+ it "should support extendedKeyUsage" do
274
+ cert = OpenSSL::X509::Certificate.new(@certificate.to_pem)
275
+ cert.extensions.map(&:oid).include?("extendedKeyUsage").should be_true
276
+ end
277
+ end
278
+
279
+ describe "Signing profile" do
280
+ before(:each) do
281
+ @certificate = CertificateAuthority::Certificate.new
282
+ @certificate.subject.common_name = "chrischandler.name"
283
+ @certificate.key_material.generate_key
284
+ @certificate.serial_number.number = 1
285
+
286
+ @signing_profile = {
287
+ "extensions" => {
288
+ "basicConstraints" => {"ca" => false},
289
+ "crlDistributionPoints" => {"uri" => "http://notme.com/other.crl" },
290
+ "subjectKeyIdentifier" => {},
291
+ "authorityKeyIdentifier" => {},
292
+ "authorityInfoAccess" => {"ocsp" => ["http://youFillThisOut/ocsp/"] },
293
+ "keyUsage" => {"usage" => ["digitalSignature","nonRepudiation"] },
294
+ "extendedKeyUsage" => {"usage" => [ "serverAuth","clientAuth"]},
295
+ "subjectAltName" => {"uris" => ["http://subdomains.youFillThisOut/"]},
296
+ "certificatePolicies" => {
297
+ "policy_identifier" => "1.3.5.8", "cps_uris" => ["http://my.host.name/", "http://my.your.name/"], "user_notice" => {
298
+ "explicit_text" => "Explicit Text Here", "organization" => "Organization name", "notice_numbers" => "1,2,3,4"
299
+ }
300
+ }
301
+ }
302
+ }
303
+ end
304
+
305
+ it "should be able to sign with an optional policy hash" do
306
+ @certificate.sign!(@signing_profile)
307
+ end
308
+
309
+ end
310
+
311
+
312
+ it "should have a distinguished name" do
313
+ @certificate.distinguished_name.should_not be_nil
314
+ end
315
+
316
+ it "should have a serial number" do
317
+ @certificate.serial_number.should_not be_nil
318
+ end
319
+
320
+ it "should have a subject" do
321
+ @certificate.subject.should_not be_nil
322
+ end
323
+
324
+ it "should be able to have a parent entity" do
325
+ @certificate.respond_to?(:parent).should be_true
326
+ end
327
+
328
+ it "should have key material" do
329
+ @certificate.key_material.should_not be_nil
330
+ end
331
+
332
+ it "should have a not_before field" do
333
+ @certificate.not_before.should_not be_nil
334
+ end
335
+
336
+ it "should have a not_after field" do
337
+ @certificate.not_after.should_not be_nil
338
+ end
339
+
340
+ it "should default to one year validity" do
341
+ @certificate.not_after.should < Time.now + 65 * 60 * 24 * 365 and
342
+ @certificate.not_after.should > Time.now + 55 * 60 * 24 * 365
343
+ end
344
+
345
+ it "should be able to have a revoked at time" do
346
+ @certificate.revoked?.should be_false
347
+ @certificate.revoked_at = Time.now
348
+ @certificate.revoked?.should be_true
349
+ end
350
+
351
+ end