mixlib-authentication 2.0.0 → 2.1.0
Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 8d5824d5178ca85fc7eb82bbd0699c21d0a3ddbbc97c9b41d78c24c6dd78af11
|
4
|
+
data.tar.gz: 522ee0cc91c67a8c824dc25d7d605cfd833f9c5c636435787cdfe8f0bc67d7b2
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: c1843f8908adb974f06173dc2a9bfc97a5b18fe11be4bbf8769c9e2af716e0cc259e275b5b2b00aa41b9d442f9e2e1f2fc8e111d5f46645e8e7544a3e8b44933
|
7
|
+
data.tar.gz: c8b6d61be5b2e9c8db0d6527f94c8ef347fbcc8aa08735dc0f00b10f82a90687a71a4058f1489a805b19ce296c1ae8f9bd7b1981e24fbac1c4ef6e9fe04ea742
|
data/Gemfile
CHANGED
@@ -95,9 +95,28 @@ module Mixlib
|
|
95
95
|
|
96
96
|
# Build the canonicalized request based on the method, other headers, etc.
|
97
97
|
# compute the signature from the request, using the looked-up user secret
|
98
|
-
#
|
99
|
-
#
|
100
|
-
|
98
|
+
#
|
99
|
+
# @param rsa_key [OpenSSL::PKey::RSA] User's RSA key. If `use_ssh_agent` is
|
100
|
+
# true, this must have the public key portion populated. If `use_ssh_agent`
|
101
|
+
# is false, this must have the private key portion populated.
|
102
|
+
# @param use_ssh_agent [Boolean] If true, use ssh-agent for request signing.
|
103
|
+
def sign(rsa_key, sign_algorithm = algorithm, sign_version = proto_version, **opts)
|
104
|
+
# Backwards compat stuff.
|
105
|
+
if sign_algorithm.is_a?(Hash)
|
106
|
+
# Was called like sign(key, sign_algorithm: 'foo', other: 'bar')
|
107
|
+
opts.update(sign_algorithm)
|
108
|
+
opts[:sign_algorithm] ||= algorithm
|
109
|
+
opts[:sign_version] ||= sign_version
|
110
|
+
else
|
111
|
+
# Was called like sign(key, 'foo', '1.3', other: 'bar')
|
112
|
+
Mixlib::Authentication.logger.warn("Using deprecated positional arguments for sign(), please update to keyword arguments (from #{caller[1][/^(.*:\d+):in /, 1]})")
|
113
|
+
opts[:sign_algorithm] ||= sign_algorithm
|
114
|
+
opts[:sign_version] ||= sign_version
|
115
|
+
end
|
116
|
+
sign_algorithm = opts[:sign_algorithm]
|
117
|
+
sign_version = opts[:sign_version]
|
118
|
+
use_ssh_agent = opts[:use_ssh_agent]
|
119
|
+
|
101
120
|
digest = validate_sign_version_digest!(sign_algorithm, sign_version)
|
102
121
|
# Our multiline hash for authorization will be encoded in multiple header
|
103
122
|
# lines - X-Ops-Authorization-1, ... (starts at 1, not 0!)
|
@@ -108,7 +127,7 @@ module Mixlib
|
|
108
127
|
"X-Ops-Content-Hash" => hashed_body(digest),
|
109
128
|
}
|
110
129
|
|
111
|
-
signature = Base64.encode64(do_sign(
|
130
|
+
signature = Base64.encode64(do_sign(rsa_key, digest, sign_algorithm, sign_version, use_ssh_agent)).chomp
|
112
131
|
signature_lines = signature.split(/\n/)
|
113
132
|
signature_lines.each_index do |idx|
|
114
133
|
key = "X-Ops-Authorization-#{idx + 1}"
|
@@ -244,18 +263,73 @@ module Mixlib
|
|
244
263
|
Mixlib::Authentication::Digester
|
245
264
|
end
|
246
265
|
|
247
|
-
#
|
248
|
-
|
266
|
+
# Low-level RSA signature implementation used in {#sign}.
|
267
|
+
#
|
268
|
+
# @api private
|
269
|
+
# @param rsa_key [OpenSSL::PKey::RSA] User's RSA key. If `use_ssh_agent` is
|
270
|
+
# true, this must have the public key portion populated. If `use_ssh_agent`
|
271
|
+
# is false, this must have the private key portion populated.
|
272
|
+
# @param digest [Class] Sublcass of OpenSSL::Digest to use while signing.
|
273
|
+
# @param sign_algorithm [String] Hash algorithm to use while signing.
|
274
|
+
# @param sign_version [String] Version number of the signing protocol to use.
|
275
|
+
# @param use_ssh_agent [Boolean] If true, use ssh-agent for request signing.
|
276
|
+
# @return [String]
|
277
|
+
def do_sign(rsa_key, digest, sign_algorithm, sign_version, use_ssh_agent)
|
249
278
|
string_to_sign = canonicalize_request(sign_algorithm, sign_version)
|
250
279
|
Mixlib::Authentication.logger.trace "String to sign: '#{string_to_sign}'"
|
251
280
|
case sign_version
|
252
281
|
when "1.3"
|
253
|
-
|
282
|
+
if use_ssh_agent
|
283
|
+
do_sign_ssh_agent(rsa_key, string_to_sign)
|
284
|
+
else
|
285
|
+
raise AuthenticationError, "RSA private key is required to sign requests, but a public key was provided" unless rsa_key.private?
|
286
|
+
rsa_key.sign(digest.new, string_to_sign)
|
287
|
+
end
|
254
288
|
else
|
255
|
-
|
289
|
+
raise AuthenticationError, "Agent signing mode requires signing protocol version 1.3 or newer" if use_ssh_agent
|
290
|
+
raise AuthenticationError, "RSA private key is required to sign requests, but a public key was provided" unless rsa_key.private?
|
291
|
+
rsa_key.private_encrypt(string_to_sign)
|
256
292
|
end
|
257
293
|
end
|
258
294
|
|
295
|
+
# Low-level signing logic for using ssh-agent. This requires the user has
|
296
|
+
# already set up ssh-agent and used ssh-add to load in a (possibly encrypted)
|
297
|
+
# RSA private key. ssh-agent supports keys other than RSA, however they
|
298
|
+
# are not supported as Chef's protocol explicitly requires RSA keys/sigs.
|
299
|
+
#
|
300
|
+
# @api private
|
301
|
+
# @param rsa_key [OpenSSL::PKey::RSA] User's RSA public key.
|
302
|
+
# @param string_to_sign [String] String data to sign with the requested key.
|
303
|
+
# @return [String]
|
304
|
+
def do_sign_ssh_agent(rsa_key, string_to_sign)
|
305
|
+
# First try loading net-ssh as it is an optional dependency.
|
306
|
+
begin
|
307
|
+
require "net/ssh"
|
308
|
+
rescue LoadError => e
|
309
|
+
# ???: Since agent mode is explicitly enabled, should we even catch
|
310
|
+
# this in the first place? Might be cleaner to let the LoadError bubble.
|
311
|
+
raise AuthenticationError, "net-ssh gem is not available, unable to use ssh-agent signing: #{e.message}"
|
312
|
+
end
|
313
|
+
|
314
|
+
# Try to connect to ssh-agent.
|
315
|
+
begin
|
316
|
+
agent = Net::SSH::Authentication::Agent.connect
|
317
|
+
rescue Net::SSH::Authentication::AgentNotAvailable => e
|
318
|
+
raise AuthenticationError, "Could not connect to ssh-agent. Make sure the SSH_AUTH_SOCK environment variable is set and ssh-agent is running: #{e.message}"
|
319
|
+
end
|
320
|
+
|
321
|
+
begin
|
322
|
+
ssh2_signature = agent.sign(rsa_key.public_key, string_to_sign, Net::SSH::Authentication::Agent::SSH_AGENT_RSA_SHA2_256)
|
323
|
+
rescue Net::SSH::Authentication::AgentError => e
|
324
|
+
raise AuthenticationError, "Unable to sign request with ssh-agent. Make sure your key is loaded with ssh-add: #{e.class.name} #{e.message})"
|
325
|
+
end
|
326
|
+
|
327
|
+
# extract signature from SSH Agent response => skip first 20 bytes for RSA keys
|
328
|
+
# "\x00\x00\x00\frsa-sha2-256\x00\x00\x01\x00"
|
329
|
+
# (see http://api.libssh.org/rfc/PROTOCOL.agent for details)
|
330
|
+
ssh2_signature[20..-1]
|
331
|
+
end
|
332
|
+
|
259
333
|
private :canonical_time, :canonical_path, :parse_signing_description, :digester, :canonicalize_user_id
|
260
334
|
|
261
335
|
end
|
@@ -25,6 +25,7 @@ require "ostruct"
|
|
25
25
|
require "openssl"
|
26
26
|
require "mixlib/authentication/signatureverification"
|
27
27
|
require "time"
|
28
|
+
require "net/ssh"
|
28
29
|
|
29
30
|
# TODO: should make these regular spec-based mock objects.
|
30
31
|
class MockRequest
|
@@ -101,6 +102,13 @@ describe "Mixlib::Authentication::SignedHeaderAuth" do
|
|
101
102
|
expect(V1_3_SHA256_SIGNING_OBJECT.sign(PRIVATE_KEY)).to eq(EXPECTED_SIGN_RESULT_V1_3_SHA256)
|
102
103
|
end
|
103
104
|
|
105
|
+
it "should generate the correct string to sign and signature for version 1.3 with SHA256 via ssh-agent" do
|
106
|
+
agent = double("ssh-agent")
|
107
|
+
expect(Net::SSH::Authentication::Agent).to receive(:connect).and_return(agent)
|
108
|
+
expect(agent).to receive(:sign).and_return(SSH_AGENT_RESPONSE)
|
109
|
+
expect(V1_3_SHA256_SIGNING_OBJECT.sign(PUBLIC_KEY, use_ssh_agent: true)).to eq(EXPECTED_SIGN_RESULT_V1_3_SHA256)
|
110
|
+
end
|
111
|
+
|
104
112
|
it "should generate the correct string to sign and signature for non-default proto version when used as a mixin" do
|
105
113
|
algorithm = "sha1"
|
106
114
|
version = "1.1"
|
@@ -113,14 +121,17 @@ describe "Mixlib::Authentication::SignedHeaderAuth" do
|
|
113
121
|
# the results of res.inspect and copy them as appropriate into the
|
114
122
|
# the constants in this file.
|
115
123
|
expect(V1_1_SIGNING_OBJECT.sign(PRIVATE_KEY, algorithm, version)).to eq(EXPECTED_SIGN_RESULT_V1_1)
|
124
|
+
expect(V1_1_SIGNING_OBJECT.sign(PRIVATE_KEY, sign_algorithm: algorithm, sign_version: version)).to eq(EXPECTED_SIGN_RESULT_V1_1)
|
116
125
|
end
|
117
126
|
|
118
127
|
it "should not choke when signing a request for a long user id with version 1.1" do
|
119
128
|
expect { LONG_SIGNING_OBJECT.sign(PRIVATE_KEY, "sha1", "1.1") }.not_to raise_error
|
129
|
+
expect { LONG_SIGNING_OBJECT.sign(PRIVATE_KEY, sign_algorithm: "sha1", sign_version: "1.1") }.not_to raise_error
|
120
130
|
end
|
121
131
|
|
122
132
|
it "should choke when signing a request for a long user id with version 1.0" do
|
123
133
|
expect { LONG_SIGNING_OBJECT.sign(PRIVATE_KEY, "sha1", "1.0") }.to raise_error(OpenSSL::PKey::RSAError)
|
134
|
+
expect { LONG_SIGNING_OBJECT.sign(PRIVATE_KEY, sign_algorithm: "sha1", sign_version: "1.0") }.to raise_error(OpenSSL::PKey::RSAError)
|
124
135
|
end
|
125
136
|
|
126
137
|
it "should choke when signing a request with a bad version" do
|
@@ -131,6 +142,18 @@ describe "Mixlib::Authentication::SignedHeaderAuth" do
|
|
131
142
|
expect { V1_1_SIGNING_OBJECT.sign(PRIVATE_KEY, "sha_poo", "1.1") }.to raise_error(Mixlib::Authentication::AuthenticationError)
|
132
143
|
end
|
133
144
|
|
145
|
+
it "should choke when signing a request via ssh-agent and ssh-agent is not reachable with version 1.3" do
|
146
|
+
expect(Net::SSH::Authentication::Agent).to receive(:connect).and_raise(Net::SSH::Authentication::AgentNotAvailable)
|
147
|
+
expect { V1_3_SHA256_SIGNING_OBJECT.sign(PUBLIC_KEY, use_ssh_agent: true) }.to raise_error(Mixlib::Authentication::AuthenticationError)
|
148
|
+
end
|
149
|
+
|
150
|
+
it "should choke when signing a request via ssh-agent and the key is not loaded with version 1.3" do
|
151
|
+
agent = double("ssh-agent")
|
152
|
+
expect(Net::SSH::Authentication::Agent).to receive(:connect).and_return(agent)
|
153
|
+
expect(agent).to receive(:sign).and_raise(Net::SSH::Authentication::AgentError)
|
154
|
+
expect { V1_3_SHA256_SIGNING_OBJECT.sign(PUBLIC_KEY, use_ssh_agent: true) }.to raise_error(Mixlib::Authentication::AuthenticationError)
|
155
|
+
end
|
156
|
+
|
134
157
|
end
|
135
158
|
|
136
159
|
describe "Mixlib::Authentication::SignatureVerification" do
|
@@ -366,6 +389,8 @@ X_OPS_AUTHORIZATION_LINES_V1_3_SHA256 = [
|
|
366
389
|
"MmbLUIm3JRYi00Yb01IUCCKdI90vUq1HHNtlTEu93YZfQaJwRxXlGkCNwIJe",
|
367
390
|
"fy49QzaCIEu1XiOx5Jn+4GmkrZch/RrK9VzQWXgs+w==",
|
368
391
|
]
|
392
|
+
|
393
|
+
SSH_AGENT_RESPONSE = "\x00\x00\x00\frsa-sha2-256\x00\x00\x01\x00\x15\x93\xA6\\\f\x8E\x04\x06PW\xFB\xB0\xD7\xCF\"\x06X\xC1%s\xA6\xFAo1C\xFF\nLb\xE4\x80l\x195\xC4E\xC6Mf\xF7\x9D\xD7\x8CM\xD6Tl\xB5tT\xFB\xE8\xA7\x9A5i\x8F\b\xDBC\x9A\x9A\xDF\x1Fi\xDA\xE5FE\xB5\xF2\xC8*\xB3\xEF\xEF\x19\xBC\xD1_\xA5\xCCL\xD3w\xD5\x81\xC2\xC7\xCF\xE3gY\xF4\xDF\x95\xF4\x8ERU\xF7\v\xFEU\xAB\xAEZ]\xC9\xB7\xDCx\x90\xB9\x8C\xE7\x0F\xE6\xDC\xDF%u\x94!<\e\xE9\x9D\xC4\xAE\r\xC3Su!\x1F\xD8}\x13J\x96\x95\x81\xAA\x9A#BV\xB0g\xA5\xEE\x92\x8BX\x14\xFC\x99~\xADyQH\xD6\xCB'\x81\xA5\x02\xB0\x0F\xB8\xBF{\xEA$\xD8%<\xC42f\xCBP\x89\xB7%\x16\"\xD3F\e\xD3R\x14\b\"\x9D#\xDD/R\xADG\x1C\xDBeLK\xBD\xDD\x86_A\xA2pG\x15\xE5\x1A@\x8D\xC0\x82^\x7F.=C6\x82 K\xB5^#\xB1\xE4\x99\xFE\xE0i\xA4\xAD\x97!\xFD\x1A\xCA\xF5\\\xD0Yx,\xFB"
|
369
394
|
# We expect Mixlib::Authentication::SignedHeaderAuth#sign to return this
|
370
395
|
# if passed the BODY above, based on version
|
371
396
|
|
@@ -529,6 +554,8 @@ YQIDAQAB
|
|
529
554
|
-----END PUBLIC KEY-----
|
530
555
|
EOS
|
531
556
|
|
557
|
+
PUBLIC_KEY = OpenSSL::PKey::RSA.new(PUBLIC_KEY_DATA)
|
558
|
+
|
532
559
|
PRIVATE_KEY_DATA = <<EOS
|
533
560
|
-----BEGIN RSA PRIVATE KEY-----
|
534
561
|
MIIEpAIBAAKCAQEA0ueqo76MXuP6XqZBILFziH/9AI7C6PaN5W0dSvkr9yInyGHS
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: mixlib-authentication
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 2.
|
4
|
+
version: 2.1.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Chef Software, Inc.
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2018-
|
11
|
+
date: 2018-05-31 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rspec-core
|
@@ -124,7 +124,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
124
124
|
version: '0'
|
125
125
|
requirements: []
|
126
126
|
rubyforge_project:
|
127
|
-
rubygems_version: 2.7.
|
127
|
+
rubygems_version: 2.7.6
|
128
128
|
signing_key:
|
129
129
|
specification_version: 4
|
130
130
|
summary: Mixes in simple per-request authentication
|