Libsecurity API
Project 3
For Project 3, we’re using OpenSSL 3’s libcrypto for cryptographic primitives. However, directly interfacing with libcrypto may prove to be quite challenging. As such, we’re providing our own libsecurity that has more course-grained cryptographic primitives specific to Project 3.
#Global Variables
EVP_PKEY* ec_peer_public_key
This variable is populated after a valid call to load_peer_public_key. Use this as the authority for verify if you’d like to check if some data was signed with the peer’s (the other client or server’s) private key.
EVP_PKEY* ec_ca_public_key
This variable is populated after a valid call to load_ca_public_key. Use this as the authority for verify if you’d like to check if some data was signed with the certificate authority’s private key.
uint8_t* certificate and size_t cert_size
This variable is populated after a valid call to load_certificate. This certificate is already encoded as TLV 0xA0 (see the Project 3 spec for more info). cert_size is the corresponding total length.
uint8_t* public_key and size_t pub_key_size
This variable is populated after a valid call to derive_public_key. This public key is encoded as ASN.1 DER; you do not need to know the specifics. This is the format used to send public keys over the wire. pub_key_size is the corresponding total length.
#Functions
void load_private_key(const char* filename);
From a file on the local file system, load in a private key.
Hint: In Project 3, the filename is just
server_key.bin.
EVP_PKEY* get_private_key();
Retrieve the current private key to potentially restore it later (using set_private_key).
Returns a pointer to the current state of the private key.
void set_private_key(EVP_PKEY* key);
Set the private key to some previously known private key state (retrieved using get_private_key).
void load_peer_public_key(const uint8_t* peer_key, size_t size);
Take some public key sent over the wire and load it as the peer’s public key (in ec_peer_public_key).
peer_key: ASN.1 encoded public keysize: Its corresponding size in bytes
void load_ca_public_key(const char* filename);
From a file on the local file system, load in the CA’s public key.
Hint: In Project 3, the filename is just
ca_public_key.bin.
void load_certificate(const char* filename);
From a file on the local file system, load in the certificate that has been signed by the CA into the certificate global variable.
Hint: In Project 3, the filename is just
server_cert.bin.
void generate_private_key();
Randomly generate a private key (can be retrieved using get_private_key). Make sure to call this (or load_private_key / set_private_key) before any operations that require a private key (such as derive_public_key, derive_secret, derive_keys, sign, encrypt_data, decrypt_cipher, and hmac).
void derive_public_key();
From the loaded private key, generate the public key in ASN.1 format and place it in the public_key global variable.
void derive_secret();
After loading the peer public key with load_peer_public_key and generating a private key, you may derive the shared (EC) Diffie-Hellman secret by calling this function.
void derive_keys(const uint8_t* salt, size_t size);
Using HKDF, derive two keys: (1) info ENC for symmetric encryption, and (2) info MAC for message authentication. Both keys need a salt.
salt: Buffer containing the salt material for key derivation. Hint: For Project 3, it’s theClient-Hellowith theServer-Helloappended right after.size: Length in bytes of given salt.
size_t sign(uint8_t* signature, const uint8_t* data, size_t size);
Generate a signature over the given data using the private key.
signature: Buffer to store the signature. ASN.1 ECDSA signatures have a maximum length of 72 bytes.data: Data to calculate signature over.size: Size of given data in bytes.
Returns the resulting size of the signature stored in the signature buffer.
int verify(const uint8_t* signature, size_t sig_size, const uint8_t* data, size_t size, EVP_PKEY* authority);
Verify if a given signature has indeed been signed by some authority over some data.
authority: Determines which public key to use when verifying the signature. (ec_ca_public_keyandec_peer_public_keyare options.)
Returns 1 if verification is successful, 0 if the signature is invalid, and any other value if there was an error in processing the parameters.
void generate_nonce(uint8_t* buf, size_t size);
Write random bytes to a buffer up to a given size.
size_t encrypt_data(uint8_t* iv, uint8_t* cipher, const uint8_t* data, size_t size);
After deriving the ENC key, this function takes some data and outputs the corresponding ciphertext + IV (AES-256-CBC).
iv: Buffer to write IV to.cipher: Buffer to write ciphertext to.data: Buffer containing the plaintext to encrypt.size: Size of plaintext.
Returns the size of the ciphertext. Note that the ciphertext can be up to 16 bytes longer than the plaintext.
size_t decrypt_cipher(uint8_t* data, const uint8_t* cipher, size_t size, const uint8_t* iv);
After deriving the ENC key, this function takes some ciphertext and IV and outputs the corresponding plaintext.
data: Buffer to write plaintext to.cipher: Buffer containing ciphertext.size: Size of ciphertext.iv: Buffer containing IV.
Returns the size of the plaintext.
void hmac(uint8_t* digest, const uint8_t* data, size_t size);
Calculates HMAC digest over some given data using the derived MAC key.
digest: Buffer to write digest to.data: Buffer to read data from.size: Size of data to calculate digest over.