libsecurity
#Overview
For Project 2, we’re using OpenSSL 3’s libcrypto
for cryptographic primitives. However, directly interfacing with libcrypto
may prove to be quite
challenging for a lot of students. As such, we’re providing our own libsecurity
that has more course-grained cryptographic primitives that are specific
to Project 2.
(If you attended week 7’s discussion, you’ve already used some of these functions.)
#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) 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 2 spec for more info).
cert_size
is the corresponding total length of the certificate
.
#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
about this encoding. This is the format that we use to send public keys over the wire.
pub_key_size
is the corresponding total length of the public_key
.
#load_private_key
void load_private_key(const char* filename);
From a file on the local file system, load in a private key.
#Parameters
const char* filename
: The absolute or relative path to the private key. (Hint: in Project 2, it’s justserver_key.bin
.)
#get_private_key
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.
#set_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
).
#Parameters
EVP_PKEY* key
: The previously known private key state.
#load_peer_public_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
).
#Parameters
const uint8_t* peer_key
: ASN.1 encoded public key.size_t size
: Its corresponding size in bytes.
#load_ca_public_key
void load_ca_public_key(const char* filename);
From a file on the local file system, load in the CA’s public key.
#Parameters
const char* filename
: The absolute or relative path to the CA’s public key. (Hint: in Project 2, it’s justca_public_key.bin
.)
#load_certificate
void load_certificate(const char* filename);
From a file on the local file system, load in your certificate that has been signed by the CA into the certificate
global variable.
#Parameters
const char* filename
: The absolute or relative path to the certificate. (Hint: in Project 2, it’s justserver_cert.bin
.)
#generate_private_key
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
).
#derive_public_key
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.
#derive_secret
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.
#derive_keys
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.
#Parameters
const uint8_t* salt
: Buffer containing the salt material for key derivation. (Hint: For Project 2, it’s theClient-Hello
with theServer-Hello
appended right after.)size_t size
: Length in bytes of given salt.
#sign
size_t sign(uint8_t* signature, const uint8_t* data, size_t size);
Generate a signature over the given data using the private key.
#Parameters
uint8_t* signature
: Buffer to store the signature. ASN.1 ECDSA signatures have a maximum length of 72 bytes.const uint8_t* data
: Data to calculate signature over.size_t size
: Size of the given data in bytes.
#Returns
The resulting size of the signature stored in the signature
buffer.
#verify
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.
#Parameters
const uint8_t* signature
: Signature to verify.size_t sig_size
: Size of signature to verify.const uint8_t* data
: Data that signature was calculated over.size_t size
: Size of data.EVP_PKEY* authority
: Determines which public key to use when verifying the signature. (ec_ca_public_key
andec_peer_public_key
are options.)
#Returns
1 if verification is successful, 0 if the signature is invalid, and any other value if there was some error in processing the parameters.
#generate_nonce
void generate_nonce(uint8_t* buf, size_t size);
Write random bytes to a buffer up to a given size.
#Parameters
uint8_t* buf
: Buffer to write random bytes to.size_t size
: Number of bytes to write.
#encrypt_data
size_t encrypt_data(uint8_t* iv, uint8_t* cipher, const uint8_t* data, size_t size);
After deriving the ENC key, this function can take some data and output the corresponding ciphertext + IV (AES-256-CBC).
#Parameters
uint8_t* iv
: Buffer to write IV to.uint8_t* cipher
: Buffer to write ciphertext to.const uint8_t* data
: Buffer containing the plaintext to encrypt.size_t size
: Size of plaintext.
#Returns
The size of the ciphertext. Please note that the ciphertext can be up to 16 bytes longer than the plaintext.
#decrypt_cipher
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 can take some ciphertext and IV and output the corresponding plaintext.
#Parameters
uint8_t* data
: Buffer to write plaintext to.const uint8_t* cipher
: Buffer containing ciphertext.size_t size
: Size of ciphertext.const uint8_t* iv
: Buffer containing IV.
#Returns
The size of the plaintext.
#hmac
void hmac(uint8_t* digest, const uint8_t* data, size_t size);
Calculates HMAC digest over some given data using the derived MAC key.
#Parameters
uint8_t* digest
: Buffer to write digest to.const uint8_t* data
: Buffer to read data from.size_t size
: Size of data to calculate digest over.