In this lab you will design and implement decentralized public-key infrastructure.
Suppose that Alice wishes to share her contact information with her friends. To make ensure that this contact information can be trusted, Alice signs the information with a private key alice_private_key
.
(alice_public_key, alice_private_key) <- Gen()
signature <- Sign(alice_private_key, Contact(username="alice", email="[email protected]", ...))
Alice writes down alice_public_key
on her business card. When she meets her friend Bob, she hands him her card. Now Bob can tie Alice's Contact
signatures, received over our photo-sharing application, to her real-life identity.
assert Verify(alice_public_key, signature, Contact(username="alice", email="[email protected]", ...))
In this way, anyone whom Alice gave her card to can verify her contact. Unfortunately, this required her to physically meet the other person (which, perhaps you might imagine, could be inconvenient for some reason). Is there a way to avoid doing this?
We'd like to allow strangers to verify Alice's contact information without having personally met her. To do this, we will implement an idea borrowed from the "web of trust". In particular, in our system, friends who verify each others' identity in person exchange public keys and sign a statement attesting to the keys' validity. Chained signatures then form a "path of trust" which allows someone to verify the contact information of not just a friend, but also a friend of a friend, or of a friend of a friend of a friend, or ...
For instance, suppose that Cedric knows Bob and Bob knows Alice, but Cedric does not know Alice directly. Since Cedric knows Bob's public key, Cedric might request from the server Bob's signature of Alice's public key in order to verify her contact information.
Suppose that G represents a directed graph of user contacts. Whenever a user U1 executes add_contact
on some other user U2 and their public key K2, this adds an edge from U1 to U2 in G.
U1 might issue an api.UploadContactBookRequest
to notify the server of this change. If the server is functioning correctly, then on subsequent api.GetTrustLinkRequest
calls by U1 for any other user U3, the server should return a path from U1 to U3 if one exists.
We assume the following.
- All non-adversarial users U have a unique key, denoted K(U).
- For all pairs of users U1 and U2, if U1 is not adversarial, then U1 will
add_contact
for U2 with key K(U2) only if U2 is not adversarial. - If the server is functioning correctly and there exists at least one path from U1 to U2 in G, the server will respond to a
api.GetTrustLinkRequest
with one of these paths.
Given these assumptions, your implementation must do the following for any user U1 which get_trusted_user_public_key
of another user U2.
- Security: The procedure must return K(U2) only if there exists a path from U1 to U2 in G. Otherwise it must raise an
errors.InvalidTrustLinkError
. - Correctness: If the server is functioning correctly, and there exists such a path, then it must return K(U2).
is to modify add_contact
and get_trusted_user_public_key
so that users can indirectly verify the public keys of one another.
As with lab 1, this is an open-ended assignment! Again, you may modify any code in client.py
so long as you do not change the signatures of the public methods of Client
(i.e., the __init__
method or methods not beginning with an underscore _
). However, since your Client
must still talk to our server, you will not be able to modify any other files given to you.
upload a ZIP file containing client.py
to Gradescope.