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,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