diff --git a/Makefile b/Makefile index 930a0a0..224d126 100644 --- a/Makefile +++ b/Makefile @@ -11,6 +11,7 @@ compile: cd hashmap; make all; cd record; make all; cd routing; make all; + cd secio; make all; ar rcs libp2p.a $(OBJS) test: compile @@ -27,5 +28,6 @@ clean: cd test; make clean; cd record; make clean; cd routing; make clean; + cd secio; make all; rm -rf libp2p.a diff --git a/crypto/key.c b/crypto/key.c new file mode 100644 index 0000000..8d91484 --- /dev/null +++ b/crypto/key.c @@ -0,0 +1,77 @@ +#include + +/** + * Utilities for public keys + */ + +enum KeyType { KEYTYPE_RSA, KEYTYPE_ED25519, KEYTYPE_INVALID }; + +struct PublicKey { + enum KeyType key_type; + char* raw_key; + size_t raw_key_size; +}; + +struct PublicKey* libp2p_crypto_public_key_new() { + struct PublicKey* retVal = malloc(sizeof(struct PublicKey)); + if (retVal == NULL) + return NULL; + retVal->key_type = KEYTYPE_INVALID; + retVal->raw_key = NULL; + retVal->raw_key_size = 0; + return retVal; +} + +void libp2p_crypto_public_key_free(struct PublicKey* in) { + if (in != NULL) { + if (in->raw_key != NULL) + free(in->raw_key); + free(in); + in = NULL; + } +} + +/** + * Unmarshal a public key from a protobuf + */ +int libp2p_crypto_public_key_protobuf_decode(unsigned char* buffer, size_t buffer_length, struct PublicKey** out) { + + // first field is type (RSA vs ED25519) + // second field is the public key + + size_t pos = 0; + int retVal = 0; + + if ( (*out = libp2p_crypto_public_key_new()) == NULL) + goto exit; + + while(pos < buffer_length) { + size_t bytes_read = 0; + int field_no; + enum WireType field_type; + if (protobuf_decode_field_and_type(&buffer[pos], buffer_length, &field_no, &field_type, &bytes_read) == 0) { + goto exit; + } + pos += bytes_read; + switch(field_no) { + case (1): // type + if (protobuf_decode_varint(&buffer[pos], buffer_length - pos, &((*out)->key_type), &bytes_read) == 0) + goto exit; + pos += bytes_read; + break; + case (2): // key + if (protobuf_decode_length_delimited(&buffer[pos], buffer_length - pos, (char**)&((*out)->raw_key), &((*out)->raw_key_size), &bytes_read) == 0) + goto exit; + pos += bytes_read; + break; + } + } + + retVal = 1; + +exit: + if (retVal == 0) { + libp2p_crypto_public_key_free(*out); + } + return retVal; +} diff --git a/include/libp2p/crypto/key.h b/include/libp2p/crypto/key.h new file mode 100644 index 0000000..f201970 --- /dev/null +++ b/include/libp2p/crypto/key.h @@ -0,0 +1,29 @@ +#pragma once + +/** + * Utilities for public keys + */ + +enum KeyType { KEYTYPE_RSA, KEYTYPE_ED25519, KEYTYPE_INVALID }; + +struct PublicKey { + enum KeyType type; + unsigned char* data; + size_t data_size; +}; + +struct PrivateKey { + enum KeyType type; + unsigned char* data; + size_t data_size; +}; + +struct PublicKey* libp2p_crypto_public_key_new(); + +void libp2p_crypto_public_key_free(struct PublicKey* in); + +/** + * Unmarshal a public key from a protobuf + */ +int libp2p_crypto_public_key_protobuf_decode(unsigned char* buffer, size_t buffer_length, struct PublicKey** out); +int libp2p_crypto_private_key_protobuf_decode(unsigned char* buffer, size_t buffer_length, struct PublicKey** out); diff --git a/include/libp2p/secio/exchange.h b/include/libp2p/secio/exchange.h new file mode 100644 index 0000000..8e94a9c --- /dev/null +++ b/include/libp2p/secio/exchange.h @@ -0,0 +1,34 @@ +#pragma once + +struct Exchange { + unsigned char* epubkey; + size_t epubkey_size; + unsigned char* signature; + size_t signature_size; +}; + +/** + * retrieves the approximate size of an encoded version of the passed in struct + * @param in the struct to look at + * @reutrns the size of buffer needed + */ +size_t libp2p_secio_exchange_protobuf_encode_size(struct Exchange* in); + +/** + * Encode the struct Exchange in protobuf format + * @param in the struct to be encoded + * @param buffer where to put the results + * @param max_buffer_length the max to write + * @param bytes_written how many bytes were written to the buffer + * @returns true(1) on success, otherwise false(0) + */ +int libp2p_secio_exchange_protobuf_encode(struct Exchange* in, unsigned char* buffer, size_t max_buffer_length, size_t* bytes_written); + +/** + * Turns a protobuf array into an Exchange struct + * @param buffer the protobuf array + * @param max_buffer_length the length of the buffer + * @param out a pointer to the new struct Exchange NOTE: this method allocates memory + * @returns true(1) on success, otherwise false(0) + */ +int libp2p_secio_exchange_protobuf_decode(unsigned char* buffer, size_t max_buffer_length, struct Exchange** out); diff --git a/include/libp2p/secio/propose.h b/include/libp2p/secio/propose.h index 80fd6d1..ca31180 100644 --- a/include/libp2p/secio/propose.h +++ b/include/libp2p/secio/propose.h @@ -13,6 +13,18 @@ struct Propose { size_t hashes_size; }; +struct Propose* libp2p_secio_propose_new(); +void libp2p_secio_propose_free(struct Propose* in); +/** + * Helper to set the property to a value + * @param to the property + * @param to_size the size that matches the property + * @param from the value to be set in the property + * @param from_size the size of from + * @returns true(1) on success, otherwise false(0) + */ +int libp2p_secio_propose_set_property(void** to, size_t* to_size, void* from, size_t from_size); + /** * retrieves the approximate size of an encoded version of the passed in struct * @param in the struct to look at diff --git a/include/libp2p/secio/secio.h b/include/libp2p/secio/secio.h index f573f73..6dc669e 100644 --- a/include/libp2p/secio/secio.h +++ b/include/libp2p/secio/secio.h @@ -1,10 +1,13 @@ #pragma once +#include "libp2p/crypto/key.h" + /** * A secure connection */ struct SecureSession { int socket_descriptor; - + struct PublicKey remote_key; + int remote_peer_id; }; diff --git a/secio/Makefile b/secio/Makefile new file mode 100644 index 0000000..0baa3c7 --- /dev/null +++ b/secio/Makefile @@ -0,0 +1,18 @@ +CC = gcc +CFLAGS = -O0 -Wall -I../include -I../../c-protobuf + +ifdef DEBUG +CFLAGS += -g3 +endif + +LFLAGS = +DEPS = +OBJS = exchange.o propose.o secio.o + +%.o: %.c $(DEPS) + $(CC) -c -o $@ $< $(CFLAGS) + +all: $(OBJS) + +clean: + rm -f *.o diff --git a/secio/propose.c b/secio/propose.c index 82bbbb1..d95c342 100644 --- a/secio/propose.c +++ b/secio/propose.c @@ -1,14 +1,14 @@ -#include +#include +#include +#include "protobuf.h" #include "libp2p/secio/propose.h" // rand pubkey exchanges ciphers hashes enum WireType secio_propose_message_fields[] = { WIRETYPE_LENGTH_DELIMITED, WIRETYPE_LENGTH_DELIMITED, WIRETYPE_LENGTH_DELIMITED, WIRETYPE_LENGTH_DELIMITED, WIRETYPE_LENGTH_DELIMITED }; -// epubkey signature -enum WireType secio_exchange_message_fields[] = { WIRETYPE_LENGTH_DELIMITED, WIRETYPE_LENGTH_DELIMITED }; struct Propose* libp2p_secio_propose_new() { - struct Propose* retVal = (struct Propose*)malloc(sizeof(struct propose)); + struct Propose* retVal = (struct Propose*)malloc(sizeof(struct Propose)); if (retVal == NULL) return NULL; memset((void*)retVal, 0, sizeof(struct Propose)); @@ -28,9 +28,21 @@ void libp2p_secio_propose_free( struct Propose* in) { if (in->hashes != NULL) free(in->hashes); free(in); + in = NULL; } } +int libp2p_secio_propose_set_property(void** to, size_t* to_size, void* from, size_t from_size) { + if (*to != NULL) + free(*to); + *to = (void*)malloc(from_size); + if (*to == NULL) + return 0; + memcpy(*to, from, from_size); + *to_size = from_size; + return 1; +} + /** * retrieves the approximate size of an encoded version of the passed in struct * @param in the struct to look at @@ -56,27 +68,26 @@ size_t libp2p_secio_propose_protobuf_encode_size(struct Propose* in) { */ int libp2p_secio_propose_protobuf_encode(struct Propose* in, unsigned char* buffer, size_t max_buffer_length, size_t* bytes_written) { *bytes_written = 0; - int retVal; size_t bytes_used; // rand - if (!protobuf_encode_length_delimited(1, secio_propose_message_fields[0], in->rand, in->rand_size, &buffer[*bytes_written], max_buffer_length - *bytes_written, &bytes_used)) - return -1; + if (!protobuf_encode_length_delimited(1, secio_propose_message_fields[0], (char*)in->rand, in->rand_size, &buffer[*bytes_written], max_buffer_length - *bytes_written, &bytes_used)) + return 0; *bytes_written += bytes_used; // public key - if (!protobuf_encode_length_delimited(2, secio_propose_message_fields[1], in->public_key, in->public_key_size, &buffer[*bytes_written], max_buffer_length - *bytes_written, &bytes_used)) - return -1; + if (!protobuf_encode_length_delimited(2, secio_propose_message_fields[1], (char*)in->public_key, in->public_key_size, &buffer[*bytes_written], max_buffer_length - *bytes_written, &bytes_used)) + return 0; *bytes_written += bytes_used; // ciphers if (!protobuf_encode_length_delimited(3, secio_propose_message_fields[2], in->ciphers, in->ciphers_size, &buffer[*bytes_written], max_buffer_length - *bytes_written, &bytes_used)) - return -1; + return 0; *bytes_written += bytes_used; // exchanges if (!protobuf_encode_length_delimited(4, secio_propose_message_fields[3], in->exchanges, in->exchanges_size, &buffer[*bytes_written], max_buffer_length - *bytes_written, &bytes_used)) - return -1; + return 0; *bytes_written += bytes_used; // hashes if (!protobuf_encode_length_delimited(5, secio_propose_message_fields[4], in->hashes, in->hashes_size, &buffer[*bytes_written], max_buffer_length - *bytes_written, &bytes_used)) - return -1; + return 0; *bytes_written += bytes_used; return 1; } @@ -91,8 +102,6 @@ int libp2p_secio_propose_protobuf_encode(struct Propose* in, unsigned char* buff int libp2p_secio_propose_protobuf_decode(unsigned char* buffer, size_t buffer_length, struct Propose** out) { size_t pos = 0; int retVal = 0; - unsigned char* temp_buffer = NULL; - size_t temp_size; if (libp2p_secio_propose_new(out) == 0) goto exit; @@ -140,8 +149,5 @@ exit: if (retVal == 0) { libp2p_secio_propose_free(*out); } - if (temp_buffer != NULL) - free(temp_buffer); - return retVal; } diff --git a/secio/secio.c b/secio/secio.c index 1da0aff..8d794d2 100644 --- a/secio/secio.c +++ b/secio/secio.c @@ -1,5 +1,8 @@ -#include +#include +#include + #include "libp2p/secio/secio.h" +#include "libp2p/secio/propose.h" const char* SupportedExchanges = "P-256,P-384,P-521"; const char* SupportedCiphers = "AES-256,AES-128,Blowfish"; @@ -33,20 +36,46 @@ void libp2p_secio_secure_session_free(struct SecureSession* in) { * @returns true(1) on success, false(0) otherwise */ int libp2p_secio_secure_session_handshake(struct SecureSession* session, struct RsaPrivateKey* private_key) { + int retVal = 0, protobuf_size = 0, results_size = 0; + unsigned char* protobuf = 0;; + unsigned char* results; + struct Propose* propose = NULL; + struct SocketMuxer* socketMuxer; // generate 16 byte nonce char nonceOut[16]; - if (!generateNonce(nonceOut, 16)) - return 0; + if (!generateNonce(&nonceOut, 16)) { + goto exit; + } + + propose = libp2p_secio_propose_new(); + libp2p_secio_propose_set_property(&propose->rand, &propose->rand_size, nonceOut, 16); // will need: - // public key + // TODO: public key // supported exchanges + libp2p_secio_propose_set_property(&propose->exchanges, &propose->exchanges_size, SupportedExchanges, strlen(SupportedExchanges)); // supported ciphers + libp2p_secio_propose_set_property(&propose->ciphers, &propose->ciphers_size, SupportedCiphers, strlen(SupportedCiphers)); // supported hashes + libp2p_secio_propose_set_property(&propose->hashes, &propose->exchanges_size, SupportedHashes, strlen(SupportedHashes)); - // send request - // receive response + // send request (protobuf, then send) + protobuf_size = libp2p_secio_propose_protobuf_encode_size(propose); + protobuf = (unsigned char*) malloc(protobuf_size); + if (protobuf == NULL) + goto exit; + if (!libp2p_secio_propose_protobuf_encode(propose, protobuf, protobuf_size, &protobuf_size)) + goto exit; + libp2p_secio_propose_free(propose); + if (!libp2p_net_socket_muxer_send(socketMuxer, protobuf, protobuf_size)) + goto exit; + + // receive response (turn back into a Propose struct) + if (!libp2p_net_socket_muxer_receive(socketMuxer, &results, &results_size)) + goto exit; + if (!libp2p_secio_propose_protobuf_decode(results, results_size, &propose)) + goto exit; // get public key // generate their peer id @@ -68,4 +97,14 @@ int libp2p_secio_secure_session_handshake(struct SecureSession* session, struct // send expected message (local nonce) to verify encryption works + retVal = 1; + + exit: + + ipfs_secio_propose_free(propose); + if (protobuf != NULL) + free(protobuf); + + return retVal; + } diff --git a/test/crypto/test_key.h b/test/crypto/test_key.h new file mode 100644 index 0000000..65b72be --- /dev/null +++ b/test/crypto/test_key.h @@ -0,0 +1,64 @@ +#include + +#include "libp2p/crypto/key.h" + + +int test_protobuf_private_key() { + int retVal = 0; + size_t decode_base64_size = 0; + unsigned char* decode_base64; + // this is a base64 encoded private key. It makes it easier to test if it is in base64 form + char* orig_priv_key = "CAASpwkwggSjAgEAAoIBAQDTDJBWjDzS/HxDNOHazvzH2bu9CPMVHUrrvKRdBUM5ansL6/CC3MVZ6HVm4O6QHRapN6EF2CbrTgI4KBOXIL125Xo8MlROnyfXYk3O5q2tgwL/MbW8kXjtkyCfBak7MUoLOdLU7Svg0gkl3l+uDAiDcCLnwJVcFfq9ch6z4wMOhYJqE5dtx0uXxn6IuKWl1B69FTvBXCc0thw8Rw54b941FDcsBH5ttV9mRNCJym3poZ5qalNgXlxoIIB+PUx5QD+aq7KMJdpAX8HkapBntCOahP/QUceRmma0grlZLeYkH6/oi/hIrM6se3KUZ+F6tBuDFys8UAZy/X2BCUbKjbxtAgMBAAECggEANWfQfpYuLhXGPBt9q6kFPm1SnJtPJ+CpvM2XqhJS2IyhZnrl+bd0GTRBwS7aL42s1lVFYf04nAK5fQxnKK8YQqX/MIxr2RldM5ukpN6qxGWKtJkXrAgD2dqJPrRoBpqKahzPxSHfIJ0Fw5dqDtjsrpYJvyt0oEDPmnDuZAbmFx4sJqnesPNhKxtRMBx1+yxGVuRVJjHcqAgqPqwNiuoMEaYMY+G9yzT6vza8ovCpbX7BBIgM5fAT9PD8TBG//Vu9THvj/ZomiVG2qv6RL0qQyVb+DUzPZz1amBsSvahtXCl72jA3JwAZ943RxSR66P934S0ashkVwLUi46z/EAbJ4QKBgQDojGIO07BEVL2+7VxlGL9XGZQp4Y3qlhh2zDDQGwkCq/KQ+BdNYWitPwqRl9GqFLgpmeQIhyHTOa/IThx+AXGKVQ24ROH+skUs4IbO6R3qY7BKtb5lkZE/Yln09x70BBngUYAzh/rtnsXO3cl1x2XDDqUbCwlGcDAs8Jh/6UnvQwKBgQDoVSQs7Uq9MJCGIUM2bixX89tHzSxq5mn9wMD3/XRVfT5Ua8YkYBuzcmlcT39N7L5BwuyFqX3Vi7lv/Ya/qaQP6XkrZ8W1OAaTlYewfE5ZgknJqSpXcNWhABKeNmqndvqyQ/8HNCv/j8AdraGB2DGO57Xso5J0CQ43W/U9+QIyjwKBgHLL2hw3o+wXaRO3WMUPUmVM2zdRgR0suybp5a7Vqb0H5NZrohUw4NulIzJ8H6Q2VjMzJL6Q9sGu2HepF6ecTtBa7ErqtiVlG4Dr1aCOs5XhYEWBMlwxX+JKSt4Cn+UVoTB7Cy5lEhn7JurX0Xuy0ylXMWoIKKv89cs5eg6quzTBAoGAaq9eEztLjKCWXOE9SetBdYnG8aunb9cqaJlwgu/h0bfXPVDYBbAUSEyLURY4MQI7Q1tM3Pu9iqfEmUZj7/LoIV5mg6X9RX/alT6etk3+dF+9nlqN1OU9U9cCtZ/rTcb2y5EptJcidRH/eCFY/pTV/PcttOJPx/S4kHcroC+N8MUCgYEA6DA5QHxHfNN6Nxv+pEzy2DIxFe9RrBxS+KPBsra1C8jgdeMf4EmfU0Nox92V0q0bRrD5ztqQwSONI0hSRb1iiMWR6MuFnAFajUJfASjjIlZ6nIQjQslI7vjlvYyyHS/p/Codxap+yJlTLWwVEOXp2D9pWwiMq1xEyf0TH1BosvM="; + struct PrivateKey* private_key = libp2p_crypto_private_key_new(); + struct RsaPrivateKey rsa_private_key = {0}; + + // convert from base64 + + // 1) take the private key and turn it back into bytes (decode base 64) + decode_base64_size = libp2p_crypto_encoding_base64_decode_size(strlen(orig_priv_key)); + decode_base64 = (unsigned char*)malloc(decode_base64_size); + memset(decode_base64, 0, decode_base64_size); + + int retVal = libp2p_crypto_encoding_base64_decode((unsigned char*)orig_priv_key, strlen(orig_priv_key), &decode_base64[0], decode_base64_size, &decode_base64_size); + if (retVal == 0) + goto exit; + + // the first 5 bytes [0-4] are protobuf metadata before the DER encoded private key + // byte 0 is "Tag 1 which is a varint" + // byte 1 is the value of the varint + // byte 2 is "Tag 2 which is a type 2, length delimited field" + // bytes 3 & 4 is a varint with the value of 1191, which is the number of bytes that follow + + if (!libp2p_crypto_private_key_protobuf_decode(decode_base64, decode_base64_size, &private_key)) + goto exit; + + // 2) take the bytes of the private key and turn it back into a private key struct + if (!libp2p_crypto_encoding_x509_der_to_private_key(private_key->data, private_key->data_size, &rsa_private_key)) + goto exit; + + // 2b) take the private key and fill in the public key DER + if (!libp2p_crypto_rsa_private_key_fill_public_key(&rsa_private_key)) + goto exit; + + // 3) grab the public key, hash it, then base58 it + unsigned char hashed[32]; + ID_FromPK_non_null_terminated((char*)hashed, (unsigned char*)rsa_private_key.public_key_der, rsa_private_key.public_key_length); + size_t final_id_size = 1600; + unsigned char final_id[final_id_size]; + memset(final_id, 0, final_id_size); + if (!PrettyID(final_id, &final_id_size, hashed, 32)) + goto exit; + + // 4) compare results + if (orig_peer_id_size != final_id_size) + goto exit; + + if (strncmp(orig_peer_id, (char*)final_id, final_id_size) != 0) + goto exit; + + retVal = 1; + exit: + if (decode_base64 != NULL) + free(decode_base64); + return retVal; +}