r509 0.8.1 → 0.9

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.
Files changed (203) hide show
  1. data/README.md +343 -151
  2. data/Rakefile +26 -23
  3. data/bin/r509 +126 -112
  4. data/bin/r509-parse +24 -24
  5. data/doc/R509.html +169 -7
  6. data/doc/R509/ASN1.html +370 -0
  7. data/doc/R509/ASN1/GeneralName.html +1121 -0
  8. data/doc/R509/ASN1/GeneralNames.html +843 -0
  9. data/doc/R509/ASN1/NoticeReference.html +392 -0
  10. data/doc/R509/ASN1/PolicyInformation.html +387 -0
  11. data/doc/R509/ASN1/PolicyQualifiers.html +455 -0
  12. data/doc/R509/ASN1/UserNotice.html +386 -0
  13. data/doc/R509/{Crl.html → CRL.html} +7 -7
  14. data/doc/R509/CRL/Administrator.html +1559 -0
  15. data/doc/R509/{Crl/Parser.html → CRL/SignedList.html} +501 -210
  16. data/doc/R509/{Csr.html → CSR.html} +444 -314
  17. data/doc/R509/Cert.html +866 -617
  18. data/doc/R509/Cert/Extensions.html +52 -41
  19. data/doc/R509/Cert/Extensions/AuthorityInfoAccess.html +70 -35
  20. data/doc/R509/Cert/Extensions/AuthorityKeyIdentifier.html +387 -4
  21. data/doc/R509/Cert/Extensions/BasicConstraints.html +61 -25
  22. data/doc/R509/Cert/Extensions/CRLDistributionPoints.html +354 -0
  23. data/doc/R509/Cert/Extensions/CertificatePolicies.html +340 -0
  24. data/doc/R509/Cert/Extensions/ExtendedKeyUsage.html +440 -49
  25. data/doc/R509/Cert/Extensions/{CrlDistributionPoints.html → InhibitAnyPolicy.html} +52 -35
  26. data/doc/R509/Cert/Extensions/KeyUsage.html +247 -121
  27. data/doc/R509/Cert/Extensions/NameConstraints.html +445 -0
  28. data/doc/R509/Cert/Extensions/OCSPNoCheck.html +239 -0
  29. data/doc/R509/Cert/Extensions/PolicyConstraints.html +424 -0
  30. data/doc/R509/Cert/Extensions/SubjectAlternativeName.html +437 -62
  31. data/doc/R509/Cert/Extensions/SubjectKeyIdentifier.html +52 -10
  32. data/doc/R509/CertificateAuthority.html +4 -4
  33. data/doc/R509/CertificateAuthority/Signer.html +154 -187
  34. data/doc/R509/Config.html +6 -6
  35. data/doc/R509/Config/{CaConfig.html → CAConfig.html} +451 -348
  36. data/doc/R509/Config/{CaConfigPool.html → CAConfigPool.html} +47 -47
  37. data/doc/R509/Config/CAProfile.html +1015 -0
  38. data/doc/R509/Config/SubjectItemPolicy.html +86 -86
  39. data/doc/R509/IOHelpers.html +22 -22
  40. data/doc/R509/MessageDigest.html +14 -14
  41. data/doc/R509/NameSanitizer.html +53 -53
  42. data/doc/R509/{Ocsp.html → OCSP.html} +9 -9
  43. data/doc/R509/{Ocsp → OCSP}/Request.html +7 -7
  44. data/doc/R509/{Ocsp → OCSP}/Request/Nonce.html +56 -11
  45. data/doc/R509/{Ocsp → OCSP}/Response.html +44 -44
  46. data/doc/R509/{OidMapper.html → OIDMapper.html} +23 -39
  47. data/doc/R509/PrivateKey.html +415 -168
  48. data/doc/R509/R509Error.html +3 -3
  49. data/doc/R509/{Spki.html → SPKI.html} +354 -192
  50. data/doc/R509/Subject.html +224 -113
  51. data/doc/R509/Validity.html +27 -5
  52. data/doc/R509/Validity/Checker.html +13 -13
  53. data/doc/R509/Validity/DefaultChecker.html +13 -13
  54. data/doc/R509/Validity/DefaultWriter.html +14 -14
  55. data/doc/R509/Validity/Status.html +39 -39
  56. data/doc/R509/Validity/Writer.html +18 -18
  57. data/doc/_index.html +138 -35
  58. data/doc/class_list.html +1 -1
  59. data/doc/css/style.css +10 -0
  60. data/doc/file.README.html +368 -171
  61. data/doc/file.r509.html +92 -69
  62. data/doc/frames.html +1 -1
  63. data/doc/index.html +368 -171
  64. data/doc/method_list.html +910 -390
  65. data/doc/top-level-namespace.html +3 -3
  66. data/lib/r509.rb +32 -16
  67. data/lib/r509/asn1.rb +375 -0
  68. data/lib/r509/cert.rb +381 -364
  69. data/lib/r509/cert/extensions.rb +443 -76
  70. data/lib/r509/certificate_authority.rb +407 -0
  71. data/lib/r509/config.rb +547 -351
  72. data/lib/r509/crl.rb +336 -366
  73. data/lib/r509/csr.rb +278 -289
  74. data/lib/r509/ec-hack.rb +37 -0
  75. data/lib/r509/exceptions.rb +3 -3
  76. data/lib/r509/io_helpers.rb +44 -44
  77. data/lib/r509/message_digest.rb +53 -0
  78. data/lib/r509/ocsp.rb +80 -70
  79. data/lib/r509/oid_mapper.rb +32 -0
  80. data/lib/r509/private_key.rb +228 -0
  81. data/lib/r509/spki.rb +145 -93
  82. data/lib/r509/subject.rb +203 -110
  83. data/lib/r509/validity.rb +70 -68
  84. data/lib/r509/version.rb +2 -2
  85. data/r509.yaml +92 -69
  86. data/spec/asn1_spec.rb +402 -0
  87. data/spec/cert/extensions_spec.rb +957 -494
  88. data/spec/cert_spec.rb +382 -307
  89. data/spec/certificate_authority_spec.rb +668 -250
  90. data/spec/config_spec.rb +515 -302
  91. data/spec/crl_spec.rb +197 -198
  92. data/spec/csr_spec.rb +334 -289
  93. data/spec/fixtures.rb +247 -171
  94. data/spec/fixtures/cert1.der +0 -0
  95. data/spec/fixtures/cert1.pem +0 -0
  96. data/spec/fixtures/cert1_public_key_modulus.txt +0 -0
  97. data/spec/fixtures/cert3.p12 +0 -0
  98. data/spec/fixtures/cert3.pem +0 -0
  99. data/spec/fixtures/cert3_key.pem +0 -0
  100. data/spec/fixtures/cert3_key_des3.pem +0 -0
  101. data/spec/fixtures/cert4.pem +0 -0
  102. data/spec/fixtures/cert5.pem +0 -0
  103. data/spec/fixtures/cert6.pem +0 -0
  104. data/spec/fixtures/cert_expired.pem +0 -0
  105. data/spec/fixtures/cert_inhibit.pem +24 -0
  106. data/spec/fixtures/cert_name_constraints.pem +29 -0
  107. data/spec/fixtures/cert_not_yet_valid.pem +0 -0
  108. data/spec/fixtures/cert_ocsp_no_check.pem +18 -0
  109. data/spec/fixtures/cert_policy_constraints.pem +31 -0
  110. data/spec/fixtures/cert_san.pem +0 -0
  111. data/spec/fixtures/cert_san2.pem +0 -0
  112. data/spec/fixtures/cert_unknown_extension.pem +28 -0
  113. data/spec/fixtures/config_pool_test_minimal.yaml +11 -11
  114. data/spec/fixtures/config_test.yaml +54 -36
  115. data/spec/fixtures/config_test_dsa.yaml +35 -0
  116. data/spec/fixtures/config_test_ec.yaml +35 -0
  117. data/spec/fixtures/config_test_engine_key.yaml +5 -5
  118. data/spec/fixtures/config_test_engine_no_key_name.yaml +4 -4
  119. data/spec/fixtures/config_test_minimal.yaml +4 -4
  120. data/spec/fixtures/config_test_password.yaml +5 -5
  121. data/spec/fixtures/config_test_various.yaml +111 -74
  122. data/spec/fixtures/crl_list_file.txt +0 -0
  123. data/spec/fixtures/crl_with_reason.pem +0 -0
  124. data/spec/fixtures/csr1.der +0 -0
  125. data/spec/fixtures/csr1.pem +0 -0
  126. data/spec/fixtures/csr1_key.der +0 -0
  127. data/spec/fixtures/csr1_key.pem +0 -0
  128. data/spec/fixtures/csr1_key_encrypted_des3.pem +0 -0
  129. data/spec/fixtures/csr1_newlines.pem +0 -0
  130. data/spec/fixtures/csr1_no_begin_end.pem +0 -0
  131. data/spec/fixtures/csr1_public_key_modulus.txt +0 -0
  132. data/spec/fixtures/csr2.pem +0 -0
  133. data/spec/fixtures/csr2_key.pem +0 -0
  134. data/spec/fixtures/csr3.pem +0 -0
  135. data/spec/fixtures/csr4.pem +0 -0
  136. data/spec/fixtures/csr_dsa.pem +0 -0
  137. data/spec/fixtures/csr_invalid_signature.pem +0 -0
  138. data/spec/fixtures/dsa_key.pem +0 -0
  139. data/spec/fixtures/dsa_root.cer +28 -0
  140. data/spec/fixtures/dsa_root.key +20 -0
  141. data/spec/fixtures/ec_csr2.der +0 -0
  142. data/spec/fixtures/ec_csr2.pem +8 -0
  143. data/spec/fixtures/ec_key1.der +0 -0
  144. data/spec/fixtures/ec_key1.pem +6 -0
  145. data/spec/fixtures/ec_key1_encrypted.pem +9 -0
  146. data/spec/fixtures/ec_key2.pem +6 -0
  147. data/spec/fixtures/hmacsha1.sig +1 -0
  148. data/spec/fixtures/hmacsha512.sig +1 -0
  149. data/spec/fixtures/key4.pem +0 -0
  150. data/spec/fixtures/key4_encrypted_des3.pem +0 -0
  151. data/spec/fixtures/missing_key_identifier_ca.cer +0 -0
  152. data/spec/fixtures/missing_key_identifier_ca.key +0 -0
  153. data/spec/fixtures/ocsptest.r509.local.pem +0 -0
  154. data/spec/fixtures/ocsptest.r509.local_ocsp_request.der +0 -0
  155. data/spec/fixtures/ocsptest2.r509.local.pem +0 -0
  156. data/spec/fixtures/second_ca.cer +0 -0
  157. data/spec/fixtures/second_ca.key +0 -0
  158. data/spec/fixtures/spkac.der +0 -0
  159. data/spec/fixtures/spkac.txt +0 -0
  160. data/spec/fixtures/spkac_dsa.txt +1 -1
  161. data/spec/fixtures/spkac_dsa_no_verify.txt +1 -0
  162. data/spec/fixtures/spkac_ec.txt +1 -0
  163. data/spec/fixtures/spkac_rsa_newlines.txt +13 -0
  164. data/spec/fixtures/stca.pem +0 -0
  165. data/spec/fixtures/stca_ocsp_request.der +0 -0
  166. data/spec/fixtures/stca_ocsp_response.der +0 -0
  167. data/spec/fixtures/test1.csr +0 -0
  168. data/spec/fixtures/test_ca.cer +0 -0
  169. data/spec/fixtures/test_ca.key +0 -0
  170. data/spec/fixtures/test_ca.p12 +0 -0
  171. data/spec/fixtures/test_ca_des3.key +0 -0
  172. data/spec/fixtures/test_ca_ec.cer +14 -0
  173. data/spec/fixtures/test_ca_ec.key +6 -0
  174. data/spec/fixtures/test_ca_ec_ee.cer +22 -0
  175. data/spec/fixtures/test_ca_ec_ee.key +6 -0
  176. data/spec/fixtures/test_ca_ocsp.cer +0 -0
  177. data/spec/fixtures/test_ca_ocsp.key +0 -0
  178. data/spec/fixtures/test_ca_ocsp.p12 +0 -0
  179. data/spec/fixtures/test_ca_ocsp_chain.txt +0 -0
  180. data/spec/fixtures/test_ca_ocsp_response.der +0 -0
  181. data/spec/fixtures/test_ca_subroot.cer +0 -0
  182. data/spec/fixtures/test_ca_subroot.key +0 -0
  183. data/spec/fixtures/test_ca_subroot_ocsp.cer +0 -0
  184. data/spec/fixtures/test_ca_subroot_ocsp.key +0 -0
  185. data/spec/fixtures/test_ca_subroot_ocsp_response.der +0 -0
  186. data/spec/fixtures/unknown_oid.csr +0 -0
  187. data/spec/message_digest_spec.rb +104 -84
  188. data/spec/ocsp_spec.rb +105 -105
  189. data/spec/oid_mapper_spec.rb +21 -21
  190. data/spec/private_key_spec.rb +275 -0
  191. data/spec/r509_spec.rb +35 -0
  192. data/spec/spec_helper.rb +15 -6
  193. data/spec/spki_spec.rb +221 -142
  194. data/spec/subject_spec.rb +232 -164
  195. data/spec/validity_spec.rb +91 -91
  196. metadata +79 -25
  197. data/doc/R509/Config/CaProfile.html +0 -651
  198. data/doc/R509/Crl/Administrator.html +0 -2073
  199. data/lib/r509/certificateauthority.rb +0 -290
  200. data/lib/r509/messagedigest.rb +0 -49
  201. data/lib/r509/oidmapper.rb +0 -32
  202. data/lib/r509/privatekey.rb +0 -185
  203. data/spec/privatekey_spec.rb +0 -198
@@ -0,0 +1,407 @@
1
+ require 'openssl'
2
+ require 'r509/config'
3
+ require 'r509/cert'
4
+ require 'r509/exceptions'
5
+ require 'r509/ec-hack'
6
+
7
+ # CertificateAuthority related classes
8
+ module R509::CertificateAuthority
9
+ # Contains the certification authority signing operation methods
10
+ class Signer
11
+ # @param [R509::Config] config
12
+ def initialize(config=nil)
13
+ @config = config
14
+
15
+ if not @config.nil? and not @config.kind_of?(R509::Config::CAConfig)
16
+ raise R509::R509Error, "config must be a kind of R509::Config::CAConfig or nil (for self-sign only)"
17
+ end
18
+ if not @config.nil? and not @config.ca_cert.has_private_key?
19
+ raise R509::R509Error, "You must have a private key associated with your CA certificate to issue"
20
+ end
21
+ end
22
+
23
+ # Signs a CSR
24
+ # @option options :csr [R509::CSR]
25
+ # @option options :spki [R509::SPKI]
26
+ # @option options :profile_name [String] The CA profile you want to use (eg "server" in your config)
27
+ # @option options :subject [R509::Subject,OpenSSL::X509::Subject,Array] (optional for R509::CSR, required for R509::SPKI)
28
+ # @option options :san_names [Array,R509::ASN1::GeneralNames] optional either an array of names that will be automatically parsed to determine their type, or an explicit R509::ASN1::GeneralNames object
29
+ # @option options :message_digest [String] the message digest to use for this certificate instead of the config's default
30
+ # @option options :serial [String] the serial number you want to issue the certificate with
31
+ # @option options :not_before [Time] the notBefore for the certificate
32
+ # @option options :not_after [Time] the notAfter for the certificate
33
+ # @return [R509::Cert] the signed cert object
34
+ def sign(options)
35
+ if @config.nil?
36
+ raise R509::R509Error, "When instantiating the signer without a config you can only call #selfsign"
37
+ elsif @config.num_profiles == 0
38
+ raise R509::R509Error, "You must have at least one CAProfile on your CAConfig to issue"
39
+ end
40
+
41
+ check_options(options)
42
+
43
+ subject, san_names, public_key = extract_public_key_subject_san(options)
44
+
45
+
46
+ if options.has_key?(:csr) and not options[:csr].verify_signature
47
+ raise R509::R509Error, "Certificate request signature is invalid."
48
+ end
49
+
50
+ # prior to OpenSSL 1.0 DSA could only use DSS1 (aka SHA1) signatures. post-1.0 anything
51
+ # goes but at the moment we don't enforce this restriction so an OpenSSL error could
52
+ # bubble up if they do it wrong.
53
+ message_digest = (options.has_key?(:message_digest))? R509::MessageDigest.new(options[:message_digest]) : R509::MessageDigest.new(@config.message_digest)
54
+
55
+ profile = @config.profile(options[:profile_name])
56
+
57
+ validated_subject = validate_subject(subject,profile)
58
+
59
+ cert = build_cert(
60
+ :subject => validated_subject.name,
61
+ :issuer => @config.ca_cert.subject.name,
62
+ :not_before => options[:not_before],
63
+ :not_after => options[:not_after],
64
+ :public_key => public_key,
65
+ :serial => options[:serial]
66
+ )
67
+
68
+ basic_constraints = profile.basic_constraints
69
+ key_usage = profile.key_usage
70
+ extended_key_usage = profile.extended_key_usage
71
+ certificate_policies = profile.certificate_policies
72
+ ocsp_no_check = profile.ocsp_no_check
73
+
74
+ build_extensions(
75
+ :subject_certificate => cert,
76
+ :issuer_certificate => @config.ca_cert.cert,
77
+ :basic_constraints => basic_constraints,
78
+ :key_usage => key_usage,
79
+ :extended_key_usage => extended_key_usage,
80
+ :ocsp_no_check => ocsp_no_check,
81
+ :certificate_policies => certificate_policies,
82
+ :san_names => san_names,
83
+ :inhibit_any_policy => profile.inhibit_any_policy,
84
+ :policy_constraints => profile.policy_constraints,
85
+ :name_constraints => profile.name_constraints
86
+ )
87
+
88
+
89
+ #@config.ca_cert.key.key ... ugly. ca_cert returns R509::Cert
90
+ # #key returns R509::PrivateKey and #key on that returns OpenSSL object we need
91
+ cert.sign( @config.ca_cert.key.key, message_digest.digest )
92
+ R509::Cert.new(:cert => cert)
93
+ end
94
+
95
+ # Self-signs a CSR
96
+ # @option options :csr [R509::CSR]
97
+ # @option options :message_digest [String] the message digest to use for this certificate (defaults to sha1)
98
+ # @option options :serial [String] the serial number you want to issue the certificate with (defaults to random)
99
+ # @option options :not_before [Time] the notBefore for the certificate (defaults to now)
100
+ # @option options :not_after [Time] the notAfter for the certificate (defaults to 1 year)
101
+ # @option options :san_names [Array,R509::ASN1::GeneralNames] optional either an array of names that will be automatically parsed to determine their type, or an explicit R509::ASN1::GeneralNames object
102
+ # @return [R509::Cert] the signed cert object
103
+ def selfsign(options)
104
+ if not options.kind_of?(Hash)
105
+ raise ArgumentError, "You must pass a hash of options consisting of at minimum :csr"
106
+ end
107
+ csr = options[:csr]
108
+ if csr.key.nil?
109
+ raise ArgumentError, 'CSR must also have a private key to self sign'
110
+ end
111
+ cert = build_cert(
112
+ :subject => csr.subject.name,
113
+ :issuer => csr.subject.name,
114
+ :not_before => options[:not_before],
115
+ :not_after => options[:not_after],
116
+ :public_key => csr.public_key,
117
+ :serial => options[:serial]
118
+ )
119
+
120
+ sans = (options.has_key?(:san_names))? options[:san_names] : csr.san
121
+ san_names = parse_san_names(sans)
122
+
123
+ build_extensions(
124
+ :subject_certificate => cert,
125
+ :issuer_certificate => cert,
126
+ :basic_constraints => {"ca" => true },
127
+ :san_names => san_names
128
+ )
129
+
130
+
131
+ if options.has_key?(:message_digest)
132
+ message_digest = R509::MessageDigest.new(options[:message_digest])
133
+ else
134
+ message_digest = R509::MessageDigest.new('sha1')
135
+ end
136
+
137
+ # CSR#key returns R509::PrivateKey and #key on that returns OpenSSL object we need
138
+ cert.sign( csr.key.key, message_digest.digest )
139
+ R509::Cert.new(:cert => cert)
140
+ end
141
+
142
+ private
143
+
144
+ def check_options(options)
145
+ if options.has_key?(:csr) and options.has_key?(:spki)
146
+ raise ArgumentError, "You can't pass both :csr and :spki"
147
+ elsif not options.has_key?(:csr) and not options.has_key?(:spki)
148
+ raise ArgumentError, "You must supply either :csr or :spki"
149
+ elsif options.has_key?(:csr)
150
+ if not options[:csr].kind_of?(R509::CSR)
151
+ raise ArgumentError, "You must pass an R509::CSR object for :csr"
152
+ end
153
+ elsif not options.has_key?(:csr) and options.has_key?(:spki)
154
+ if not options[:spki].kind_of?(R509::SPKI)
155
+ raise ArgumentError, "You must pass an R509::SPKI object for :spki"
156
+ end
157
+ end
158
+ end
159
+
160
+ def extract_public_key_subject_san(options)
161
+ if options.has_key?(:csr)
162
+ subject = (options.has_key?(:subject))? R509::Subject.new(options[:subject]) : options[:csr].subject
163
+ sans = (options.has_key?(:san_names))? options[:san_names] : options[:csr].san
164
+ san_names = parse_san_names(sans)
165
+ public_key = options[:csr].public_key
166
+ else
167
+ # spki
168
+ if not options.has_key?(:subject)
169
+ raise ArgumentError, "You must supply :subject when passing :spki"
170
+ end
171
+ public_key = options[:spki].public_key
172
+ subject = R509::Subject.new(options[:subject])
173
+ san_names = parse_san_names(options[:san_names]) # optional
174
+ end
175
+
176
+ [subject,san_names,public_key]
177
+ end
178
+
179
+ def parse_san_names(sans)
180
+ case sans
181
+ when nil then nil
182
+ when R509::ASN1::GeneralNames then sans
183
+ when Array then R509::ASN1.general_name_parser(sans)
184
+ else
185
+ raise ArgumentError, "When passing SAN names it must be provided as either an array of strings or an R509::ASN1::GeneralNames object"
186
+ end
187
+ end
188
+
189
+ def build_conf(section,hash,index)
190
+ conf = ["[#{section}]"]
191
+ conf.push "policyIdentifier=#{hash["policy_identifier"]}" unless hash["policy_identifier"].nil?
192
+ hash["cps_uris"].each_with_index do |cps,idx|
193
+ conf.push "CPS.#{idx+1}=\"#{cps}\""
194
+ end if hash["cps_uris"].respond_to?(:each_with_index)
195
+
196
+ user_notice_confs = []
197
+ hash["user_notices"].each_with_index do |un,k|
198
+ conf.push "userNotice.#{k+1}=@user_notice#{k+1}#{index}"
199
+ user_notice_confs.push "[user_notice#{k+1}#{index}]"
200
+ user_notice_confs.push "explicitText=\"#{un["explicit_text"]}\"" unless un["explicit_text"].nil?
201
+ # if org is supplied notice numbers is also required (and vice versa). enforced in CAProfile
202
+ user_notice_confs.push "organization=\"#{un["organization"]}\"" unless un["organization"].nil?
203
+ user_notice_confs.push "noticeNumbers=\"#{un["notice_numbers"]}\"" unless un["notice_numbers"].nil?
204
+ end unless not hash["user_notices"].kind_of?(Array)
205
+
206
+ conf.concat(user_notice_confs)
207
+ conf.join "\n"
208
+ end
209
+
210
+ def validate_subject(subject,profile)
211
+ if profile.subject_item_policy.nil? then
212
+ subject
213
+ else
214
+ profile.subject_item_policy.validate_subject(subject)
215
+ end
216
+ end
217
+
218
+ def build_cert(options)
219
+
220
+ cert = OpenSSL::X509::Certificate.new
221
+
222
+ cert.subject = options[:subject]
223
+ cert.issuer = options[:issuer]
224
+ cert.not_before = calculate_not_before(options[:not_before])
225
+ cert.not_after = calculate_not_after(options[:not_after],cert.not_before)
226
+ cert.public_key = options[:public_key]
227
+ cert.serial = create_serial(options[:serial])
228
+ cert.version = 2 #2 means v3
229
+ cert
230
+ end
231
+
232
+ def create_serial(serial)
233
+ if not serial.nil?
234
+ serial = OpenSSL::BN.new(serial.to_s)
235
+ else
236
+ # generate random serial in accordance with best practices
237
+ # guidelines state 20-bits of entropy, but we can cram more in
238
+ # per rfc5280 conforming CAs can make the serial field up to 20 octets
239
+ # to prevent even the incredibly remote possibility of collision we'll
240
+ # concatenate current time (to the microsecond) with a random num
241
+ rand = OpenSSL::BN.rand(96,0) # 96 bits is 12 bytes (octets).
242
+ serial = OpenSSL::BN.new((Time.now.to_f*1000000).to_i.to_s + rand.to_s)
243
+ # since second param is 0 the most significant bit must always be 1
244
+ # this theoretically gives us 95 bits of entropy + microtime, which
245
+ # adds a non-zero quantity of entropy. depending upon how predictable
246
+ # your issuance is, this could range from a reasonably large quantity
247
+ # of entropy to very little
248
+ end
249
+ serial
250
+ end
251
+
252
+ def build_extensions(options)
253
+ ef = OpenSSL::X509::ExtensionFactory.new
254
+
255
+ ef.subject_certificate = options[:subject_certificate]
256
+
257
+ ef.issuer_certificate = options[:issuer_certificate]
258
+
259
+ ext = []
260
+ if not options[:basic_constraints].nil?
261
+ bc = options[:basic_constraints]
262
+ if bc["ca"] == true
263
+ bc_value = "CA:TRUE"
264
+ if not bc["path_length"].nil?
265
+ bc_value += ",pathlen:#{bc["path_length"]}"
266
+ end
267
+ else
268
+ bc_value = "CA:FALSE"
269
+ end
270
+
271
+ ext << ef.create_extension("basicConstraints", bc_value, true)
272
+ end
273
+ if not options[:key_usage].nil? and not options[:key_usage].empty?
274
+ ext << ef.create_extension("keyUsage", options[:key_usage].join(","))
275
+ end
276
+ if not options[:extended_key_usage].nil? and not options[:extended_key_usage].empty?
277
+ ext << ef.create_extension("extendedKeyUsage", options[:extended_key_usage].join(","))
278
+ end
279
+ ext << ef.create_extension("subjectKeyIdentifier", "hash")
280
+
281
+ #attach the key identifier if it's not a self-sign
282
+ if not ef.subject_certificate == ef.issuer_certificate and not R509::Cert.new(:cert=>options[:issuer_certificate]).authority_key_identifier.nil?
283
+ ext << ef.create_extension("authorityKeyIdentifier", "keyid:always") # this could also be keyid:always,issuer:always
284
+ end
285
+
286
+ if not options[:certificate_policies].nil? and options[:certificate_policies].respond_to?(:each)
287
+ conf = []
288
+ policy_names = ["ia5org"]
289
+ options[:certificate_policies].each_with_index do |policy,i|
290
+ conf << build_conf("certPolicies#{i}",policy,i)
291
+ policy_names << "@certPolicies#{i}"
292
+ end
293
+ ef.config = OpenSSL::Config.parse(conf.join("\n"))
294
+ ext << ef.create_extension("certificatePolicies", policy_names.join(","))
295
+ end
296
+
297
+ if not options[:san_names].nil? and not options[:san_names].names.empty?
298
+ serialize = options[:san_names].serialize_names
299
+ ef.config = OpenSSL::Config.parse(serialize[:conf])
300
+ ext << ef.create_extension("subjectAltName", serialize[:extension_string])
301
+ end
302
+
303
+ if not @config.nil? and not @config.cdp_location.nil? and not @config.cdp_location.empty?
304
+ gns = R509::ASN1.general_name_parser(@config.cdp_location)
305
+ serialize = gns.serialize_names
306
+ ef.config = OpenSSL::Config.parse(serialize[:conf])
307
+ ext << ef.create_extension("crlDistributionPoints", serialize[:extension_string])
308
+ end
309
+
310
+ #authorityInfoAccess processing
311
+ if not @config.nil?
312
+ aia = []
313
+ aia_conf = []
314
+
315
+ if not @config.ocsp_location.nil? and not @config.ocsp_location.empty?
316
+ gns = R509::ASN1.general_name_parser(@config.ocsp_location)
317
+ gns.names.each do |ocsp|
318
+ serialize = ocsp.serialize_name
319
+ aia.push "OCSP;#{serialize[:extension_string]}"
320
+ aia_conf.push serialize[:conf]
321
+ end
322
+ end
323
+
324
+ if not @config.nil? and not @config.ca_issuers_location.nil? and not @config.ca_issuers_location.empty?
325
+ gns = R509::ASN1.general_name_parser(@config.ca_issuers_location)
326
+ gns.names.each do |ca_issuers|
327
+ serialize = ca_issuers.serialize_name
328
+ aia.push "caIssuers;#{serialize[:extension_string]}"
329
+ aia_conf.push serialize[:conf]
330
+ end
331
+ end
332
+
333
+ if not aia.empty?
334
+ ef.config = OpenSSL::Config.parse(aia_conf.join("\n"))
335
+ ext << ef.create_extension("authorityInfoAccess",aia.join(","))
336
+ end
337
+ end
338
+
339
+ if options[:inhibit_any_policy]
340
+ ext << ef.create_extension("inhibitAnyPolicy",options[:inhibit_any_policy].to_s,true) # must be set critical per RFC 5280
341
+ end
342
+
343
+ if options[:policy_constraints]
344
+ pc = options[:policy_constraints]
345
+ constraints = []
346
+ constraints << "requireExplicitPolicy:#{pc["require_explicit_policy"]}" unless pc["require_explicit_policy"].nil?
347
+ constraints << "inhibitPolicyMapping:#{pc["inhibit_policy_mapping"]}" unless pc["inhibit_policy_mapping"].nil?
348
+ ext << ef.create_extension("policyConstraints",constraints.join(","),true) # must be set critical per RFC 5280
349
+ end
350
+
351
+ if options[:name_constraints]
352
+ nc = options[:name_constraints]
353
+ nc_data = []
354
+ nc_conf = []
355
+ if not nc["permitted"].nil?
356
+ gns = R509::ASN1::GeneralNames.new
357
+ nc["permitted"].each do |p|
358
+ gns.create_item(:type => p["type"], :value => p["value"])
359
+ end
360
+ gns.names.each do |permitted|
361
+ serialize = permitted.serialize_name
362
+ nc_data.push "permitted;#{serialize[:extension_string]}"
363
+ nc_conf.push serialize[:conf]
364
+ end
365
+ end
366
+ if not nc["excluded"].nil?
367
+ gns = R509::ASN1::GeneralNames.new
368
+ nc["excluded"].each do |p|
369
+ gns.create_item(:type => p["type"], :value => p["value"])
370
+ end
371
+ gns.names.each do |excluded|
372
+ serialize = excluded.serialize_name
373
+ nc_data.push "excluded;#{serialize[:extension_string]}"
374
+ nc_conf.push serialize[:conf]
375
+ end
376
+ end
377
+
378
+ ef.config = OpenSSL::Config.parse nc_conf.join("\n")
379
+ ext << ef.create_extension("nameConstraints",nc_data.join(","))
380
+ end
381
+
382
+ if options[:ocsp_no_check]
383
+ # the value of this extension is not encoded. presence is all that matters
384
+ ext << ef.create_extension("noCheck","yes")
385
+ end
386
+
387
+ options[:subject_certificate].extensions = ext
388
+ nil
389
+ end
390
+
391
+ def calculate_not_before(not_before)
392
+ if not_before.nil?
393
+ #not_before will be set to 6 hours before now to prevent issues with bad system clocks (clients don't sync)
394
+ not_before = Time.now - 6 * 60 * 60
395
+ end
396
+ not_before
397
+ end
398
+
399
+ def calculate_not_after(not_after,not_before)
400
+ if not_after.nil?
401
+ not_after = not_before + 365 * 24 * 60 * 60
402
+ end
403
+ not_after
404
+ end
405
+
406
+ end
407
+ end
data/lib/r509/config.rb CHANGED
@@ -3,405 +3,601 @@ require 'openssl'
3
3
  require 'r509/exceptions'
4
4
  require 'r509/io_helpers'
5
5
  require 'r509/subject'
6
- require 'r509/privatekey'
6
+ require 'r509/private_key'
7
7
  require 'fileutils'
8
8
  require 'pathname'
9
9
 
10
10
  module R509
11
- # Module to contain all configuration related classes (e.g. CaConfig, CaProfile, SubjectItemPolicy)
12
- module Config
13
- # Provides access to configuration profiles
14
- class CaProfile
15
- attr_reader :basic_constraints, :key_usage, :extended_key_usage,
16
- :certificate_policies, :subject_item_policy
17
-
18
- # @option [String] :basic_constraints
19
- # @option [Array] :key_usage
20
- # @option [Array] :extended_key_usage
21
- # @option [Array] :certificate_policies
22
- # @option [R509::Config::SubjectItemPolicy] :subject_item_policy optional
23
- def initialize(opts = {})
24
- @basic_constraints = opts[:basic_constraints]
25
- @key_usage = opts[:key_usage]
26
- @extended_key_usage = opts[:extended_key_usage]
27
- @certificate_policies = opts[:certificate_policies]
28
- if opts.has_key?(:subject_item_policy) and not opts[:subject_item_policy].kind_of?(R509::Config::SubjectItemPolicy)
29
- end
30
- @subject_item_policy = opts[:subject_item_policy] || nil
31
- end
11
+ # Module to contain all configuration related classes (e.g. CAConfig, CAProfile, SubjectItemPolicy)
12
+ module Config
13
+ # Provides access to configuration profiles
14
+ class CAProfile
15
+ attr_reader :basic_constraints, :key_usage, :extended_key_usage,
16
+ :certificate_policies, :subject_item_policy, :ocsp_no_check,
17
+ :inhibit_any_policy, :policy_constraints, :name_constraints
18
+
19
+ # All hash options for CAProfile are optional.
20
+ # @option opts [String] :basic_constraints
21
+ # @option opts [Array] :key_usage
22
+ # @option opts [Array] :extended_key_usage
23
+ # @option opts [Array] :certificate_policies
24
+ # @option opts [Boolean] :ocsp_no_check Sets OCSP No Check extension in the certificate if true
25
+ # @option opts [Integer] :inhibit_any_policy Sets the value of the inhibitAnyPolicy extension
26
+ # @option opts [Hash] :policy_constraints Sets the value of the policyConstriants extension
27
+ # @option opts [Hash] :name_constraints Sets the value of the nameConstraints extension
28
+ # @option opts [R509::Config::SubjectItemPolicy] :subject_item_policy
29
+ def initialize(opts = {})
30
+ validate_basic_constraints opts[:basic_constraints]
31
+ validate_key_usage opts[:key_usage]
32
+ validate_extended_key_usage opts[:extended_key_usage]
33
+ validate_certificate_policies opts[:certificate_policies]
34
+ validate_inhibit_any_policy opts[:inhibit_any_policy]
35
+ validate_policy_constraints opts[:policy_constraints]
36
+ validate_name_constraints opts[:name_constraints]
37
+ @ocsp_no_check = (opts[:ocsp_no_check] == true or opts[:ocsp_no_check] == "true")?true:false
38
+ validate_subject_item_policy opts[:subject_item_policy]
39
+ end
40
+
41
+ private
42
+ # @private
43
+ # validates subject item policy
44
+ def validate_subject_item_policy(sip)
45
+ if not sip.nil? and not sip.kind_of?(R509::Config::SubjectItemPolicy)
46
+ raise ArgumentError, "subject_item_policy must be of type R509::Config::SubjectItemPolicy"
32
47
  end
33
-
34
- # returns information about the subject item policy for a profile
35
- class SubjectItemPolicy
36
- attr_reader :required, :optional
37
-
38
- # @param [Hash] hash of required/optional subject items. These must be in OpenSSL shortname format.
39
- # @example sample hash
40
- # {"CN" => "required",
41
- # "O" => "required",
42
- # "OU" => "optional",
43
- # "ST" => "required",
44
- # "C" => "required",
45
- # "L" => "required",
46
- # "emailAddress" => "optional"}
47
- def initialize(hash={})
48
- if not hash.kind_of?(Hash)
49
- raise ArgumentError, "Must supply a hash in form 'shortname'=>'required/optional'"
48
+ @subject_item_policy = sip
49
+ end
50
+
51
+ # @private
52
+ # validates key usage array
53
+ def validate_key_usage(ku)
54
+ if not ku.nil? and not ku.kind_of?(Array)
55
+ raise ArgumentError, "key_usage must be an array of strings (see README)"
56
+ end
57
+ @key_usage = ku
58
+ end
59
+
60
+ # @private
61
+ # validates inhibit any policy
62
+ def validate_inhibit_any_policy(iap)
63
+ if not iap.nil?
64
+ validate_non_negative_integer("Inhibit any policy",iap)
65
+ end
66
+ @inhibit_any_policy = iap
67
+ end
68
+
69
+ # @private
70
+ def validate_policy_constraints(pc)
71
+ if not pc.nil?
72
+ if not pc.kind_of?(Hash)
73
+ raise ArgumentError, 'Policy constraints must be provided as a hash with at least one of the two allowed keys: "inhibit_policy_mapping" and "require_explicit_policy"'
74
+ end
75
+ if not pc["inhibit_policy_mapping"].nil?
76
+ ipm = validate_non_negative_integer("inhibit_policy_mapping",pc["inhibit_policy_mapping"])
77
+ end
78
+ if not pc["require_explicit_policy"].nil?
79
+ rep = validate_non_negative_integer("require_explicit_policy",pc["require_explicit_policy"])
80
+ end
81
+ if not ipm and not rep
82
+ raise ArgumentError, 'Policy constraints must have at least one of two keys: "inhibit_policy_mapping" and "require_explicit_policy" and the value must be non-negative'
83
+ end
84
+ end
85
+ @policy_constraints = pc
86
+ end
87
+
88
+ # @private
89
+ # used by iap and pc validation methods
90
+ def validate_non_negative_integer(source,value)
91
+ if not value.kind_of?(Integer) or value < 0
92
+ raise ArgumentError, "#{source} must be a non-negative integer"
93
+ end
94
+ value
95
+ end
96
+
97
+ # @private
98
+ # validates extended key usage array
99
+ def validate_extended_key_usage(eku)
100
+ if not eku.nil? and not eku.kind_of?(Array)
101
+ raise ArgumentError, "extended_key_usage must be an array of strings (see README)"
102
+ end
103
+ @extended_key_usage = eku
104
+ end
105
+
106
+
107
+ # @private
108
+ # validates the structure of the certificate policies array
109
+ def validate_certificate_policies(policies)
110
+ if not policies.nil?
111
+ if not policies.respond_to?(:each)
112
+ raise ArgumentError, "Not a valid certificate policy structure. Must be an array of hashes"
113
+ else
114
+ policies.each do |policy|
115
+ if policy["policy_identifier"].nil?
116
+ raise ArgumentError, "Each policy requires a policy identifier"
117
+ end
118
+ if not policy["cps_uris"].nil?
119
+ if not policy["cps_uris"].respond_to?(:each)
120
+ raise ArgumentError, "CPS URIs must be an array of strings"
50
121
  end
51
- @required = []
52
- @optional = []
53
- if not hash.empty?
54
- hash.each_pair do |key,value|
55
- if value == "required"
56
- @required.push(key)
57
- elsif value == "optional"
58
- @optional.push(key)
59
- else
60
- raise ArgumentError, "Unknown subject item policy value. Allowed values are required and optional"
61
- end
122
+ end
123
+ if not policy["user_notices"].nil?
124
+ if not policy["user_notices"].respond_to?(:each)
125
+ raise ArgumentError, "User notices must be an array of hashes"
126
+ else
127
+ policy["user_notices"].each do |un|
128
+ if not un["organization"].nil? and un["notice_numbers"].nil?
129
+ raise ArgumentError, "If you provide an organization you must provide notice numbers"
62
130
  end
131
+ if not un["notice_numbers"].nil? and un["organization"].nil?
132
+ raise ArgumentError, "If you provide notice numbers you must provide an organization"
133
+ end
134
+ end
63
135
  end
136
+ end
64
137
  end
65
-
66
- # @param [R509::Subject] subject
67
- # @return [R509::Subject] validated version of the subject or error
68
- def validate_subject(subject)
69
- # convert the subject components into an array of component names that match
70
- # those that are on the required list
71
- supplied = subject.to_a.each do |item|
72
- @required.include?(item[0])
73
- end.map do |item|
74
- item[0]
75
- end
76
- # so we can make sure they gave us everything that's required
77
- diff = @required - supplied
78
- if not diff.empty?
79
- raise R509::R509Error, "This profile requires you supply "+@required.join(", ")
80
- end
81
-
82
- # the validated subject contains only those subject components that are either
83
- # required or optional
84
- R509::Subject.new(subject.to_a.select do |item|
85
- @required.include?(item[0]) or @optional.include?(item[0])
86
- end)
87
- end
138
+ end
139
+ @certificate_policies = policies
88
140
  end
89
-
90
- # pool of configs, so we can support multiple CAs from a single config file
91
- class CaConfigPool
92
- # @option configs [Hash<String, R509::Config::CaConfig>] the configs to add to the pool
93
- def initialize(configs)
94
- @configs = configs
95
- end
96
-
97
- # get all the config names
98
- def names
99
- @configs.keys
100
- end
101
-
102
- # retrieve a particular config by its name
103
- def [](name)
104
- @configs[name]
141
+ end
142
+
143
+ # @private
144
+ def validate_name_constraints(nc)
145
+ if not nc.nil?
146
+ if not nc.kind_of?(Hash)
147
+ raise ArgumentError, "name_constraints must be provided as a hash"
148
+ end
149
+ ["permitted","excluded"].each do |key|
150
+ if not nc[key].nil?
151
+ validate_name_constraints_elements(key,nc[key])
105
152
  end
153
+ end
154
+ if (nc["permitted"].nil? or nc["permitted"].empty?) and (nc["excluded"].nil? or nc["excluded"].empty?)
155
+ raise ArgumentError, "If name_constraints are supplied you must have at least one valid permitted or excluded element"
156
+ end
157
+ end
158
+ @name_constraints = nc
159
+ end
106
160
 
107
- # @return a list of all the configs in this pool
108
- def all
109
- @configs.values
110
- end
161
+ # @private
162
+ def validate_name_constraints_elements(type,arr)
163
+ if not arr.kind_of?(Array)
164
+ raise ArgumentError, "#{type} must be an array"
165
+ end
166
+ arr.each do |el|
167
+ if not el.kind_of?(Hash) or not el.has_key?("type") or not el.has_key?("value")
168
+ raise ArgumentError, "Elements within the #{type} array must be hashes with both type and value"
169
+ end
170
+ if R509::ASN1::GeneralName.map_type_to_tag(el["type"]) == nil
171
+ raise ArgumentError, "#{el["type"]} is not an allowed type. Check R509::ASN1::GeneralName.map_type_to_tag to see a list of types"
172
+ end
173
+ end
174
+ end
175
+
176
+ # @private
177
+ # validates the structure of the certificate policies array
178
+ def validate_basic_constraints(constraints)
179
+ if not constraints.nil?
180
+ if not constraints.respond_to?(:has_key?) or not constraints.has_key?("ca")
181
+ raise ArgumentError, "You must supply a hash with a key named \"ca\" with a boolean value"
182
+ end
183
+ if constraints["ca"].nil? or (not constraints["ca"].kind_of?(TrueClass) and not constraints["ca"].kind_of?(FalseClass))
184
+ raise ArgumentError, "You must supply true/false for the ca key when specifying basic constraints"
185
+ end
186
+ if constraints["ca"] == false and not constraints["path_length"].nil?
187
+ raise ArgumentError, "path_length is not allowed when ca is false"
188
+ end
189
+ if constraints["ca"] == true and not constraints["path_length"].nil? and (constraints["path_length"] < 0 or not constraints["path_length"].kind_of?(Integer))
190
+ raise ArgumentError, "Path length must be a non-negative integer (>= 0)"
191
+ end
192
+ end
193
+ @basic_constraints = constraints
194
+ end
195
+ end
111
196
 
112
- # Loads the named configuration config from a yaml string.
113
- # @param [String] name The name of the config within the file. Note
114
- # that a single yaml file can contain more than one configuration.
115
- # @param [String] yaml_data The filename to load yaml config data from.
116
- def self.from_yaml(name, yaml_data, opts = {})
117
- conf = YAML.load(yaml_data)
118
- configs = {}
119
- conf[name].each_pair do |ca_name, data|
120
- configs[ca_name] = R509::Config::CaConfig.load_from_hash(data, opts)
121
- end
122
- R509::Config::CaConfigPool.new(configs)
197
+ # returns information about the subject item policy for a profile
198
+ class SubjectItemPolicy
199
+ attr_reader :required, :optional
200
+
201
+ # @param [Hash] hash of required/optional subject items. These must be in OpenSSL shortname format.
202
+ # @example sample hash
203
+ # {"CN" => "required",
204
+ # "O" => "required",
205
+ # "OU" => "optional",
206
+ # "ST" => "required",
207
+ # "C" => "required",
208
+ # "L" => "required",
209
+ # "emailAddress" => "optional"}
210
+ def initialize(hash={})
211
+ if not hash.kind_of?(Hash)
212
+ raise ArgumentError, "Must supply a hash in form 'shortname'=>'required/optional'"
213
+ end
214
+ @required = []
215
+ @optional = []
216
+ if not hash.empty?
217
+ hash.each_pair do |key,value|
218
+ if value == "required"
219
+ @required.push(key)
220
+ elsif value == "optional"
221
+ @optional.push(key)
222
+ else
223
+ raise ArgumentError, "Unknown subject item policy value. Allowed values are required and optional"
123
224
  end
225
+ end
226
+ end
227
+ end
228
+
229
+ # @param [R509::Subject] subject
230
+ # @return [R509::Subject] validated version of the subject or error
231
+ def validate_subject(subject)
232
+ # convert the subject components into an array of component names that match
233
+ # those that are on the required list
234
+ supplied = subject.to_a.each do |item|
235
+ @required.include?(item[0])
236
+ end.map do |item|
237
+ item[0]
238
+ end
239
+ # so we can make sure they gave us everything that's required
240
+ diff = @required - supplied
241
+ if not diff.empty?
242
+ raise R509::R509Error, "This profile requires you supply "+@required.join(", ")
124
243
  end
125
244
 
126
- # Stores a configuration for our CA.
127
- class CaConfig
128
- include R509::IOHelpers
129
- extend R509::IOHelpers
130
- attr_accessor :ca_cert, :crl_validity_hours, :message_digest,
131
- :cdp_location, :crl_start_skew_seconds, :ocsp_location, :ocsp_chain,
132
- :ocsp_start_skew_seconds, :ocsp_validity_hours, :crl_number_file, :crl_list_file
133
-
134
- # @option opts [R509::Cert] :ca_cert Cert+Key pair
135
- # @option opts [Integer] :crl_validity_hours (168) The number of hours that
136
- # a CRL will be valid. Defaults to 7 days.
137
- # @option opts [Hash<String, R509::Config::CaProfile>] :profiles
138
- # @option opts [String] :message_digest (SHA1) The hashing algorithm to use.
139
- # @option opts [String] :cdp_location
140
- # @option opts [String] :ocsp_location
141
- # @option opts [String] :crl_number_file The file that we will save
142
- # the CRL numbers to. defaults to a StringIO object if not provided
143
- # @option opts [String] :crl_list_file The file that we will save
144
- # the CRL list data to. defaults to a StringIO object if not provided
145
- # @option opts [R509::Cert] :ocsp_cert An optional cert+key pair
146
- # OCSP signing delegate
147
- # @option opts [Array<OpenSSL::X509::Certificate>] :ocsp_chain An optional array
148
- # that constitutes the chain to attach to an OCSP response
149
- #
150
- def initialize(opts = {} )
151
- if not opts.has_key?(:ca_cert) then
152
- raise ArgumentError, 'Config object requires that you pass :ca_cert'
153
- end
154
-
155
- @ca_cert = opts[:ca_cert]
156
-
157
- if not @ca_cert.kind_of?(R509::Cert) then
158
- raise ArgumentError, ':ca_cert must be of type R509::Cert'
159
- end
160
-
161
- #ocsp data
162
- if opts.has_key?(:ocsp_cert) and not opts[:ocsp_cert].kind_of?(R509::Cert) and not opts[:ocsp_cert].nil?
163
- raise ArgumentError, ':ocsp_cert, if provided, must be of type R509::Cert'
164
- end
165
- if opts.has_key?(:ocsp_cert) and not opts[:ocsp_cert].nil? and not opts[:ocsp_cert].has_private_key?
166
- raise ArgumentError, ':ocsp_cert must contain a private key, not just a certificate'
167
- end
168
- @ocsp_cert = opts[:ocsp_cert] unless opts[:ocsp_cert].nil?
169
- @ocsp_location = opts[:ocsp_location]
170
- @ocsp_chain = opts[:ocsp_chain] if opts[:ocsp_chain].kind_of?(Array)
171
- @ocsp_validity_hours = opts[:ocsp_validity_hours] || 168
172
- @ocsp_start_skew_seconds = opts[:ocsp_start_skew_seconds] || 3600
173
-
174
- @crl_validity_hours = opts[:crl_validity_hours] || 168
175
- @crl_start_skew_seconds = opts[:crl_start_skew_seconds] || 3600
176
- @crl_number_file = opts[:crl_number_file] || nil
177
- @crl_list_file = opts[:crl_list_file] || nil
178
- @cdp_location = opts[:cdp_location]
179
- @message_digest = opts[:message_digest] || "SHA1"
180
-
181
-
245
+ # the validated subject contains only those subject components that are either
246
+ # required or optional
247
+ R509::Subject.new(subject.to_a.select do |item|
248
+ @required.include?(item[0]) or @optional.include?(item[0])
249
+ end)
250
+ end
251
+ end
182
252
 
183
- @profiles = {}
184
- if opts[:profiles]
185
- opts[:profiles].each_pair do |name, prof|
186
- set_profile(name, prof)
187
- end
188
- end
253
+ # pool of configs, so we can support multiple CAs from a single config file
254
+ class CAConfigPool
255
+ # @option configs [Hash<String, R509::Config::CAConfig>] the configs to add to the pool
256
+ def initialize(configs)
257
+ @configs = configs
258
+ end
259
+
260
+ # get all the config names
261
+ def names
262
+ @configs.keys
263
+ end
264
+
265
+ # retrieve a particular config by its name
266
+ def [](name)
267
+ @configs[name]
268
+ end
269
+
270
+ # @return a list of all the configs in this pool
271
+ def all
272
+ @configs.values
273
+ end
274
+
275
+ # Loads the named configuration config from a yaml string.
276
+ # @param [String] name The name of the config within the file. Note
277
+ # that a single yaml file can contain more than one configuration.
278
+ # @param [String] yaml_data The filename to load yaml config data from.
279
+ def self.from_yaml(name, yaml_data, opts = {})
280
+ conf = YAML.load(yaml_data)
281
+ configs = {}
282
+ conf[name].each_pair do |ca_name, data|
283
+ configs[ca_name] = R509::Config::CAConfig.load_from_hash(data, opts)
284
+ end
285
+ R509::Config::CAConfigPool.new(configs)
286
+ end
287
+ end
189
288
 
190
- end
289
+ # Stores a configuration for our CA.
290
+ class CAConfig
291
+ include R509::IOHelpers
292
+ extend R509::IOHelpers
293
+ attr_accessor :ca_cert, :crl_validity_hours, :message_digest,
294
+ :cdp_location, :crl_start_skew_seconds, :ocsp_location, :ocsp_chain,
295
+ :ocsp_start_skew_seconds, :ocsp_validity_hours, :crl_number_file, :crl_list_file,
296
+ :ca_issuers_location
297
+
298
+ # @option opts [R509::Cert] :ca_cert Cert+Key pair
299
+ # @option opts [Integer] :crl_validity_hours (168) The number of hours that
300
+ # a CRL will be valid. Defaults to 7 days.
301
+ # @option opts [Hash<String, R509::Config::CAProfile>] :profiles
302
+ # @option opts [String] :message_digest (SHA1) The hashing algorithm to use.
303
+ # @option opts [Array] :cdp_location array of strings (URLs)
304
+ # @option opts [Array] :ocsp_location array of strings (URLs)
305
+ # @option opts [Array] :ca_issuers_location array of strings (URLs)
306
+ # @option opts [String] :crl_number_file The file that we will save
307
+ # the CRL numbers to. defaults to a StringIO object if not provided
308
+ # @option opts [String] :crl_list_file The file that we will save
309
+ # the CRL list data to. defaults to a StringIO object if not provided
310
+ # @option opts [R509::Cert] :ocsp_cert An optional cert+key pair
311
+ # OCSP signing delegate
312
+ # @option opts [Array<OpenSSL::X509::Certificate>] :ocsp_chain An optional array
313
+ # that constitutes the chain to attach to an OCSP response
314
+ #
315
+ def initialize(opts = {} )
316
+ if not opts.has_key?(:ca_cert) then
317
+ raise ArgumentError, 'Config object requires that you pass :ca_cert'
318
+ end
191
319
 
192
- # @return [R509::Cert] either a custom OCSP cert or the ca_cert
193
- def ocsp_cert
194
- if @ocsp_cert.nil? then @ca_cert else @ocsp_cert end
195
- end
320
+ @ca_cert = opts[:ca_cert]
196
321
 
197
- # @param [String] name The name of the profile
198
- # @param [R509::Config::CaProfile] prof The profile configuration
199
- def set_profile(name, prof)
200
- unless prof.is_a?(R509::Config::CaProfile)
201
- raise TypeError, "profile is supposed to be a R509::Config::CaProfile"
202
- end
203
- @profiles[name] = prof
204
- end
322
+ if not @ca_cert.kind_of?(R509::Cert) then
323
+ raise ArgumentError, ':ca_cert must be of type R509::Cert'
324
+ end
205
325
 
206
- # @param [String] prof
207
- # @return [R509::Config::CaProfile] The config profile.
208
- def profile(prof)
209
- if !@profiles.has_key?(prof)
210
- raise R509::R509Error, "unknown profile '#{prof}'"
211
- end
212
- @profiles[prof]
213
- end
326
+ #ocsp data
327
+ if opts.has_key?(:ocsp_cert) and not opts[:ocsp_cert].kind_of?(R509::Cert) and not opts[:ocsp_cert].nil?
328
+ raise ArgumentError, ':ocsp_cert, if provided, must be of type R509::Cert'
329
+ end
330
+ if opts.has_key?(:ocsp_cert) and not opts[:ocsp_cert].nil? and not opts[:ocsp_cert].has_private_key?
331
+ raise ArgumentError, ':ocsp_cert must contain a private key, not just a certificate'
332
+ end
333
+ @ocsp_cert = opts[:ocsp_cert] unless opts[:ocsp_cert].nil?
334
+ validate_ocsp_location opts[:ocsp_location]
335
+ validate_ca_issuers_location opts[:ca_issuers_location]
336
+ @ocsp_chain = opts[:ocsp_chain] if opts[:ocsp_chain].kind_of?(Array)
337
+ @ocsp_validity_hours = opts[:ocsp_validity_hours] || 168
338
+ @ocsp_start_skew_seconds = opts[:ocsp_start_skew_seconds] || 3600
339
+
340
+ @crl_validity_hours = opts[:crl_validity_hours] || 168
341
+ @crl_start_skew_seconds = opts[:crl_start_skew_seconds] || 3600
342
+ @crl_number_file = opts[:crl_number_file] || nil
343
+ @crl_list_file = opts[:crl_list_file] || nil
344
+ validate_cdp_location opts[:cdp_location]
345
+ @message_digest = opts[:message_digest] || "SHA1"
346
+
347
+
348
+
349
+ @profiles = {}
350
+ if opts[:profiles]
351
+ opts[:profiles].each_pair do |name, prof|
352
+ set_profile(name, prof)
353
+ end
354
+ end
214
355
 
215
- # @return [Integer] The number of profiles
216
- def num_profiles
217
- @profiles.count
218
- end
356
+ end
219
357
 
358
+ # @return [R509::Cert] either a custom OCSP cert or the ca_cert
359
+ def ocsp_cert
360
+ if @ocsp_cert.nil? then @ca_cert else @ocsp_cert end
361
+ end
220
362
 
221
- ######### Class Methods ##########
363
+ # @param [String] name The name of the profile
364
+ # @param [R509::Config::CAProfile] prof The profile configuration
365
+ def set_profile(name, prof)
366
+ unless prof.is_a?(R509::Config::CAProfile)
367
+ raise TypeError, "profile is supposed to be a R509::Config::CAProfile"
368
+ end
369
+ @profiles[name] = prof
370
+ end
371
+
372
+ # @param [String] prof
373
+ # @return [R509::Config::CAProfile] The config profile.
374
+ def profile(prof)
375
+ if !@profiles.has_key?(prof)
376
+ raise R509::R509Error, "unknown profile '#{prof}'"
377
+ end
378
+ @profiles[prof]
379
+ end
222
380
 
223
- # Load the configuration from a data hash. The same type that might be
224
- # used when loading from a YAML file.
225
- # @param [Hash] conf A hash containing all the configuration options
226
- # @option opts [String] :ca_root_path The root path for the CA. Defaults to
227
- # the current working directory.
228
- def self.load_from_hash(conf, opts = {})
229
- if conf.nil?
230
- raise ArgumentError, "conf not found"
231
- end
232
- unless conf.kind_of?(Hash)
233
- raise ArgumentError, "conf must be a Hash"
234
- end
381
+ # @return [Integer] The number of profiles
382
+ def num_profiles
383
+ @profiles.count
384
+ end
235
385
 
236
- ca_root_path = Pathname.new(opts[:ca_root_path] || FileUtils.getwd)
237
386
 
238
- unless File.directory?(ca_root_path)
239
- raise R509Error, "ca_root_path is not a directory: #{ca_root_path}"
240
- end
387
+ ######### Class Methods ##########
241
388
 
242
- ca_cert_hash = conf['ca_cert']
389
+ # Load the configuration from a data hash. The same type that might be
390
+ # used when loading from a YAML file.
391
+ # @param [Hash] conf A hash containing all the configuration options
392
+ # @option opts [String] :ca_root_path The root path for the CA. Defaults to
393
+ # the current working directory.
394
+ def self.load_from_hash(conf, opts = {})
395
+ if conf.nil?
396
+ raise ArgumentError, "conf not found"
397
+ end
398
+ unless conf.kind_of?(Hash)
399
+ raise ArgumentError, "conf must be a Hash"
400
+ end
243
401
 
244
- if ca_cert_hash.has_key?('engine')
245
- ca_cert = self.load_with_engine(ca_cert_hash,ca_root_path)
246
- end
402
+ ca_root_path = Pathname.new(opts[:ca_root_path] || FileUtils.getwd)
247
403
 
248
- if ca_cert.nil? and ca_cert_hash.has_key?('pkcs12')
249
- ca_cert = self.load_with_pkcs12(ca_cert_hash,ca_root_path)
250
- end
404
+ unless File.directory?(ca_root_path)
405
+ raise R509Error, "ca_root_path is not a directory: #{ca_root_path}"
406
+ end
251
407
 
252
- if ca_cert.nil? and ca_cert_hash.has_key?('cert')
253
- ca_cert = self.load_with_key(ca_cert_hash,ca_root_path)
254
- end
408
+ ca_cert_hash = conf['ca_cert']
255
409
 
256
- if conf.has_key?("ocsp_cert")
257
- if conf["ocsp_cert"].has_key?('engine')
258
- ocsp_cert = self.load_with_engine(conf["ocsp_cert"],ca_root_path)
259
- end
410
+ if ca_cert_hash.has_key?('engine')
411
+ ca_cert = self.load_with_engine(ca_cert_hash,ca_root_path)
412
+ end
260
413
 
261
- if ocsp_cert.nil? and conf["ocsp_cert"].has_key?('pkcs12')
262
- ocsp_cert = self.load_with_pkcs12(conf["ocsp_cert"],ca_root_path)
263
- end
414
+ if ca_cert.nil? and ca_cert_hash.has_key?('pkcs12')
415
+ ca_cert = self.load_with_pkcs12(ca_cert_hash,ca_root_path)
416
+ end
264
417
 
265
- if ocsp_cert.nil? and conf["ocsp_cert"].has_key?('cert')
266
- ocsp_cert = self.load_with_key(conf["ocsp_cert"],ca_root_path)
267
- end
268
- end
418
+ if ca_cert.nil? and ca_cert_hash.has_key?('cert')
419
+ ca_cert = self.load_with_key(ca_cert_hash,ca_root_path)
420
+ end
269
421
 
270
- ocsp_chain = []
271
- if conf.has_key?("ocsp_chain")
272
- ocsp_chain_data = read_data(ca_root_path+conf["ocsp_chain"])
273
- cert_regex = /-----BEGIN CERTIFICATE-----.+?-----END CERTIFICATE-----/m
274
- ocsp_chain_data.scan(cert_regex) do |cert|
275
- ocsp_chain.push(OpenSSL::X509::Certificate.new(cert))
276
- end
277
- end
422
+ if conf.has_key?("ocsp_cert")
423
+ if conf["ocsp_cert"].has_key?('engine')
424
+ ocsp_cert = self.load_with_engine(conf["ocsp_cert"],ca_root_path)
425
+ end
278
426
 
279
- opts = {
280
- :ca_cert => ca_cert,
281
- :ocsp_cert => ocsp_cert,
282
- :ocsp_chain => ocsp_chain,
283
- :crl_validity_hours => conf['crl_validity_hours'],
284
- :ocsp_validity_hours => conf['ocsp_validity_hours'],
285
- :ocsp_start_skew_seconds => conf['ocsp_start_skew_seconds'],
286
- :ocsp_location => conf['ocsp_location'],
287
- :cdp_location => conf['cdp_location'],
288
- :message_digest => conf['message_digest'],
289
- }
290
-
291
- if conf.has_key?("crl_list")
292
- opts[:crl_list_file] = (ca_root_path + conf['crl_list']).to_s
293
- end
427
+ if ocsp_cert.nil? and conf["ocsp_cert"].has_key?('pkcs12')
428
+ ocsp_cert = self.load_with_pkcs12(conf["ocsp_cert"],ca_root_path)
429
+ end
294
430
 
295
- if conf.has_key?("crl_number")
296
- opts[:crl_number_file] = (ca_root_path + conf['crl_number']).to_s
297
- end
431
+ if ocsp_cert.nil? and conf["ocsp_cert"].has_key?('cert')
432
+ ocsp_cert = self.load_with_key(conf["ocsp_cert"],ca_root_path)
433
+ end
434
+ end
298
435
 
436
+ ocsp_chain = []
437
+ if conf.has_key?("ocsp_chain")
438
+ ocsp_chain_data = read_data(ca_root_path+conf["ocsp_chain"])
439
+ cert_regex = /-----BEGIN CERTIFICATE-----.+?-----END CERTIFICATE-----/m
440
+ ocsp_chain_data.scan(cert_regex) do |cert|
441
+ ocsp_chain.push(OpenSSL::X509::Certificate.new(cert))
442
+ end
443
+ end
299
444
 
300
- profs = {}
301
- conf['profiles'].keys.each do |profile|
302
- data = conf['profiles'][profile]
303
- if not data["subject_item_policy"].nil?
304
- subject_item_policy = R509::Config::SubjectItemPolicy.new(data["subject_item_policy"])
305
- end
306
- profs[profile] = R509::Config::CaProfile.new(:key_usage => data["key_usage"],
307
- :extended_key_usage => data["extended_key_usage"],
308
- :basic_constraints => data["basic_constraints"],
309
- :certificate_policies => data["certificate_policies"],
310
- :subject_item_policy => subject_item_policy)
311
- end unless conf['profiles'].nil?
312
- opts[:profiles] = profs
313
-
314
- # Create the instance.
315
- self.new(opts)
316
- end
445
+ opts = {
446
+ :ca_cert => ca_cert,
447
+ :ocsp_cert => ocsp_cert,
448
+ :ocsp_chain => ocsp_chain,
449
+ :crl_validity_hours => conf['crl_validity_hours'],
450
+ :ocsp_validity_hours => conf['ocsp_validity_hours'],
451
+ :ocsp_start_skew_seconds => conf['ocsp_start_skew_seconds'],
452
+ :ocsp_location => conf['ocsp_location'],
453
+ :ca_issuers_location => conf['ca_issuers_location'],
454
+ :cdp_location => conf['cdp_location'],
455
+ :message_digest => conf['message_digest'],
456
+ }
457
+
458
+ if conf.has_key?("crl_list")
459
+ opts[:crl_list_file] = (ca_root_path + conf['crl_list']).to_s
460
+ end
317
461
 
318
- # Loads the named configuration config from a yaml file.
319
- # @param [String] conf_name The name of the config within the file. Note
320
- # that a single yaml file can contain more than one configuration.
321
- # @param [String] yaml_file The filename to load yaml config data from.
322
- def self.load_yaml(conf_name, yaml_file, opts = {})
323
- conf = YAML.load_file(yaml_file)
324
- self.load_from_hash(conf[conf_name], opts)
325
- end
462
+ if conf.has_key?("crl_number")
463
+ opts[:crl_number_file] = (ca_root_path + conf['crl_number']).to_s
464
+ end
326
465
 
327
- # Loads the named configuration config from a yaml string.
328
- # @param [String] conf_name The name of the config within the file. Note
329
- # that a single yaml file can contain more than one configuration.
330
- # @param [String] yaml_data The filename to load yaml config data from.
331
- def self.from_yaml(conf_name, yaml_data, opts = {})
332
- conf = YAML.load(yaml_data)
333
- self.load_from_hash(conf[conf_name], opts)
334
- end
335
466
 
336
- private
467
+ profs = {}
468
+ conf['profiles'].keys.each do |profile|
469
+ data = conf['profiles'][profile]
470
+ if not data["subject_item_policy"].nil?
471
+ subject_item_policy = R509::Config::SubjectItemPolicy.new(data["subject_item_policy"])
472
+ end
473
+ profs[profile] = R509::Config::CAProfile.new(:key_usage => data["key_usage"],
474
+ :extended_key_usage => data["extended_key_usage"],
475
+ :basic_constraints => data["basic_constraints"],
476
+ :certificate_policies => data["certificate_policies"],
477
+ :ocsp_no_check => data["ocsp_no_check"],
478
+ :inhibit_any_policy => data["inhibit_any_policy"],
479
+ :policy_constraints => data["policy_constraints"],
480
+ :name_constraints => data["name_constraints"],
481
+ :subject_item_policy => subject_item_policy)
482
+ end unless conf['profiles'].nil?
483
+ opts[:profiles] = profs
484
+
485
+ # Create the instance.
486
+ self.new(opts)
487
+ end
488
+
489
+ # Loads the named configuration config from a yaml file.
490
+ # @param [String] conf_name The name of the config within the file. Note
491
+ # that a single yaml file can contain more than one configuration.
492
+ # @param [String] yaml_file The filename to load yaml config data from.
493
+ def self.load_yaml(conf_name, yaml_file, opts = {})
494
+ conf = YAML.load_file(yaml_file)
495
+ self.load_from_hash(conf[conf_name], opts)
496
+ end
497
+
498
+ # Loads the named configuration config from a yaml string.
499
+ # @param [String] conf_name The name of the config within the file. Note
500
+ # that a single yaml file can contain more than one configuration.
501
+ # @param [String] yaml_data The filename to load yaml config data from.
502
+ def self.from_yaml(conf_name, yaml_data, opts = {})
503
+ conf = YAML.load(yaml_data)
504
+ self.load_from_hash(conf[conf_name], opts)
505
+ end
506
+
507
+ private
508
+
509
+ def self.load_with_engine(ca_cert_hash,ca_root_path)
510
+ if ca_cert_hash.has_key?('key')
511
+ raise ArgumentError, "You can't specify both key and engine"
512
+ end
513
+ if ca_cert_hash.has_key?('pkcs12')
514
+ raise ArgumentError, "You can't specify both engine and pkcs12"
515
+ end
516
+ if not ca_cert_hash.has_key?('key_name')
517
+ raise ArgumentError, "You must supply a key_name with an engine"
518
+ end
337
519
 
338
- def self.load_with_engine(ca_cert_hash,ca_root_path)
339
- if ca_cert_hash.has_key?('key')
340
- raise R509Error, "You can't specify both key and engine"
341
- end
342
- if ca_cert_hash.has_key?('pkcs12')
343
- raise R509Error, "You can't specify both engine and pkcs12"
344
- end
345
- if not ca_cert_hash.has_key?('key_name')
346
- raise R509Error, "You must supply a key_name with an engine"
347
- end
520
+ if ca_cert_hash['engine'].respond_to?(:load_private_key)
521
+ #this path is only for testing...ugh
522
+ engine = ca_cert_hash['engine']
523
+ else
524
+ #this path can't be tested by unit tests. bah!
525
+ engine = OpenSSL::Engine.by_id(ca_cert_hash['engine'])
526
+ end
527
+ ca_key = R509::PrivateKey.new(
528
+ :engine => engine,
529
+ :key_name => ca_cert_hash['key_name']
530
+ )
531
+ ca_cert_file = ca_root_path + ca_cert_hash['cert']
532
+ ca_cert = R509::Cert.new(
533
+ :cert => read_data(ca_cert_file),
534
+ :key => ca_key
535
+ )
536
+ ca_cert
537
+ end
538
+
539
+ def self.load_with_pkcs12(ca_cert_hash,ca_root_path)
540
+ if ca_cert_hash.has_key?('cert')
541
+ raise ArgumentError, "You can't specify both pkcs12 and cert"
542
+ end
543
+ if ca_cert_hash.has_key?('key')
544
+ raise ArgumentError, "You can't specify both pkcs12 and key"
545
+ end
348
546
 
349
- if ca_cert_hash['engine'].respond_to?(:load_private_key)
350
- #this path is only for testing...ugh
351
- engine = ca_cert_hash['engine']
352
- else
353
- #this path can't be tested by unit tests. bah!
354
- engine = OpenSSL::Engine.by_id(ca_cert_hash['engine'])
355
- end
356
- ca_key = R509::PrivateKey.new(
357
- :engine => engine,
358
- :key_name => ca_cert_hash['key_name']
359
- )
360
- ca_cert_file = ca_root_path + ca_cert_hash['cert']
361
- ca_cert = R509::Cert.new(
362
- :cert => read_data(ca_cert_file),
363
- :key => ca_key
364
- )
365
- ca_cert
366
- end
547
+ pkcs12_file = ca_root_path + ca_cert_hash['pkcs12']
548
+ ca_cert = R509::Cert.new(
549
+ :pkcs12 => read_data(pkcs12_file),
550
+ :password => ca_cert_hash['password']
551
+ )
552
+ ca_cert
553
+ end
554
+
555
+ def self.load_with_key(ca_cert_hash,ca_root_path)
556
+ ca_cert_file = ca_root_path + ca_cert_hash['cert']
557
+
558
+ if ca_cert_hash.has_key?('key')
559
+ ca_key_file = ca_root_path + ca_cert_hash['key']
560
+ ca_key = R509::PrivateKey.new(
561
+ :key => read_data(ca_key_file),
562
+ :password => ca_cert_hash['password']
563
+ )
564
+ ca_cert = R509::Cert.new(
565
+ :cert => read_data(ca_cert_file),
566
+ :key => ca_key
567
+ )
568
+ else
569
+ # in certain cases (OCSP responders for example) we may want
570
+ # to load a ca_cert with no private key
571
+ ca_cert = R509::Cert.new(:cert => read_data(ca_cert_file))
572
+ end
573
+ ca_cert
574
+ end
367
575
 
368
- def self.load_with_pkcs12(ca_cert_hash,ca_root_path)
369
- if ca_cert_hash.has_key?('cert')
370
- raise R509Error, "You can't specify both pkcs12 and cert"
371
- end
372
- if ca_cert_hash.has_key?('key')
373
- raise R509Error, "You can't specify both pkcs12 and key"
374
- end
576
+ private
375
577
 
376
- pkcs12_file = ca_root_path + ca_cert_hash['pkcs12']
377
- ca_cert = R509::Cert.new(
378
- :pkcs12 => read_data(pkcs12_file),
379
- :password => ca_cert_hash['password']
380
- )
381
- ca_cert
382
- end
578
+ # @private
579
+ def validate_cdp_location(location)
580
+ if not location.nil? and not location.kind_of?(Array)
581
+ raise ArgumentError, "cdp_location must be an array if provided"
582
+ end
583
+ @cdp_location = location
584
+ end
383
585
 
384
- def self.load_with_key(ca_cert_hash,ca_root_path)
385
- ca_cert_file = ca_root_path + ca_cert_hash['cert']
386
-
387
- if ca_cert_hash.has_key?('key')
388
- ca_key_file = ca_root_path + ca_cert_hash['key']
389
- ca_key = R509::PrivateKey.new(
390
- :key => read_data(ca_key_file),
391
- :password => ca_cert_hash['password']
392
- )
393
- ca_cert = R509::Cert.new(
394
- :cert => read_data(ca_cert_file),
395
- :key => ca_key
396
- )
397
- else
398
- # in certain cases (OCSP responders for example) we may want
399
- # to load a ca_cert with no private key
400
- ca_cert = R509::Cert.new(:cert => read_data(ca_cert_file))
401
- end
402
- ca_cert
403
- end
586
+ # @private
587
+ def validate_ocsp_location(location)
588
+ if not location.nil? and not location.kind_of?(Array)
589
+ raise ArgumentError, "ocsp_location must be an array if provided"
590
+ end
591
+ @ocsp_location = location
592
+ end
404
593
 
594
+ # @private
595
+ def validate_ca_issuers_location(location)
596
+ if not location.nil? and not location.kind_of?(Array)
597
+ raise ArgumentError, "ca_issuers_location must be an array if provided"
405
598
  end
599
+ @ca_issuers_location = location
600
+ end
406
601
  end
602
+ end
407
603
  end