Initial import to Tizen
[profile/ivi/python-twisted.git] / twisted / conch / ssh / agent.py
1 # Copyright (c) Twisted Matrix Laboratories.
2 # See LICENSE for details.
3
4 """
5 Implements the SSH v2 key agent protocol.  This protocol is documented in the
6 SSH source code, in the file
7 U{PROTOCOL.agent<http://www.openbsd.org/cgi-bin/cvsweb/src/usr.bin/ssh/PROTOCOL.agent>}.
8
9 Maintainer: Paul Swartz
10 """
11
12 import struct
13
14 from twisted.conch.ssh.common import NS, getNS, getMP
15 from twisted.conch.error import ConchError, MissingKeyStoreError
16 from twisted.conch.ssh import keys
17 from twisted.internet import defer, protocol
18
19
20
21 class SSHAgentClient(protocol.Protocol):
22     """
23     The client side of the SSH agent protocol.  This is equivalent to
24     ssh-add(1) and can be used with either ssh-agent(1) or the SSHAgentServer
25     protocol, also in this package.
26     """
27
28     def __init__(self):
29         self.buf = ''
30         self.deferreds = []
31
32
33     def dataReceived(self, data):
34         self.buf += data
35         while 1:
36             if len(self.buf) <= 4:
37                 return
38             packLen = struct.unpack('!L', self.buf[:4])[0]
39             if len(self.buf) < 4 + packLen:
40                 return
41             packet, self.buf = self.buf[4:4 + packLen], self.buf[4 + packLen:]
42             reqType = ord(packet[0])
43             d = self.deferreds.pop(0)
44             if reqType == AGENT_FAILURE:
45                 d.errback(ConchError('agent failure'))
46             elif reqType == AGENT_SUCCESS:
47                 d.callback('')
48             else:
49                 d.callback(packet)
50
51
52     def sendRequest(self, reqType, data):
53         pack = struct.pack('!LB',len(data) + 1, reqType) + data
54         self.transport.write(pack)
55         d = defer.Deferred()
56         self.deferreds.append(d)
57         return d
58
59
60     def requestIdentities(self):
61         """
62         @return: A L{Deferred} which will fire with a list of all keys found in
63             the SSH agent. The list of keys is comprised of (public key blob,
64             comment) tuples.
65         """
66         d = self.sendRequest(AGENTC_REQUEST_IDENTITIES, '')
67         d.addCallback(self._cbRequestIdentities)
68         return d
69
70
71     def _cbRequestIdentities(self, data):
72         """
73         Unpack a collection of identities into a list of tuples comprised of
74         public key blobs and comments.
75         """
76         if ord(data[0]) != AGENT_IDENTITIES_ANSWER:
77             raise ConchError('unexpected response: %i' % ord(data[0]))
78         numKeys = struct.unpack('!L', data[1:5])[0]
79         keys = []
80         data = data[5:]
81         for i in range(numKeys):
82             blob, data = getNS(data)
83             comment, data = getNS(data)
84             keys.append((blob, comment))
85         return keys
86
87
88     def addIdentity(self, blob, comment = ''):
89         """
90         Add a private key blob to the agent's collection of keys.
91         """
92         req = blob
93         req += NS(comment)
94         return self.sendRequest(AGENTC_ADD_IDENTITY, req)
95
96
97     def signData(self, blob, data):
98         """
99         Request that the agent sign the given C{data} with the private key
100         which corresponds to the public key given by C{blob}.  The private
101         key should have been added to the agent already.
102
103         @type blob: C{str}
104         @type data: C{str}
105         @return: A L{Deferred} which fires with a signature for given data
106             created with the given key.
107         """
108         req = NS(blob)
109         req += NS(data)
110         req += '\000\000\000\000' # flags
111         return self.sendRequest(AGENTC_SIGN_REQUEST, req).addCallback(self._cbSignData)
112
113
114     def _cbSignData(self, data):
115         if ord(data[0]) != AGENT_SIGN_RESPONSE:
116             raise ConchError('unexpected data: %i' % ord(data[0]))
117         signature = getNS(data[1:])[0]
118         return signature
119
120
121     def removeIdentity(self, blob):
122         """
123         Remove the private key corresponding to the public key in blob from the
124         running agent.
125         """
126         req = NS(blob)
127         return self.sendRequest(AGENTC_REMOVE_IDENTITY, req)
128
129
130     def removeAllIdentities(self):
131         """
132         Remove all keys from the running agent.
133         """
134         return self.sendRequest(AGENTC_REMOVE_ALL_IDENTITIES, '')
135
136
137
138 class SSHAgentServer(protocol.Protocol):
139     """
140     The server side of the SSH agent protocol.  This is equivalent to
141     ssh-agent(1) and can be used with either ssh-add(1) or the SSHAgentClient
142     protocol, also in this package.
143     """
144
145     def __init__(self):
146         self.buf = ''
147
148
149     def dataReceived(self, data):
150         self.buf += data
151         while 1:
152             if len(self.buf) <= 4:
153                 return
154             packLen = struct.unpack('!L', self.buf[:4])[0]
155             if len(self.buf) < 4 + packLen:
156                 return
157             packet, self.buf = self.buf[4:4 + packLen], self.buf[4 + packLen:]
158             reqType = ord(packet[0])
159             reqName = messages.get(reqType, None)
160             if not reqName:
161                 self.sendResponse(AGENT_FAILURE, '')
162             else:
163                 f = getattr(self, 'agentc_%s' % reqName)
164                 if getattr(self.factory, 'keys', None) is None:
165                     self.sendResponse(AGENT_FAILURE, '')
166                     raise MissingKeyStoreError()
167                 f(packet[1:])
168
169
170     def sendResponse(self, reqType, data):
171         pack = struct.pack('!LB', len(data) + 1, reqType) + data
172         self.transport.write(pack)
173
174
175     def agentc_REQUEST_IDENTITIES(self, data):
176         """
177         Return all of the identities that have been added to the server
178         """
179         assert data == ''
180         numKeys = len(self.factory.keys)
181         resp = []
182
183         resp.append(struct.pack('!L', numKeys))
184         for key, comment in self.factory.keys.itervalues():
185             resp.append(NS(key.blob())) # yes, wrapped in an NS
186             resp.append(NS(comment))
187         self.sendResponse(AGENT_IDENTITIES_ANSWER, ''.join(resp))
188
189
190     def agentc_SIGN_REQUEST(self, data):
191         """
192         Data is a structure with a reference to an already added key object and
193         some data that the clients wants signed with that key.  If the key
194         object wasn't loaded, return AGENT_FAILURE, else return the signature.
195         """
196         blob, data = getNS(data)
197         if blob not in self.factory.keys:
198             return self.sendResponse(AGENT_FAILURE, '')
199         signData, data = getNS(data)
200         assert data == '\000\000\000\000'
201         self.sendResponse(AGENT_SIGN_RESPONSE, NS(self.factory.keys[blob][0].sign(signData)))
202
203
204     def agentc_ADD_IDENTITY(self, data):
205         """
206         Adds a private key to the agent's collection of identities.  On
207         subsequent interactions, the private key can be accessed using only the
208         corresponding public key.
209         """
210
211         # need to pre-read the key data so we can get past it to the comment string
212         keyType, rest = getNS(data)
213         if keyType == 'ssh-rsa':
214             nmp = 6
215         elif keyType == 'ssh-dss':
216             nmp = 5
217         else:
218             raise keys.BadKeyError('unknown blob type: %s' % keyType)
219
220         rest = getMP(rest, nmp)[-1] # ignore the key data for now, we just want the comment
221         comment, rest = getNS(rest) # the comment, tacked onto the end of the key blob
222
223         k = keys.Key.fromString(data, type='private_blob') # not wrapped in NS here
224         self.factory.keys[k.blob()] = (k, comment)
225         self.sendResponse(AGENT_SUCCESS, '')
226
227
228     def agentc_REMOVE_IDENTITY(self, data):
229         """
230         Remove a specific key from the agent's collection of identities.
231         """
232         blob, _ = getNS(data)
233         k = keys.Key.fromString(blob, type='blob')
234         del self.factory.keys[k.blob()]
235         self.sendResponse(AGENT_SUCCESS, '')
236
237
238     def agentc_REMOVE_ALL_IDENTITIES(self, data):
239         """
240         Remove all keys from the agent's collection of identities.
241         """
242         assert data == ''
243         self.factory.keys = {}
244         self.sendResponse(AGENT_SUCCESS, '')
245
246     # v1 messages that we ignore because we don't keep v1 keys
247     # open-ssh sends both v1 and v2 commands, so we have to
248     # do no-ops for v1 commands or we'll get "bad request" errors
249
250     def agentc_REQUEST_RSA_IDENTITIES(self, data):
251         """
252         v1 message for listing RSA1 keys; superseded by
253         agentc_REQUEST_IDENTITIES, which handles different key types.
254         """
255         self.sendResponse(AGENT_RSA_IDENTITIES_ANSWER, struct.pack('!L', 0))
256
257
258     def agentc_REMOVE_RSA_IDENTITY(self, data):
259         """
260         v1 message for removing RSA1 keys; superseded by
261         agentc_REMOVE_IDENTITY, which handles different key types.
262         """
263         self.sendResponse(AGENT_SUCCESS, '')
264
265
266     def agentc_REMOVE_ALL_RSA_IDENTITIES(self, data):
267         """
268         v1 message for removing all RSA1 keys; superseded by
269         agentc_REMOVE_ALL_IDENTITIES, which handles different key types.
270         """
271         self.sendResponse(AGENT_SUCCESS, '')
272
273
274 AGENTC_REQUEST_RSA_IDENTITIES   = 1
275 AGENT_RSA_IDENTITIES_ANSWER     = 2
276 AGENT_FAILURE                   = 5
277 AGENT_SUCCESS                   = 6
278
279 AGENTC_REMOVE_RSA_IDENTITY         = 8
280 AGENTC_REMOVE_ALL_RSA_IDENTITIES   = 9
281
282 AGENTC_REQUEST_IDENTITIES       = 11
283 AGENT_IDENTITIES_ANSWER         = 12
284 AGENTC_SIGN_REQUEST             = 13
285 AGENT_SIGN_RESPONSE             = 14
286 AGENTC_ADD_IDENTITY             = 17
287 AGENTC_REMOVE_IDENTITY          = 18
288 AGENTC_REMOVE_ALL_IDENTITIES    = 19
289
290 messages = {}
291 for name, value in locals().copy().items():
292     if name[:7] == 'AGENTC_':
293         messages[value] = name[7:] # doesn't handle doubles
294