-
Notifications
You must be signed in to change notification settings - Fork 73
Spring20Cs361sLab4
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.
- 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.
- 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
- 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:
- Creating network connections between you and others
- Hand-crafting HTTP request and response headers
- Using
scapy
to quickly build and parse packets - Using Python's
cryptography
module to do some basic crypto - MAYBE do some cryptography attacks... we'll see how it goes
For this lab, you MUST use a virtual machine. You will need the virtual machine for three reasons.
- A uniform setup for simplified professor technical support
- Security protections for TLS visibility (preventing your computer from being hacked...)
- 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):
- Download Ubuntu
- Install in virtualbox (or a VM of your choice)
- Install Ubuntu in your VM
- Launch Ubuntu. The software will probably update
- sudo apt install gcc make perl
- install vbox add ons for screen resizing, drag and drop
- sudo apt install git
- use git/ssh/drag and drop to get files onto system
- sudo apt install python3-pip
- sudo apt install openssh-server, wget
- 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)
The following reading assignments are helpful for the lab and what we will discuss in class.
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:
- Bind to a specific address and port (a computer may have multiple addresses)
- Establish a backlog for how many connections can wait to be processed
- 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?
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:
- Will be confidential; no one else can read them
- Will be authenticated; no one else can forge them
- 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:
- Setup a forward over Hub and Spoke. (e.g.,
forward 9876 prof_mitm
) - Configure wget to use your hub and spoke forward as the HTTP proxy (e.g.,
export http_proxy=127.0.0.1:9876
) - 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.
- On your spoke
tap prof_mitm
- Make sure you've created a fifo pipe at
/tmp/dump1.pcap
(mkfifo /tmp/dump1.pcap
) - 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
set_tap_sink made_up_filename
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.
wget http://example.com
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:
- Client sends a "client hello"
- Server sends "Server Hello", "Certificate", "Key Share", and "Server Done"
- Client sends "Key Share", "Cipher Spec Change", "Finished"
- 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.
- The server's certificate cannot be forged by the MITM so long as the client correctly verifies it
- 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.
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 Method list:
- pkcs_os2ip(b) - converts bytes
b
to integer. Useful for converting DH params to integers - PRF.compute_master_secret(pms, cr, sr) - computes a PRF-generated master secret from
pms
, the pre-master secret,cr
, the client random, andsr
, the server random. Note that your PRF object is generated in theTLSSession
constructor - PRF.derive_key_block(ms, cs, sr, len) - computes a
len
length PRF-generated key block fromms
, the master secret,cr, the client random, and
srthe server random. Note that your PRF object is generated in the
TLSSession`constructor. - 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'sdone
message or"write"
for creating the server's done message.msgs
is all of the handshake messages so far andms
is the master secret. - _TLSSignature(sig_alg) - constructor for a
TLSSignature
object for signing the DH public key. For our purposes, thesig_alg
is 0x0401 - sig._update_sig(bytes, rsa_pri) - Update a
TLSSignature
object with bytes being signed using an RSA private key.sig
is aTLSSignature
object. The update is internal. - 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!
- Cert - A Certificate object for use in Scapy TLS classes. DO NOT CONFUSE WITH x509 cryptography classes! This is from SCAPY!
- TLSServerHello(gmt_unix_time. random_bytes, version, cipher) - Create a TLS Server HEllo Scapy object. The version value should be
0x303
and cipher should beTLS_DHE_RSA_WITH_AES_128_CBC_SHA.val
(don't forget the.val
). - TLSCertificate(certs) - Create a TLS Certificate Scapy object. The
certs
field should be a list of Scapy certificates (classCert
as shown above). - TLSServerKeyExchange(params, sig) - Create a TLS Server Key Exchange Scapy object.
params
should be the server's DH parameters andsig
should be aTLSSignature
over the parameters. - TLSChangeCipherSpec() - Create a Scapy TLS Change Cipher Spect message object
- 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
) - 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 thetls_session
. - 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
- TLSApplicationData(plaintext) - Create a TLS Application Data object from the bytes transmitted. The Data will have to be decrypted first
- 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 - tls_client_key_exchange.exchkeys - Extract the exchange keys from a Scapy
TLSClientKeyExchange
method - ClientDiffieHellmanPublic(exchkeys) - Create a Scapy DH Public key structure from exchange keys.
- dhp.dh_Yc - Extract the
Y
component from a Scapy DH public key structure - raw(pkt) - Convert any scapy packet
pkt
into bytes
Cryptography list:
- dh.DHParameterNumbers(p,q) - generates DH parameter numbers from
p
andq
- dh.DHPublicNumbers(y, pn) - generates a DH public numbers object from
y
andpn
, wherepn
, is a DH parameter numbers object. - pno.public_key(default_backend()) - generate a DH public key from
pno
, a public numbers object - 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 - hmac.HMAC(key, hash_type, default_backend()) - Create an HMAC object. The key is the MAC key. For our purposes,
hash_type
ishashes.SHA1()
. WARNING: Because of name collisions, I recommend `from cryptography.hazmat.primitives import hmac as crypto_hmac' - h.update(bytes) - Update an HMAC object
h
with bytes - h.finalize() - Get the final HMAC from
h
over all input bytes - algorithms.AES(key) - Create an AES algorithm object with a key
- modes.CBC(iv) - Create a CBC mode object with an IV
- Cipher(alg, mode, default_backend()) - Create a Cipher object; for our purposes, alg and mode will be AES algorithms and CBC modes.
- c.encryptor() - Create an encryptor object from
c
, a Cipher - c.decryptor() - Create a decryptor object from
c
, a Cipher - d.update(ciphertext) - Recover plaintext from ciphertext using
d
, a decryptor. - 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 - e.update(plaintext) - Create ciphertext from plaintext uing
e
, an encryptor - 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 - 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. - prikey.pub_key() - For any Cryptography private key class, generate a public key
- x509.CertificateBuilder - A Certificate Builder class. Look up documentation in the Python cryptography module
- cert.public_bytes(encoding) - Convert a certificate object to bytes.
encoding
should be eitherserialization.Encoding.PEM
orserialization.Encoding.DER
- pri_key.private_bytes(encoding, format, encryption) - Get the bytes of a private key for serialization. Encoding should be
serialization.Encoding.PEM
orserialization.Encoding.DER
. Format and encryption algorithm should beserialization.PrivateFormat.TraditionalOpenSSL
andserialization.NoEncryption()
respectively
Other utilities:
- struct.pack
- struct.unpack
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.
- Create a subdirectory in your github repository called labs/lab4. This should be the same repo that you used in the previous labs.
- 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.
- Add your finalized
http_proxy_student.py
file - 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