certificate_authority 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
data/Gemfile ADDED
@@ -0,0 +1,9 @@
1
+ source 'http://rubygems.org'
2
+
3
+ gem 'activemodel'
4
+
5
+ #group :development do
6
+ gem 'rspec'
7
+ gem "jeweler", "~> 1.5.2"
8
+ gem "rcov", ">= 0"
9
+ #end
data/Gemfile.lock ADDED
@@ -0,0 +1,35 @@
1
+ GEM
2
+ remote: http://rubygems.org/
3
+ specs:
4
+ activemodel (3.0.6)
5
+ activesupport (= 3.0.6)
6
+ builder (~> 2.1.2)
7
+ i18n (~> 0.5.0)
8
+ activesupport (3.0.6)
9
+ builder (2.1.2)
10
+ diff-lcs (1.1.2)
11
+ git (1.2.5)
12
+ i18n (0.5.0)
13
+ jeweler (1.5.2)
14
+ bundler (~> 1.0.0)
15
+ git (>= 1.2.5)
16
+ rake
17
+ rake (0.8.7)
18
+ rcov (0.9.9)
19
+ rspec (2.5.0)
20
+ rspec-core (~> 2.5.0)
21
+ rspec-expectations (~> 2.5.0)
22
+ rspec-mocks (~> 2.5.0)
23
+ rspec-core (2.5.1)
24
+ rspec-expectations (2.5.0)
25
+ diff-lcs (~> 1.1.2)
26
+ rspec-mocks (2.5.0)
27
+
28
+ PLATFORMS
29
+ ruby
30
+
31
+ DEPENDENCIES
32
+ activemodel
33
+ jeweler (~> 1.5.2)
34
+ rcov
35
+ rspec
data/README.rdoc ADDED
@@ -0,0 +1,68 @@
1
+ = CertificateAuthority - Because it shouldn't be this damned complicated
2
+
3
+ This is meant to provide a programmer-friendly implementation of all the basic functionality contained in RFC-3280 to implement your own certificate authority.
4
+
5
+ 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.
6
+
7
+ 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.
8
+
9
+ = The important parts
10
+ Coming soon.
11
+
12
+ = Examples
13
+
14
+ == Creating a self-signed certificate/root (probably what you want)
15
+
16
+ require 'certificate_authority'
17
+ root = CertificateAuthority::Certificate.new
18
+ root.subject.common_name "http://mydomain.com"
19
+ root.key_material.generate_key
20
+ root.signing_entity = true
21
+ root.sign!
22
+
23
+ == Creating an intermediate certificate (much less common use-case)
24
+
25
+ require 'certificate_authority'
26
+ root = CertificateAuthority::Certificate.new
27
+ root.subject.common_name "My snazzy root!"
28
+ root.key_material.generate_key
29
+ root.signing_entity = true
30
+ root.sign!
31
+
32
+ intermediate = CertificateAuthority::Certificate.new
33
+ intermediate.subject.common_name "My snazzy intermediate!"
34
+ intermediate.key_material.generate_key
35
+ intermediate.signing_entity = true
36
+ intermediate.parent = root
37
+ intermediate.sign!
38
+
39
+ == Creating a terminal (non-signing) cert
40
+
41
+ require 'certificate_authority'
42
+ plain_cert = CertificateAuthority::Certificate.new
43
+ plain_cert.subject.common_name "http://mydomain.com"
44
+ plain_cert.key_material.generate_key
45
+ plain_cert.parent = root # or intermediate
46
+ plain_cert.sign!
47
+
48
+ == Getting the certificate body
49
+
50
+ ...
51
+ certificate.sign!
52
+ certificate.to_pem # <= Returns a PEM formatted string of your certificate
53
+ certificate.key_material.private_key.to_pem # <= If you need the private key (and it's in memory)
54
+
55
+ = Coming Soon
56
+
57
+ * More PKCS#11 hardware (I need driver support from the manufacturers)
58
+ * Configurable V3 extensions for all the extended functionality
59
+
60
+ == Meta
61
+
62
+ Written by Chris Chandler(http://chrischandler.name) of Flatterline(http://flatterline.com)
63
+
64
+ Released under the MIT License: http://www.opensource.org/licenses/mit-license.php
65
+
66
+ Main page: http://github.com/cchandler/certificateauthority
67
+
68
+ Issue tracking: https://github.com/cchandler/certificateauthority/issues
data/Rakefile ADDED
@@ -0,0 +1,52 @@
1
+ require 'rubygems'
2
+ require 'bundler'
3
+ require 'rspec'
4
+ require 'rspec/core/rake_task'
5
+
6
+ begin
7
+ Bundler.setup(:default, :development)
8
+ rescue Bundler::BundlerError => e
9
+ $stderr.puts e.message
10
+ $stderr.puts "Run `bundle install` to install missing gems"
11
+ exit e.status_code
12
+ end
13
+
14
+ require 'rake'
15
+
16
+ desc 'Default: run specs.'
17
+ task :default => :spec
18
+
19
+ require 'jeweler'
20
+ Jeweler::Tasks.new do |gem|
21
+ # gem is a Gem::Specification... see http://docs.rubygems.org/read/chapter/20 for more options
22
+ gem.name = "certificate_authority"
23
+ gem.homepage = "http://github.com/cchandler/certificate_authority"
24
+ gem.license = "MIT"
25
+ gem.summary = 'Ruby gem for managing the core functions outlined in RFC-3280 for PKI'
26
+ # gem.description = ''
27
+ gem.email = "chris@flatterline.com"
28
+ gem.authors = ["Chris Chandler"]
29
+
30
+ gem.add_dependency('activemodel', '3.0.6')
31
+ end
32
+ Jeweler::RubygemsDotOrgTasks.new
33
+
34
+ task :spec do
35
+ Rake::Task["spec:units"].invoke
36
+ end
37
+
38
+ namespace :spec do
39
+ desc "Run unit specs."
40
+ RSpec::Core::RakeTask.new(:units) do |t|
41
+ t.rspec_opts = ['--colour --format progress --tag ~pkcs11']
42
+ end
43
+
44
+ desc "Run integration specs."
45
+ RSpec::Core::RakeTask.new(:integrations) do |t|
46
+ t.rspec_opts = ['--colour --format progress']
47
+ end
48
+ end
49
+
50
+ RSpec::Core::RakeTask.new(:doc) do |t|
51
+ t.rspec_opts = ['--format specdoc ']
52
+ end
data/VERSION.yml ADDED
@@ -0,0 +1,5 @@
1
+ ---
2
+ :major: 0
3
+ :minor: 1
4
+ :build:
5
+ :patch: 1
@@ -0,0 +1,90 @@
1
+ # Generated by jeweler
2
+ # DO NOT EDIT THIS FILE DIRECTLY
3
+ # Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec'
4
+ # -*- encoding: utf-8 -*-
5
+
6
+ Gem::Specification.new do |s|
7
+ s.name = %q{certificate_authority}
8
+ s.version = "0.1.1"
9
+
10
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
+ s.authors = ["Chris Chandler"]
12
+ s.date = %q{2011-04-20}
13
+ s.email = %q{chris@flatterline.com}
14
+ s.extra_rdoc_files = [
15
+ "README.rdoc"
16
+ ]
17
+ s.files = [
18
+ "Gemfile",
19
+ "Gemfile.lock",
20
+ "README.rdoc",
21
+ "Rakefile",
22
+ "VERSION.yml",
23
+ "certificate_authority.gemspec",
24
+ "lib/certificate_authority.rb",
25
+ "lib/certificate_authority/certificate.rb",
26
+ "lib/certificate_authority/certificate_revocation_list.rb",
27
+ "lib/certificate_authority/distinguished_name.rb",
28
+ "lib/certificate_authority/extensions.rb",
29
+ "lib/certificate_authority/key_material.rb",
30
+ "lib/certificate_authority/ocsp_handler.rb",
31
+ "lib/certificate_authority/pkcs11_key_material.rb",
32
+ "lib/certificate_authority/serial_number.rb",
33
+ "lib/certificate_authority/signing_entity.rb",
34
+ "lib/tasks/certificate_authority.rake",
35
+ "spec/spec_helper.rb",
36
+ "spec/units/certificate_authority_spec.rb",
37
+ "spec/units/certificate_revocation_list_spec.rb",
38
+ "spec/units/certificate_spec.rb",
39
+ "spec/units/distinguished_name_spec.rb",
40
+ "spec/units/extensions_spec.rb",
41
+ "spec/units/key_material_spec.rb",
42
+ "spec/units/ocsp_handler_spec.rb",
43
+ "spec/units/serial_number_spec.rb",
44
+ "spec/units/signing_entity_spec.rb",
45
+ "spec/units/units_helper.rb"
46
+ ]
47
+ s.homepage = %q{http://github.com/cchandler/certificate_authority}
48
+ s.licenses = ["MIT"]
49
+ 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}
52
+ s.test_files = [
53
+ "spec/spec_helper.rb",
54
+ "spec/units/certificate_authority_spec.rb",
55
+ "spec/units/certificate_revocation_list_spec.rb",
56
+ "spec/units/certificate_spec.rb",
57
+ "spec/units/distinguished_name_spec.rb",
58
+ "spec/units/extensions_spec.rb",
59
+ "spec/units/key_material_spec.rb",
60
+ "spec/units/ocsp_handler_spec.rb",
61
+ "spec/units/serial_number_spec.rb",
62
+ "spec/units/signing_entity_spec.rb",
63
+ "spec/units/units_helper.rb"
64
+ ]
65
+
66
+ if s.respond_to? :specification_version then
67
+ s.specification_version = 3
68
+
69
+ if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
70
+ s.add_runtime_dependency(%q<activemodel>, [">= 0"])
71
+ s.add_runtime_dependency(%q<rspec>, [">= 0"])
72
+ s.add_runtime_dependency(%q<jeweler>, ["~> 1.5.2"])
73
+ s.add_runtime_dependency(%q<rcov>, [">= 0"])
74
+ s.add_runtime_dependency(%q<activemodel>, ["= 3.0.6"])
75
+ else
76
+ s.add_dependency(%q<activemodel>, [">= 0"])
77
+ s.add_dependency(%q<rspec>, [">= 0"])
78
+ s.add_dependency(%q<jeweler>, ["~> 1.5.2"])
79
+ s.add_dependency(%q<rcov>, [">= 0"])
80
+ s.add_dependency(%q<activemodel>, ["= 3.0.6"])
81
+ end
82
+ else
83
+ s.add_dependency(%q<activemodel>, [">= 0"])
84
+ s.add_dependency(%q<rspec>, [">= 0"])
85
+ s.add_dependency(%q<jeweler>, ["~> 1.5.2"])
86
+ s.add_dependency(%q<rcov>, [">= 0"])
87
+ s.add_dependency(%q<activemodel>, ["= 3.0.6"])
88
+ end
89
+ end
90
+
@@ -0,0 +1,176 @@
1
+ module CertificateAuthority
2
+ class Certificate
3
+ # include SigningEntity
4
+ include ActiveModel::Validations
5
+
6
+ attr_accessor :distinguished_name
7
+ attr_accessor :serial_number
8
+ attr_accessor :key_material
9
+ attr_accessor :not_before
10
+ attr_accessor :not_after
11
+ attr_accessor :revoked_at
12
+ attr_accessor :extensions
13
+ attr_accessor :openssl_body
14
+
15
+ alias :subject :distinguished_name #Same thing as the DN
16
+
17
+ attr_accessor :parent
18
+
19
+ validate do |certificate|
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?
22
+ errors.add :base, "Serial number must be valid" unless serial_number.valid?
23
+ errors.add :base, "Extensions must be valid" unless extensions.each do |item|
24
+ return true unless item.respond_to?(:valid?)
25
+ item.valid?
26
+ end
27
+ end
28
+
29
+ def initialize
30
+ self.distinguished_name = DistinguishedName.new
31
+ self.serial_number = SerialNumber.new
32
+ self.key_material = MemoryKeyMaterial.new
33
+ self.not_before = Time.now
34
+ self.not_after = Time.now + 60 * 60 * 24 * 365 #One year
35
+ self.parent = self
36
+ self.extensions = load_extensions()
37
+
38
+ self.signing_entity = false
39
+
40
+ end
41
+
42
+ def sign!(signing_profile={})
43
+ raise "Invalid certificate" unless valid?
44
+ merge_profile_with_extensions(signing_profile)
45
+
46
+ openssl_cert = OpenSSL::X509::Certificate.new
47
+ openssl_cert.version = 2
48
+ openssl_cert.not_before = self.not_before
49
+ openssl_cert.not_after = self.not_after
50
+ openssl_cert.public_key = self.key_material.public_key
51
+
52
+ openssl_cert.serial = self.serial_number.number
53
+
54
+ openssl_cert.subject = self.distinguished_name.to_x509_name
55
+ openssl_cert.issuer = parent.distinguished_name.to_x509_name
56
+
57
+ require 'tempfile'
58
+ t = Tempfile.new("bullshit_conf")
59
+ # t = File.new("/tmp/openssl.cnf")
60
+ ## The config requires a file even though we won't use it
61
+ openssl_config = OpenSSL::Config.new(t.path)
62
+
63
+ factory = OpenSSL::X509::ExtensionFactory.new
64
+ factory.subject_certificate = openssl_cert
65
+
66
+ #NB: If the parent doesn't have an SSL body we're making this a self-signed cert
67
+ if parent.openssl_body.nil?
68
+ factory.issuer_certificate = openssl_cert
69
+ else
70
+ factory.issuer_certificate = parent.openssl_body
71
+ end
72
+
73
+ self.extensions.keys.each do |k|
74
+ config_extensions = extensions[k].config_extensions
75
+ openssl_config = merge_options(openssl_config,config_extensions)
76
+ end
77
+
78
+ # p openssl_config.sections
79
+
80
+ factory.config = openssl_config
81
+
82
+ self.extensions.keys.each do |k|
83
+ e = extensions[k]
84
+ next if e.to_s.nil? or e.to_s == "" ## If the extension returns an empty string we won't include it
85
+ ext = factory.create_ext(e.openssl_identifier, e.to_s)
86
+ openssl_cert.add_extension(ext)
87
+ end
88
+
89
+ digest = OpenSSL::Digest::Digest.new("SHA512")
90
+ self.openssl_body = openssl_cert.sign(parent.key_material.private_key,digest)
91
+ t.close! if t.is_a?(Tempfile)# We can get rid of the ridiculous temp file
92
+ self.openssl_body
93
+ end
94
+
95
+ def is_signing_entity?
96
+ self.extensions["basicConstraints"].ca
97
+ end
98
+
99
+ def signing_entity=(signing)
100
+ self.extensions["basicConstraints"].ca = signing
101
+ end
102
+
103
+ def revoked?
104
+ !self.revoked_at.nil?
105
+ end
106
+
107
+ def to_pem
108
+ raise "Certificate has no signed body" if self.openssl_body.nil?
109
+ self.openssl_body.to_pem
110
+ end
111
+
112
+ def is_root_entity?
113
+ self.parent == self && is_signing_entity?
114
+ end
115
+
116
+ def is_intermediate_entity?
117
+ (self.parent != self) && is_signing_entity?
118
+ end
119
+
120
+ private
121
+
122
+ def merge_profile_with_extensions(signing_profile={})
123
+ return self.extensions if signing_profile["extensions"].nil?
124
+ signing_config = signing_profile["extensions"]
125
+ signing_config.keys.each do |k|
126
+ extension = self.extensions[k]
127
+ items = signing_config[k]
128
+ items.keys.each do |profile_item_key|
129
+ if extension.respond_to?("#{profile_item_key}=".to_sym)
130
+ extension.send("#{profile_item_key}=".to_sym, items[profile_item_key] )
131
+ else
132
+ p "Tried applying '#{profile_item_key}' to #{extension.class} but it doesn't respond!"
133
+ end
134
+ end
135
+ end
136
+ end
137
+
138
+ def load_extensions
139
+ extension_hash = {}
140
+
141
+ temp_extensions = []
142
+ basic_constraints = CertificateAuthority::Extensions::BasicContraints.new
143
+ temp_extensions << basic_constraints
144
+ crl_distribution_points = CertificateAuthority::Extensions::CrlDistributionPoints.new
145
+ temp_extensions << crl_distribution_points
146
+ subject_key_identifier = CertificateAuthority::Extensions::SubjectKeyIdentifier.new
147
+ temp_extensions << subject_key_identifier
148
+ authority_key_identifier = CertificateAuthority::Extensions::AuthorityKeyIdentifier.new
149
+ temp_extensions << authority_key_identifier
150
+ authority_info_access = CertificateAuthority::Extensions::AuthorityInfoAccess.new
151
+ temp_extensions << authority_info_access
152
+ key_usage = CertificateAuthority::Extensions::KeyUsage.new
153
+ temp_extensions << key_usage
154
+ extended_key_usage = CertificateAuthority::Extensions::ExtendedKeyUsage.new
155
+ temp_extensions << extended_key_usage
156
+ subject_alternative_name = CertificateAuthority::Extensions::SubjectAlternativeName.new
157
+ temp_extensions << subject_alternative_name
158
+ certificate_policies = CertificateAuthority::Extensions::CertificatePolicies.new
159
+ temp_extensions << certificate_policies
160
+
161
+ temp_extensions.each do |extension|
162
+ extension_hash[extension.openssl_identifier] = extension
163
+ end
164
+
165
+ extension_hash
166
+ end
167
+
168
+ def merge_options(config,hash)
169
+ hash.keys.each do |k|
170
+ config[k] = hash[k]
171
+ end
172
+ config
173
+ end
174
+
175
+ end
176
+ end
@@ -0,0 +1,59 @@
1
+ module CertificateAuthority
2
+ class CertificateRevocationList
3
+ include ActiveModel::Validations
4
+
5
+ attr_accessor :certificates
6
+ attr_accessor :parent
7
+ attr_accessor :crl_body
8
+ attr_accessor :next_update
9
+
10
+ validate do |crl|
11
+ errors.add :next_update, "Next update must be a positive value" if crl.next_update < 0
12
+ errors.add :parent, "A parent entity must be set" if crl.parent.nil?
13
+ end
14
+
15
+ def initialize
16
+ self.certificates = []
17
+ self.next_update = 60 * 60 * 4 # 4 hour default
18
+ end
19
+
20
+ def <<(cert)
21
+ raise "Only revoked certificates can be added to a CRL" unless cert.revoked?
22
+ self.certificates << cert
23
+ end
24
+
25
+ def sign!
26
+ raise "No parent entity has been set!" if self.parent.nil?
27
+ raise "Invalid CRL" unless self.valid?
28
+
29
+ revocations = self.certificates.collect do |certificate|
30
+ revocation = OpenSSL::X509::Revoked.new
31
+ x509_cert = OpenSSL::X509::Certificate.new(certificate.to_pem)
32
+ revocation.serial = x509_cert.serial
33
+ revocation.time = certificate.revoked_at
34
+ revocation
35
+ end
36
+
37
+ crl = OpenSSL::X509::CRL.new
38
+ revocations.each do |revocation|
39
+ crl.add_revoked(revocation)
40
+ end
41
+
42
+ crl.version = 1
43
+ crl.last_update = Time.now
44
+ crl.next_update = Time.now + self.next_update
45
+
46
+ signing_cert = OpenSSL::X509::Certificate.new(self.parent.to_pem)
47
+ digest = OpenSSL::Digest::Digest.new("SHA512")
48
+ crl.issuer = signing_cert.subject
49
+ self.crl_body = crl.sign(self.parent.key_material.private_key, digest)
50
+
51
+ self.crl_body
52
+ end
53
+
54
+ def to_pem
55
+ raise "No signed CRL body" if self.crl_body.nil?
56
+ self.crl_body.to_pem
57
+ end
58
+ end#CertificateRevocationList
59
+ end