scep 0.0.1

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.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: ceb79fd8756349928c7653f5c59191e3cc8cbfe7
4
+ data.tar.gz: 9f322dce3a3d7a45f7ae7c220d67b2908dc57380
5
+ SHA512:
6
+ metadata.gz: 26c7b9378ffd100b39788cd61973e568b4413887bb6dac749ff68bb5d29419fb34028eade582235cd29970f9f27cdeeb39d73eb542ba011a0bddf4c5dc2f44fc
7
+ data.tar.gz: c864e7169c4ab8403d706b294cd48bfc535d010807d5e793b7347bd5463659095ea702db69cef2be1c2956afab5400c4e435b4d3a5d0809c220a972018b87d43
data/.gitignore ADDED
@@ -0,0 +1,15 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
10
+ *.bundle
11
+ *.so
12
+ *.o
13
+ *.a
14
+ mkmf.log
15
+ .idea
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --color
2
+ --format=documentation
data/.ruby-version ADDED
@@ -0,0 +1 @@
1
+ 2.2.0
data/.travis.yml ADDED
@@ -0,0 +1,6 @@
1
+ language: ruby
2
+ cache: bundler
3
+ rvm:
4
+ - 1.8.7
5
+ - 2.2.1
6
+ script: bundle exec rspec
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in scep-gem.gemspec
4
+ gemspec
data/README.md ADDED
@@ -0,0 +1,144 @@
1
+ SCEP Gem
2
+ ========
3
+ Libraries that allow you to be a SCEP server, be a SCEP proxy, etc.
4
+
5
+ If you believe you have discovered a security vulnerability in this gem, please email security@onelogin.com with a description. You can also use the form based submission located here: https://www.onelogin.com/security. We follow responsible disclosure guidelines, and will work with you to quickly find a resolution.
6
+
7
+ ## Terminology
8
+
9
+ * CSR - Certificate Signing Request. "I want you to sign my public key so you know who I am in the future"
10
+ * CA - Certificate Authority. The ultimate authority who can sign certificates.
11
+ * RA - Registration Authority. RA Certificates must be signed by CA. Accepts CSR on behalf of CA.
12
+ Forwards CSR to CA for signing. You can proxy through to multiple CA
13
+
14
+ Note that a CA can be an RA.
15
+
16
+
17
+ ## SCEP Requests
18
+ Contains a CSR that requires signing
19
+
20
+ ### Encrypt request
21
+ When we want to forward a CSR to a RA. We may or may not be an RA ourselves.
22
+
23
+ First,collect some certificates:
24
+
25
+ ```ruby
26
+ their_ra_cert = OpenSSL::X509::Certificate.new File.read(their-ra.crt')
27
+ our_keypair = SCEP::Keypair.read 'our.crt', 'our.key'
28
+ csr = OpenSSL::X509::Request.new File.read('some.csr')
29
+ ```
30
+
31
+ Now create a request object and get the encrypted result:
32
+
33
+ ```ruby
34
+ request = SCEP::PKIOperation::Request.new(our_keypair)
35
+ request.csr = csr
36
+ encrypted = request.encrypt(their_ra_cert)
37
+ ```
38
+
39
+ We can then send `encrypted` to an RA
40
+
41
+ ### Decrypt Request
42
+ When we are an RA.
43
+
44
+ First, collect some certificates:
45
+
46
+ ```ruby
47
+ their_cert = OpenSSL::X509::Certificate.new File.read('their.crt')
48
+ our_ra_keypair = SCEP::Keypair.read 'our-ra.crt', 'our-ra.key'
49
+ ```
50
+
51
+ Now we can make a request object and get the original CSR:
52
+
53
+ ```ruby
54
+ request = SCEP::PKIOperation::Request.new(our_ra_keypair)
55
+ request.verify_against(their_cert) # Make sure the response was signed by someone we trust
56
+
57
+ # Fails decryption if not signed by `their_cert`
58
+ request.decrypt(encrypted_request)
59
+
60
+ # Will always decrypt and ignore signature verification
61
+ request.decrypt(encrypted_request, false)
62
+
63
+ # Now get the encoded CSR
64
+ puts request.csr # => OpenSSL::X509::Request
65
+ ```
66
+
67
+ ### Proxying a Request
68
+ If we are an RA, we can decrypt a request intended for us and re-encrypt it for another RA
69
+ and grab the CSR in the process
70
+
71
+
72
+ ```ruby
73
+ their_cert = OpenSSL::X509::Certificate.new File.read('their.crt')
74
+ their_ra_cert = OpenSSL::X509::Certificate.new File.read('their-ra.crt')
75
+ our_ra_keypair = SCEP::Keypair.read 'our-ra.crt', 'our-ra.key'
76
+
77
+ request = SCEP::PKIOperation::Request.new(our_ra_keypair)
78
+ request.verify_against(their_cert)
79
+ newly_encrypted = request.proxy(original_encrypted_result, their_ra_cert)
80
+ p request.csr # => OpenSSL::X509::Request
81
+ ```
82
+
83
+ ## SCEP Response
84
+ Contains the signed X509 Certificate
85
+
86
+ ### Encrypting a Response
87
+ When we are an RA
88
+
89
+ ```ruby
90
+ their_cert = OpenSSL::X509::Certificate.new File.read('their.crt')
91
+ our_ra_keypair = SCEP::Keypair.read 'our-ra.crt', 'our-ra.key'
92
+
93
+ signed_cert = OpenSSL::X509::Certificate.new File.read('cert-signed-by-ca.crt')
94
+
95
+ response = SCEP::PKIOperation::Response.new(our_ra_keypair)
96
+ encrypted = response.encrypt(their_cert)
97
+ ```
98
+
99
+ ### Decrypting a Response
100
+ When we are decrypting information from an RA
101
+
102
+ ```ruby
103
+ their_ra_cert = OpenSSL::X509::Certificate.new File.read('their-ra.crt')
104
+ our_keypair = SCEP::Keypair.read 'our.crt', 'our.key'
105
+
106
+ response = SCEP::PKIOperation::Response.new(our_keypair)
107
+ response.verify_against(their_ra_cert)
108
+ response.decrypt(encrypted_response)
109
+ p response.signed_certificates # => [OpenSSL::X509::Certificate]
110
+ ```
111
+
112
+ ## SCEP Proxy
113
+
114
+ Easily be a SCEP proxy (psuedo sinatra syntax):
115
+
116
+ ```ruby
117
+ require 'scep'
118
+
119
+ ra_keypair = SCEP::Keypair.read('certs/ra.crt', 'certs/ra.key')
120
+ scep_server = SCEP::Endpoint.new 'https://some-final-endpoint.com'
121
+
122
+ post '/scep?operation=PKIOperation' do
123
+ proxy = SCEP::PKIOperation::Proxy.new(server, ra_keypair)
124
+
125
+ # Options to verify certificates:
126
+
127
+ # Verify request came from apple certificate
128
+ proxy.add_request_verification_certificate(@apple_cert) # OpenSSL::X509::Certificate
129
+
130
+ # Verify response came from CA certificate
131
+ proxy.add_response_verificaion_certificate(@ca_certificate) # OpenSSL::X509::Certificate
132
+
133
+ # Or don't verify anything
134
+ proxy.no_verify!
135
+
136
+ result = proxy.forward_pki_request(request.raw_post)
137
+
138
+ puts result.csr # The CSR they sent
139
+ puts result.signed_certificates # Returned signed certs from the SCEP server
140
+
141
+ headers['content-type'] = 'application/x-pki-message'
142
+ render results.p7enc_response.to_der
143
+ end
144
+ ```
data/Rakefile ADDED
@@ -0,0 +1,2 @@
1
+ require "bundler/gem_tasks"
2
+
@@ -0,0 +1,169 @@
1
+ require 'httparty'
2
+ require 'set'
3
+
4
+ module SCEP
5
+ # Handles making requests to a SCEP server and storing the RA and CA certs. Currently uses
6
+ # the URL defined in the `config/endpoints.yml` file.
7
+ #
8
+ # @example
9
+ # scep_endpoint = SCEP::Endpoint.new('https://scep-server-url.com')
10
+ # # Downloads RA, CA certs
11
+ # puts scep_endpoint.ca_certificate # => OpenSSL::X509::Certificate
12
+ # puts scep_endpoint.ra_certificate
13
+ #
14
+ # @todo GetCACaps
15
+ class Endpoint
16
+ include HTTParty
17
+ include SCEP::Loggable
18
+
19
+ # An exception raised if the SCEP server does not properly support the SCEP protocol.
20
+ class ProtocolError < StandardError; end
21
+
22
+ default_timeout 2
23
+
24
+ attr_writer :ra_certificate
25
+
26
+ attr_writer :ca_certificate
27
+
28
+ attr_accessor :default_options
29
+
30
+ def initialize(base_uri, default_options = {})
31
+ @default_options = default_options.merge(:base_uri => base_uri)
32
+ end
33
+
34
+ # Gets the CA certificate. Will automatically download the CA certificate from
35
+ # the server if it has not yet been downloaded.
36
+ # @return [OpenSSL::X509::Certificate]
37
+ # @raise [ProtocolError] if the SCEP server does not return valid certs
38
+ def ca_certificate
39
+ download_certificates if @ca_certificate.nil?
40
+ return @ca_certificate
41
+ end
42
+
43
+ # Gets the RA certificate.
44
+ # @return [OpenSSL::X509::Certificate]
45
+ # @raise [ProtocolError] if the SCEP server does not return valid certs
46
+ # @note This will return the {#ca_certificate CA certificate} if the SCEP server does not
47
+ # support RA certs.
48
+ def ra_certificate
49
+ # Force download of CA, possibly RA certificate
50
+ @ra_certificate || ca_certificate
51
+ end
52
+
53
+ # Checks to see if the SCEP server supports the RA certificate
54
+ # @return [Boolean]
55
+ def supports_ra_certificate?
56
+ ca_certificate != ra_certificate
57
+ end
58
+
59
+ # Downloads RA and CA certificates from the SCEP server using the `GetCACert` operation.
60
+ # Will give {#ra_certificate} and {#ca_certificate} values.
61
+ # @return [HTTParty::Response] the response from the SCEP server.
62
+ # @raise [ProtocolError] if the
63
+ def download_certificates
64
+ logger.debug 'Downloading CA, possibly RA certificate from SCEP server'
65
+ response = scep_request 'GetCACert'
66
+ if response.content_type == 'application/x-x509-ca-cert' # Only a CA cert
67
+ handle_ca_only_cert(response.body)
68
+ elsif response.content_type == 'application/x-x509-ca-ra-cert'
69
+ handle_ca_ra_cert(response.body)
70
+ else
71
+ fail ProtocolError, "SCEP server returned invalid content type of #{response.content_type}"
72
+ end
73
+ return response
74
+ end
75
+ alias_method :get_ca_cert, :download_certificates
76
+
77
+
78
+ # Gets server capabilities. Memoized version of {#fetch_capabilities}
79
+ # @return [Set<String>] a set of capabilities
80
+ def capabilities
81
+ @capabilities || fetch_capabilities
82
+ end
83
+
84
+ # Gets server capabilities. Always triggers a download of capabilities
85
+ # @return [Set<String>] a set of capabilities
86
+ def fetch_capabilities
87
+ logger.debug 'Getting SCEP endpoint capabilities'
88
+ response = scep_request 'GetCACaps'
89
+ caps = response.body.strip.split("\n")
90
+ @capabilities = Set.new(caps)
91
+ logger.debug "SCEP endpoint supports capabilities: #{@capabilities.inspect}"
92
+ return @capabilities
93
+ end
94
+
95
+ # Whether the SCEP endpoint supports the POSTPKIOperation
96
+ # @return [Boolean] TRUE if it is supported, FALSE otherwise
97
+ def post_pki_operation?
98
+ capabilities.include?('POSTPKIOperation')
99
+ end
100
+
101
+ # Executes a SCEP request.
102
+ # @param [String] operation the SCEP operation to perform
103
+ # @param [String] message an optional message to send
104
+ # @return [HTTParty::Response] the httparty response
105
+ def scep_request(operation, message = nil, is_post = false)
106
+ query = { :operation => operation }
107
+ query[:message] = message unless message.nil?
108
+ if is_post
109
+ logger.debug "Executing POST ?operation=#{operation}"
110
+ response = self.class.post '/', { :query => { :operation => operation}, :body => message }.merge(default_options)
111
+ else
112
+ logger.debug "Executing GET ?operation=#{operation}&message=#{message}"
113
+ response = self.class.get '/', { :query => query }.merge(default_options)
114
+ end
115
+
116
+ if response.code != 200
117
+ raise ProtocolError, "SCEP request returned non-200 code of #{response.code}"
118
+ end
119
+
120
+ return response
121
+ end
122
+
123
+
124
+ # @todo: handle GET PKIOperations
125
+ # @todo: verify actually signed by CA?
126
+ # @param [String] payload the raw payload to send
127
+ # @return [String] the response body
128
+ def pki_operation(payload)
129
+ response = scep_request('PKIOperation', payload, true)
130
+ if response.content_type != 'application/x-pki-message'
131
+ raise ProtocolError,
132
+ "SCEP PKIOperation didn't return content-type of application/x-pki-message (returned #{response.content_type})"
133
+ end
134
+ return response.body
135
+ end
136
+
137
+ protected
138
+
139
+ def handle_ca_only_cert(response_body)
140
+ logger.debug 'SCEP server does not support RA certificate - only using CA cert'
141
+ @ca_certificate = OpenSSL::X509::Certificate.new(response_body)
142
+ rescue StandardError
143
+ fail ProtocolError, 'SCEP server did not return parseable X509::Certificate'
144
+ end
145
+
146
+ def handle_ca_ra_cert(response_body)
147
+ logger.debug 'SCEP server has both RA and CA certificate'
148
+
149
+ begin
150
+ pcerts = PKCS7CertOnly.decode(response_body)
151
+ rescue StandardError
152
+ fail ProtocolError, 'SCEP server did not return a parseable PKCS#7'
153
+ end
154
+
155
+ fail ProtocolError,
156
+ 'SCEP server did not return two certificates in PKCS#7 cert chain' unless
157
+ pcerts.certificates.length == 2
158
+
159
+
160
+ unless pcerts.certificates[1].verify(pcerts.certificates[0].public_key)
161
+ fail ProtocolError,
162
+ 'RA certificate must be signed by CA certificate when using RA/CA cert combination'
163
+ end
164
+
165
+ @ca_certificate = pcerts.certificates[0]
166
+ @ra_certificate = pcerts.certificates[1]
167
+ end
168
+ end
169
+ end
@@ -0,0 +1,69 @@
1
+ module SCEP
2
+ # A public / private keypair
3
+ class Keypair
4
+
5
+ # The various cryptosystems we support. Used mostly for ruby 1.8.7, which cannot
6
+ # automatically determine which cryptosystem is being used.
7
+ SUPPORTED_CRYPTOSYSTEMS = [
8
+ OpenSSL::PKey::RSA,
9
+ OpenSSL::PKey::DSA,
10
+ OpenSSL::PKey::EC,
11
+ OpenSSL::PKey::DH
12
+ ]
13
+
14
+ # @return [OpenSSL::X509::Certificate]
15
+ attr_accessor :certificate
16
+ alias_method :cert, :certificate
17
+
18
+ # @return [OpenSSL::PKey]
19
+ attr_accessor :private_key
20
+
21
+ def initialize(certificate, private_key)
22
+ raise ArgumentError, '`certificate` must be an OpenSSL::X509::Certificate' unless
23
+ certificate.is_a?(OpenSSL::X509::Certificate)
24
+
25
+ @certificate = certificate
26
+ @private_key = private_key
27
+ end
28
+
29
+ # Loads a keypair from a file
30
+ # @param [String] certificate_filepath
31
+ # @param [String] private_key_filepath
32
+ # @param [String] private_key_passphrase add this if you
33
+ # @return [Keypair]
34
+ def self.read(certificate_filepath, private_key_filepath, private_key_passphrase = nil)
35
+ x509_cert = OpenSSL::X509::Certificate.new File.read(certificate_filepath.to_s)
36
+ pkey = read_private_key(File.open(private_key_filepath.to_s).read, private_key_passphrase)
37
+ new(x509_cert, pkey)
38
+ end
39
+
40
+ protected
41
+
42
+ # Reads a DER or PEM encoded private key that is one of the {SUPPORTED_CRYPTOSYSTEMS}. In
43
+ # ruby 1.9+ we can do this easily. In ruby 1.8.7 we have to keep on guessing until we get
44
+ # it right.
45
+ def self.read_private_key(encoded_key, passphrase = nil)
46
+ # Ruby 1.9.3+
47
+ if OpenSSL::PKey.respond_to?(:read)
48
+ OpenSSL::PKey.read encoded_key, passphrase
49
+
50
+ # Ruby 1.8.7 - keep on guessing which cryptosystem until we're correct
51
+ else
52
+ SUPPORTED_CRYPTOSYSTEMS.each do |system|
53
+ begin
54
+ return system.new(encoded_key, passphrase)
55
+ rescue
56
+ end
57
+ end
58
+
59
+ # If we're here, then the file is probably invalid
60
+ raise UnsupportedCryptosystem,
61
+ "Either private key is invalid, passphrase is invalid, or does not support one " \
62
+ "of cryptosystems: #{SUPPORTED_CRYPTOSYSTEMS.map(&:name).join(', ')}"
63
+ end
64
+ end
65
+
66
+
67
+ class UnsupportedCryptosystem < StandardError; end
68
+ end
69
+ end
@@ -0,0 +1,86 @@
1
+ module SCEP
2
+ # Workaround for issue generating PKCS#7 certificate only in ruby 2.x.
3
+ #
4
+ # Normally you could generate the certificate chain using the following code:
5
+ #
6
+ # ```ruby
7
+ # p7certs = OpenSSL::PKCS7.new
8
+ # p7certs.type = 'signed'
9
+ # p7certs.certificates = [x509_cert_1, x509_cert_2]
10
+ # ````
11
+ #
12
+ # But producing this in a der format is not valid:
13
+ #
14
+ # ```ruby
15
+ # der = p7certs.to_der
16
+ # p7decoded = OpenSSL::PKCS7.new(der) # exception!
17
+ # ```
18
+ #
19
+ # This class manually creates the ASN1 notation and creates a correctly formatted result:
20
+ #
21
+ # ```ruby
22
+ # p7certs = Pkcs7CertOnly.new([x509_cert_1, x509_cert_2])
23
+ # der = p7certs.to_der
24
+ # p7decoded = OpenSSL::PKCS7.new(der) # works!
25
+ # p7decoded.certificates # => [ array of the original x509 certificates ]
26
+ # ```
27
+ #
28
+ # @see https://groups.google.com/forum/#!topic/mailing.openssl.users/AIZndhJuG7I
29
+ # @see https://gist.github.com/cgthornt/fe1f9d68e18cc4d1ba20
30
+ class PKCS7CertOnly
31
+ include OpenSSL::ASN1
32
+
33
+ # @return [Array<OpenSSL::X509::Certificate>]
34
+ attr_accessor :certificates
35
+
36
+ def initialize(certificates = [])
37
+ @certificates = certificates
38
+ end
39
+
40
+ # Takes a binary encoded DER PKCS#7 certificates only payload and decodes it
41
+ # @param [String] der_encoded the encoded payload
42
+ # @return [Pkcs7CertOnly]
43
+ def self.decode(der_encoded)
44
+ p7certs = OpenSSL::PKCS7.new(der_encoded)
45
+ new(p7certs.certificates)
46
+ end
47
+
48
+ # Converts this into an ASN1 sequence
49
+ # @return [OpenSSL::ASN1::Sequence]
50
+ def to_asn1
51
+
52
+ # Converts to an array of ASN1 encoded certs
53
+ asn1_certs = certificates.map do |cert|
54
+ decode(cert.to_der)
55
+ end
56
+
57
+ Sequence.new([
58
+ OpenSSL::ASN1::ObjectId.new('1.2.840.113549.1.7.2'),
59
+ ASN1Data.new([
60
+ Sequence.new([
61
+ OpenSSL::ASN1::Integer.new(1),
62
+ OpenSSL::ASN1::Set.new([]),
63
+ Sequence.new([
64
+ OpenSSL::ASN1::ObjectId.new('1.2.840.113549.1.7.1')
65
+ ]),
66
+ ASN1Data.new(asn1_certs, 0, :CONTEXT_SPECIFIC),
67
+ ASN1Data.new([], 1, :CONTEXT_SPECIFIC),
68
+ OpenSSL::ASN1::Set.new([])
69
+ ])
70
+ ], 0, :CONTEXT_SPECIFIC)
71
+ ])
72
+ end
73
+
74
+ # Gets this in a der (binary) format
75
+ # @return [String] binary encoded format
76
+ def to_der
77
+ to_asn1.to_der
78
+ end
79
+
80
+ # Gets this as a PKCS7 object
81
+ # @return [OpenSSL::PKCS7]
82
+ def to_pkcs7
83
+ OpenSSL::PKCS7.new(to_der)
84
+ end
85
+ end
86
+ end
@@ -0,0 +1,121 @@
1
+ module SCEP
2
+ module PKIOperation
3
+
4
+ # Base class that contains commonalities between both requests and repsonses:
5
+ #
6
+ # * {#ra_certificate RA Certificate}
7
+ # * {#ra_private_key RA Private Key}
8
+ #
9
+ class Base
10
+ DEFAULT_CIPHER_ALGORITHM = 'aes-256-cbc'
11
+
12
+ # Our keypair
13
+ # @return [Keypair]
14
+ attr_accessor :ra_keypair
15
+
16
+ # The last signed payload
17
+ # @return [OpenSSL::PKCS7]
18
+ attr_reader :p7sign
19
+
20
+ # The last encrypted payload
21
+ # @return [OpenSSL::PKCS7]
22
+ attr_reader :p7enc
23
+
24
+ # The store of trusted certs
25
+ # @return [OpenSSL::X509::Store]
26
+ attr_writer :x509_store
27
+
28
+ # Creates a new payload
29
+ # @param [Keypair] ra_keypair
30
+ def initialize(ra_keypair)
31
+ @ra_keypair = ra_keypair
32
+ end
33
+
34
+ # Gets an x509 store. Defaults to a store with system default paths. Used for
35
+ # {#unsign_and_unencrypt_raw decryption}.
36
+ # @return [OpenSSL::X509::Store]
37
+ def x509_store
38
+ @x509_store ||= OpenSSL::X509::Store.new
39
+ end
40
+
41
+ # Adds a certificate to verify against
42
+ # @param [OpenSSL::X509::Certificate] cert
43
+ def add_verification_certificate(cert)
44
+ x509_store.add_cert(cert)
45
+ end
46
+ alias_method :verify_against, :add_verification_certificate
47
+
48
+ protected
49
+
50
+ # Takes a raw binary string and returns the raw, unencrypted data
51
+ # @param [String] signed_and_encrypted_csr the signed and encrypted data
52
+ # @param [Boolean] verify if TRUE, verifies the signed PKCS7 payload against the {#x509_store}
53
+ # @raise [SCEP::PKIOperation::VerificationFailed] if `verify` is TRUE and the signed payload
54
+ # was *not* verified against the {#x509_store}.
55
+ # @return [String] the decrypted and unsigned data (original format)
56
+ def unsign_and_unencrypt_raw(signed_and_encrypted_csr, verify = true)
57
+ # Remove signature
58
+ @p7sign = OpenSSL::PKCS7.new(signed_and_encrypted_csr)
59
+
60
+ flags = OpenSSL::PKCS7::BINARY
61
+ flags |= OpenSSL::PKCS7::NOVERIFY unless verify
62
+
63
+ # See http://openssl.6102.n7.nabble.com/pkcs7-verification-with-ruby-td28455.html
64
+ verified = @p7sign.verify([], x509_store, nil, flags)
65
+
66
+ if !verified
67
+ raise SCEP::PKIOperation::VerificationFailed,
68
+ 'Unable to verify signature against certificate store - did you add the correct certificates?'
69
+ end
70
+
71
+
72
+ # Decrypt
73
+ @p7enc = OpenSSL::PKCS7.new(@p7sign.data)
74
+ @p7enc.decrypt(ra_keypair.private_key, ra_keypair.certificate, OpenSSL::PKCS7::BINARY)
75
+ end
76
+
77
+ # Signs and encrypts the given raw data
78
+ # @param [String] raw_data the raw data to sign and encrypt
79
+ # @param [OpenSSL::X509::Certificate] target_encryption_certs the cert(s) to encrypt for
80
+ # @param [OpenSSL::Cipher::Cipher] cipher the cipher to use. Defaults to {.create_default_cipher}
81
+ # @return [OpenSSL::PKCS7] the signed and encrypted payload
82
+ def sign_and_encrypt_raw(raw_data, target_encryption_certs, cipher = nil)
83
+ cipher ||= self.class.create_default_cipher
84
+
85
+ encrypted = OpenSSL::PKCS7.encrypt(
86
+ wrap_array(target_encryption_certs),
87
+ raw_data,
88
+ cipher,
89
+ OpenSSL::PKCS7::BINARY)
90
+
91
+ OpenSSL::PKCS7.sign(
92
+ ra_keypair.certificate,
93
+ ra_keypair.private_key,
94
+ encrypted.to_der,
95
+ [ra_keypair.certificate],
96
+ OpenSSL::PKCS7::BINARY)
97
+ end
98
+
99
+ # Creates an {OpenSSL::Cipher} using the {DEFAULT_CIPHER_ALGORITHM}. It's best to create a new Cipher object
100
+ # for every new encryption call so that we don't re-use sensitive data (IV's) [citation needed].
101
+ # @return [OpenSSL::Cipher]
102
+ def self.create_default_cipher
103
+ OpenSSL::Cipher.new(DEFAULT_CIPHER_ALGORITHM)
104
+ end
105
+
106
+ protected
107
+
108
+ # Same as `Array.wrap`
109
+ # @see http://apidock.com/rails/Array/wrap/class
110
+ def wrap_array(object)
111
+ if object.nil?
112
+ []
113
+ elsif object.respond_to?(:to_ary)
114
+ object.to_ary || [object]
115
+ else
116
+ [object]
117
+ end
118
+ end
119
+ end
120
+ end
121
+ end