summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMike Crute <mcrute@gmail.com>2010-05-05 00:35:50 -0400
committerMike Crute <mcrute@gmail.com>2010-05-05 00:35:50 -0400
commitd758df16807d451e791971c69accfb85fd207e6f (patch)
tree353129fa532bcefd55b72b19c8e43514b37796db
downloadhg_sshsign-d758df16807d451e791971c69accfb85fd207e6f.tar.bz2
hg_sshsign-d758df16807d451e791971c69accfb85fd207e6f.tar.xz
hg_sshsign-d758df16807d451e791971c69accfb85fd207e6f.zip
Initial checkin
-rw-r--r--keymanifest.py36
-rw-r--r--keys.py49
-rw-r--r--ssh.py20
-rw-r--r--sshagent.py75
-rw-r--r--structutils.py108
5 files changed, 288 insertions, 0 deletions
diff --git a/keymanifest.py b/keymanifest.py
new file mode 100644
index 0000000..df41f1e
--- /dev/null
+++ b/keymanifest.py
@@ -0,0 +1,36 @@
1# vim: set filencoding=utf8
2"""
3Key Manifest
4
5@author: Mike Crute (mcrute@gmail.com)
6@organization: SoftGroup Interactive, Inc.
7@date: May 05, 2010
8"""
9
10from keys import load_public_key
11
12
13class KeyManifest(dict):
14 """
15 KeyManifest stores a list of public keys indexed by their
16 comment field. This object acts like a dictionary and will
17 return public key instances for getitems.
18 """
19
20 @classmethod
21 def from_file(cls, filename):
22 inst = cls()
23 fp = open(filename)
24
25 for line in fp:
26 line = line.strip()
27 if not line or line.startswith('#'):
28 continue
29
30 _, key, user = line.split()
31 inst[user.strip()] = key.strip()
32
33 return inst
34
35 def __getitem__(self, key):
36 return load_public_key(dict.__getitem__(self, key))
diff --git a/keys.py b/keys.py
new file mode 100644
index 0000000..9b6b837
--- /dev/null
+++ b/keys.py
@@ -0,0 +1,49 @@
1# vim: set filencoding=utf8
2"""
3Key Loader Functions
4
5@author: Mike Crute (mcrute@gmail.com)
6@organization: SoftGroup Interactive, Inc.
7@date: May 05, 2010
8"""
9
10from M2Crypto import RSA, DSA
11from structutils import unpack_string, get_packed_mp_ints, int_to_bytes
12
13
14def load_public_key(key):
15 """
16 Loads an RFC 4716 formatted public key.
17 """
18 if key.startswith('ssh-'):
19 blob = key.split()[1].decode('base64')
20 else:
21 blob = key.decode('base64')
22
23 ktype, remainder = unpack_string(blob)
24
25 if ktype == 'ssh-rsa':
26 e, n = get_packed_mp_ints(remainder, 2)
27 return hawt.new_pub_key((e, n))
28 elif ktype == 'ssh-dss':
29 p, q, g, y = get_packed_mp_ints(remainder, 4)
30 return DSA.set_params(p, q, g)
31
32 raise ValueError("Invalid key")
33
34
35def load_private_key(filename):
36 first_line = open(filename).readline()
37 type = DSA if 'DSA' in first_line else RSA
38 return type.load_key(filename)
39
40
41def sign(what, key):
42 pk = load_private_key(key)
43 val = pk.sign(what, None)[0]
44 return int_to_bytes(val)
45
46
47def verify(what, signature, key):
48 signature = int_to_bytes(signature)
49 return bool(key.verify(what, signature))
diff --git a/ssh.py b/ssh.py
new file mode 100644
index 0000000..a4209dc
--- /dev/null
+++ b/ssh.py
@@ -0,0 +1,20 @@
1# vim: set filencoding=utf8
2"""
3SSH Key Signing
4
5@author: Mike Crute (mcrute@ag.com)
6@organization: American Greetings Interactive
7@date: May 03, 2010
8
9Commands to sign and verify revisions with your
10ssh key.
11"""
12
13
14
15
16
17
18
19if __name__ == '__main__':
20 pass
diff --git a/sshagent.py b/sshagent.py
new file mode 100644
index 0000000..2a43f5f
--- /dev/null
+++ b/sshagent.py
@@ -0,0 +1,75 @@
1# vim: set filencoding=utf8
2"""
3SSH Agent Management
4
5@author: Mike Crute (mcrute@gmail.com)
6@organization: SoftGroup Interactive, Inc.
7@date: May 05, 2010
8"""
9import os
10import socket
11import struct
12
13from structutils import pack_string, pack_int
14from structutils import unpack_int, unpack_string, unpack_mp_int
15
16
17class SSHAgent(object):
18 """
19 SSH Agent communication protocol for signing only.
20 """
21
22 SSH2_AGENT_SIGN_RESPONSE = 14
23 SSH2_AGENTC_SIGN_REQUEST = 13
24
25 def __init__(self, socket_path):
26 default_path = os.environ.get('SSH_AUTH_SOCK')
27 socket_path = default_path if not socket_path else socket_path
28
29 if not socket_path:
30 raise ValueError("Could not find an ssh agent.")
31
32 self.socket = None
33
34 def connect(self):
35 self.socket = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
36 self.socket.connect(self.socket_path)
37
38 def _build_packet(self, data, key):
39 key = pack_string(key)
40 data = pack_string(data)
41 flags = pack_int(0)
42
43 to_send = ''.join([chr(SSHAgent.SSH2_AGENTC_SIGN_REQUEST),
44 key, data, flags])
45 pkt_length = len(to_send)
46 packet = pack_int(pkg_length) + to_send
47
48 return packet
49
50 def _parse_response(self):
51 response_length = unpack_int(self.socket.recv(4, socket.MSG_WAITALL))[0]
52 if response_length == 1:
53 raise ValueError("Agent failed")
54
55 response = auth_sock.recv(response_length, socket.MSG_WAITALL)
56
57 status = ord(response[0])
58 if status != SSHAgent.SSH2_AGENT_SIGN_RESPONSE:
59 raise ValueError("Invalid response from agent")
60
61 _, remainder = unpack_int(response[1:])
62 _, remainder = unpack_string(remainder)
63 response, _ = unpack_mp_int(remainder)
64
65 return response
66
67 def sign(self, data, key):
68 packet = self._build_packet(data, key)
69
70 remaining = 0
71 while remaining < len(packet):
72 sent = self.socket.send(packet[remaining:])
73 remaining += sent
74
75 return self._parse_response()
diff --git a/structutils.py b/structutils.py
new file mode 100644
index 0000000..26b0188
--- /dev/null
+++ b/structutils.py
@@ -0,0 +1,108 @@
1# vim: set filencoding=utf8
2"""
3Utilities for Manipulating Byte Streams
4
5@author: Mike Crute (mcrute@gmail.com)
6@organization: SoftGroup Interactive, Inc.
7@date: May 05, 2010
8"""
9
10import struct
11
12
13INT_FORMAT = struct.Struct('>I')
14
15
16def pack_string(string):
17 """
18 Pack a string into a network style byte array.
19 """
20 return pack_int(len(string)) + string
21
22
23def pack_int(integer):
24 """
25 Pack an integer into a byte array.
26 """
27 return INT_FORMAT.pack(integer)
28
29
30def pack_mp_int(mp_int):
31 """
32 Pack a multiple-percision integer into a byte array.
33 """
34 return pack_string(int_to_bytes(mp_int))
35
36
37def unpack_string(buf):
38 """
39 Unpack a string from a byte array buffer returning
40 the string and the remainder of the buffer.
41 """
42 length, = INT_FORMAT.unpack(buf[:4])
43 string = buf[4:4+length]
44 remainder = buf[4+length:]
45 return string, remainder
46
47
48def unpack_mp_int(buf):
49 """
50 Unpack a multiple-percision integer from a byte array
51 buffer returning the string and the remainder of the
52 buffer.
53 """
54 length, = INT_FORMAT.unpack(buf[:4])
55 remainder = buf[4+length:]
56
57 return bytes_to_int(buf[4:4+length]), remainder
58
59
60def unpack_int(buf):
61 """
62 Unpack an integer from a byte array buffer returning the
63 string and the remainder of the buffer.
64 """
65 integer, = INT_FORMAT.unpack(buf[:4])
66 remainder = buf[4:]
67 return integer, remainder
68
69
70def get_packed_mp_ints(buf, count=1):
71 """
72 Get a number of multiple-percision integers from a byte
73 array buffer but leaves them as network style mpints.
74 """
75 ints = []
76 for _ in range(count):
77 length, = INT_FORMAT.unpack(buf[:4])
78 ints.append(buf[:4+length]
79 buf = buf[4+length:]
80
81 return ints
82
83
84def int_to_bytes(integer):
85 """
86 Convert an integer or a long integer to an array of
87 bytes.
88 """
89 bytes = []
90
91 while integer > 0:
92 integer, chunk = divmod(integer, 256)
93 bytes.insert(0, chr(chunk))
94
95 return ''.join(bytes)
96
97
98def bytes_to_int(buf):
99 """
100 Convert an array of bytes into an integer or long
101 integer.
102 """
103 integer = 0
104 for byte in buf:
105 integer <<= 8
106 integer += ord(byte)
107
108 return integer