diff --git a/crypto/key.c b/crypto/key.c index 075f7a6..4f5e188 100644 --- a/crypto/key.c +++ b/crypto/key.c @@ -187,6 +187,9 @@ exit: */ int libp2p_crypto_public_key_to_peer_id(struct PublicKey* public_key, char** peer_id) { + /** + * Converting to a peer id involves protobufing the struct PublicKey, SHA256 it, turn it into a MultiHash and base58 it + */ size_t protobuf_len = libp2p_crypto_public_key_protobuf_encode_size(public_key); unsigned char protobuf[protobuf_len]; @@ -198,6 +201,7 @@ int libp2p_crypto_public_key_to_peer_id(struct PublicKey* public_key, char** pee size_t final_id_size = 100; unsigned char final_id[final_id_size]; memset(final_id, 0, final_id_size); + // turn it into a multihash and base58 it if (!PrettyID(final_id, &final_id_size, hashed, 32)) return 0; *peer_id = (char*)malloc(final_id_size + 1); diff --git a/include/libp2p/secio/propose.h b/include/libp2p/secio/propose.h index 3762c4d..c47bea9 100644 --- a/include/libp2p/secio/propose.h +++ b/include/libp2p/secio/propose.h @@ -3,6 +3,7 @@ struct Propose { unsigned char* rand; size_t rand_size; + // a protobuf'd struct PublicKey (defined in crypto/key.h) unsigned char* public_key; size_t public_key_size; char* exchanges; diff --git a/net/multistream.c b/net/multistream.c index ad90b98..8732727 100644 --- a/net/multistream.c +++ b/net/multistream.c @@ -33,49 +33,46 @@ int libp2p_net_multistream_send(int socket_fd, const unsigned char* data, size_t return num_bytes; } -/*** - * Parse the incoming data, removing the size indicator at the front of the array - * @param incoming the incoming data - * @param incoming_size the size of the incoming data - * @param outgoing the buffer to hold the outgoing data, allocated within this function - * @param outgoing_size the outgoing size - * @returns true(1) on success, otherwise false(0) - */ -int libp2p_net_multistream_parse_results(char* incoming, size_t incoming_size, char** outgoing, size_t* outgoing_size) { - int retVal = 0; - if (incoming_size > 0) { - // TODO: handle this differently - // read the varint - // allocate memory - *outgoing = (char*)malloc(incoming_size - 1); - if (*outgoing == NULL) - return 0; - // copy in the data - memcpy(*outgoing, &incoming[1], incoming_size - 1); - *outgoing_size = incoming_size - 1; - retVal = 1; - } - return retVal; -} - /** * Read from a multistream socket * @param socket_fd the socket file descriptor * @param results where to put the results. NOTE: this memory is allocated * @param results_size the size of the results in bytes - * @returns true(1) on success, otherwise false(0) + * @returns number of bytes received */ int libp2p_net_multistream_receive(int socket_fd, char** results, size_t* results_size) { int bytes = 0; size_t buffer_size = 65535; char buffer[buffer_size]; + char* pos = buffer; + int num_bytes = 0; + + // first read the varint + while(1) { + unsigned char c; + bytes = socket_read(socket_fd, (char*)&c, 1, 0); + pos[0] = c; + if (c >> 7 == 0) { + pos[1] = 0; + num_bytes = varint_decode((unsigned char*)buffer, strlen(buffer), NULL); + break; + } + pos++; + } + if (num_bytes <= 0) + return 0; + + // now read the number of bytes we've found + bytes = socket_read(socket_fd, buffer, num_bytes, 0); - bytes = socket_read(socket_fd, buffer, buffer_size, 0); if (bytes == 0) return 0; // parse the results, removing the leading size indicator - return libp2p_net_multistream_parse_results(buffer, bytes, results, results_size); + *results = malloc(bytes); + memcpy(*results, buffer, bytes); + *results_size = bytes; + return bytes; } diff --git a/secio/propose.c b/secio/propose.c index 3acc5a5..fce5c24 100644 --- a/secio/propose.c +++ b/secio/propose.c @@ -101,7 +101,7 @@ 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; + int retVal = 0, got_something = 0;; if ( (*out = libp2p_secio_propose_new()) == NULL) goto exit; @@ -113,37 +113,45 @@ int libp2p_secio_propose_protobuf_decode(unsigned char* buffer, size_t buffer_le if (protobuf_decode_field_and_type(&buffer[pos], buffer_length, &field_no, &field_type, &bytes_read) == 0) { goto exit; } + if (field_no < 1 || field_no > 5) { + fprintf(stderr, "Invalid character in Propose protobuf at position %lu. Value: %02x\n", pos, buffer[pos]); + } pos += bytes_read; switch(field_no) { case (1): // rand if (protobuf_decode_length_delimited(&buffer[pos], buffer_length - pos, (char**)&((*out)->rand), &((*out)->rand_size), &bytes_read) == 0) goto exit; pos += bytes_read; + got_something = 1; break; case (2): // public key if (protobuf_decode_length_delimited(&buffer[pos], buffer_length - pos, (char**)&((*out)->public_key), &((*out)->public_key_size), &bytes_read) == 0) goto exit; pos += bytes_read; + got_something = 1; break; case (3): // ciphers if (protobuf_decode_length_delimited(&buffer[pos], buffer_length - pos, (char**)&((*out)->ciphers), &((*out)->ciphers_size), &bytes_read) == 0) goto exit; pos += bytes_read; + got_something = 1; break; case (4): // exchanges if (protobuf_decode_length_delimited(&buffer[pos], buffer_length - pos, (char**)&((*out)->exchanges), &((*out)->exchanges_size), &bytes_read) == 0) goto exit; pos += bytes_read; + got_something = 1; break; case (5): // hashes if (protobuf_decode_length_delimited(&buffer[pos], buffer_length - pos, (char**)&((*out)->hashes), &((*out)->hashes_size), &bytes_read) == 0) goto exit; pos += bytes_read; + got_something = 1; break; } } - retVal = 1; + retVal = got_something; exit: if (retVal == 0) { diff --git a/secio/secio.c b/secio/secio.c index 16d5cfd..66448a0 100644 --- a/secio/secio.c +++ b/secio/secio.c @@ -2,11 +2,14 @@ #include // for debugging, can remove #include #include +#include +#include #include "libp2p/secio/secio.h" #include "libp2p/secio/propose.h" #include "libp2p/secio/exchange.h" #include "libp2p/net/multistream.h" +#include "libp2p/net/p2pnet.h" #include "libp2p/crypto/ephemeral.h" #include "libp2p/crypto/sha1.h" #include "libp2p/crypto/sha256.h" @@ -196,7 +199,8 @@ int libp2p_secio_sign(struct PrivateKey* private_key, const char* in, size_t in_ rsa_key.der = (char*)private_key->data; rsa_key.der_length = private_key->data_size; // SHA2-256 signatures are 32 bytes - *signature = (unsigned char*)malloc(32); + *signature_size = 32; + *signature = (unsigned char*)malloc(*signature_size); return libp2p_crypto_rsa_sign(&rsa_key, in, in_length, *signature); } // TODO: Implement this method for non-RSA @@ -361,14 +365,103 @@ int libp2p_secio_make_mac_and_cipher(struct SecureSession* session) { return 0; } -int libp2p_secio_write(struct SecureSession* session, unsigned char* bytes, size_t length) { - // TODO: Implement this method - return 0; +int libp2p_secio_write(struct SecureSession* session, unsigned char* bytes, size_t data_length) { + int num_bytes = 0; + + if (data_length > 0) { // only do this is if there is something to send + // first send the size + uint32_t size = htonl(data_length); + char* size_as_char = (char*)&size; + int left = 4; + int written = 0; + int written_this_time = 0; + do { + written_this_time = socket_write(session->socket_descriptor, &size_as_char[written], left, 0); + if (written_this_time < 0) { + written_this_time = 0; + if ( (errno == EAGAIN) || (errno == EWOULDBLOCK)) { + // TODO: use epoll or select to wait for socket to be writable + } else { + return 0; + } + } + left = left - written_this_time; + } while (left > 0); + // then send the actual data + left = data_length; + written = 0; + do { + written_this_time = socket_write(session->socket_descriptor, (char*)&bytes[written], left, 0); + if (written_this_time < 0) { + written_this_time = 0; + if ( (errno == EAGAIN) || (errno == EWOULDBLOCK)) { + // TODO: use epoll or select to wait for socket to be writable + } else { + return 0; + } + } + left = left - written_this_time; + written += written_this_time; + } while (left > 0); + num_bytes = written; + } // there was something to send + + return num_bytes; } -int libp2p_secio_read(struct SecureSession* session, unsigned char** bytes, size_t* bytes_read) { - // TODO: Implement this method - return 0; +int libp2p_secio_read(struct SecureSession* session, unsigned char** results, size_t* results_size) { + uint32_t buffer_size; + + // first read the 4 byte integer + char* size = (char*)&buffer_size; + int left = 4; + int read = 0; + int read_this_time = 0; + do { + read_this_time = socket_read(session->socket_descriptor, &size[read], 1, 0); + if (read_this_time < 0) { + read_this_time = 0; + if ( (errno == EAGAIN) || (errno == EWOULDBLOCK)) { + // TODO: use epoll or select to wait for socket to be writable + } else { + return 0; + } + } + if (read == 0 && size[0] == 10) { + // a spurious \n + // write over this value by not adding it + } else { + left = left - read_this_time; + read += read_this_time; + } + } while (left > 0); + // now read the number of bytes we've found, minus the 4 that we just read + buffer_size = ntohl(buffer_size); + // JMJ + fprintf(stderr, "which switching bits results in %u.\n", buffer_size); + if (buffer_size == 0) + return 0; + + left = buffer_size; + read = 0; + read_this_time = 0; + *results = malloc(left); + unsigned char* ptr = *results; + do { + read_this_time = socket_read(session->socket_descriptor, (char*)&ptr[read], left, 0); + if (read_this_time < 0) { + read_this_time = 0; + if ( (errno == EAGAIN) || (errno == EWOULDBLOCK)) { + // TODO: use epoll or select to wait for socket to be writable + } else { + return 0; + } + } + left = left - read_this_time; + } while (left > 0); + + *results_size = buffer_size; + return buffer_size; } /*** @@ -406,41 +499,15 @@ int libp2p_secio_handshake(struct SecureSession* local_session, struct RsaPrivat if (bytes_written <= 0) goto exit; - // we should get back the protocol to signify it was accepted, as well as the protobuf of the Propose struct + // we should get back the secio confirmation bytes_written = libp2p_net_multistream_receive(local_session->socket_descriptor, (char**)&results, &results_size); if (bytes_written < 1 || strstr((char*)results, "secio") == NULL) goto exit; - // skip to the protobuf section - propose_in_bytes = (unsigned char*)strchr((char*)results, '\n'); - if (propose_in_bytes == NULL) - goto exit; - // are we at the end of the buffer? - if (propose_in_bytes - results + 1 >= results_size) { - free(results); - // read some more - bytes_written = libp2p_net_multistream_receive(local_session->socket_descriptor, (char**)&results, &results_size); - propose_in_bytes = results; - } else { - propose_in_bytes++; - } - propose_in_size = results_size - (propose_in_bytes - results); - - if (!libp2p_secio_propose_protobuf_decode(propose_in_bytes, propose_in_size, &propose_in)) - goto exit; - - // clear results free(results); results = NULL; results_size = 0; - // get public key and put it in a struct PublicKey - if (!libp2p_crypto_public_key_protobuf_decode(propose_in->public_key, propose_in->public_key_size, &public_key)) - goto exit; - // generate their peer id - char* remote_peer_id; - libp2p_crypto_public_key_to_peer_id(public_key, &remote_peer_id); - //TODO: make sure we're not talking to ourself // generate 16 byte nonce @@ -451,17 +518,54 @@ int libp2p_secio_handshake(struct SecureSession* local_session, struct RsaPrivat propose_out = libp2p_secio_propose_new(); libp2p_secio_propose_set_property((void**)&propose_out->rand, &propose_out->rand_size, local_session->nonce, 16); - // we have their information, now we need to gather ours. - // public key - propose_out->public_key_size = public_key->data_size; - propose_out->public_key = (unsigned char*)malloc(public_key->data_size); - memcpy(propose_out->public_key, public_key->data, public_key->data_size); + // public key - protobuf it and stick it in propose_out + struct PublicKey pub_key; + pub_key.type = KEYTYPE_RSA; + pub_key.data_size = private_key->public_key_length; + pub_key.data = malloc(pub_key.data_size); + results_size = libp2p_crypto_public_key_protobuf_encode_size(&pub_key); + results = malloc(results_size); + if (libp2p_crypto_public_key_protobuf_encode(&pub_key, results, result_size, &results_size) == 0) + goto exit; + propose_out->public_key_size = results_size; + propose_out->public_key = malloc(results_size); + memcpy(propose_out->public_key, results, results_size); + free(results); + results = NULL; + results_size = 0; // supported exchanges libp2p_secio_propose_set_property((void**)&propose_out->exchanges, &propose_out->exchanges_size, SupportedExchanges, strlen(SupportedExchanges)); // supported ciphers libp2p_secio_propose_set_property((void**)&propose_out->ciphers, &propose_out->ciphers_size, SupportedCiphers, strlen(SupportedCiphers)); // supported hashes libp2p_secio_propose_set_property((void**)&propose_out->hashes, &propose_out->hashes_size, SupportedHashes, strlen(SupportedHashes)); + + // send proposal + propose_out_size = libp2p_secio_propose_protobuf_encode_size(propose_out); + propose_out_bytes = (unsigned char*)malloc(propose_out_size); + if (libp2p_secio_propose_protobuf_encode(propose_out, propose_out_bytes, propose_out_size, &propose_out_size) == 0) + goto exit; + + bytes_written = libp2p_secio_write(local_session, propose_out_bytes, propose_out_size); + if (bytes_written < propose_out_size) + goto exit; + + // try to get the propose object from the server + bytes_written = libp2p_secio_read(local_session, &propose_in_bytes, &propose_in_size); + if (bytes_written <= 0) + goto exit; + + if (!libp2p_secio_propose_protobuf_decode(propose_in_bytes, propose_in_size, &propose_in)) + goto exit; + + // get public key and put it in a struct PublicKey + if (!libp2p_crypto_public_key_protobuf_decode(propose_in->public_key, propose_in->public_key_size, &public_key)) + goto exit; + // generate their peer id + char* remote_peer_id; + libp2p_crypto_public_key_to_peer_id(public_key, &remote_peer_id); + + // negotiate encryption parameters NOTE: SelectBest must match, otherwise this won't work // first determine order libp2p_secio_hash(propose_in, order_hash_in); @@ -474,6 +578,7 @@ int libp2p_secio_handshake(struct SecureSession* local_session, struct RsaPrivat // hash libp2p_secio_select_best(order, propose_out->hashes, propose_out->hashes_size, propose_in->hashes, propose_in->hashes_size, &local_session->chosen_hash); + // prepare exchange of encryption parameters struct SecureSession remote_session; remote_session.chosen_cipher = local_session->chosen_cipher; @@ -515,11 +620,11 @@ int libp2p_secio_handshake(struct SecureSession* local_session, struct RsaPrivat if (exchange_out_protobuf == NULL) goto exit; libp2p_secio_exchange_protobuf_encode(exchange_out, exchange_out_protobuf, exchange_out_protobuf_size, &bytes_written); - bytes_written = libp2p_net_multistream_send(local_session->socket_descriptor, exchange_out_protobuf, exchange_out_protobuf_size); + bytes_written = libp2p_secio_write(local_session, exchange_out_protobuf, exchange_out_protobuf_size); free(exchange_out_protobuf); // receive Exchange packet - bytes_written = libp2p_net_multistream_receive(local_session->socket_descriptor, (char**)&results, &results_size); + bytes_written = libp2p_secio_read(local_session, &results, &results_size); if (bytes_written == 0) goto exit; libp2p_secio_exchange_protobuf_decode(results, results_size, &exchange_in); @@ -568,6 +673,13 @@ int libp2p_secio_handshake(struct SecureSession* local_session, struct RsaPrivat exit: + if (propose_in_bytes != NULL) + free(propose_in_bytes); + if (propose_out_bytes != NULL) + free(propose_out_bytes); + if (results != NULL) + free(results); + libp2p_secio_propose_free(propose_out); libp2p_secio_propose_free(propose_in);