Skip to content

Spring20Cs361sLab4

sethnielson edited this page Apr 27, 2020 · 12 revisions

TLS Man In The Middle

This Page is Under Construction and May Change

Introduction

Class. Given the realities of the COVID-19 outbreak, we are going to integrate a significant portion of our class-time learning with the lab itself.

There are THREE major goals for this lab, and the integrated classroom experiences.

  1. You will learn a lot more hands-on networking. This includes a deeper understanding of protocols and protocol stacks as well as TCP/IP programming with sockets and asynchronous communication patterns.
  2. You will learn how the TLs protocol works and how to read the RFC's that define this protocol. We will FOCUS on TLS 1.2, and all the labwork will be implemented around TLS 1.2, but we will also discuss the new TLS 1.3 protocol
  3. You will learn a little about cryptography including certificates, certificate chains, asymmetric cryptography, symmetric ciphers, MACs, and so forth

In addition to any assigned readings, we will have in-class exercises that will help you complete the lab as well as learn the material in general. These include:

  1. Creating network connections between you and others
  2. Hand-crafting HTTP request and response headers
  3. Using scapy to quickly build and parse packets
  4. Using Python's cryptography module to do some basic crypto
  5. MAYBE do some cryptography attacks... we'll see how it goes

Setup

For this lab, you MUST use a virtual machine. You will need the virtual machine for three reasons.

  1. A uniform setup for simplified professor technical support
  2. Security protections for TLS visibility (preventing your computer from being hacked...)
  3. Security protections for interconnecting with other students

There is no pre-configured virtual machine image. You will need to install VirtualBox, or a virtual machine hypervisor of your choosing, and then follow these instructions (some of which may be VirtualBox specific):

  1. Download Ubuntu
  2. Install in virtualbox (or a VM of your choice)
  3. Install Ubuntu in your VM
  4. Launch Ubuntu. The software will probably update
  5. sudo apt install gcc make perl
  6. install vbox add ons for screen resizing, drag and drop
  7. sudo apt install git
  8. use git/ssh/drag and drop to get files onto system
  9. sudo apt install python3-pip
  10. sudo apt install openssh-server, wget
  11. pip install cryptography, scapy

This lab and the associated in-class learning experiences will be in Python 3.

For the in-class learning experiences, please setup the [Network Classroom](Virtual Network Classroom)

Reading Assignments

The following reading assignments are helpful for the lab and what we will discuss in class.

  1. RFC 5246

In Class Experience 1: Sockets, Web Traffic, and Sniffing

For the first in-class experience, we will learn to use python to create some simple sockets and then use those sockets to send a basic http request.

First, let's talk about Python sockets. You can find the basic documentation here. It's a lot to sift through, so here's a much shorter version.

A "socket" is an abstraction around a TCP communication channel. Remember from our networking lessons that data gets to your computer via the IP protocol, but it gets to specific applications via TCP ports. It wouldn't be very useful if your web browser and your Spotify app couldn't differentiate their data. A TCP port provides what is called "demultiplexing"

Here's an ASCII art visualization.

SPOTIFY SERVER                    ----------------------------------
     |                            |   + - port x -> web browser    |
      ==========================> | = |                            |
     |                            |   + - port y -> spotify client |
   CNN.com                        ----------------------------------

A socket represents an individual TCP channel. You can send data over this channel or receive data over this channel.

So, let's take a quick look. We'll start with an outbound initial connection. Recall that TCP is bidirectional (full-duplex). Once a socket is open, you can send data in either direction. But we say it is an outbound connection for the party that initiates the communication. It is an inbound listner/server for the party that waits to be contacted.

To open a socket in python, import the socket module, create a socket object, and call the connect method. It's pretty simple.

import socket
s = socket.socket()
s.connect(('example.com',80))

That's it! With this three-line bit of python code, you have a TCP connection created to http://example.com. If you're not familiar with python syntax, notice that s.connect takes ONE argument, NOT TWO. The single argument is called a "tuple". In this case, it is a two-element tuple: ('example.com',80). The first element is the server's address and the second element is the server's port.

What about your own port? What is the outgoing port for your connection? Well, it's picked automatically. You can check your own hostname and port with s.getsockname().

Once you're connected, you can send data using the send() method. We'll practice sending data to example.com in a minute. But for now, let's work on setting up the other side of a connection.

The server side of a TCP connection is a bit more complicated, partially because it has to be able to handle multiple incoming connections. The steps of the process are:

  1. Bind to a specific address and port (a computer may have multiple addresses)
  2. Establish a backlog for how many connections can wait to be processed
  3. Wait for an incoming connection; when it arrives, spawn A NEW socket for that individual channel

Each of these steps can be a bit confusing, so we'll walk through them one at a time.

s = socket.socket() # starts out the same!
s.bind(('127.0.0.1', 8888))

This instructs the socket to "bind" the loopback interface (127.0.0.1) and port 8888 for the listener. By binding the interface 127.0.0.1, you are making it so the socket can ONLY receive connections from the local computer. If you want to receive connections from other computers, you must bind a public interface. Or, alternatively, if you use the empty string, the socket will bind ALL interfaces.

Now that we have a bound address, we have to "listen". This is an instruction for establishing how many incoming connections can wait in queue while a new one is being handled. For our tests, we won't have multiple connections, so even setting the value to 1 is fine:

s.listen(1)

This means that, if a second connection was attempted while the first was being processed, their connection attempt would be immediately closed.

Now that the socket is listening, it can accept connections. This is a blocking call, so once you call it the program will wait (stop) until a connection is available. Once a connection is available, it will create a NEW socket. The NEW socket is a 1:1 connection between this computer and the remote computer. The OLD socket still exists and can be used to accept additional/new connections. The listen() limit ONLY applies to how many are waiting in queue while in the process of accepting a new connection. Once the new connection is established, the old socket can still accept new ones.

The accept() method returns a data pair consisting of the new socket and the addr/port of the new connection.

new_sock, addr = s.accept()

To test this out, let's connect to ourselves on our own computers first. Do the following exercise in two separate Python interactive shells:

In shell 1, do the "server"

>>> import socket
>>> server_sock = socket.socket()
>>> server_sock.bind(('127.0.0.1', 8888))
>>> server_sock.listen(1)
>>> client1, addr1 = server_sock.accept()

At this point, the server shell should "stop". It is blocking on accept while it waits for a new connection.

In shell 2, we'll connect to the server with an outbound connection.

>>> import socket
>>> conn1 = socket.socket()
>>> conn1.connect(('127.0.0.1',8888))

Now, back in shell 1, you should see that accept returned. Print addr1 to see the connection data.

>>> client1, addr1 = server_sock.accept()
>>> addr1
('127.0.0.1', 6754)

Your port will be different of course. Before we send some data through these connections, let's get a second connection. In shell 1, call accept again

>>> client2, addr2 = server_sock.accept()

Again, it stops. To proceed, in shell 2 create a second connection

>>> conn2 = socket.socket()
>>> conn2.connect(('',8888))

In shell 1, you should no longer be waiting. Now let's send some data. First, have the server send the clients some data like this:

>>> client1.send(b'Hello client 1!')
>>> client2.send(b'Hello client 2!')

To receive the data, over on shell 2, call recv

>>> conn1.recv(1024)
b'Hello client 1!'
>>> conn2.recv(1024)
b'Hello client 2!'

You can send data in either direction. Please note that recv is a blocking call if no data is available. That is, if you call recv on one side, but have not sent any data from the other side, it will block until data arrives.

However, although the 1024 in the argument is how much data it can accept at once, it does not have to receive 1024 bytes to proceed. Any data is fine.

Either side may close the connection with close() (on the socket).

>>> conn2.close()

As an exercise, call the getsockname() and getpeername() methods on all of your sockets. What correspondence do you find?

Now that you have these basics set up, let's send data between ourselves and our friends in the class. Using the virtual network, please setup a server on a port and name it something unique. From the Spoke shell

>>> listen 8888 my_unique_name

Once you have this set up, in a python shell, setup a server to listen on port 8888, call accept() and wait.

Now, reach out to someone else in the class and have them connect to your server, or you connect to theirs. To setup a forward from your computer to theirs, get their unique name and call

>>> forward 9876 their_unique_name

In your second python shell, open a local connection to your port 9876 and the data will be forwarded to your colleague's server through Hub and Spoke.

>>> s = socket.socket()
>>> s.connect(('', 9876))

If you are hosting the server, you should now have accepted the connection.

>>> client, addr = s.accept()

Now try sending some hello messages between yourself and your peer.

But beware! You can sniff the traffic of your colleagues and they can sniff yours! You can sniff ALL the traffic going to and from a server using your spoke:

>>> tap somebody_server_name

Use the tap command and wireshark to see what kinds of messages your friends are sending each other.

The last part of our exercise today is to learn a bit about HTTP and HTTP proxying. In your labs folder you will find a file called http_proxy_student.py. This file does HTTP proxying. as you know, HTTP is for web traffic.

HTTP requests can be very, very simple. For example, using your newfound socket skills, send a basic GET request to example.com. A GET request looks like this:

GET / HTTP/1.0

To send this in a socket:

s.send(b"GET / HTTP/1.0\r\n\r\n")

The \r\n is the two-character code for marking a newline for some types of systems. It is used in HTTP to mark the end of a line. The HTTP request ends with a blank line (so, \r\n\ ends a line, but \r\n\r\n marks the end of two lines, one of which is empty).

If you check what the server sends back (use s.recv), you'll see that it looks weird. You don't see what you get if you go to example.com on a web browser.

The problem is, a single server is providing HTTP traffic for multiple domains. It can't figure out which domain you want. So, you have to provide the HOST: header. The GET requests looks like this

GET / HTTP/1.0
HOST: example.com

Give it a try and see what you get.

An HTTP proxy is a special type of proxy for HTTP traffic specifically. The Hub and Spoke we use in class just forward data without any concern about what type of data it is. An HTTP proxy knows how to read all or parts of the HTTP protocol and can help it in ways that a generic proxy cant.

To get started, take a look at your http_proxy_student.py file in your lab dir. This is a fully working Python HTTP proxy. For our experiments today, you do need to make a one line change. On line 446 (at the time of this writing), is a line that reads self.tls = True. Please change this to self.tls = False for now.

Now launch the proxy. Note that it uses port 8888, so if you have any Python socket's open on that port, please close them

python3 http_student_proxy.py 

Now, you can direct your wget through this proxy. To do so, you need to set an environment variable http_proxy and https_proxy.

export http_proxy=127.0.0.1:8888
export https_proxy=127.0.0.1:8888

Now, test out wget on example.com using both http and https

wget http://example.com
wget https://example.com

Both should work and you might not notice any difference. However, the differences are pretty extreme.

To see this, we're going to hook up our hub and spoke to the http proxy so we can spy on it. If you haven't already, setup your spoke to listen on 8888

>>> listen 8888 my_unique_server

Next, tap it

>>> tap my_unique_server

And make sure Wireshark is up and running.

Now, we're going to redirect our wget THROUGH hub and spoke, instead of through the HTTP proxy directly. Set up a forward of some random port

>>> forward 9876 my_unique_server

And change the http_proxy and https_proxy values to point to this

export http_proxy=127.0.0.1:9876
export https_proxy=127.0.0.1:9876

Here's a visual of how wget USED to work:

 wget (http_proxy=127.0.0.1:8888) ---> http_proxy_student.py (on 8888) ---> example.com

We changed it to go through hub and spoke

wget (http_proxy=127.0.0.1:9876)
  |
spoke (forwarding 9876 to my_unique_server) -----+
                                                 |
                                                hub
                                                 |
spoke (accept data for 8888) <-------------------+
  |
http_proxy_student.py (on 8888) -----> example.com

Now, you can observe the traffic between wget and http_proxy_student.py. What do you observe?

In Class Experience 2: HTTP Proxy and HTTPS Proxy

Now that you've experienced sending and receiving data over the network, and tapping that data, let's talk about using cryptography to secure communications between two parties. The communications between the two:

  1. Will be confidential; no one else can read them
  2. Will be authenticated; no one else can forge them
  3. Will have integrity; no one can undetectably change them en-route

These policies will be enforced even in the presence of a "man in the middle" (MITM). A MITM is an attacker that can intercept, drop, or even modify messages as they go between one party and another. The two parties are generally unaware of the MITM's presence.

Let's start our learning with regular HTTP proxying. You've already run an HTTP proxy in the previous experience. But let's review.

I will setup a web proxy over the Hub and Spoke system as prof_mitm1. Please setup wget to forward through it. This will take a couple of steps:

  1. Setup a forward over Hub and Spoke. (e.g., forward 9876 prof_mitm)
  2. Configure wget to use your hub and spoke forward as the HTTP proxy (e.g., export http_proxy=127.0.0.1:9876)
  3. Configure wget to use your hub and spoke forward as the HTTPS proxy (e.g., export https_proxy=127.0.0.1:9876)

You're setting up the FORWARD as the HTTP proxy. The real proxy is on my machine but the hub/spoke system will forward everything wget sends to it.

Now, setup a tap and make sure you have wireshark running.

  1. On your spoke tap prof_mitm
  2. Make sure you've created a fifo pipe at /tmp/dump1.pcap (mkfifo /tmp/dump1.pcap)
  3. Run wireshark using the pipe as the input (wireshark -k -i /tmp/dump1.pcap)

It is possible when tapping to get wireshark out of sync with the tap. Wireshark has to have the header and if it doesn't see it, it will freak out. If this happens, "reset" wireshark's data using the following commands

  1. set_tap_sink made_up_filename
  2. set_tap_sink /tmp/dump1.pcap

Now, ANY files flowing from or to my web proxy will be shown in your wireshark.

You can generate your own traffic by using wget to download a webfile.

  1. wget http://example.com
  2. wget https://example.com

Take a look at the traffic from these two connections. Can you see what's different?

In the HTTP proxying, what data was sent to the Proxy? Well, it's pretty much just a regular GET request. How does the proxy know what to do with it?

The HTTP GET request typically has a header field called host that indicates the end-host. It looks like this:

GET / HTTP/1.1
Host: www.example.com

When the proxy receives this request, it uses the Host field to know where to send the actual data.

With HTTPS, however, even the GET message is meant to be confidential (and otherwise secured). So, in the HTTPS proxying, you will see that it sends an HTTP CONNECT request. This request is used for establishing the connection only.

Once the connection is established, the client and server begin the TLS handshake. In TLS 1.2:

  1. Client sends a "client hello"
  2. Server sends "Server Hello", "Certificate", "Key Share", and "Server Done"
  3. Client sends "Key Share", "Cipher Spec Change", "Finished"
  4. Server sends "Cipher Spec Change", "Finished"

This exchange creates a confidential, authenticated, and integrous channel even if there is a MITM, as there is in this case. The MITM cannot read the encrypted data sent after the handshake, even though it observed the whole handshake.

How does this work.

  1. The server's certificate cannot be forged by the MITM so long as the client correctly verifies it
  2. The server sends a DH public key SIGNED by its certificate that cannot be forged

The Client's DH public key could be forged (creating a fake connection between the MITM and the server), but the client is usually authenticated after the fact (e.g., client login).

But the Client's DH public key aside, the client and server use DH to create a secret key between them. This key agreement protocol does not reveal the derived key, even to an eavesdropper.

The MITM Shell

We have already provided you a shell file for your MITM project. The provided python file can already handle HTTP and HTTPS proxying WITHOUT TLS visibility (no TLS packets are decrypted). Your assignment is to modify it to be able to decrypt and forge intercepted packets.

The shell file also includes some instructions for stub functions/methods that you need to fill in.

This section provides a quick overview of the provided code and the stubs.

First, let's talk about the networking. You have already created some sockets in Python, so you know the most basic way of creating sockets and using them to send/receive data. If you look through this file, there's not a socket to be found. Why is that?

If you remember, sockets are typically "blocking". That is, an accept() call blocks until new connections arrive. Or, recv() blocks until data is ready. If we wrote this code using sockets in blocking mode, we would have to use threads, or an equivalent, in order to make the code no get "stuck".

Sockets can also be opened in a non-blocking mode. In this mode, accept() doesn't block, but even still, how do you know when to accept and not accept? For an "event loop", many programmers use something called select. This mechanism allows the programmer to know when data is ready. Once ready, the non-blocking calls are executed.

But whether threads or select is used, it's a bit complicated to write.

So instead, we will use Python3's asyncio module. This module uses non-blocking sockets and select internally, but wraps it into a very nice event-loop API.

The asyncio module's more recent approach to network communication is to create asynchronous streams. I'm still not as familiar with those, so I'm using the older Protocol approach. The basic idea is that, instead of writing a handler for a socket's read/write, you create a Protocol class that implements the following methods:

connection_made(self, transport):

data_received(self, data):

connection_lost(self, reason=None):

Once the class is defined, it is passed to the Event Loop for either a Server (listening on a socket) or an Outbound Connection (on an outbound socket). Once the TCP connection is established, connection_made is called. When the TCP connection closes, connection_lost is called. In between, any time data is received, it is passed to the class through data_received. Outbound data can be sent using transport.write, where transport is passed as an argument to the class during connection_made.

All-in-all, I think it's straight-forward. You can see some more documentation (here)[https://docs.python.org/3/library/asyncio-protocol.html]. Make sure to look at the EchoServer and EchoClient examples.

You shouldn't need to create any Protocol classes in this code. We have already provided them. The classes are:

class ProxySocket(asyncio.Protocol):

class HTTPProxy(asyncio.Protocol):

These are the only two protocols that you'll need to deal with. HTTPProxy is the protocol that handles incoming requests from the client (e.g., the browser or wget). ProxySocket is the protocol that forwards the HTTP request to the end host (e.g., the web server) and receives the responses. The ProxySocket class, when created, keeps a back pointer to the HTTPProxy class that triggers it. When it receives data, it simply passes it back to HTTPProxy.

This process can be visualized as:

[client] -- new connection ---->  [HTTPProxy]

[client] -- GET / HTTP/1.1 ---->  [HTTPProxy]
            Host: example.com
                                           +->-+
[client]                          [HTTPProxy][ProxySocket] -- GET / HTTP/1.1 --> [Server]
                                                              Host: example.com
                                           +-<-+
[client]                          [HTTPProxy][ProxySocket] <-- HTTP/1.1 200 OK -- [Server]

In the code, the HTTPProxy hands data off to ProxySocket like this:

self.proxy_socket.transport.write(data)

(proxy_socket is set by the ProxySocket class in its connection_made).

And, ProxySocket passes the data back to HTTPProxy in its data_received method:

def data_received(self, data):
    self.proxy.handle_remote_response(data)

Both classes are already defined and do not need any changes.

But the HTTPPRoxy class also has a utility class called TLS_Visibility. This is the class that enables the HTTPPRoxy to intercept the TLS handshake and subvert it. It also enables it to

self.tls_handler = TLS_Visibility(server, port)

The TLS_Visbility class also handles decrypting encrypted data coming in from the client or the server and re-encrypting it to send to the other end of the connection. TLS_Visibility also records the data that it receives and processes the TLS state using another class called TLSSession. Both of these classes have the structural elements in place and only require you to fill in stub methods.

For example, here is a stub method in the TLSSession class:

def set_client_random(self, time_part, random_part):
    # STUDENT TODO
    """
    1. set client_time, client_bytes
    2. calculate client_random. There is a method for this
    """
    pass

Remember, for the TLS visibility, you receive data from the client. You have to store, for example, the random number the client sends in its hello message. This method allows you to do that. The client will send both a time_part and a completely random_part. As the instructions say, you need to set three values:

client_time
client_bytes
client_random

The first two should be obvious. The second one says there is "a method for this". Can you look through the methods of this class and see if there's something to help you?

On the other hand, if you look at the stub for set_server_random, you will see that the instructions are similar, but there are no inputs? How come there are no inputs for this one? In the client case, you are reading the data from the client. In the server case, you are generating the data. Both the time and random components can be calculated/generated within the function itself.

For some methods, however, you will need to use some methods provided by scapy's TLS library. You COULD implement these yourself, but it's error prone and not necessary. I've put hints in the code where you should use another method. A complete list of the scapy TLS methods you should know are listed in the next section.

Scapy, Cryptography, and Utilities

Scapy Method list:

  1. pkcs_os2ip(b) - converts bytes b to integer. Useful for converting DH params to integers
  2. PRF.compute_master_secret(pms, cr, sr) - computes a PRF-generated master secret from pms, the pre-master secret, cr, the client random, and sr, the server random. Note that your PRF object is generated in the TLSSession constructor
  3. PRF.derive_key_block(ms, cs, sr, len) - computes a len length PRF-generated key block from ms, the master secret, cr, the client random, and srthe server random. Note that your PRF object is generated in theTLSSession`constructor.
  4. PRF.compute_verify_data(party, mode, msgs, ms) - computes a PRF generated verify field. For our purposes, the party is always the string "server". Mode will b"server", mode is always the string "read" for processing the client's done message or "write" for creating the server's done message. msgs is all of the handshake messages so far and ms is the master secret.
  5. _TLSSignature(sig_alg) - constructor for a TLSSignature object for signing the DH public key. For our purposes, the sig_alg is 0x0401
  6. sig._update_sig(bytes, rsa_pri) - Update a TLSSignature object with bytes being signed using an RSA private key. sig is a TLSSignature object. The update is internal.
  7. X509_Cert(der_bytes) - An X509 certificate object for use in Scapy TLS classes. It can be filled in from DER-encoded bytes of an existing certificate. DO NOT CONFUSE WITH x509 cryptography classes! This is from SCAPY!
  8. Cert - A Certificate object for use in Scapy TLS classes. DO NOT CONFUSE WITH x509 cryptography classes! This is from SCAPY!
  9. TLSServerHello(gmt_unix_time. random_bytes, version, cipher) - Create a TLS Server HEllo Scapy object. The version value should be 0x303and cipher should be TLS_DHE_RSA_WITH_AES_128_CBC_SHA.val (don't forget the .val).
  10. TLSCertificate(certs) - Create a TLS Certificate Scapy object. The certs field should be a list of Scapy certificates (class Cert as shown above).
  11. TLSServerKeyExchange(params, sig) - Create a TLS Server Key Exchange Scapy object. params should be the server's DH parameters and sig should be a TLSSignature over the parameters.
  12. TLSChangeCipherSpec() - Create a Scapy TLS Change Cipher Spect message object
  13. tlsSession() - A Scapy TLS Session object. DO NOT CONFUSE with your own TLSSession object. We rarely need this, except occasionally to set the version (session.tls_version = 0x303)
  14. TLSFinished(vdata, tls_session) - Create a Scapy TLSFinished message. vdata is the verify data over handshake packets. Because Scapy screws up if the wrong version is set, you have to set the TLS version using the tls_session.
  15. TLSFinished(plaintext_data, tls_session=f_session) - RE-create a TLS Finished message from the client transmitted bytes. Because these bytes were encrypted, they have to be decrypted first. Also, set the version
  16. TLSApplicationData(plaintext) - Create a TLS Application Data object from the bytes transmitted. The Data will have to be decrypted first
  17. TLS(msg) - Create a TLS Scapy message complete with the TLS record layer. msg is the internal TLS message type, such as TLS Server Hello
  18. tls_client_key_exchange.exchkeys - Extract the exchange keys from a Scapy TLSClientKeyExchange method
  19. ClientDiffieHellmanPublic(exchkeys) - Create a Scapy DH Public key structure from exchange keys.
  20. dhp.dh_Yc - Extract the Y component from a Scapy DH public key structure
  21. raw(pkt) - Convert any scapy packet pkt into bytes

Cryptography list:

  1. dh.DHParameterNumbers(p,q) - generates DH parameter numbers from p and q
  2. dh.DHPublicNumbers(y, pn) - generates a DH public numbers object from y and pn, where pn, is a DH parameter numbers object.
  3. pno.public_key(default_backend()) - generate a DH public key from pno, a public numbers object
  4. dh_pri.exchange(dh_pub) - generate a derived key from a private key/public key DH exchange. Note that your private key is already generated for you in the TLSSession constructor
  5. hmac.HMAC(key, hash_type, default_backend()) - Create an HMAC object. The key is the MAC key. For our purposes, hash_type is hashes.SHA1(). WARNING: Because of name collisions, I recommend `from cryptography.hazmat.primitives import hmac as crypto_hmac'
  6. h.update(bytes) - Update an HMAC object h with bytes
  7. h.finalize() - Get the final HMAC from h over all input bytes
  8. algorithms.AES(key) - Create an AES algorithm object with a key
  9. modes.CBC(iv) - Create a CBC mode object with an IV
  10. Cipher(alg, mode, default_backend()) - Create a Cipher object; for our purposes, alg and mode will be AES algorithms and CBC modes.
  11. c.encryptor() - Create an encryptor object from c, a Cipher
  12. c.decryptor() - Create a decryptor object from c, a Cipher
  13. d.update(ciphertext) - Recover plaintext from ciphertext using d, a decryptor.
  14. d.finalize() - Finish a decryption operation for d, a decryptor. If the decryptor decrypts in blocks, finalize should not be called until all of the data processed is a multiple of blocklen
  15. e.update(plaintext) - Create ciphertext from plaintext uing e, an encryptor
  16. e.finalize() - Finish an encryption operation for e, an encryptor. If the encryptor encrypts in blocks, finalize should not be called unless a multiple of blocklen has been encrypted
  17. rsa.generate_private_key(exp, ksize, default_backend()) - Generate a new RSA private key object. The exp values should always be 65537. ksize is the size of the RSA key.
  18. prikey.pub_key() - For any Cryptography private key class, generate a public key
  19. x509.CertificateBuilder - A Certificate Builder class. Look up documentation in the Python cryptography module
  20. cert.public_bytes(encoding) - Convert a certificate object to bytes. encoding should be either serialization.Encoding.PEM or serialization.Encoding.DER
  21. pri_key.private_bytes(encoding, format, encryption) - Get the bytes of a private key for serialization. Encoding should be serialization.Encoding.PEM or serialization.Encoding.DER. Format and encryption algorithm should be serialization.PrivateFormat.TraditionalOpenSSL and serialization.NoEncryption() respectively

Other utilities:

  1. struct.pack
  2. struct.unpack

Documentation

  1. Python Cryptography
  2. Scapy TLS handshake

Submission and Grading

This lab is due on the last day of classes, April 27th 2020 at midnight (23:59). You may work in pairs.

As with other labs, please submit via github and tag your submission.

  1. Create a subdirectory in your github repository called labs/lab4. This should be the same repo that you used in the previous labs.
  2. Add a file called labs/lab4/ID.txt that contains either one line (if you worked by yourself) or two lines (if you worked with a partner), each in the following format: ProjID EID FirstName LastName. Here ProjID is the four-digit ID that you used in previous labs.
  3. Add your finalized http_proxy_student.py file
  4. Tag your commit using git tag lab4-1.0. If you make a change after submission, re-commit and tag with git tag lab4-1.x where x is one greater than the last submission