Skip to content
This repository was archived by the owner on Apr 13, 2024. It is now read-only.

Commit 7af3958

Browse files
author
David Sutton
committed
Broke out the SSH agent functionality into its own
class.
1 parent 141f265 commit 7af3958

File tree

2 files changed

+54
-58
lines changed

2 files changed

+54
-58
lines changed

http_signature/sign.py

Lines changed: 51 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -17,25 +17,19 @@
1717
'sha512': SHA512}
1818

1919
class Signer(object):
20-
def __init__(self, secret='~/.ssh/id_rsa', algorithm='rsa-sha256',
21-
allow_agent=False):
20+
def __init__(self, secret='~/.ssh/id_rsa', algorithm='rsa-sha256'):
2221
assert algorithm in ALGORITHMS, "Unknown algorithm"
23-
self._agent_key = False
2422
self._rsa = False
2523
self._hash = None
2624
self.sign_algorithm, self.hash_algorithm = algorithm.split('-')
27-
if allow_agent:
28-
try:
29-
import paramiko as ssh
30-
except ImportError:
31-
import ssh
32-
keys = ssh.Agent().get_keys()
33-
self._keys = filter(is_rsa, keys)
34-
if self._keys:
35-
self._agent_key = self._keys[0]
36-
self._keys = self._keys[1:]
37-
self.sign_algorithm, self.hash_algorithm = ('rsa', 'sha1')
38-
if not self._agent_key and self.sign_algorithm == 'rsa':
25+
self._get_key(secret)
26+
27+
@property
28+
def algorithm(self):
29+
return '%s-%s' % (self.sign_algorithm, self.hash_algorithm)
30+
31+
def _get_key(self, secret):
32+
if self.sign_algorithm == 'rsa':
3933
if (secret.startswith('-----BEGIN RSA PRIVATE KEY-----') or
4034
secret.startswith('-----BEGIN PRIVATE KEY-----')):
4135
# string with PEM encoded key data
@@ -54,13 +48,7 @@ def __init__(self, secret='~/.ssh/id_rsa', algorithm='rsa-sha256',
5448
elif self.sign_algorithm == 'hmac':
5549
self._hash = HMAC.new(secret, digestmod=HASHES[self.hash_algorithm])
5650

57-
@property
58-
def algorithm(self):
59-
return '%s-%s' % (self.sign_algorithm, self.hash_algorithm)
60-
61-
def sign_agent(self, sign_string):
62-
data = self._agent_key.sign_ssh_data(None, sign_string)
63-
return sig(data)
51+
return ""
6452

6553
def sign_rsa(self, sign_string):
6654
h = self._hash.new()
@@ -72,18 +60,10 @@ def sign_hmac(self, sign_string):
7260
hmac.update(sign_string)
7361
return hmac.digest()
7462

75-
def swap_keys(self):
76-
if self._keys:
77-
self._agent_key = self._keys[0]
78-
self._keys = self._keys[1:]
79-
else:
80-
self._agent_key = None
8163

8264
def sign(self, sign_string):
8365
data = None
84-
if self._agent_key:
85-
data = self.sign_agent(sign_string)
86-
elif self._rsa:
66+
if self._rsa:
8767
data = self.sign_rsa(sign_string)
8868
elif self._hash:
8969
data = self.sign_hmac(sign_string)
@@ -92,7 +72,40 @@ def sign(self, sign_string):
9272
return base64.b64encode(data)
9373

9474

95-
class HeaderSigner(object):
75+
class AgentSigner(Signer):
76+
def __init__(self, secret='~/.ssh/id_rsa', algorithm='rsa-sha256'):
77+
super(AgentSigner, self).__init__()
78+
self._agent_key = False
79+
80+
def _get_key(self):
81+
try:
82+
import paramiko as ssh
83+
except ImportError:
84+
import ssh
85+
keys = ssh.Agent().get_keys()
86+
self._keys = filter(is_rsa, keys)
87+
if self._keys:
88+
self._agent_key = self._keys[0]
89+
self._keys = self._keys[1:]
90+
self.sign_algorithm, self.hash_algorithm = ('rsa', 'sha1')
91+
92+
def swap_keys(self):
93+
if self._keys:
94+
self._agent_key = self._keys[0]
95+
self._keys = self._keys[1:]
96+
else:
97+
self._agent_key = None
98+
99+
def sign_agent(self, sign_string):
100+
data = self._agent_key.sign_ssh_data(None, sign_string)
101+
return sig(data)
102+
103+
def sign(self, sign_string):
104+
data = self.sign_agent(sign_string)
105+
return base64.b64encode(data)
106+
107+
108+
class HeaderSigner(Signer):
96109
'''
97110
Generic object that will sign headers as a dictionary using the http-signature scheme.
98111
https://github.com/joyent/node-http-signature/blob/master/http_signing.md
@@ -103,9 +116,8 @@ class HeaderSigner(object):
103116
headers is a list of http headers to be included in the signing string, defaulting to "Date" alone.
104117
'''
105118
def __init__(self, key_id='', secret='~/.ssh/id_rsa',
106-
algorithm='rsa-sha256', headers=None, allow_agent=False):
107-
self.signer = Signer(secret=secret, algorithm=algorithm,
108-
allow_agent=allow_agent)
119+
algorithm='rsa-sha256', headers=None):
120+
super(HeaderSigner, self).__init__(secret=secret, algorithm=algorithm)
109121
self.headers = headers
110122
self.signature_template = self.build_signature_template(
111123
key_id, algorithm, headers)
@@ -138,9 +150,10 @@ def sign_headers(self, headers, host=None, method=None, path=None,
138150
headers is a case-insensitive dict of mutable headers.
139151
host is a override for the 'host' header (defaults to value in headers).
140152
method is the HTTP method (used for 'request-line').
141-
pat is the HTTP path (used for 'request-line').
153+
path is the HTTP path (used for 'request-line').
142154
http_version is the HTTP version (used for 'request-line').
143155
"""
156+
headers = CaseInsensitiveDict(headers)
144157
if 'date' not in headers:
145158
now = datetime.now()
146159
stamp = mktime(now.timetuple())
@@ -170,24 +183,7 @@ def sign_headers(self, headers, host=None, method=None, path=None,
170183

171184
signable_list.append('%s: %s' % (h.lower(), headers[h]))
172185
signable = '\n'.join(signable_list)
173-
signature = self.signer.sign(signable)
186+
signature = self.sign(signable)
174187
headers['Authorization'] = self.signature_template % signature
175-
176-
def sign(self, h, method=None, path=None, http_version='1.1'):
177-
"""
178-
Return a dict of headers with the Authorization header added.
179-
180-
h is a dict of headers.
181-
method is the HTTP method (used for 'request-line').
182-
pat is the HTTP path (used for 'request-line').
183-
http_version is the HTTP version (used for 'request-line').
184-
"""
185-
# case insensitive copy of headers
186-
headers = CaseInsensitiveDict(h)
187-
self.sign_headers(
188-
headers=headers,
189-
method=method,
190-
path=path,
191-
http_version=http_version
192-
)
193188
return headers
189+

tests/test_signature.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ def setUp(self):
3030
def test_date_added(self):
3131
hs = HeaderSigner(key_id='', secret=self.key)
3232
unsigned = {}
33-
signed = hs.sign(unsigned)
33+
signed = hs.sign_headers(unsigned)
3434
self.assertIn('Date', signed)
3535
self.assertIn('Authorization', signed)
3636

@@ -39,7 +39,7 @@ def test_default(self):
3939
unsigned = {
4040
'Date': 'Thu, 05 Jan 2012 21:31:40 GMT'
4141
}
42-
signed = hs.sign(unsigned)
42+
signed = hs.sign_headers(unsigned)
4343
self.assertIn('Date', signed)
4444
self.assertEqual(unsigned['Date'], signed['Date'])
4545
self.assertIn('Authorization', signed)
@@ -67,7 +67,7 @@ def test_all(self):
6767
'Content-MD5': 'Sd/dVLAcvNLSq16eXua5uQ==',
6868
'Content-Length': '18',
6969
}
70-
signed = hs.sign(unsigned, method='POST',
70+
signed = hs.sign_headers(unsigned, method='POST',
7171
path='/foo?param=value&pet=dog')
7272
self.assertIn('Date', signed)
7373
self.assertEqual(unsigned['Date'], signed['Date'])

0 commit comments

Comments
 (0)