1
<?xml version="1.0" encoding="utf-8"?><!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'><html lang="en" xmlns="http://www.w3.org/1999/xhtml">
3
<title>Twisted Documentation: Writing a client with Twisted.Conch</title>
4
<link href="stylesheet.css" rel="stylesheet" type="text/css"/>
8
<h1 class="title">Writing a client with Twisted.Conch</h1>
9
<div class="toc"><ol><li><a href="#auto0">Introduction</a></li><li><a href="#auto1">Writing a client</a></li><li><a href="#auto2">The Transport</a></li><li><a href="#auto3">The Authorization Client</a></li><li><a href="#auto4">The Connection</a></li><li><a href="#auto5">The Channel</a></li><li><a href="#auto6">The main() function</a></li></ol></div>
13
<h2>Introduction<a name="auto0"/></h2>
15
<p>In the original days of computing, rsh/rlogin were used to connect to
16
remote computers and execute commands. These commands had the problem
17
that the passwords and commands were sent in the clear. To solve this
18
problem, the SSH protocol was created. Twisted.Conch implements the
19
second version of this protocol.</p>
21
<h2>Writing a client<a name="auto1"/></h2>
23
<p>Writing a client with Conch involves sub-classing 4 classes: <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.conch.ssh.transport.SSHClientTransport.html" title="twisted.conch.ssh.transport.SSHClientTransport">twisted.conch.ssh.transport.SSHClientTransport</a></code>, <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.conch.ssh.userauth.SSHUserAuthClient.html" title="twisted.conch.ssh.userauth.SSHUserAuthClient">twisted.conch.ssh.userauth.SSHUserAuthClient</a></code>, <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.conch.ssh.connection.SSHConnection.html" title="twisted.conch.ssh.connection.SSHConnection">twisted.conch.ssh.connection.SSHConnection</a></code>, and <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.conch.ssh.channel.SSHChannel.html" title="twisted.conch.ssh.channel.SSHChannel">twisted.conch.ssh.channel.SSHChannel</a></code>. We'll start out
24
with <code class="python">SSHClientTransport</code> because it's the base
27
<h2>The Transport<a name="auto2"/></h2>
29
<pre class="python"><p class="py-linenumber"> 1
43
</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">conch</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">error</span>
44
<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">conch</span>.<span class="py-src-variable">ssh</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">transport</span>
45
<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">defer</span>
47
<span class="py-src-keyword">class</span> <span class="py-src-identifier">ClientTransport</span>(<span class="py-src-parameter">transport</span>.<span class="py-src-parameter">SSHClientTransport</span>):
49
<span class="py-src-keyword">def</span> <span class="py-src-identifier">verifyHostKey</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">pubKey</span>, <span class="py-src-parameter">fingerprint</span>):
50
<span class="py-src-keyword">if</span> <span class="py-src-variable">fingerprint</span> != <span class="py-src-string">'b1:94:6a:c9:24:92:d2:34:7c:62:35:b4:d2:61:11:84'</span>:
51
<span class="py-src-keyword">return</span> <span class="py-src-variable">defer</span>.<span class="py-src-variable">fail</span>(<span class="py-src-variable">error</span>.<span class="py-src-variable">ConchError</span>(<span class="py-src-string">'bad key'</span>))
52
<span class="py-src-keyword">else</span>:
53
<span class="py-src-keyword">return</span> <span class="py-src-variable">defer</span>.<span class="py-src-variable">succeed</span>(<span class="py-src-number">1</span>)
55
<span class="py-src-keyword">def</span> <span class="py-src-identifier">connectionSecure</span>(<span class="py-src-parameter">self</span>):
56
<span class="py-src-variable">self</span>.<span class="py-src-variable">requestService</span>(<span class="py-src-variable">ClientUserAuth</span>(<span class="py-src-string">'user'</span>, <span class="py-src-variable">ClientConnection</span>()))
59
<p>See how easy it is? <code class="python">SSHClientTransport</code>
60
handles the negotiation of encryption and the verification of keys
61
for you. The one security element that you as a client writer need to
62
implement is <code class="python">verifyHostKey()</code>. This method
63
is called with two strings: the public key sent by the server and its
64
fingerprint. You should verify the host key the server sends, either
65
by checking against a hard-coded value as in the example, or by asking
66
the user. <code class="python">verifyHostKey</code> returns a <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.internet.defer.Deferred.html" title="twisted.internet.defer.Deferred">twisted.internet.defer.Deferred</a></code> which gets a callback
67
if the host key is valid, or an errback if it is not. Note that in the
68
above, replace 'user' with the username you're attempting to ssh with,
69
for instance a call to <code class="python">os.getlogin()</code> for the
72
<p>The second method you need to implement is <code class="python">connectionSecure()</code>. It is called when the
73
encryption is set up and other services can be run. The example requests
74
that the <code class="python">ClientUserAuth</code> service be started.
75
This service will be discussed next.</p>
77
<h2>The Authorization Client<a name="auto3"/></h2>
79
<pre class="python"><p class="py-linenumber"> 1
111
</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">conch</span>.<span class="py-src-variable">ssh</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">keys</span>, <span class="py-src-variable">userauth</span>
113
<span class="py-src-comment"># these are the public/private keys from test_conch</span>
115
<span class="py-src-variable">publicKey</span> = <span class="py-src-string">'ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAGEArzJx8OYOnJmzf4tfBEvLi8DVPrJ3\
116
/c9k2I/Az64fxjHf9imyRJbixtQhlH9lfNjUIx+4LmrJH5QNRsFporcHDKOTwTTYLh5KmRpslkYHR\
117
ivcJSkbh/C+BR3utDS555mV'</span>
119
<span class="py-src-variable">privateKey</span> = <span class="py-src-string">"""-----BEGIN RSA PRIVATE KEY-----
120
MIIByAIBAAJhAK8ycfDmDpyZs3+LXwRLy4vA1T6yd/3PZNiPwM+uH8Yx3/YpskSW
121
4sbUIZR/ZXzY1CMfuC5qyR+UDUbBaaK3Bwyjk8E02C4eSpkabJZGB0Yr3CUpG4fw
122
vgUd7rQ0ueeZlQIBIwJgbh+1VZfr7WftK5lu7MHtqE1S1vPWZQYE3+VUn8yJADyb
123
Z4fsZaCrzW9lkIqXkE3GIY+ojdhZhkO1gbG0118sIgphwSWKRxK0mvh6ERxKqIt1
124
xJEJO74EykXZV4oNJ8sjAjEA3J9r2ZghVhGN6V8DnQrTk24Td0E8hU8AcP0FVP+8
125
PQm/g/aXf2QQkQT+omdHVEJrAjEAy0pL0EBH6EVS98evDCBtQw22OZT52qXlAwZ2
126
gyTriKFVoqjeEjt3SZKKqXHSApP/AjBLpF99zcJJZRq2abgYlf9lv1chkrWqDHUu
127
DZttmYJeEfiFBBavVYIF1dOlZT0G8jMCMBc7sOSZodFnAiryP+Qg9otSBjJ3bQML
128
pSTqy7c3a2AScC/YyOwkDaICHnnD3XyjMwIxALRzl0tQEKMXs6hH8ToUdlLROCrP
129
EhQ0wahUTCk1gKA4uPD6TMTChavbh4K63OvbKg==
130
-----END RSA PRIVATE KEY-----"""</span>
132
<span class="py-src-keyword">class</span> <span class="py-src-identifier">ClientUserAuth</span>(<span class="py-src-parameter">userauth</span>.<span class="py-src-parameter">SSHUserAuthClient</span>):
134
<span class="py-src-keyword">def</span> <span class="py-src-identifier">getPassword</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">prompt</span> = <span class="py-src-parameter">None</span>):
135
<span class="py-src-keyword">return</span>
136
<span class="py-src-comment"># this says we won't do password authentication</span>
138
<span class="py-src-keyword">def</span> <span class="py-src-identifier">getPublicKey</span>(<span class="py-src-parameter">self</span>):
139
<span class="py-src-keyword">return</span> <span class="py-src-variable">keys</span>.<span class="py-src-variable">getPublicKeyString</span>(<span class="py-src-variable">data</span> = <span class="py-src-variable">publicKey</span>)
141
<span class="py-src-keyword">def</span> <span class="py-src-identifier">getPrivateKey</span>(<span class="py-src-parameter">self</span>):
142
<span class="py-src-keyword">return</span> <span class="py-src-variable">defer</span>.<span class="py-src-variable">succeed</span>(<span class="py-src-variable">keys</span>.<span class="py-src-variable">getPrivateKeyObject</span>(<span class="py-src-variable">data</span> = <span class="py-src-variable">privateKey</span>))
145
<p>Again, fairly simple. The <code class="python">SSHUserAuthClient</code> takes care of most
146
of the work, but the actual authentication data needs to be
147
supplied. <code class="python">getPassword()</code> asks for a
148
password, <code class="python">getPublicKey()</code> and <code class="python">getPrivateKey()</code> get public and private keys,
149
respectively. <code class="python">getPassword()</code> returns
150
a <code class="python">Deferred</code> that is called back with
151
the password to use. <code class="python">getPublicKey()</code>
152
returns the SSH key data for the public key to use. <code class="python">keys.getPublicKeyString()</code> will take
153
keys in OpenSSH and LSH format, and convert them to the
154
required format. <code class="python">getPrivateKey()</code>
155
returns a <code class="python">Deferred</code> which is
156
called back with the key object (as used in PyCrypto) for
157
the private key. <code class="python">getPassword()</code>
158
and <code class="python">getPrivateKey()</code> return <code class="python">Deferreds</code> because they may need to ask the user
161
<p>Once the authentication is complete, <code class="python">SSHUserAuthClient</code> takes care of starting the code
162
<code class="python">SSHConnection</code> object given to it. Next, we'll
163
look at how to use the <code class="python">SSHConnection</code></p>
165
<h2>The Connection<a name="auto4"/></h2>
167
<pre class="python"><p class="py-linenumber">1
173
</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">conch</span>.<span class="py-src-variable">ssh</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">connection</span>
175
<span class="py-src-keyword">class</span> <span class="py-src-identifier">ClientConnection</span>(<span class="py-src-parameter">connection</span>.<span class="py-src-parameter">SSHConnection</span>):
177
<span class="py-src-keyword">def</span> <span class="py-src-identifier">serviceStarted</span>(<span class="py-src-parameter">self</span>):
178
<span class="py-src-variable">self</span>.<span class="py-src-variable">openChannel</span>(<span class="py-src-variable">CatChannel</span>(<span class="py-src-variable">conn</span> = <span class="py-src-variable">self</span>))
181
<p><code class="python">SSHConnection</code> is the easiest,
182
as it's only responsible for starting the channels. It has
183
other methods, those will be examined when we look at <code class="python">SSHChannel</code>.</p>
185
<h2>The Channel<a name="auto5"/></h2>
187
<pre class="python"><p class="py-linenumber"> 1
209
</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">conch</span>.<span class="py-src-variable">ssh</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">channel</span>, <span class="py-src-variable">common</span>
211
<span class="py-src-keyword">class</span> <span class="py-src-identifier">CatChannel</span>(<span class="py-src-parameter">channel</span>.<span class="py-src-parameter">SSHChannel</span>):
213
<span class="py-src-variable">name</span> = <span class="py-src-string">'session'</span>
215
<span class="py-src-keyword">def</span> <span class="py-src-identifier">channelOpen</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">data</span>):
216
<span class="py-src-variable">d</span> = <span class="py-src-variable">self</span>.<span class="py-src-variable">conn</span>.<span class="py-src-variable">sendRequest</span>(<span class="py-src-variable">self</span>, <span class="py-src-string">'exec'</span>, <span class="py-src-variable">common</span>.<span class="py-src-variable">NS</span>(<span class="py-src-string">'cat'</span>),
217
<span class="py-src-variable">wantReply</span> = <span class="py-src-number">1</span>)
218
<span class="py-src-variable">d</span>.<span class="py-src-variable">addCallback</span>(<span class="py-src-variable">self</span>.<span class="py-src-variable">_cbSendRequest</span>)
219
<span class="py-src-variable">self</span>.<span class="py-src-variable">catData</span> = <span class="py-src-string">''</span>
221
<span class="py-src-keyword">def</span> <span class="py-src-identifier">_cbSendRequest</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">ignored</span>):
222
<span class="py-src-variable">self</span>.<span class="py-src-variable">write</span>(<span class="py-src-string">'This data will be echoed back to us by "cat."\r\n'</span>)
223
<span class="py-src-variable">self</span>.<span class="py-src-variable">conn</span>.<span class="py-src-variable">sendEOF</span>(<span class="py-src-variable">self</span>)
224
<span class="py-src-variable">self</span>.<span class="py-src-variable">loseConnection</span>()
226
<span class="py-src-keyword">def</span> <span class="py-src-identifier">dataReceived</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">data</span>):
227
<span class="py-src-variable">self</span>.<span class="py-src-variable">catData</span> += <span class="py-src-variable">data</span>
229
<span class="py-src-keyword">def</span> <span class="py-src-identifier">closed</span>(<span class="py-src-parameter">self</span>):
230
<span class="py-src-keyword">print</span> <span class="py-src-string">'We got this from "cat":'</span>, <span class="py-src-variable">self</span>.<span class="py-src-variable">catData</span>
233
<p>Now that we've spent all this time getting the server and
234
client connected, here is where that work pays off. <code class="python">SSHChannel</code> is the interface between you and the
235
other side. This particular channel opens a session and plays with the
236
'cat' program, but your channel can implement anything, so long as the
237
server supports it.</p>
239
<p>The <code class="python">channelOpen()</code> method is
240
where everything gets started. It gets passed a chunk of data;
241
however, this chunk is usually nothing and can be ignored.
242
Our <code class="python">channelOpen()</code> initializes our
243
channel, and sends a request to the other side, using the
244
<code class="python">sendRequest()</code> method of the <code class="python">SSHConnection</code> object. Requests are used to send
245
events to the other side. We pass the method self so that it knows to
246
send the request for this channel. The 2nd argument of 'exec' tells the
247
server that we want to execute a command. The third argument is the data
248
that accompanies the request. <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/common.NS.html" title="common.NS">common.NS</a></code> encodes
249
the data as a length-prefixed string, which is how the server expects
250
the data. We also say that we want a reply saying that the process has a
251
been started. <code class="python">sendRequest()</code> then returns a
252
<code class="python">Deferred</code> which we add a callback for.</p>
254
<p>Once the callback fires, we send the data. <code class="python">SSHChannel</code> supports the <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/
257
twisted.internet.interface.Transport</a></code> interface, so
258
it can be given to Protocols to run them over the secure
259
connection. In our case, we just write the data directly. <code class="python">sendEOF()</code> does not follow the interface,
260
but Conch uses it to tell the other side that we will write no
261
more data. <code class="python">loseConnection()</code> shuts
262
down our side of the connection, but we will still receive data
263
through <code class="python">dataReceived()</code>. The <code class="python">closed()</code> method is called when both sides of the
264
connection are closed, and we use it to display the data we received
265
(which should be the same as the data we sent.)</p>
267
<p>Finally, let's actually invoke the code we've set up.</p>
269
<h2>The main() function<a name="auto6"/></h2>
270
<pre class="python"><p class="py-linenumber"> 1
280
</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">protocol</span>, <span class="py-src-variable">reactor</span>
282
<span class="py-src-keyword">def</span> <span class="py-src-identifier">main</span>():
283
<span class="py-src-variable">factory</span> = <span class="py-src-variable">protocol</span>.<span class="py-src-variable">ClientFactory</span>()
284
<span class="py-src-variable">factory</span>.<span class="py-src-variable">protocol</span> = <span class="py-src-variable">ClientTransport</span>
285
<span class="py-src-variable">reactor</span>.<span class="py-src-variable">connectTCP</span>(<span class="py-src-string">'localhost'</span>, <span class="py-src-number">22</span>, <span class="py-src-variable">factory</span>)
286
<span class="py-src-variable">reactor</span>.<span class="py-src-variable">run</span>()
288
<span class="py-src-keyword">if</span> <span class="py-src-variable">__name__</span> == <span class="py-src-string">"__main__"</span>:
289
<span class="py-src-variable">main</span>()
292
<P>We call <code class="python">connectTCP()</code> to connect to
293
localhost, port 22 (the standard port for ssh), and pass it an instance
294
of <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.internet.protocol.ClientFactory.html" title="twisted.internet.protocol.ClientFactory">twisted.internet.protocol.ClientFactory</a></code>.
295
This instance has the attribute <code class="python">protocol</code>
296
set to our earlier <code class="python">ClientTransport</code>
297
class. Note that the protocol attribute is set to the class <code class="python">ClientTransport</code>, not an instance of
298
<code class="python">ClientTransport</code>! When the <code class="python">connectTCP</code> call completes, the protocol will be
299
called to create a <code class="python">ClientTransport()</code> object
300
- this then invokes all our previous work.</P>
302
<P>It's worth noting that in the example <code class="python">main()</code>
303
routine, the <code class="python">reactor.run()</code> call never returns.
304
If you want to make the program exit, call
305
<code class="python">reactor.stop()</code> in the earlier
306
<code class="python">closed()</code> method.</P>
308
<P>If you wish to observe the interactions in more detail, adding a call
309
to <code class="python">log.startLogging(sys.stdout, setStdout=0)</code>
310
before the <code class="python">reactor.run()</code> call will send all
311
logging to stdout.</P>
315
<p><a href="index.html">Index</a></p>
316
<span class="version">Version: 10.0.0</span>
b'\\ No newline at end of file'