certificate_authority 0.1.2 → 1.0.0
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.
- checksums.yaml +7 -0
- data/.gitignore +6 -0
- data/.rspec +3 -0
- data/.travis.yml +11 -0
- data/Gemfile +2 -8
- data/Gemfile.lock +71 -27
- data/README.rdoc +184 -89
- data/Rakefile +6 -41
- data/certificate_authority.gemspec +22 -81
- data/lib/certificate_authority.rb +7 -6
- data/lib/certificate_authority/certificate.rb +151 -71
- data/lib/certificate_authority/certificate_revocation_list.rb +46 -26
- data/lib/certificate_authority/core_extensions.rb +46 -0
- data/lib/certificate_authority/distinguished_name.rb +84 -17
- data/lib/certificate_authority/extensions.rb +483 -96
- data/lib/certificate_authority/key_material.rb +75 -21
- data/lib/certificate_authority/ocsp_handler.rb +99 -29
- data/lib/certificate_authority/pkcs11_key_material.rb +13 -15
- data/lib/certificate_authority/revocable.rb +14 -0
- data/lib/certificate_authority/serial_number.rb +18 -5
- data/lib/certificate_authority/signing_entity.rb +5 -7
- data/lib/certificate_authority/signing_request.rb +91 -0
- data/lib/certificate_authority/validations.rb +31 -0
- data/lib/certificate_authority/version.rb +3 -0
- metadata +96 -94
- data/VERSION.yml +0 -5
- data/spec/spec_helper.rb +0 -4
- data/spec/units/certificate_authority_spec.rb +0 -4
- data/spec/units/certificate_revocation_list_spec.rb +0 -68
- data/spec/units/certificate_spec.rb +0 -351
- data/spec/units/distinguished_name_spec.rb +0 -38
- data/spec/units/extensions_spec.rb +0 -53
- data/spec/units/key_material_spec.rb +0 -96
- data/spec/units/ocsp_handler_spec.rb +0 -104
- data/spec/units/serial_number_spec.rb +0 -20
- data/spec/units/signing_entity_spec.rb +0 -4
- data/spec/units/units_helper.rb +0 -1
@@ -1,59 +1,79 @@
|
|
1
1
|
module CertificateAuthority
|
2
2
|
class CertificateRevocationList
|
3
|
-
include
|
4
|
-
|
3
|
+
include Validations
|
4
|
+
|
5
5
|
attr_accessor :certificates
|
6
6
|
attr_accessor :parent
|
7
7
|
attr_accessor :crl_body
|
8
8
|
attr_accessor :next_update
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
errors.add :
|
9
|
+
attr_accessor :last_update_skew_seconds
|
10
|
+
|
11
|
+
def validate
|
12
|
+
errors.add :next_update, "Next update must be a positive value" if self.next_update < 0
|
13
|
+
errors.add :parent, "A parent entity must be set" if self.parent.nil?
|
13
14
|
end
|
14
|
-
|
15
|
+
|
15
16
|
def initialize
|
16
17
|
self.certificates = []
|
17
18
|
self.next_update = 60 * 60 * 4 # 4 hour default
|
19
|
+
self.last_update_skew_seconds = 0
|
18
20
|
end
|
19
|
-
|
20
|
-
def <<(
|
21
|
-
|
22
|
-
|
21
|
+
|
22
|
+
def <<(revocable)
|
23
|
+
case revocable
|
24
|
+
when Revocable
|
25
|
+
raise "Only revoked entities can be added to a CRL" unless revocable.revoked?
|
26
|
+
self.certificates << revocable
|
27
|
+
when OpenSSL::X509::Certificate
|
28
|
+
raise "Not implemented yet"
|
29
|
+
else
|
30
|
+
raise "#{revocable.class} cannot be included in a CRL"
|
31
|
+
end
|
23
32
|
end
|
24
|
-
|
25
|
-
def sign!
|
33
|
+
|
34
|
+
def sign!(signing_profile={})
|
26
35
|
raise "No parent entity has been set!" if self.parent.nil?
|
27
36
|
raise "Invalid CRL" unless self.valid?
|
28
|
-
|
29
|
-
revocations = self.certificates.collect do |
|
37
|
+
|
38
|
+
revocations = self.certificates.collect do |revocable|
|
30
39
|
revocation = OpenSSL::X509::Revoked.new
|
31
|
-
|
32
|
-
|
33
|
-
|
40
|
+
|
41
|
+
## We really just need a serial number, now we have to dig it out
|
42
|
+
case revocable
|
43
|
+
when Certificate
|
44
|
+
x509_cert = OpenSSL::X509::Certificate.new(revocable.to_pem)
|
45
|
+
revocation.serial = x509_cert.serial
|
46
|
+
when SerialNumber
|
47
|
+
revocation.serial = revocable.number
|
48
|
+
end
|
49
|
+
revocation.time = revocable.revoked_at
|
34
50
|
revocation
|
35
51
|
end
|
36
|
-
|
52
|
+
|
37
53
|
crl = OpenSSL::X509::CRL.new
|
38
54
|
revocations.each do |revocation|
|
39
55
|
crl.add_revoked(revocation)
|
40
56
|
end
|
41
|
-
|
57
|
+
|
42
58
|
crl.version = 1
|
43
|
-
crl.last_update = Time.now
|
59
|
+
crl.last_update = Time.now - self.last_update_skew_seconds
|
44
60
|
crl.next_update = Time.now + self.next_update
|
45
|
-
|
61
|
+
|
46
62
|
signing_cert = OpenSSL::X509::Certificate.new(self.parent.to_pem)
|
47
|
-
|
63
|
+
if signing_profile["digest"].nil?
|
64
|
+
digest = OpenSSL::Digest.new("SHA512")
|
65
|
+
else
|
66
|
+
digest = OpenSSL::Digest.new(signing_profile["digest"])
|
67
|
+
end
|
48
68
|
crl.issuer = signing_cert.subject
|
49
69
|
self.crl_body = crl.sign(self.parent.key_material.private_key, digest)
|
50
|
-
|
70
|
+
|
51
71
|
self.crl_body
|
52
72
|
end
|
53
|
-
|
73
|
+
|
54
74
|
def to_pem
|
55
75
|
raise "No signed CRL body" if self.crl_body.nil?
|
56
76
|
self.crl_body.to_pem
|
57
77
|
end
|
58
78
|
end#CertificateRevocationList
|
59
|
-
end
|
79
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
#
|
2
|
+
# ActiveSupport has these modifications. Now that we don't use ActiveSupport,
|
3
|
+
# these are added here as a kindness.
|
4
|
+
#
|
5
|
+
|
6
|
+
require 'date'
|
7
|
+
|
8
|
+
unless nil.respond_to?(:blank?)
|
9
|
+
class NilClass
|
10
|
+
def blank?
|
11
|
+
true
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
unless String.respond_to?(:blank?)
|
17
|
+
class String
|
18
|
+
def blank?
|
19
|
+
self.empty?
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
class Date
|
25
|
+
|
26
|
+
def today
|
27
|
+
t = Time.now.utc
|
28
|
+
Date.new(t.year, t.month, t.day)
|
29
|
+
end
|
30
|
+
|
31
|
+
def utc
|
32
|
+
self.to_datetime.to_time.utc
|
33
|
+
end
|
34
|
+
|
35
|
+
unless Date.respond_to?(:advance)
|
36
|
+
def advance(options)
|
37
|
+
options = options.dup
|
38
|
+
d = self
|
39
|
+
d = d >> options.delete(:years) * 12 if options[:years]
|
40
|
+
d = d >> options.delete(:months) if options[:months]
|
41
|
+
d = d + options.delete(:weeks) * 7 if options[:weeks]
|
42
|
+
d = d + options.delete(:days) if options[:days]
|
43
|
+
d
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
@@ -1,39 +1,106 @@
|
|
1
1
|
module CertificateAuthority
|
2
2
|
class DistinguishedName
|
3
|
-
include
|
4
|
-
|
5
|
-
|
6
|
-
|
3
|
+
include Validations
|
4
|
+
|
5
|
+
def validate
|
6
|
+
if self.common_name.nil? || self.common_name.empty?
|
7
|
+
errors.add :common_name, 'cannot be blank'
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
7
11
|
attr_accessor :common_name
|
8
12
|
alias :cn :common_name
|
9
|
-
|
13
|
+
alias :cn= :common_name=
|
14
|
+
|
10
15
|
attr_accessor :locality
|
11
16
|
alias :l :locality
|
12
|
-
|
17
|
+
alias :l= :locality=
|
18
|
+
|
13
19
|
attr_accessor :state
|
14
20
|
alias :s :state
|
15
|
-
|
21
|
+
alias :st= :state=
|
22
|
+
|
16
23
|
attr_accessor :country
|
17
24
|
alias :c :country
|
18
|
-
|
25
|
+
alias :c= :country=
|
26
|
+
|
19
27
|
attr_accessor :organization
|
20
28
|
alias :o :organization
|
21
|
-
|
29
|
+
alias :o= :organization=
|
30
|
+
|
22
31
|
attr_accessor :organizational_unit
|
23
32
|
alias :ou :organizational_unit
|
24
|
-
|
33
|
+
alias :ou= :organizational_unit=
|
34
|
+
|
35
|
+
attr_accessor :email_address
|
36
|
+
alias :emailAddress :email_address
|
37
|
+
alias :emailAddress= :email_address=
|
38
|
+
|
39
|
+
attr_accessor :serial_number
|
40
|
+
alias :serialNumber :serial_number
|
41
|
+
alias :serialNumber= :serial_number=
|
42
|
+
|
25
43
|
def to_x509_name
|
26
44
|
raise "Invalid Distinguished Name" unless valid?
|
27
|
-
|
45
|
+
|
28
46
|
# NB: the capitalization in the strings counts
|
29
47
|
name = OpenSSL::X509::Name.new
|
30
|
-
name.add_entry("
|
48
|
+
name.add_entry("serialNumber", serial_number) unless serial_number.blank?
|
49
|
+
name.add_entry("C", country) unless country.blank?
|
50
|
+
name.add_entry("ST", state) unless state.blank?
|
51
|
+
name.add_entry("L", locality) unless locality.blank?
|
31
52
|
name.add_entry("O", organization) unless organization.blank?
|
32
|
-
name.add_entry("OU",
|
33
|
-
name.add_entry("
|
34
|
-
name.add_entry("
|
35
|
-
|
53
|
+
name.add_entry("OU", organizational_unit) unless organizational_unit.blank?
|
54
|
+
name.add_entry("CN", common_name)
|
55
|
+
name.add_entry("emailAddress", email_address) unless email_address.blank?
|
36
56
|
name
|
37
57
|
end
|
58
|
+
|
59
|
+
def ==(other)
|
60
|
+
# Use the established OpenSSL comparison
|
61
|
+
self.to_x509_name() == other.to_x509_name()
|
62
|
+
end
|
63
|
+
|
64
|
+
def self.from_openssl openssl_name
|
65
|
+
unless openssl_name.is_a? OpenSSL::X509::Name
|
66
|
+
raise "Argument must be a OpenSSL::X509::Name"
|
67
|
+
end
|
68
|
+
|
69
|
+
WrappedDistinguishedName.new(openssl_name)
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
## This is a significantly more complicated case. It's possible that
|
74
|
+
## generically handled certificates will include custom OIDs in the
|
75
|
+
## subject.
|
76
|
+
class WrappedDistinguishedName < DistinguishedName
|
77
|
+
attr_accessor :x509_name
|
78
|
+
|
79
|
+
def initialize(x509_name)
|
80
|
+
@x509_name = x509_name
|
81
|
+
|
82
|
+
subject = @x509_name.to_a
|
83
|
+
subject.each do |element|
|
84
|
+
field = element[0].downcase
|
85
|
+
value = element[1]
|
86
|
+
#type = element[2] ## -not used
|
87
|
+
method_sym = "#{field}=".to_sym
|
88
|
+
if self.respond_to?(method_sym)
|
89
|
+
self.send("#{field}=",value)
|
90
|
+
else
|
91
|
+
## Custom OID
|
92
|
+
@custom_oids = true
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
end
|
97
|
+
|
98
|
+
def to_x509_name
|
99
|
+
@x509_name
|
100
|
+
end
|
101
|
+
|
102
|
+
def custom_oids?
|
103
|
+
@custom_oids
|
104
|
+
end
|
38
105
|
end
|
39
|
-
end
|
106
|
+
end
|
@@ -4,61 +4,116 @@ module CertificateAuthority
|
|
4
4
|
def to_s
|
5
5
|
raise "Implementation required"
|
6
6
|
end
|
7
|
-
|
7
|
+
|
8
|
+
def self.parse(value, critical)
|
9
|
+
raise "Implementation required"
|
10
|
+
end
|
11
|
+
|
8
12
|
def config_extensions
|
9
13
|
{}
|
10
14
|
end
|
11
|
-
|
15
|
+
|
12
16
|
def openssl_identifier
|
13
17
|
raise "Implementation required"
|
14
18
|
end
|
19
|
+
|
20
|
+
def ==(value)
|
21
|
+
raise "Implementation required"
|
22
|
+
end
|
15
23
|
end
|
16
|
-
|
17
|
-
|
24
|
+
|
25
|
+
# Specifies whether an X.509v3 certificate can act as a CA, signing other
|
26
|
+
# certificates to be verified. If set, a path length constraint can also be
|
27
|
+
# specified.
|
28
|
+
# Reference: Section 4.2.1.10 of RFC3280
|
29
|
+
# http://tools.ietf.org/html/rfc3280#section-4.2.1.10
|
30
|
+
class BasicConstraints
|
31
|
+
OPENSSL_IDENTIFIER = "basicConstraints"
|
32
|
+
|
18
33
|
include ExtensionAPI
|
19
|
-
include
|
34
|
+
include Validations
|
35
|
+
|
36
|
+
attr_accessor :critical
|
20
37
|
attr_accessor :ca
|
21
38
|
attr_accessor :path_len
|
22
|
-
|
23
|
-
|
39
|
+
|
40
|
+
def validate
|
41
|
+
unless [true, false].include? self.critical
|
42
|
+
errors.add :critical, 'must be true or false'
|
43
|
+
end
|
44
|
+
unless [true, false].include? self.ca
|
45
|
+
errors.add :ca, 'must be true or false'
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
24
49
|
def initialize
|
25
|
-
|
50
|
+
@critical = false
|
51
|
+
@ca = false
|
26
52
|
end
|
27
|
-
|
53
|
+
|
54
|
+
def openssl_identifier
|
55
|
+
OPENSSL_IDENTIFIER
|
56
|
+
end
|
57
|
+
|
28
58
|
def is_ca?
|
29
|
-
|
59
|
+
@ca
|
30
60
|
end
|
31
|
-
|
61
|
+
|
32
62
|
def path_len=(value)
|
33
|
-
|
63
|
+
fail(ArgumentError, "path_len must be a non-negative integer") if !value.is_a?(Integer) || value < 0
|
34
64
|
@path_len = value
|
35
65
|
end
|
36
|
-
|
37
|
-
def openssl_identifier
|
38
|
-
"basicConstraints"
|
39
|
-
end
|
40
|
-
|
66
|
+
|
41
67
|
def to_s
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
68
|
+
res = []
|
69
|
+
res << "CA:#{@ca}"
|
70
|
+
res << "pathlen:#{@path_len}" unless @path_len.nil?
|
71
|
+
res.join(',')
|
72
|
+
end
|
73
|
+
|
74
|
+
def ==(o)
|
75
|
+
o.class == self.class && o.state == state
|
76
|
+
end
|
77
|
+
|
78
|
+
def self.parse(value, critical)
|
79
|
+
obj = self.new
|
80
|
+
return obj if value.nil?
|
81
|
+
obj.critical = critical
|
82
|
+
value.split(/,\s*/).each do |v|
|
83
|
+
c = v.split(':', 2)
|
84
|
+
obj.ca = (c.last.upcase == "TRUE") if c.first == "CA"
|
85
|
+
obj.path_len = c.last.to_i if c.first == "pathlen"
|
86
|
+
end
|
87
|
+
obj
|
88
|
+
end
|
89
|
+
|
90
|
+
protected
|
91
|
+
def state
|
92
|
+
[@critical,@ca,@path_len]
|
46
93
|
end
|
47
94
|
end
|
48
|
-
|
95
|
+
|
96
|
+
# Specifies where CRL information be be retrieved. This extension isn't
|
97
|
+
# critical, but is recommended for proper CAs.
|
98
|
+
# Reference: Section 4.2.1.14 of RFC3280
|
99
|
+
# http://tools.ietf.org/html/rfc3280#section-4.2.1.14
|
49
100
|
class CrlDistributionPoints
|
101
|
+
OPENSSL_IDENTIFIER = "crlDistributionPoints"
|
102
|
+
|
50
103
|
include ExtensionAPI
|
51
|
-
|
52
|
-
attr_accessor :
|
53
|
-
|
104
|
+
|
105
|
+
attr_accessor :critical
|
106
|
+
attr_accessor :uris
|
107
|
+
|
54
108
|
def initialize
|
55
|
-
|
109
|
+
@critical = false
|
110
|
+
@uris = []
|
56
111
|
end
|
57
|
-
|
112
|
+
|
58
113
|
def openssl_identifier
|
59
|
-
|
114
|
+
OPENSSL_IDENTIFIER
|
60
115
|
end
|
61
|
-
|
116
|
+
|
62
117
|
## NB: At this time it seems OpenSSL's extension handlers don't support
|
63
118
|
## any of the config options the docs claim to support... everything comes back
|
64
119
|
## "missing value" on GENERAL NAME. Even if copied verbatim
|
@@ -68,140 +123,386 @@ module CertificateAuthority
|
|
68
123
|
# "issuer_sect" => {"CN" => "crlissuer.com", "C" => "US", "O" => "shudder"}
|
69
124
|
}
|
70
125
|
end
|
71
|
-
|
126
|
+
|
127
|
+
# This is for legacy support. Technically it can (and probably should)
|
128
|
+
# be an array. But if someone is calling the old accessor we shouldn't
|
129
|
+
# necessarily break it.
|
130
|
+
def uri=(value)
|
131
|
+
@uris << value
|
132
|
+
end
|
133
|
+
|
72
134
|
def to_s
|
73
|
-
|
135
|
+
res = []
|
136
|
+
@uris.each do |uri|
|
137
|
+
res << "URI:#{uri}"
|
138
|
+
end
|
139
|
+
res.join(',')
|
140
|
+
end
|
141
|
+
|
142
|
+
def ==(o)
|
143
|
+
o.class == self.class && o.state == state
|
144
|
+
end
|
145
|
+
|
146
|
+
def self.parse(value, critical)
|
147
|
+
obj = self.new
|
148
|
+
return obj if value.nil?
|
149
|
+
obj.critical = critical
|
150
|
+
value.split(/,\s*/).each do |v|
|
151
|
+
c = v.split(':', 2)
|
152
|
+
obj.uris << c.last if c.first == "URI"
|
153
|
+
end
|
154
|
+
obj
|
155
|
+
end
|
156
|
+
|
157
|
+
protected
|
158
|
+
def state
|
159
|
+
[@critical,@uri]
|
74
160
|
end
|
75
161
|
end
|
76
|
-
|
162
|
+
|
163
|
+
# Identifies the public key associated with a given certificate.
|
164
|
+
# Should be required for "CA" certificates.
|
165
|
+
# Reference: Section 4.2.1.2 of RFC3280
|
166
|
+
# http://tools.ietf.org/html/rfc3280#section-4.2.1.2
|
77
167
|
class SubjectKeyIdentifier
|
168
|
+
OPENSSL_IDENTIFIER = "subjectKeyIdentifier"
|
169
|
+
|
78
170
|
include ExtensionAPI
|
171
|
+
|
172
|
+
attr_accessor :critical
|
173
|
+
attr_accessor :identifier
|
174
|
+
|
175
|
+
def initialize
|
176
|
+
@critical = false
|
177
|
+
@identifier = "hash"
|
178
|
+
end
|
179
|
+
|
79
180
|
def openssl_identifier
|
80
|
-
|
181
|
+
OPENSSL_IDENTIFIER
|
81
182
|
end
|
82
|
-
|
183
|
+
|
83
184
|
def to_s
|
84
|
-
|
185
|
+
res = []
|
186
|
+
res << @identifier
|
187
|
+
res.join(',')
|
188
|
+
end
|
189
|
+
|
190
|
+
def ==(o)
|
191
|
+
o.class == self.class && o.state == state
|
192
|
+
end
|
193
|
+
|
194
|
+
def self.parse(value, critical)
|
195
|
+
obj = self.new
|
196
|
+
return obj if value.nil?
|
197
|
+
obj.critical = critical
|
198
|
+
obj.identifier = value
|
199
|
+
obj
|
200
|
+
end
|
201
|
+
|
202
|
+
protected
|
203
|
+
def state
|
204
|
+
[@critical,@identifier]
|
85
205
|
end
|
86
206
|
end
|
87
|
-
|
207
|
+
|
208
|
+
# Identifies the public key associated with a given private key.
|
209
|
+
# Reference: Section 4.2.1.1 of RFC3280
|
210
|
+
# http://tools.ietf.org/html/rfc3280#section-4.2.1.1
|
88
211
|
class AuthorityKeyIdentifier
|
212
|
+
OPENSSL_IDENTIFIER = "authorityKeyIdentifier"
|
213
|
+
|
89
214
|
include ExtensionAPI
|
90
|
-
|
215
|
+
|
216
|
+
attr_accessor :critical
|
217
|
+
attr_accessor :identifier
|
218
|
+
|
219
|
+
def initialize
|
220
|
+
@critical = false
|
221
|
+
@identifier = ["keyid", "issuer"]
|
222
|
+
end
|
223
|
+
|
91
224
|
def openssl_identifier
|
92
|
-
|
225
|
+
OPENSSL_IDENTIFIER
|
93
226
|
end
|
94
|
-
|
227
|
+
|
95
228
|
def to_s
|
96
|
-
|
229
|
+
res = []
|
230
|
+
res += @identifier
|
231
|
+
res.join(',')
|
232
|
+
end
|
233
|
+
|
234
|
+
def ==(o)
|
235
|
+
o.class == self.class && o.state == state
|
236
|
+
end
|
237
|
+
|
238
|
+
def self.parse(value, critical)
|
239
|
+
obj = self.new
|
240
|
+
return obj if value.nil?
|
241
|
+
obj.critical = critical
|
242
|
+
obj.identifier = value.split(/,\s*/).last.chomp
|
243
|
+
obj
|
244
|
+
end
|
245
|
+
|
246
|
+
protected
|
247
|
+
def state
|
248
|
+
[@critical,@identifier]
|
97
249
|
end
|
98
250
|
end
|
99
|
-
|
251
|
+
|
252
|
+
# Specifies how to access CA information and services for the CA that
|
253
|
+
# issued this certificate.
|
254
|
+
# Generally used to specify OCSP servers.
|
255
|
+
# Reference: Section 4.2.2.1 of RFC3280
|
256
|
+
# http://tools.ietf.org/html/rfc3280#section-4.2.2.1
|
100
257
|
class AuthorityInfoAccess
|
258
|
+
OPENSSL_IDENTIFIER = "authorityInfoAccess"
|
259
|
+
|
101
260
|
include ExtensionAPI
|
102
|
-
|
261
|
+
|
262
|
+
attr_accessor :critical
|
103
263
|
attr_accessor :ocsp
|
104
|
-
|
264
|
+
attr_accessor :ca_issuers
|
265
|
+
|
105
266
|
def initialize
|
106
|
-
|
267
|
+
@critical = false
|
268
|
+
@ocsp = []
|
269
|
+
@ca_issuers = []
|
107
270
|
end
|
108
|
-
|
271
|
+
|
109
272
|
def openssl_identifier
|
110
|
-
|
273
|
+
OPENSSL_IDENTIFIER
|
111
274
|
end
|
112
|
-
|
275
|
+
|
113
276
|
def to_s
|
114
|
-
|
277
|
+
res = []
|
278
|
+
res += @ocsp.map {|o| "OCSP;URI:#{o}" }
|
279
|
+
res += @ca_issuers.map {|c| "caIssuers;URI:#{c}" }
|
280
|
+
res.join(',')
|
281
|
+
end
|
282
|
+
|
283
|
+
def ==(o)
|
284
|
+
o.class == self.class && o.state == state
|
285
|
+
end
|
286
|
+
|
287
|
+
def self.parse(value, critical)
|
288
|
+
obj = self.new
|
289
|
+
return obj if value.nil?
|
290
|
+
obj.critical = critical
|
291
|
+
value.split("\n").each do |v|
|
292
|
+
if v =~ /^OCSP/
|
293
|
+
obj.ocsp << v.split.last
|
294
|
+
end
|
295
|
+
|
296
|
+
if v =~ /^CA Issuers/
|
297
|
+
obj.ca_issuers << v.split.last
|
298
|
+
end
|
299
|
+
end
|
300
|
+
obj
|
301
|
+
end
|
302
|
+
|
303
|
+
protected
|
304
|
+
def state
|
305
|
+
[@critical,@ocsp,@ca_issuers]
|
115
306
|
end
|
116
307
|
end
|
117
|
-
|
308
|
+
|
309
|
+
# Specifies the allowed usage purposes of the keypair specified in this certificate.
|
310
|
+
# Reference: Section 4.2.1.3 of RFC3280
|
311
|
+
# http://tools.ietf.org/html/rfc3280#section-4.2.1.3
|
312
|
+
#
|
313
|
+
# Note: OpenSSL when parsing an extension will return results in the form
|
314
|
+
# 'Digital Signature', but on signing you have to set it to 'digitalSignature'.
|
315
|
+
# So copying an extension from an imported cert isn't going to work yet.
|
118
316
|
class KeyUsage
|
317
|
+
OPENSSL_IDENTIFIER = "keyUsage"
|
318
|
+
|
119
319
|
include ExtensionAPI
|
120
|
-
|
320
|
+
|
321
|
+
attr_accessor :critical
|
121
322
|
attr_accessor :usage
|
122
|
-
|
323
|
+
|
123
324
|
def initialize
|
124
|
-
|
325
|
+
@critical = false
|
326
|
+
@usage = ["digitalSignature", "nonRepudiation"]
|
125
327
|
end
|
126
|
-
|
328
|
+
|
127
329
|
def openssl_identifier
|
128
|
-
|
330
|
+
OPENSSL_IDENTIFIER
|
129
331
|
end
|
130
|
-
|
332
|
+
|
131
333
|
def to_s
|
132
|
-
|
334
|
+
res = []
|
335
|
+
res += @usage
|
336
|
+
res.join(',')
|
337
|
+
end
|
338
|
+
|
339
|
+
def ==(o)
|
340
|
+
o.class == self.class && o.state == state
|
341
|
+
end
|
342
|
+
|
343
|
+
def self.parse(value, critical)
|
344
|
+
obj = self.new
|
345
|
+
return obj if value.nil?
|
346
|
+
obj.critical = critical
|
347
|
+
obj.usage = value.split(/,\s*/)
|
348
|
+
obj
|
349
|
+
end
|
350
|
+
|
351
|
+
protected
|
352
|
+
def state
|
353
|
+
[@critical,@usage]
|
133
354
|
end
|
134
355
|
end
|
135
|
-
|
356
|
+
|
357
|
+
# Specifies even more allowed usages in addition to what is specified in
|
358
|
+
# the Key Usage extension.
|
359
|
+
# Reference: Section 4.2.1.13 of RFC3280
|
360
|
+
# http://tools.ietf.org/html/rfc3280#section-4.2.1.13
|
136
361
|
class ExtendedKeyUsage
|
362
|
+
OPENSSL_IDENTIFIER = "extendedKeyUsage"
|
363
|
+
|
137
364
|
include ExtensionAPI
|
138
|
-
|
365
|
+
|
366
|
+
attr_accessor :critical
|
139
367
|
attr_accessor :usage
|
140
|
-
|
368
|
+
|
141
369
|
def initialize
|
142
|
-
|
370
|
+
@critical = false
|
371
|
+
@usage = ["serverAuth"]
|
143
372
|
end
|
144
|
-
|
373
|
+
|
145
374
|
def openssl_identifier
|
146
|
-
|
375
|
+
OPENSSL_IDENTIFIER
|
147
376
|
end
|
148
|
-
|
377
|
+
|
149
378
|
def to_s
|
150
|
-
|
379
|
+
res = []
|
380
|
+
res += @usage
|
381
|
+
res.join(',')
|
382
|
+
end
|
383
|
+
|
384
|
+
def ==(o)
|
385
|
+
o.class == self.class && o.state == state
|
386
|
+
end
|
387
|
+
|
388
|
+
def self.parse(value, critical)
|
389
|
+
obj = self.new
|
390
|
+
return obj if value.nil?
|
391
|
+
obj.critical = critical
|
392
|
+
obj.usage = value.split(/,\s*/)
|
393
|
+
obj
|
394
|
+
end
|
395
|
+
|
396
|
+
protected
|
397
|
+
def state
|
398
|
+
[@critical,@usage]
|
151
399
|
end
|
152
400
|
end
|
153
|
-
|
401
|
+
|
402
|
+
# Specifies additional "names" for which this certificate is valid.
|
403
|
+
# Reference: Section 4.2.1.7 of RFC3280
|
404
|
+
# http://tools.ietf.org/html/rfc3280#section-4.2.1.7
|
154
405
|
class SubjectAlternativeName
|
406
|
+
OPENSSL_IDENTIFIER = "subjectAltName"
|
407
|
+
|
155
408
|
include ExtensionAPI
|
156
|
-
|
157
|
-
attr_accessor :
|
158
|
-
|
409
|
+
|
410
|
+
attr_accessor :critical
|
411
|
+
attr_accessor :uris, :dns_names, :ips, :emails
|
412
|
+
|
159
413
|
def initialize
|
160
|
-
|
414
|
+
@critical = false
|
415
|
+
@uris = []
|
416
|
+
@dns_names = []
|
417
|
+
@ips = []
|
418
|
+
@emails = []
|
419
|
+
end
|
420
|
+
|
421
|
+
def openssl_identifier
|
422
|
+
OPENSSL_IDENTIFIER
|
161
423
|
end
|
162
|
-
|
424
|
+
|
163
425
|
def uris=(value)
|
164
426
|
raise "URIs must be an array" unless value.is_a?(Array)
|
165
427
|
@uris = value
|
166
428
|
end
|
167
|
-
|
168
|
-
def
|
169
|
-
"
|
429
|
+
|
430
|
+
def dns_names=(value)
|
431
|
+
raise "DNS names must be an array" unless value.is_a?(Array)
|
432
|
+
@dns_names = value
|
433
|
+
end
|
434
|
+
|
435
|
+
def ips=(value)
|
436
|
+
raise "IPs must be an array" unless value.is_a?(Array)
|
437
|
+
@ips = value
|
438
|
+
end
|
439
|
+
|
440
|
+
def emails=(value)
|
441
|
+
raise "Emails must be an array" unless value.is_a?(Array)
|
442
|
+
@emails = value
|
170
443
|
end
|
171
|
-
|
444
|
+
|
172
445
|
def to_s
|
173
|
-
|
174
|
-
|
446
|
+
res = []
|
447
|
+
res += @uris.map {|u| "URI:#{u}" }
|
448
|
+
res += @dns_names.map {|d| "DNS:#{d}" }
|
449
|
+
res += @ips.map {|i| "IP:#{i}" }
|
450
|
+
res += @emails.map {|i| "email:#{i}" }
|
451
|
+
res.join(',')
|
452
|
+
end
|
453
|
+
|
454
|
+
def ==(o)
|
455
|
+
o.class == self.class && o.state == state
|
456
|
+
end
|
457
|
+
|
458
|
+
def self.parse(value, critical)
|
459
|
+
obj = self.new
|
460
|
+
return obj if value.nil?
|
461
|
+
obj.critical = critical
|
462
|
+
value.split(/,\s*/).each do |v|
|
463
|
+
c = v.split(':', 2)
|
464
|
+
obj.uris << c.last if c.first == "URI"
|
465
|
+
obj.dns_names << c.last if c.first == "DNS"
|
466
|
+
obj.ips << c.last if c.first == "IP"
|
467
|
+
obj.emails << c.last if c.first == "EMAIL"
|
175
468
|
end
|
176
|
-
|
469
|
+
obj
|
470
|
+
end
|
471
|
+
|
472
|
+
protected
|
473
|
+
def state
|
474
|
+
[@critical,@uris,@dns_names,@ips,@emails]
|
177
475
|
end
|
178
476
|
end
|
179
|
-
|
477
|
+
|
180
478
|
class CertificatePolicies
|
479
|
+
OPENSSL_IDENTIFIER = "certificatePolicies"
|
480
|
+
|
181
481
|
include ExtensionAPI
|
182
|
-
|
482
|
+
|
483
|
+
attr_accessor :critical
|
183
484
|
attr_accessor :policy_identifier
|
184
485
|
attr_accessor :cps_uris
|
185
486
|
##User notice
|
186
487
|
attr_accessor :explicit_text
|
187
488
|
attr_accessor :organization
|
188
489
|
attr_accessor :notice_numbers
|
189
|
-
|
490
|
+
|
190
491
|
def initialize
|
492
|
+
self.critical = false
|
191
493
|
@contains_data = false
|
192
494
|
end
|
193
|
-
|
194
|
-
|
495
|
+
|
195
496
|
def openssl_identifier
|
196
|
-
|
497
|
+
OPENSSL_IDENTIFIER
|
197
498
|
end
|
198
|
-
|
499
|
+
|
199
500
|
def user_notice=(value={})
|
200
501
|
value.keys.each do |key|
|
201
502
|
self.send("#{key}=".to_sym, value[key])
|
202
503
|
end
|
203
504
|
end
|
204
|
-
|
505
|
+
|
205
506
|
def config_extensions
|
206
507
|
config_extension = {}
|
207
508
|
custom_policies = {}
|
@@ -209,43 +510,129 @@ module CertificateAuthority
|
|
209
510
|
unless self.policy_identifier.nil?
|
210
511
|
custom_policies["policyIdentifier"] = self.policy_identifier
|
211
512
|
end
|
212
|
-
|
513
|
+
|
213
514
|
if !self.cps_uris.nil? and self.cps_uris.is_a?(Array)
|
214
515
|
self.cps_uris.each_with_index do |cps_uri,i|
|
215
516
|
custom_policies["CPS.#{i}"] = cps_uri
|
216
517
|
end
|
217
518
|
end
|
218
|
-
|
519
|
+
|
219
520
|
unless self.explicit_text.nil?
|
220
521
|
notice["explicitText"] = self.explicit_text
|
221
522
|
end
|
222
|
-
|
523
|
+
|
223
524
|
unless self.organization.nil?
|
224
525
|
notice["organization"] = self.organization
|
225
526
|
end
|
226
|
-
|
527
|
+
|
227
528
|
unless self.notice_numbers.nil?
|
228
529
|
notice["noticeNumbers"] = self.notice_numbers
|
229
530
|
end
|
230
|
-
|
531
|
+
|
231
532
|
if notice.keys.size > 0
|
232
533
|
custom_policies["userNotice.1"] = "@notice"
|
233
534
|
config_extension["notice"] = notice
|
234
535
|
end
|
235
|
-
|
536
|
+
|
236
537
|
if custom_policies.keys.size > 0
|
237
538
|
config_extension["custom_policies"] = custom_policies
|
238
539
|
@contains_data = true
|
239
540
|
end
|
240
|
-
|
541
|
+
|
241
542
|
config_extension
|
242
543
|
end
|
243
|
-
|
544
|
+
|
244
545
|
def to_s
|
245
546
|
return "" unless @contains_data
|
246
|
-
|
547
|
+
res = []
|
548
|
+
res << "ia5org"
|
549
|
+
res += @config_extensions["custom_policies"] unless @config_extensions.nil?
|
550
|
+
res.join(',')
|
551
|
+
end
|
552
|
+
|
553
|
+
def self.parse(value, critical)
|
554
|
+
obj = self.new
|
555
|
+
return obj if value.nil?
|
556
|
+
obj.critical = critical
|
557
|
+
value.split(/,\s*/).each do |v|
|
558
|
+
c = v.split(':', 2)
|
559
|
+
obj.policy_identifier = c.last if c.first == "policyIdentifier"
|
560
|
+
obj.cps_uris << c.last if c.first =~ %r{CPS.\d+}
|
561
|
+
# TODO: explicit_text, organization, notice_numbers
|
562
|
+
end
|
563
|
+
obj
|
564
|
+
end
|
565
|
+
end
|
566
|
+
|
567
|
+
# DEPRECATED
|
568
|
+
# Specifics the purposes for which a certificate can be used.
|
569
|
+
# The basicConstraints, keyUsage, and extendedKeyUsage extensions are now used instead.
|
570
|
+
# https://www.openssl.org/docs/apps/x509v3_config.html#Netscape_Certificate_Type
|
571
|
+
class NetscapeCertificateType
|
572
|
+
OPENSSL_IDENTIFIER = "nsCertType"
|
573
|
+
|
574
|
+
include ExtensionAPI
|
575
|
+
|
576
|
+
attr_accessor :critical
|
577
|
+
attr_accessor :flags
|
578
|
+
|
579
|
+
def initialize
|
580
|
+
self.critical = false
|
581
|
+
self.flags = []
|
582
|
+
end
|
583
|
+
|
584
|
+
def openssl_identifier
|
585
|
+
OPENSSL_IDENTIFIER
|
586
|
+
end
|
587
|
+
|
588
|
+
def to_s
|
589
|
+
res = []
|
590
|
+
res += self.flags
|
591
|
+
res.join(',')
|
592
|
+
end
|
593
|
+
|
594
|
+
def self.parse(value, critical)
|
595
|
+
obj = self.new
|
596
|
+
return obj if value.nil?
|
597
|
+
obj.critical = critical
|
598
|
+
obj.flags = value.split(/,\s*/)
|
599
|
+
obj
|
600
|
+
end
|
601
|
+
end
|
602
|
+
|
603
|
+
# DEPRECATED
|
604
|
+
# Contains a comment which will be displayed when the certificate is viewed in some browsers.
|
605
|
+
# https://www.openssl.org/docs/apps/x509v3_config.html#Netscape_String_extensions_
|
606
|
+
class NetscapeComment
|
607
|
+
OPENSSL_IDENTIFIER = "nsComment"
|
608
|
+
|
609
|
+
include ExtensionAPI
|
610
|
+
|
611
|
+
attr_accessor :critical
|
612
|
+
attr_accessor :comment
|
613
|
+
|
614
|
+
def initialize
|
615
|
+
self.critical = false
|
616
|
+
end
|
617
|
+
|
618
|
+
def openssl_identifier
|
619
|
+
OPENSSL_IDENTIFIER
|
620
|
+
end
|
621
|
+
|
622
|
+
def to_s
|
623
|
+
res = []
|
624
|
+
res << self.comment if self.comment
|
625
|
+
res.join(',')
|
626
|
+
end
|
627
|
+
|
628
|
+
def self.parse(value, critical)
|
629
|
+
obj = self.new
|
630
|
+
return obj if value.nil?
|
631
|
+
obj.critical = critical
|
632
|
+
obj.comment = value
|
633
|
+
obj
|
247
634
|
end
|
248
635
|
end
|
249
|
-
|
636
|
+
|
250
637
|
end
|
251
|
-
end
|
638
|
+
end
|