certificate_authority 0.1.2 → 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|