blob: 48035ce2cd15c18503d2ff9cdde32d4096d68c3d [file] [log] [blame]
davidben@chromium.org9a59f362014-04-10 17:56:44 +00001#!/usr/bin/env python
initial.commit4c2903c2008-07-27 00:38:33 +00002
davidben@chromium.org9a59f362014-04-10 17:56:44 +00003# Authors:
4# Trevor Perrin
5# Marcelo Fernandez - bugfix and NPN support
6# Martin von Loewis - python 3 port
7#
8# See the LICENSE file for legal information regarding use of this file.
9from __future__ import print_function
initial.commit4c2903c2008-07-27 00:38:33 +000010import sys
11import os
12import os.path
13import socket
initial.commit4c2903c2008-07-27 00:38:33 +000014import time
davidben@chromium.org9a59f362014-04-10 17:56:44 +000015import getopt
initial.commit4c2903c2008-07-27 00:38:33 +000016try:
davidben@chromium.org9a59f362014-04-10 17:56:44 +000017 import httplib
18 from SocketServer import *
19 from BaseHTTPServer import *
20 from SimpleHTTPServer import *
21except ImportError:
22 # Python 3.x
23 from http import client as httplib
24 from socketserver import *
25 from http.server import *
initial.commit4c2903c2008-07-27 00:38:33 +000026
27if __name__ != "__main__":
28 raise "This must be run as a command, not used as a module!"
29
initial.commit4c2903c2008-07-27 00:38:33 +000030from tlslite.api import *
davidben@chromium.org9a59f362014-04-10 17:56:44 +000031from tlslite import __version__
initial.commit4c2903c2008-07-27 00:38:33 +000032
33try:
davidben@chromium.org9a59f362014-04-10 17:56:44 +000034 from tack.structures.Tack import Tack
initial.commit4c2903c2008-07-27 00:38:33 +000035
davidben@chromium.org9a59f362014-04-10 17:56:44 +000036except ImportError:
37 pass
initial.commit4c2903c2008-07-27 00:38:33 +000038
davidben@chromium.org9a59f362014-04-10 17:56:44 +000039def printUsage(s=None):
40 if s:
41 print("ERROR: %s" % s)
initial.commit4c2903c2008-07-27 00:38:33 +000042
davidben@chromium.org9a59f362014-04-10 17:56:44 +000043 print("")
44 print("Version: %s" % __version__)
45 print("")
46 print("RNG: %s" % prngName)
47 print("")
48 print("Modules:")
49 if tackpyLoaded:
50 print(" tackpy : Loaded")
initial.commit4c2903c2008-07-27 00:38:33 +000051 else:
davidben@chromium.org9a59f362014-04-10 17:56:44 +000052 print(" tackpy : Not Loaded")
53 if m2cryptoLoaded:
54 print(" M2Crypto : Loaded")
55 else:
56 print(" M2Crypto : Not Loaded")
57 if pycryptoLoaded:
58 print(" pycrypto : Loaded")
59 else:
60 print(" pycrypto : Not Loaded")
61 if gmpyLoaded:
62 print(" GMPY : Loaded")
63 else:
64 print(" GMPY : Not Loaded")
65
66 print("")
67 print("""Commands:
68
69 server
70 [-k KEY] [-c CERT] [-t TACK] [-v VERIFIERDB] [-d DIR]
71 [--reqcert] HOST:PORT
72
73 client
74 [-k KEY] [-c CERT] [-u USER] [-p PASS]
75 HOST:PORT
76""")
77 sys.exit(-1)
78
79def printError(s):
80 """Print error message and exit"""
81 sys.stderr.write("ERROR: %s\n" % s)
82 sys.exit(-1)
initial.commit4c2903c2008-07-27 00:38:33 +000083
84
davidben@chromium.org9a59f362014-04-10 17:56:44 +000085def handleArgs(argv, argString, flagsList=[]):
86 # Convert to getopt argstring format:
87 # Add ":" after each arg, ie "abc" -> "a:b:c:"
88 getOptArgString = ":".join(argString) + ":"
89 try:
90 opts, argv = getopt.getopt(argv, getOptArgString, flagsList)
91 except getopt.GetoptError as e:
92 printError(e)
93 # Default values if arg not present
94 privateKey = None
95 certChain = None
96 username = None
97 password = None
98 tacks = None
99 verifierDB = None
100 reqCert = False
101 directory = None
102
103 for opt, arg in opts:
104 if opt == "-k":
105 s = open(arg, "rb").read()
106 privateKey = parsePEMKey(s, private=True)
107 elif opt == "-c":
108 s = open(arg, "rb").read()
109 x509 = X509()
110 x509.parse(s)
111 certChain = X509CertChain([x509])
112 elif opt == "-u":
113 username = arg
114 elif opt == "-p":
115 password = arg
116 elif opt == "-t":
117 if tackpyLoaded:
118 s = open(arg, "rU").read()
119 tacks = Tack.createFromPemList(s)
120 elif opt == "-v":
121 verifierDB = VerifierDB(arg)
122 verifierDB.open()
123 elif opt == "-d":
124 directory = arg
125 elif opt == "--reqcert":
126 reqCert = True
127 else:
128 assert(False)
129
130 if not argv:
131 printError("Missing address")
132 if len(argv)>1:
133 printError("Too many arguments")
134 #Split address into hostname/port tuple
135 address = argv[0]
136 address = address.split(":")
137 if len(address) != 2:
138 raise SyntaxError("Must specify <host>:<port>")
139 address = ( address[0], int(address[1]) )
140
141 # Populate the return list
142 retList = [address]
143 if "k" in argString:
144 retList.append(privateKey)
145 if "c" in argString:
146 retList.append(certChain)
147 if "u" in argString:
148 retList.append(username)
149 if "p" in argString:
150 retList.append(password)
151 if "t" in argString:
152 retList.append(tacks)
153 if "v" in argString:
154 retList.append(verifierDB)
155 if "d" in argString:
156 retList.append(directory)
157 if "reqcert" in flagsList:
158 retList.append(reqCert)
159 return retList
initial.commit4c2903c2008-07-27 00:38:33 +0000160
161
davidben@chromium.org9a59f362014-04-10 17:56:44 +0000162def printGoodConnection(connection, seconds):
163 print(" Handshake time: %.3f seconds" % seconds)
164 print(" Version: %s" % connection.getVersionName())
165 print(" Cipher: %s %s" % (connection.getCipherName(),
166 connection.getCipherImplementation()))
167 if connection.session.srpUsername:
168 print(" Client SRP username: %s" % connection.session.srpUsername)
169 if connection.session.clientCertChain:
170 print(" Client X.509 SHA1 fingerprint: %s" %
171 connection.session.clientCertChain.getFingerprint())
172 if connection.session.serverCertChain:
173 print(" Server X.509 SHA1 fingerprint: %s" %
174 connection.session.serverCertChain.getFingerprint())
175 if connection.session.serverName:
176 print(" SNI: %s" % connection.session.serverName)
177 if connection.session.tackExt:
178 if connection.session.tackInHelloExt:
179 emptyStr = "\n (via TLS Extension)"
180 else:
181 emptyStr = "\n (via TACK Certificate)"
182 print(" TACK: %s" % emptyStr)
183 print(str(connection.session.tackExt))
184 print(" Next-Protocol Negotiated: %s" % connection.next_proto)
185
186
187def clientCmd(argv):
188 (address, privateKey, certChain, username, password) = \
189 handleArgs(argv, "kcup")
190
191 if (certChain and not privateKey) or (not certChain and privateKey):
192 raise SyntaxError("Must specify CERT and KEY together")
193 if (username and not password) or (not username and password):
194 raise SyntaxError("Must specify USER with PASS")
195 if certChain and username:
196 raise SyntaxError("Can use SRP or client cert for auth, not both")
197
198 #Connect to server
199 sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
200 sock.settimeout(5)
201 sock.connect(address)
202 connection = TLSConnection(sock)
203
204 settings = HandshakeSettings()
205 settings.useExperimentalTackExtension = True
206
207 try:
208 start = time.clock()
209 if username and password:
210 connection.handshakeClientSRP(username, password,
211 settings=settings, serverName=address[0])
212 else:
213 connection.handshakeClientCert(certChain, privateKey,
214 settings=settings, serverName=address[0])
215 stop = time.clock()
216 print("Handshake success")
217 except TLSLocalAlert as a:
218 if a.description == AlertDescription.user_canceled:
219 print(str(a))
220 else:
221 raise
222 sys.exit(-1)
223 except TLSRemoteAlert as a:
224 if a.description == AlertDescription.unknown_psk_identity:
225 if username:
226 print("Unknown username")
227 else:
228 raise
229 elif a.description == AlertDescription.bad_record_mac:
230 if username:
231 print("Bad username or password")
232 else:
233 raise
234 elif a.description == AlertDescription.handshake_failure:
235 print("Unable to negotiate mutually acceptable parameters")
236 else:
237 raise
238 sys.exit(-1)
239 printGoodConnection(connection, stop-start)
240 connection.close()
241
242
243def serverCmd(argv):
244 (address, privateKey, certChain, tacks,
245 verifierDB, directory, reqCert) = handleArgs(argv, "kctbvd", ["reqcert"])
246
247
248 if (certChain and not privateKey) or (not certChain and privateKey):
249 raise SyntaxError("Must specify CERT and KEY together")
250 if tacks and not certChain:
251 raise SyntaxError("Must specify CERT with Tacks")
252
253 print("I am an HTTPS test server, I will listen on %s:%d" %
254 (address[0], address[1]))
255 if directory:
256 os.chdir(directory)
257 print("Serving files from %s" % os.getcwd())
258
259 if certChain and privateKey:
260 print("Using certificate and private key...")
261 if verifierDB:
262 print("Using verifier DB...")
263 if tacks:
264 print("Using Tacks...")
265
266 #############
267 sessionCache = SessionCache()
268
269 class MyHTTPServer(ThreadingMixIn, TLSSocketServerMixIn, HTTPServer):
270 def handshake(self, connection):
271 print("About to handshake...")
272 activationFlags = 0
273 if tacks:
274 if len(tacks) == 1:
275 activationFlags = 1
276 elif len(tacks) == 2:
277 activationFlags = 3
278
279 try:
280 start = time.clock()
281 settings = HandshakeSettings()
282 settings.useExperimentalTackExtension=True
283 connection.handshakeServer(certChain=certChain,
284 privateKey=privateKey,
285 verifierDB=verifierDB,
286 tacks=tacks,
287 activationFlags=activationFlags,
288 sessionCache=sessionCache,
289 settings=settings,
290 nextProtos=[b"http/1.1"])
291 # As an example (does not work here):
292 #nextProtos=[b"spdy/3", b"spdy/2", b"http/1.1"])
293 stop = time.clock()
294 except TLSRemoteAlert as a:
295 if a.description == AlertDescription.user_canceled:
296 print(str(a))
297 return False
298 else:
299 raise
300 except TLSLocalAlert as a:
301 if a.description == AlertDescription.unknown_psk_identity:
302 if username:
303 print("Unknown username")
304 return False
305 else:
306 raise
307 elif a.description == AlertDescription.bad_record_mac:
308 if username:
309 print("Bad username or password")
310 return False
311 else:
312 raise
313 elif a.description == AlertDescription.handshake_failure:
314 print("Unable to negotiate mutually acceptable parameters")
315 return False
316 else:
317 raise
318
319 connection.ignoreAbruptClose = True
320 printGoodConnection(connection, stop-start)
321 return True
322
323 httpd = MyHTTPServer(address, SimpleHTTPRequestHandler)
324 httpd.serve_forever()
325
326
327if __name__ == '__main__':
328 if len(sys.argv) < 2:
329 printUsage("Missing command")
330 elif sys.argv[1] == "client"[:len(sys.argv[1])]:
331 clientCmd(sys.argv[2:])
332 elif sys.argv[1] == "server"[:len(sys.argv[1])]:
333 serverCmd(sys.argv[2:])
334 else:
335 printUsage("Unknown command: %s" % sys.argv[1])
initial.commit4c2903c2008-07-27 00:38:33 +0000336