certificate_authority 0.1.3 → 0.1.4

Sign up to get free protection for your applications and to get access to all the features.
data/README.rdoc CHANGED
@@ -24,14 +24,15 @@ Let's look at a complete example for generating a new root certificate. Assuming
24
24
 
25
25
  Generating a self-signed root certificate is fairly easy:
26
26
 
27
- require 'certificate_authority'
28
- root = CertificateAuthority::Certificate.new
29
- root.subject.common_name= "http://mydomain.com"
30
- root.serial_number.number=1
31
- root.key_material.generate_key
32
- root.signing_entity = true
33
- root.sign!
34
-
27
+ require 'certificate_authority'
28
+ root = CertificateAuthority::Certificate.new
29
+ root.subject.common_name= "http://mydomain.com"
30
+ root.serial_number.number=1
31
+ root.key_material.generate_key
32
+ root.signing_entity = true
33
+ signing_profile = {"extensions" => {"keyUsage" => {"usage" => ["critical", "keyCertSign"] }} }
34
+ root.sign!(signing_profile)
35
+
35
36
  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.
36
37
 
37
38
  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:
@@ -48,29 +49,30 @@ Lastly, we declare that the certificate we're about to sign is itself a signing
48
49
 
49
50
  == Creating a new intermediate
50
51
 
51
- 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 a root certificate you would do something like the following:
52
+ 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:
53
+
54
+ intermediate = CertificateAuthority::Certificate.new
55
+ intermediate.subject.common_name= "My snazzy intermediate!"
56
+ intermediate.serial_number.number=2
57
+ intermediate.key_material.generate_key
58
+ intermediate.signing_entity = true
59
+ intermediate.parent = root
60
+ signing_profile = {"extensions" => {"keyUsage" => {"usage" => ["critical", "keyCertSign"] }} }
61
+ intermediate.sign!(signing_profile)
52
62
 
53
- intermediate = CertificateAuthority::Certificate.new
54
- intermediate.subject.common_name= "My snazzy intermediate!"
55
- intermediate.serial_number.number=2
56
- intermediate.key_material.generate_key
57
- intermediate.signing_entity = true
58
- intermediate.parent = root
59
- intermediate.sign!
60
-
61
63
  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!+.
62
64
 
63
65
  = Creating new certificates (in general)
64
66
 
65
67
  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:
66
68
 
67
- plain_cert = CertificateAuthority::Certificate.new
68
- plain_cert.subject.common_name= "http://mydomain.com"
69
- plain_cert.serial_number.number=4
70
- plain_cert.key_material.generate_key
71
- plain_cert.parent = root # or intermediate
72
- plain_cert.sign!
73
-
69
+ plain_cert = CertificateAuthority::Certificate.new
70
+ plain_cert.subject.common_name= "http://mydomain.com"
71
+ plain_cert.serial_number.number=4
72
+ plain_cert.key_material.generate_key
73
+ plain_cert.parent = root # or intermediate
74
+ plain_cert.sign!
75
+
74
76
  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:
75
77
 
76
78
  plain_cert.to_pem
@@ -83,27 +85,27 @@ Creating basic certificates is all well and good, but maybe you want _more_ sign
83
85
 
84
86
  Here's an example of a full signing profile for most of the common V3 extensions:
85
87
 
86
- signing_profile = {
87
- "extensions" => {
88
- "basicConstraints" => {"ca" => false},
89
- "crlDistributionPoints" => {"uri" => "http://notme.com/other.crl" },
90
- "subjectKeyIdentifier" => {},
91
- "authorityKeyIdentifier" => {},
92
- "authorityInfoAccess" => {"ocsp" => ["http://youFillThisOut/ocsp/"] },
93
- "keyUsage" => {"usage" => ["digitalSignature","nonRepudiation"] },
94
- "extendedKeyUsage" => {"usage" => [ "serverAuth","clientAuth"]},
95
- "subjectAltName" => {"uris" => ["http://subdomains.youFillThisOut/"]},
96
- "certificatePolicies" => {
97
- "policy_identifier" => "1.3.5.8", "cps_uris" => ["http://my.host.name/", "http://my.your.name/"],
98
- "user_notice" => {
99
- "explicit_text" => "Explicit Text Here",
100
- "organization" => "Organization name",
101
- "notice_numbers" => "1,2,3,4"
102
- }
103
- }
104
- }
105
- }
106
-
88
+ signing_profile = {
89
+ "extensions" => {
90
+ "basicConstraints" => {"ca" => false},
91
+ "crlDistributionPoints" => {"uri" => "http://notme.com/other.crl" },
92
+ "subjectKeyIdentifier" => {},
93
+ "authorityKeyIdentifier" => {},
94
+ "authorityInfoAccess" => {"ocsp" => ["http://youFillThisOut/ocsp/"] },
95
+ "keyUsage" => {"usage" => ["digitalSignature","nonRepudiation"] },
96
+ "extendedKeyUsage" => {"usage" => [ "serverAuth","clientAuth"]},
97
+ "subjectAltName" => {"uris" => ["http://subdomains.youFillThisOut/"]},
98
+ "certificatePolicies" => {
99
+ "policy_identifier" => "1.3.5.8", "cps_uris" => ["http://my.host.name/", "http://my.your.name/"],
100
+ "user_notice" => {
101
+ "explicit_text" => "Explicit Text Here",
102
+ "organization" => "Organization name",
103
+ "notice_numbers" => "1,2,3,4"
104
+ }
105
+ }
106
+ }
107
+ }
108
+
107
109
  Using a signing profile is done this way:
108
110
 
109
111
  certificate.sign!(signing_profile)
@@ -128,33 +130,33 @@ This extension controls where a conformant client can go to obtain a list of cer
128
130
 
129
131
  This extension is required to be present, but doesn't offer any configurable parameters. Directly from the RFC:
130
132
 
131
- The subject key identifier extension provides a means of identifying
132
- certificates that contain a particular public key.
133
-
134
- To facilitate certification path construction, this extension MUST
135
- appear in all conforming CA certificates, that is, all certificates
136
- including the basic constraints extension (section 4.2.1.10) where
137
- the value of cA is TRUE. The value of the subject key identifier
138
- MUST be the value placed in the key identifier field of the Authority
139
- Key Identifier extension (section 4.2.1.1) of certificates issued by
140
- the subject of this certificate.
141
-
133
+ The subject key identifier extension provides a means of identifying
134
+ certificates that contain a particular public key.
135
+
136
+ To facilitate certification path construction, this extension MUST
137
+ appear in all conforming CA certificates, that is, all certificates
138
+ including the basic constraints extension (section 4.2.1.10) where
139
+ the value of cA is TRUE. The value of the subject key identifier
140
+ MUST be the value placed in the key identifier field of the Authority
141
+ Key Identifier extension (section 4.2.1.1) of certificates issued by
142
+ the subject of this certificate.
143
+
142
144
  == Authority Key Identifier
143
145
 
144
146
  Just like the subject key identifier, this is required under most circumstances and doesn't contain any meaningful configuration options. From the RFC:
145
147
 
146
- The keyIdentifier field of the authorityKeyIdentifier extension MUST
147
- be included in all certificates generated by conforming CAs to
148
- facilitate certification path construction. There is one exception;
149
- where a CA distributes its public key in the form of a "self-signed"
150
- certificate, the authority key identifier MAY be omitted. The
151
- signature on a self-signed certificate is generated with the private
152
- key associated with the certificate's subject public key. (This
153
- proves that the issuer possesses both the public and private keys.)
154
- In this case, the subject and authority key identifiers would be
155
- identical, but only the subject key identifier is needed for
156
- certification path building.
157
-
148
+ The keyIdentifier field of the authorityKeyIdentifier extension MUST
149
+ be included in all certificates generated by conforming CAs to
150
+ facilitate certification path construction. There is one exception;
151
+ where a CA distributes its public key in the form of a "self-signed"
152
+ certificate, the authority key identifier MAY be omitted. The
153
+ signature on a self-signed certificate is generated with the private
154
+ key associated with the certificate's subject public key. (This
155
+ proves that the issuer possesses both the public and private keys.)
156
+ In this case, the subject and authority key identifiers would be
157
+ identical, but only the subject key identifier is needed for
158
+ certification path building.
159
+
158
160
  == Authority Info Access
159
161
 
160
162
  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.
@@ -197,20 +199,20 @@ If you happen to have a PKCS#11 compliant hardware token you can use +certificat
197
199
 
198
200
  To configure a certificate to utilize PKCS#11 instead of in memory keys all you need to do is:
199
201
 
200
- root = CertificateAuthority::Certificate.new
201
- root.subject.common_name= "http://mydomain.com"
202
- root.serial_number.number=1
203
- root.signing_entity = true
204
-
205
- key_material_in_hardware = CertificateAuthority::Pkcs11KeyMaterial.new
206
- key_material_in_hardware.token_id = "46"
207
- key_material_in_hardware.pkcs11_lib = "/usr/lib/libeTPkcs11.so"
208
- key_material_in_hardware.openssl_pkcs11_engine_lib = "/usr/lib/engines/engine_pkcs11.so"
209
- key_material_in_hardware.pin = "11111111"
210
-
211
- root.key_material = key_material_in_hardware
212
- root.sign!
213
-
202
+ root = CertificateAuthority::Certificate.new
203
+ root.subject.common_name= "http://mydomain.com"
204
+ root.serial_number.number=1
205
+ root.signing_entity = true
206
+
207
+ key_material_in_hardware = CertificateAuthority::Pkcs11KeyMaterial.new
208
+ key_material_in_hardware.token_id = "46"
209
+ key_material_in_hardware.pkcs11_lib = "/usr/lib/libeTPkcs11.so"
210
+ key_material_in_hardware.openssl_pkcs11_engine_lib = "/usr/lib/engines/engine_pkcs11.so"
211
+ key_material_in_hardware.pin = "11111111"
212
+
213
+ root.key_material = key_material_in_hardware
214
+ root.sign!
215
+
214
216
  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:
215
217
 
216
218
  [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.
@@ -224,14 +226,18 @@ Also of note, I have gotten these to work with 32-bit copies of Ubuntu 10.10 and
224
226
  = Coming Soon
225
227
 
226
228
  * More PKCS#11 hardware (I need driver support from the manufacturers)
227
- * Configurable V3 extensions for all the extended functionality
229
+ * Support for working with CSRs to request & issue certificates
230
+
231
+ = Misc notes
232
+
233
+ * Firefox will complain about root/intermediate certificates unless both digitalSignature and keyEncipherment are specified as keyUsage attributes. Thanks diogomonica
228
234
 
229
235
  == Meta
230
236
 
231
- Written by Chris Chandler(http://chrischandler.name) of Flatterline(http://flatterline.com)
237
+ Written by Chris Chandler(http://chrischandler.name)
232
238
 
233
239
  Released under the MIT License: http://www.opensource.org/licenses/mit-license.php
234
240
 
235
- Main page: http://github.com/cchandler/certificateauthority
241
+ Main page: http://github.com/cchandler/certificate_authority
236
242
 
237
- Issue tracking: https://github.com/cchandler/certificateauthority/issues
243
+ Issue tracking: https://github.com/cchandler/certificate_authority/issues
data/VERSION.yml CHANGED
@@ -1,5 +1,5 @@
1
- ---
1
+ ---
2
2
  :major: 0
3
3
  :minor: 1
4
- :patch: 3
4
+ :patch: 4
5
5
  :build:
@@ -4,13 +4,13 @@
4
4
  # -*- encoding: utf-8 -*-
5
5
 
6
6
  Gem::Specification.new do |s|
7
- s.name = %q{certificate_authority}
8
- s.version = "0.1.3"
7
+ s.name = "certificate_authority"
8
+ s.version = "0.1.4"
9
9
 
10
10
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
11
  s.authors = ["Chris Chandler"]
12
- s.date = %q{2011-05-08}
13
- s.email = %q{chris@flatterline.com}
12
+ s.date = "2012-08-12"
13
+ s.email = "chris@flatterline.com"
14
14
  s.extra_rdoc_files = [
15
15
  "README.rdoc"
16
16
  ]
@@ -40,15 +40,16 @@ Gem::Specification.new do |s|
40
40
  "spec/units/extensions_spec.rb",
41
41
  "spec/units/key_material_spec.rb",
42
42
  "spec/units/ocsp_handler_spec.rb",
43
+ "spec/units/pkcs11_key_material_spec.rb",
43
44
  "spec/units/serial_number_spec.rb",
44
45
  "spec/units/signing_entity_spec.rb",
45
46
  "spec/units/units_helper.rb"
46
47
  ]
47
- s.homepage = %q{http://github.com/cchandler/certificate_authority}
48
+ s.homepage = "http://github.com/cchandler/certificate_authority"
48
49
  s.licenses = ["MIT"]
49
50
  s.require_paths = ["lib"]
50
- s.rubygems_version = %q{1.7.2}
51
- s.summary = %q{Ruby gem for managing the core functions outlined in RFC-3280 for PKI}
51
+ s.rubygems_version = "1.8.15"
52
+ s.summary = "Ruby gem for managing the core functions outlined in RFC-3280 for PKI"
52
53
  s.test_files = [
53
54
  "spec/spec_helper.rb",
54
55
  "spec/units/certificate_authority_spec.rb",
@@ -58,6 +59,7 @@ Gem::Specification.new do |s|
58
59
  "spec/units/extensions_spec.rb",
59
60
  "spec/units/key_material_spec.rb",
60
61
  "spec/units/ocsp_handler_spec.rb",
62
+ "spec/units/pkcs11_key_material_spec.rb",
61
63
  "spec/units/serial_number_spec.rb",
62
64
  "spec/units/signing_entity_spec.rb",
63
65
  "spec/units/units_helper.rb"
@@ -16,4 +16,4 @@ require 'certificate_authority/certificate_revocation_list'
16
16
  require 'certificate_authority/ocsp_handler'
17
17
 
18
18
  module CertificateAuthority
19
- end
19
+ end
@@ -2,7 +2,7 @@ module CertificateAuthority
2
2
  class Certificate
3
3
  # include SigningEntity
4
4
  include ActiveModel::Validations
5
-
5
+
6
6
  attr_accessor :distinguished_name
7
7
  attr_accessor :serial_number
8
8
  attr_accessor :key_material
@@ -11,24 +11,24 @@ module CertificateAuthority
11
11
  attr_accessor :revoked_at
12
12
  attr_accessor :extensions
13
13
  attr_accessor :openssl_body
14
-
14
+
15
15
  alias :subject :distinguished_name #Same thing as the DN
16
-
16
+
17
17
  attr_accessor :parent
18
-
18
+
19
19
  validate do |certificate|
20
20
  errors.add :base, "Distinguished name must be valid" unless distinguished_name.valid?
21
- errors.add :base, "Key material name must be valid" unless key_material.valid?
21
+ errors.add :base, "Key material must be valid" unless key_material.valid?
22
22
  errors.add :base, "Serial number must be valid" unless serial_number.valid?
23
23
  errors.add :base, "Extensions must be valid" unless extensions.each do |item|
24
24
  unless item.respond_to?(:valid?)
25
- true
25
+ true
26
26
  else
27
27
  item.valid?
28
28
  end
29
29
  end
30
30
  end
31
-
31
+
32
32
  def initialize
33
33
  self.distinguished_name = DistinguishedName.new
34
34
  self.serial_number = SerialNumber.new
@@ -37,35 +37,35 @@ module CertificateAuthority
37
37
  self.not_after = Time.now + 60 * 60 * 24 * 365 #One year
38
38
  self.parent = self
39
39
  self.extensions = load_extensions()
40
-
40
+
41
41
  self.signing_entity = false
42
-
42
+
43
43
  end
44
-
44
+
45
45
  def sign!(signing_profile={})
46
46
  raise "Invalid certificate #{self.errors.full_messages}" unless valid?
47
47
  merge_profile_with_extensions(signing_profile)
48
-
48
+
49
49
  openssl_cert = OpenSSL::X509::Certificate.new
50
50
  openssl_cert.version = 2
51
51
  openssl_cert.not_before = self.not_before
52
52
  openssl_cert.not_after = self.not_after
53
53
  openssl_cert.public_key = self.key_material.public_key
54
-
54
+
55
55
  openssl_cert.serial = self.serial_number.number
56
-
56
+
57
57
  openssl_cert.subject = self.distinguished_name.to_x509_name
58
58
  openssl_cert.issuer = parent.distinguished_name.to_x509_name
59
-
59
+
60
60
  require 'tempfile'
61
61
  t = Tempfile.new("bullshit_conf")
62
62
  # t = File.new("/tmp/openssl.cnf")
63
63
  ## The config requires a file even though we won't use it
64
64
  openssl_config = OpenSSL::Config.new(t.path)
65
-
65
+
66
66
  factory = OpenSSL::X509::ExtensionFactory.new
67
67
  factory.subject_certificate = openssl_cert
68
-
68
+
69
69
  #NB: If the parent doesn't have an SSL body we're making this a self-signed cert
70
70
  if parent.openssl_body.nil?
71
71
  factory.issuer_certificate = openssl_cert
@@ -77,51 +77,56 @@ module CertificateAuthority
77
77
  config_extensions = extensions[k].config_extensions
78
78
  openssl_config = merge_options(openssl_config,config_extensions)
79
79
  end
80
-
80
+
81
81
  # p openssl_config.sections
82
-
82
+
83
83
  factory.config = openssl_config
84
-
85
- self.extensions.keys.each do |k|
84
+
85
+ # Order matters: e.g. for self-signed, subjectKeyIdentifier must come before authorityKeyIdentifier
86
+ self.extensions.keys.sort{|a,b| b<=>a}.each do |k|
86
87
  e = extensions[k]
87
88
  next if e.to_s.nil? or e.to_s == "" ## If the extension returns an empty string we won't include it
88
89
  ext = factory.create_ext(e.openssl_identifier, e.to_s)
89
90
  openssl_cert.add_extension(ext)
90
91
  end
91
-
92
- digest = OpenSSL::Digest::Digest.new("SHA512")
92
+
93
+ if signing_profile["digest"].nil?
94
+ digest = OpenSSL::Digest::Digest.new("SHA512")
95
+ else
96
+ digest = OpenSSL::Digest::Digest.new(signing_profile["digest"])
97
+ end
93
98
  self.openssl_body = openssl_cert.sign(parent.key_material.private_key,digest)
94
99
  t.close! if t.is_a?(Tempfile)# We can get rid of the ridiculous temp file
95
100
  self.openssl_body
96
101
  end
97
-
102
+
98
103
  def is_signing_entity?
99
104
  self.extensions["basicConstraints"].ca
100
105
  end
101
-
106
+
102
107
  def signing_entity=(signing)
103
108
  self.extensions["basicConstraints"].ca = signing
104
109
  end
105
-
110
+
106
111
  def revoked?
107
112
  !self.revoked_at.nil?
108
113
  end
109
-
114
+
110
115
  def to_pem
111
116
  raise "Certificate has no signed body" if self.openssl_body.nil?
112
117
  self.openssl_body.to_pem
113
118
  end
114
-
119
+
115
120
  def is_root_entity?
116
121
  self.parent == self && is_signing_entity?
117
122
  end
118
-
123
+
119
124
  def is_intermediate_entity?
120
125
  (self.parent != self) && is_signing_entity?
121
126
  end
122
-
127
+
123
128
  private
124
-
129
+
125
130
  def merge_profile_with_extensions(signing_profile={})
126
131
  return self.extensions if signing_profile["extensions"].nil?
127
132
  signing_config = signing_profile["extensions"]
@@ -130,17 +135,17 @@ module CertificateAuthority
130
135
  items = signing_config[k]
131
136
  items.keys.each do |profile_item_key|
132
137
  if extension.respond_to?("#{profile_item_key}=".to_sym)
133
- extension.send("#{profile_item_key}=".to_sym, items[profile_item_key] )
138
+ extension.send("#{profile_item_key}=".to_sym, items[profile_item_key] )
134
139
  else
135
140
  p "Tried applying '#{profile_item_key}' to #{extension.class} but it doesn't respond!"
136
141
  end
137
142
  end
138
143
  end
139
144
  end
140
-
145
+
141
146
  def load_extensions
142
147
  extension_hash = {}
143
-
148
+
144
149
  temp_extensions = []
145
150
  basic_constraints = CertificateAuthority::Extensions::BasicContraints.new
146
151
  temp_extensions << basic_constraints
@@ -160,20 +165,37 @@ module CertificateAuthority
160
165
  temp_extensions << subject_alternative_name
161
166
  certificate_policies = CertificateAuthority::Extensions::CertificatePolicies.new
162
167
  temp_extensions << certificate_policies
163
-
168
+
164
169
  temp_extensions.each do |extension|
165
170
  extension_hash[extension.openssl_identifier] = extension
166
171
  end
167
-
172
+
168
173
  extension_hash
169
174
  end
170
-
175
+
171
176
  def merge_options(config,hash)
172
177
  hash.keys.each do |k|
173
178
  config[k] = hash[k]
174
179
  end
175
180
  config
176
181
  end
177
-
182
+
183
+ def self.from_openssl openssl_cert
184
+ unless openssl_cert.is_a? OpenSSL::X509::Certificate
185
+ raise "Can only construct from an OpenSSL::X509::Certificate"
186
+ end
187
+
188
+ certificate = Certificate.new
189
+ # Only subject, key_material, and body are used for signing
190
+ certificate.distinguished_name = DistinguishedName.from_openssl openssl_cert.subject
191
+ certificate.key_material.public_key = openssl_cert.public_key
192
+ certificate.openssl_body = openssl_cert
193
+ certificate.serial_number.number = openssl_cert.serial.to_i
194
+ certificate.not_before = openssl_cert.not_before
195
+ certificate.not_after = openssl_cert.not_after
196
+ # TODO extensions
197
+ certificate
198
+ end
199
+
178
200
  end
179
- end
201
+ end