r509 0.9.2 → 0.10.0

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