r509 0.8.1 → 0.9

Sign up to get free protection for your applications and to get access to all the features.
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,37 @@
1
+ # this hack exists to work around a major issue with the OpenSSL::PKey::EC interface
2
+ # as it is currently configured in ruby <= 2.0.0-p0
3
+ # the signing methods on OpenSSL::X509::Request and OpenSSL::X509::Certificate look for
4
+ # a method named #private? on the PKey object. OpenSSL::PKey::RSA and OpenSSL::PKey::DSA
5
+ # both define this method, but OpenSSL::PKey::EC defines #private_key? instead. This
6
+ # will open up the class and add #private? as an alias to allow successful signing
7
+ if defined?(OpenSSL::PKey::EC) and not OpenSSL::PKey::EC.method_defined?('private?')
8
+ # marked as @private so it won't appear in the yard doc
9
+ # @private
10
+ module OpenSSL::PKey
11
+ # marked as @private so it won't appear in the yard doc
12
+ # @private
13
+ class EC
14
+ def private?
15
+ private_key?
16
+ end
17
+ end
18
+ end
19
+ elsif not defined?(OpenSSL::PKey::EC)
20
+ # this is a stub implementation for when EC is unavailable. Any method called against
21
+ # it will raise an R509Error
22
+ # marked as @private so it won't appear in the yard doc
23
+ # @private
24
+ module OpenSSL::PKey
25
+ # marked as @private so it won't appear in the yard doc
26
+ # @private
27
+ class EC
28
+ UNSUPPORTED = true
29
+ def initialize(*args)
30
+ raise R509::R509Error, "EC is unavailable. You may need to recompile Ruby with an OpenSSL that has elliptic curve support."
31
+ end
32
+ def method_missing(method, *args, &block)
33
+ raise R509::R509Error, "EC is unavailable. You may need to recompile Ruby with an OpenSSL that has elliptic curve support."
34
+ end
35
+ end
36
+ end
37
+ end
@@ -1,5 +1,5 @@
1
1
  module R509
2
- #An error r509 sometimes raises. You know, when it feels like it.
3
- class R509Error < StandardError
4
- end
2
+ #An error r509 sometimes raises. You know, when it feels like it.
3
+ class R509Error < StandardError
4
+ end
5
5
  end
@@ -1,52 +1,52 @@
1
1
  module R509
2
- # helper methods for I/O
3
- module IOHelpers
4
- # Writes data into an IO or file
5
- # @param [String, #write] filename_or_io Either a string of the path for
6
- # the file that you'd like to write, or an IO-like object.
7
- # @param [String] data The data that we want to write
8
- def self.write_data(filename_or_io, data)
9
- if filename_or_io.respond_to?(:write)
10
- filename_or_io.write(data)
11
- else
12
- begin
13
- file = File.open(filename_or_io, 'wb:ascii-8bit')
14
- return file.write(data)
15
- ensure
16
- file.close()
17
- end
18
- end
2
+ # helper methods for I/O
3
+ module IOHelpers
4
+ # Writes data into an IO or file
5
+ # @param [String, #write] filename_or_io Either a string of the path for
6
+ # the file that you'd like to write, or an IO-like object.
7
+ # @param [String] data The data that we want to write
8
+ def self.write_data(filename_or_io, data)
9
+ if filename_or_io.respond_to?(:write)
10
+ filename_or_io.write(data)
11
+ else
12
+ begin
13
+ file = File.open(filename_or_io, 'wb:ascii-8bit')
14
+ return file.write(data)
15
+ ensure
16
+ file.close()
19
17
  end
18
+ end
19
+ end
20
20
 
21
- # Reads data from an IO or file
22
- # @param [String, #read] filename_or_io Either a string of the path for
23
- # the file that you'd like to read, or an IO-like object.
24
- def self.read_data(filename_or_io)
25
- if filename_or_io.respond_to?(:read)
26
- filename_or_io.read()
27
- else
28
- begin
29
- file = File.open(filename_or_io, 'rb:ascii-8bit')
30
- return file.read()
31
- ensure
32
- file.close() unless file.nil?
33
- end
34
- end
21
+ # Reads data from an IO or file
22
+ # @param [String, #read] filename_or_io Either a string of the path for
23
+ # the file that you'd like to read, or an IO-like object.
24
+ def self.read_data(filename_or_io)
25
+ if filename_or_io.respond_to?(:read)
26
+ filename_or_io.read()
27
+ else
28
+ begin
29
+ file = File.open(filename_or_io, 'rb:ascii-8bit')
30
+ return file.read()
31
+ ensure
32
+ file.close() unless file.nil?
35
33
  end
34
+ end
35
+ end
36
36
 
37
- # Writes data into an IO or file
38
- # @param [String, #write] filename_or_io Either a string of the path for
39
- # the file that you'd like to write, or an IO-like object.
40
- # @param [String] data The data that we want to write
41
- def write_data(filename_or_io, data)
42
- IOHelpers.write_data(filename_or_io, data)
43
- end
37
+ # Writes data into an IO or file
38
+ # @param [String, #write] filename_or_io Either a string of the path for
39
+ # the file that you'd like to write, or an IO-like object.
40
+ # @param [String] data The data that we want to write
41
+ def write_data(filename_or_io, data)
42
+ IOHelpers.write_data(filename_or_io, data)
43
+ end
44
44
 
45
- # Reads data from an IO or file
46
- # @param [String, #read] filename_or_io Either a string of the path for
47
- # the file that you'd like to read, or an IO-like object.
48
- def read_data(filename_or_io)
49
- IOHelpers.read_data(filename_or_io)
50
- end
45
+ # Reads data from an IO or file
46
+ # @param [String, #read] filename_or_io Either a string of the path for
47
+ # the file that you'd like to read, or an IO-like object.
48
+ def read_data(filename_or_io)
49
+ IOHelpers.read_data(filename_or_io)
51
50
  end
51
+ end
52
52
  end
@@ -0,0 +1,53 @@
1
+ require 'openssl'
2
+
3
+ module R509
4
+ #MessageDigest allows you to specify MDs in a more friendly fashion
5
+ class MessageDigest
6
+ attr_reader :name, :digest
7
+
8
+ # @param [String,OpenSSL::Digest] arg
9
+ def initialize(arg)
10
+ if arg.kind_of?(String)
11
+ @name = arg.downcase
12
+ @digest = translate_name_to_digest
13
+ else
14
+ @digest = arg
15
+ @name = translate_digest_to_name
16
+ end
17
+ end
18
+
19
+ private
20
+
21
+ # @return [OpenSSL::Digest]
22
+ def translate_name_to_digest
23
+ case @name
24
+ when 'sha1' then OpenSSL::Digest::SHA1.new
25
+ when 'sha224' then OpenSSL::Digest::SHA224.new
26
+ when 'sha256' then OpenSSL::Digest::SHA256.new
27
+ when 'sha384' then OpenSSL::Digest::SHA384.new
28
+ when 'sha512' then OpenSSL::Digest::SHA512.new
29
+ when 'md5' then OpenSSL::Digest::MD5.new
30
+ when 'dss1' then OpenSSL::Digest::DSS1.new
31
+ else
32
+ @name = "sha1"
33
+ OpenSSL::Digest::SHA1.new
34
+ end
35
+ end
36
+
37
+ # @return [String]
38
+ def translate_digest_to_name
39
+ case @digest
40
+ when OpenSSL::Digest::SHA1 then 'sha1'
41
+ when OpenSSL::Digest::SHA224 then 'sha224'
42
+ when OpenSSL::Digest::SHA256 then 'sha256'
43
+ when OpenSSL::Digest::SHA384 then 'sha384'
44
+ when OpenSSL::Digest::SHA512 then 'sha512'
45
+ when OpenSSL::Digest::MD5 then 'md5'
46
+ when OpenSSL::Digest::DSS1 then 'dss1'
47
+ else
48
+ raise ArgumentError, "Unknown digest"
49
+ end
50
+ end
51
+ end
52
+ end
53
+
data/lib/r509/ocsp.rb CHANGED
@@ -2,84 +2,94 @@ require 'openssl'
2
2
  require 'r509/exceptions'
3
3
  require 'r509/config'
4
4
 
5
- #Ocsp module
6
- module R509::Ocsp
5
+ #OCSP module
6
+ module R509::OCSP
7
7
 
8
- #builds OCSP responses
9
- class Response
10
- # @param ocsp_response [OpenSSL::OCSP::Response]
11
- def initialize(ocsp_response)
12
- if not ocsp_response.kind_of?(OpenSSL::OCSP::Response)
13
- raise R509::R509Error, 'You must pass an OpenSSL::OCSP::Response object to the constructor. See R509::Ocsp::Response.parse if you are trying to parse'
14
- end
15
- @ocsp_response = ocsp_response
16
- end
17
- # @param [String,OpenSSL::OCSP::Response] ocsp_string parses an existing response
18
- # @return [R509::Ocsp::Response]
19
- def self.parse(ocsp_string)
20
- if ocsp_string.nil?
21
- raise R509::R509Error, 'You must pass a DER encoded OCSP response to this method'
22
- end
23
- R509::Ocsp::Response.new(OpenSSL::OCSP::Response.new(ocsp_string))
24
- end
25
-
26
- # @return [OpenSSL::OCSP] response status of this response
27
- def status
28
- @ocsp_response.status
29
- end
8
+ #builds OCSP responses
9
+ class Response
10
+ # @param ocsp_response [OpenSSL::OCSP::Response]
11
+ def initialize(ocsp_response)
12
+ if not ocsp_response.kind_of?(OpenSSL::OCSP::Response)
13
+ raise R509::R509Error, 'You must pass an OpenSSL::OCSP::Response object to the constructor. See R509::OCSP::Response.parse if you are trying to parse'
14
+ end
15
+ @ocsp_response = ocsp_response
16
+ end
17
+ # @param [String,OpenSSL::OCSP::Response] ocsp_string parses an existing response
18
+ # @return [R509::OCSP::Response]
19
+ def self.parse(ocsp_string)
20
+ if ocsp_string.nil?
21
+ raise R509::R509Error, 'You must pass a DER encoded OCSP response to this method'
22
+ end
23
+ R509::OCSP::Response.new(OpenSSL::OCSP::Response.new(ocsp_string))
24
+ end
30
25
 
31
- # @return [String] der encoded string
32
- def to_der
33
- @ocsp_response.to_der
34
- end
26
+ # @return [OpenSSL::OCSP] response status of this response
27
+ def status
28
+ @ocsp_response.status
29
+ end
35
30
 
36
- # @return [OpenSSL::OCSP::BasicResponse]
37
- def basic
38
- @ocsp_response.basic
39
- end
31
+ # @return [String] der encoded string
32
+ def to_der
33
+ @ocsp_response.to_der
34
+ end
40
35
 
41
- # @param [Array<OpenSSL::X509::Certificate>,OpenSSL::X509::Certificate] certs A cert or array of certs to verify against
42
- # @return [Boolean] true if the response is valid according to the given root
43
- def verify(certs)
44
- store = OpenSSL::X509::Store.new
45
- if certs.kind_of?(Array)
46
- stack = certs
47
- certs.each do |cert|
48
- store.add_cert(cert)
49
- end
50
- else
51
- stack = [certs]
52
- store.add_cert(certs)
53
- end
36
+ # @return [OpenSSL::OCSP::BasicResponse]
37
+ def basic
38
+ @ocsp_response.basic
39
+ end
54
40
 
55
- #suppress verbosity since #verify will output a warning if it does not match
56
- #as well as returning false. we just want the boolean
57
- original_verbosity = $VERBOSE
58
- $VERBOSE = nil
59
- #still a bit unclear on why we add to store and pass in array to verify
60
- result = @ocsp_response.basic.verify(stack, store)
61
- $VERBOSE = original_verbosity
62
- return result
41
+ # @param [Array<OpenSSL::X509::Certificate>,OpenSSL::X509::Certificate] certs A cert or array of certs to verify against
42
+ # @return [Boolean] true if the response is valid according to the given root
43
+ def verify(certs)
44
+ store = OpenSSL::X509::Store.new
45
+ if certs.kind_of?(Array)
46
+ stack = certs
47
+ certs.each do |cert|
48
+ store.add_cert(cert)
63
49
  end
50
+ else
51
+ stack = [certs]
52
+ store.add_cert(certs)
53
+ end
64
54
 
65
- # @param [OpenSSL::OCSP::Request] ocsp_request the OCSP request whose nonce to check
66
- # @return [R509::Ocsp::Request::Nonce::CONSTANT] the status code of the nonce check
67
- def check_nonce(ocsp_request)
68
- ocsp_request.check_nonce(@ocsp_response.basic)
69
- end
55
+ #suppress verbosity since #verify will output a warning if it does not match
56
+ #as well as returning false. we just want the boolean
57
+ original_verbosity = $VERBOSE
58
+ $VERBOSE = nil
59
+ #still a bit unclear on why we add to store and pass in array to verify
60
+ result = @ocsp_response.basic.verify(stack, store)
61
+ $VERBOSE = original_verbosity
62
+ return result
70
63
  end
71
64
 
72
- #holds OCSP request related items
73
- module Request
74
- # contains constants r509 uses for OCSP responses
75
- module Nonce
76
- #these values are defined at
77
- #http://www.ruby-doc.org/stdlib-1.9.3/libdoc/openssl/rdoc/OpenSSL/OCSP/Request.html
78
- PRESENT_AND_EQUAL = 1
79
- BOTH_ABSENT = 2
80
- RESPONSE_ONLY = 3
81
- NOT_EQUAL = 0
82
- REQUEST_ONLY = -1
83
- end
65
+ # @param [OpenSSL::OCSP::Request] ocsp_request the OCSP request whose nonce to check
66
+ # @return [R509::OCSP::Request::Nonce::CONSTANT] the status code of the nonce check
67
+ def check_nonce(ocsp_request)
68
+ ocsp_request.check_nonce(@ocsp_response.basic)
69
+ end
70
+ end
71
+
72
+ #holds OCSP request related items
73
+ module Request
74
+ # contains constants r509 uses for OCSP responses
75
+ module Nonce
76
+ #these values are defined at
77
+ #http://www.ruby-doc.org/stdlib-1.9.3/libdoc/openssl/rdoc/OpenSSL/OCSP/Request.html
78
+ # nonce is present and matches
79
+ PRESENT_AND_EQUAL = 1
80
+
81
+ # nonce is missing in request and response
82
+ BOTH_ABSENT = 2
83
+
84
+ # nonce is present in response only
85
+ RESPONSE_ONLY = 3
86
+
87
+ # nonce is in both request and response, but does not match
88
+ NOT_EQUAL = 0
89
+
90
+ # nonce is present in request only
91
+ REQUEST_ONLY = -1
92
+
84
93
  end
94
+ end
85
95
  end
@@ -0,0 +1,32 @@
1
+ require 'openssl'
2
+
3
+ module R509
4
+ # Helps map raw OIDs to friendlier short names
5
+ module OIDMapper
6
+ # Register an OID so we have a friendly short name
7
+ # @param [String] oid A string representation of the OID you want to map (e.g. "1.6.2.3.55")
8
+ # @param [String] short_name The short name (e.g. CN, O, OU, emailAddress)
9
+ # @param [String] long_name Optional long name. Defaults to the same as short_name
10
+ # @return [Boolean] success/failure
11
+ def self.register(oid,short_name,long_name=nil)
12
+ if long_name.nil?
13
+ long_name = short_name
14
+ end
15
+ OpenSSL::ASN1::ObjectId.register(oid, short_name, long_name)
16
+ end
17
+
18
+ # Register a batch of OIDs so we have friendly short names
19
+ # @param [Array] oids An array of hashes
20
+ # @example
21
+ # R509::OIDMapper.batch_register([
22
+ # {:oid => "1.2.3.4.5", :short_name => "sName", :long_name => "lName"},
23
+ # {:oid => "1.2.3.4.6", :short_name => "oName"}
24
+ # ]
25
+ def self.batch_register(oids)
26
+ oids.each do |oid_hash|
27
+ self.register(oid_hash[:oid],oid_hash[:short_name],oid_hash[:long_name])
28
+ end
29
+ nil
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,228 @@
1
+ require 'openssl'
2
+ require 'r509/io_helpers'
3
+ require 'r509/exceptions'
4
+
5
+ module R509
6
+ #private key management
7
+ class PrivateKey
8
+ include R509::IOHelpers
9
+
10
+ # @option opts [Symbol] :type :rsa/:dsa/:ec
11
+ # @option opts [String] :curve_name ("secp384r1") Only used if :type is :ec
12
+ # @option opts [Integer] :bit_strength (2048) Only used if :type is :rsa or :dsa.
13
+ # @option opts [String] :password
14
+ # @option opts [String,OpenSSL::PKey::RSA,OpenSSL::PKey::DSA,OpenSSL::PKey::EC] :key
15
+ # @option opts [OpenSSL::Engine] :engine
16
+ # @option opts [string] :key_name (used with engine)
17
+ def initialize(opts={})
18
+ if not opts.kind_of?(Hash)
19
+ raise ArgumentError, 'Must provide a hash of options'
20
+ end
21
+
22
+ if opts.has_key?(:engine) and opts.has_key?(:key)
23
+ raise ArgumentError, 'You can\'t pass both :key and :engine'
24
+ elsif opts.has_key?(:key_name) and not opts.has_key?(:engine)
25
+ raise ArgumentError, 'When providing a :key_name you MUST provide an :engine'
26
+ elsif opts.has_key?(:engine) and not opts.has_key?(:key_name)
27
+ raise ArgumentError, 'When providing an :engine you MUST provide a :key_name'
28
+ elsif opts.has_key?(:engine) and opts.has_key?(:key_name)
29
+ if not opts[:engine].kind_of?(OpenSSL::Engine)
30
+ raise ArgumentError, 'When providing an engine, it must be of type OpenSSL::Engine'
31
+ end
32
+ @engine = opts[:engine]
33
+ @key_name = opts[:key_name]
34
+ end
35
+
36
+ if opts.has_key?(:key)
37
+ password = opts[:password] || nil
38
+ #OpenSSL::PKey.read solves this begin/rescue garbage but is only
39
+ #available to Ruby 1.9.3+ and may not solve the EC portion
40
+ begin
41
+ @key = OpenSSL::PKey::RSA.new(opts[:key],password)
42
+ rescue OpenSSL::PKey::RSAError
43
+ begin
44
+ @key = OpenSSL::PKey::DSA.new(opts[:key],password)
45
+ rescue
46
+ begin
47
+ @key = OpenSSL::PKey::EC.new(opts[:key],password)
48
+ rescue
49
+ raise R509::R509Error, "Failed to load private key. Invalid key or incorrect password."
50
+ end
51
+ end
52
+ end
53
+ else
54
+ bit_strength = opts[:bit_strength] || 2048
55
+ type = opts[:type] || :rsa
56
+ case type
57
+ when :rsa
58
+ @key = OpenSSL::PKey::RSA.new(bit_strength)
59
+ when :dsa
60
+ @key = OpenSSL::PKey::DSA.new(bit_strength)
61
+ when :ec
62
+ curve_name = opts[:curve_name] || "secp384r1"
63
+ @key = OpenSSL::PKey::EC.new(curve_name)
64
+ @key.generate_key
65
+ else
66
+ raise ArgumentError, 'Must provide :rsa, :dsa , or :ec as type when key or engine is nil'
67
+ end
68
+ end
69
+ end
70
+
71
+ # Helper method to quickly load a private key from the filesystem
72
+ #
73
+ # @param [String] filename Path to file you want to load
74
+ # @return [R509::PrivateKey] PrivateKey object
75
+ def self.load_from_file( filename, password = nil )
76
+ return R509::PrivateKey.new(:key => IOHelpers.read_data(filename), :password => password )
77
+ end
78
+
79
+
80
+ # Returns the bit strength of the key
81
+ #
82
+ # @return [Integer]
83
+ def bit_strength
84
+ if self.rsa?
85
+ return self.public_key.n.num_bits
86
+ elsif self.dsa?
87
+ return self.public_key.p.num_bits
88
+ elsif self.ec?
89
+ raise R509::R509Error, 'Bit strength is not available for EC at this time.'
90
+ end
91
+ end
92
+
93
+ # Returns the short name of the elliptic curve used to generate the private key
94
+ # if the key is EC. If not, raises an error.
95
+ #
96
+ # @return [String] elliptic curve name
97
+ def curve_name
98
+ if self.ec?
99
+ self.key.group.curve_name
100
+ else
101
+ raise R509::R509Error, 'Curve name is only available with EC private keys'
102
+ end
103
+ end
104
+
105
+ # @return [OpenSSL::PKey::RSA,OpenSSL::PKey::DSA,OpenSSL::Engine pkey] this method may return the PKey object itself or a handle to the private key in the HSM (which will not show the private key, just public)
106
+ def key
107
+ if in_hardware?
108
+ @engine.load_private_key(@key_name)
109
+ else
110
+ @key
111
+ end
112
+ end
113
+
114
+ # @return [Boolean] whether the key is resident in hardware or not
115
+ def in_hardware?
116
+ if not @engine.nil?
117
+ true
118
+ else
119
+ false
120
+ end
121
+ end
122
+
123
+ # @return [OpenSSL::PKey::RSA,OpenSSL::PKey::DSA,OpenSSL::PKey::EC] public key
124
+ def public_key
125
+ if self.ec?
126
+ # OpenSSL::PKey::EC.public_key returns an OpenSSL::PKey::EC::Point, which isn't consistent
127
+ # with the way OpenSSL::PKey::RSA/DSA do it. We could return the original PKey::EC object
128
+ # but if we do that then it has the private_key as well. Here's a ghetto workaround.
129
+ # We have to supply the curve name to the temporary key object or else #public_key= fails
130
+ curve_name = self.key.group.curve_name
131
+ temp_key = OpenSSL::PKey::EC.new(curve_name)
132
+ temp_key.public_key=self.key.public_key
133
+ temp_key
134
+ else
135
+ self.key.public_key
136
+ end
137
+ end
138
+
139
+ alias :to_s :public_key
140
+
141
+ # Converts the key into the PEM format
142
+ #
143
+ # @return [String] the key converted into PEM format.
144
+ def to_pem
145
+ if in_hardware?
146
+ raise R509::R509Error, "This method cannot be called when using keys in hardware"
147
+ end
148
+ self.key.to_pem
149
+ end
150
+
151
+ # Converts the key into encrypted PEM format
152
+ #
153
+ # @param [String,OpenSSL::Cipher] cipher to use for encryption
154
+ # full list of available ciphers can be obtained with OpenSSL::Cipher.ciphers
155
+ # (common ones are des3, aes256, aes128)
156
+ # @param [String] password password
157
+ # @return [String] the key converted into encrypted PEM format.
158
+ def to_encrypted_pem(cipher,password)
159
+ if in_hardware?
160
+ raise R509::R509Error, "This method cannot be called when using keys in hardware"
161
+ end
162
+ cipher = OpenSSL::Cipher::Cipher.new(cipher)
163
+ self.key.to_pem(cipher,password)
164
+ end
165
+
166
+
167
+ # Converts the key into the DER format
168
+ #
169
+ # @return [String] the key converted into DER format.
170
+ def to_der
171
+ if in_hardware?
172
+ raise R509::R509Error, "This method cannot be called when using keys in hardware"
173
+ end
174
+ self.key.to_der
175
+ end
176
+
177
+ # Writes the key into the PEM format
178
+ #
179
+ # @param [String, #write] filename_or_io Either a string of the path for
180
+ # the file that you'd like to write, or an IO-like object.
181
+ def write_pem(filename_or_io)
182
+ write_data(filename_or_io, self.to_pem)
183
+ end
184
+
185
+
186
+ # Writes the key into encrypted PEM format with specified cipher
187
+ #
188
+ # @param [String, #write] filename_or_io Either a string of the path for
189
+ # the file that you'd like to write, or an IO-like object.
190
+ # @param [String,OpenSSL::Cipher] cipher to use for encryption
191
+ # full list of available ciphers can be obtained with OpenSSL::Cipher.ciphers
192
+ # (common ones are des3, aes256, aes128)
193
+ # @param [String] password password
194
+ def write_encrypted_pem(filename_or_io,cipher,password)
195
+ write_data(filename_or_io, to_encrypted_pem(cipher,password))
196
+ end
197
+
198
+ # Writes the key into the DER format
199
+ #
200
+ # @param [String, #write] filename_or_io Either a string of the path for
201
+ # the file that you'd like to write, or an IO-like object.
202
+ def write_der(filename_or_io)
203
+ write_data(filename_or_io, self.to_der)
204
+ end
205
+
206
+
207
+ # Returns whether the key is RSA
208
+ #
209
+ # @return [Boolean] true if the key is RSA, false otherwise
210
+ def rsa?
211
+ self.key.kind_of?(OpenSSL::PKey::RSA)
212
+ end
213
+
214
+ # Returns whether the key is DSA
215
+ #
216
+ # @return [Boolean] true if the key is DSA, false otherwise
217
+ def dsa?
218
+ self.key.kind_of?(OpenSSL::PKey::DSA)
219
+ end
220
+
221
+ # Returns whether the key is EC
222
+ #
223
+ # @return [Boolean] true if the key is EC, false otherwise
224
+ def ec?
225
+ self.key.kind_of?(OpenSSL::PKey::EC)
226
+ end
227
+ end
228
+ end