1#!/usr/bin/env python
2
3# 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
10import sys
11import os
12import os.path
13import socket
14import time
15import getopt
16try:
17    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 *
26
27if __name__ != "__main__":
28    raise "This must be run as a command, not used as a module!"
29
30from tlslite.api import *
31from tlslite import __version__
32
33try:
34    from tack.structures.Tack import Tack
35
36except ImportError:
37    pass
38
39def printUsage(s=None):
40    if s:
41        print("ERROR: %s" % s)
42
43    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")
51    else:
52        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)
83
84
85def 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
160
161
162def 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])
336
337