mixlib-authentication 2.0.0 → 2.1.0
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
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
|