| #include <stdio.h> |
| #include <string.h> |
| #include <getopt.h> |
| #include <pcap/pcap.h> |
| |
| #include "tinydtls.h" |
| #include "dtls_debug.h" |
| #include "dtls.h" |
| |
| #define TRANSPORT_HEADER_SIZE (14+20+8) /* Ethernet + IP + UDP */ |
| |
| /* the pre_master_secret is generated from the PSK at startup */ |
| unsigned char pre_master_secret[60]; |
| size_t pre_master_len = 0; |
| |
| unsigned char master_secret[DTLS_MASTER_SECRET_LENGTH]; |
| size_t master_secret_len = 0; |
| |
| dtls_security_parameters_t security_params[2]; |
| int config = 0; |
| unsigned int epoch[2] = { 0, 0 }; |
| |
| #if DTLS_VERSION == 0xfeff |
| dtls_hash_t hs_hash[2]; |
| #elif DTLS_VERSION == 0xfefd |
| dtls_hash_t hs_hash[1]; |
| #endif |
| |
| static inline void |
| update_hash(uint8 *record, size_t rlength, |
| uint8 *data, size_t data_length) { |
| int i; |
| |
| if (!hs_hash[0]) |
| return; |
| |
| for (i = 0; i < sizeof(hs_hash) / sizeof(dtls_hash_t *); ++i) { |
| dtls_hash_update(hs_hash[i], data, data_length); |
| } |
| } |
| |
| static inline void |
| finalize_hash(uint8 *buf) { |
| #if DTLS_VERSION == 0xfeff |
| unsigned char statebuf[sizeof(md5_state_t) + sizeof(SHA_CTX)]; |
| #elif DTLS_VERSION == 0xfefd |
| unsigned char statebuf[sizeof(dtls_sha256_ctx)]; |
| #endif |
| |
| if (!hs_hash[0]) |
| return; |
| |
| /* temporarily store hash status for roll-back after finalize */ |
| #if DTLS_VERSION == 0xfeff |
| memcpy(statebuf, hs_hash[0], sizeof(md5_state_t)); |
| memcpy(statebuf + sizeof(md5_state_t), |
| hs_hash[1], |
| sizeof(SHA_CTX)); |
| #elif DTLS_VERSION == 0xfefd |
| memcpy(statebuf, hs_hash[0], sizeof(statebuf)); |
| #endif |
| |
| dtls_hash_finalize(buf, hs_hash[0]); |
| #if DTLS_VERSION == 0xfeff |
| dtls_hash_finalize(buf + 16, hs_hash[1]); |
| #endif |
| |
| /* restore hash status */ |
| #if DTLS_VERSION == 0xfeff |
| memcpy(hs_hash[0], statebuf, sizeof(md5_state_t)); |
| memcpy(hs_hash[1], |
| statebuf + sizeof(md5_state_t), |
| sizeof(SHA_CTX)); |
| #elif DTLS_VERSION == 0xfefd |
| memcpy(hs_hash[0], statebuf, sizeof(statebuf)); |
| #endif |
| } |
| |
| static inline void |
| clear_hash() { |
| int i; |
| |
| for (i = 0; i < sizeof(hs_hash) / sizeof(dtls_hash_t *); ++i) |
| free(hs_hash[i]); |
| memset(hs_hash, 0, sizeof(hs_hash)); |
| } |
| |
| #undef CURRENT_CONFIG |
| #undef OTHER_CONFIG |
| #undef SWITCH_CONFIG |
| #define CURRENT_CONFIG (&security_params[config]) |
| #define OTHER_CONFIG (&security_params[!(config & 0x01)]) |
| #define SWITCH_CONFIG (config = !(config & 0x01)) |
| |
| int |
| pcap_verify(dtls_security_parameters_t *sec, |
| int is_client, |
| const unsigned char *record, size_t record_length, |
| const unsigned char *cleartext, size_t cleartext_length) { |
| |
| unsigned char mac[DTLS_HMAC_MAX]; |
| dtls_hmac_context_t hmac_ctx; |
| int ok; |
| |
| if (cleartext_length < dtls_kb_digest_size(sec)) |
| return 0; |
| |
| dtls_hmac_init(&hmac_ctx, |
| is_client |
| ? dtls_kb_client_mac_secret(sec) |
| : dtls_kb_server_mac_secret(sec), |
| dtls_kb_mac_secret_size(sec)); |
| |
| cleartext_length -= dtls_kb_digest_size(sec); |
| |
| /* calculate MAC even if padding is wrong */ |
| dtls_mac(&hmac_ctx, |
| record, /* the pre-filled record header */ |
| cleartext, cleartext_length, |
| mac); |
| |
| ok = memcmp(mac, cleartext + cleartext_length, |
| dtls_kb_digest_size(sec)) == 0; |
| #ifndef NDEBUG |
| printf("MAC (%s): ", ok ? "valid" : "invalid"); |
| dump(mac, dtls_kb_digest_size(sec)); |
| printf("\n"); |
| #endif |
| return ok; |
| } |
| |
| int |
| decrypt_verify(int is_client, const uint8 *packet, size_t length, |
| uint8 **cleartext, size_t *clen) { |
| int res, ok = 0; |
| dtls_cipher_context_t *cipher; |
| |
| static unsigned char buf[1000]; |
| |
| switch (CURRENT_CONFIG->cipher) { |
| case AES128: /* TLS_PSK_WITH_AES128_CBC_SHA */ |
| *cleartext = buf; |
| *clen = length - sizeof(dtls_record_header_t); |
| |
| if (is_client) |
| cipher = CURRENT_CONFIG->read_cipher; |
| else |
| cipher = CURRENT_CONFIG->write_cipher; |
| |
| res = dtls_decrypt(cipher, |
| (uint8 *)packet + sizeof(dtls_record_header_t), *clen, |
| buf, NULL, 0); |
| |
| if (res < 0) { |
| warn("decryption failed!\n"); |
| } else { |
| ok = pcap_verify(CURRENT_CONFIG, is_client, (uint8 *)packet, length, |
| *cleartext, res); |
| |
| if (ok) |
| *clen = res - dtls_kb_digest_size(CURRENT_CONFIG); |
| } |
| break; |
| default: /* no cipher suite selected */ |
| *cleartext = (uint8 *)packet + sizeof(dtls_record_header_t); |
| *clen = length - sizeof(dtls_record_header_t); |
| |
| ok = 1; |
| } |
| |
| if (ok) |
| printf("verify OK\n"); |
| else |
| printf("verification failed!\n"); |
| return ok; |
| } |
| |
| #define SKIP_ETH_HEADER(M,L) \ |
| if ((L) < 14) \ |
| return; \ |
| else { \ |
| (M) += 14; \ |
| (L) -= 14; \ |
| } |
| |
| #define SKIP_IP_HEADER(M,L) \ |
| if (((M)[0] & 0xF0) == 0x40) { /* IPv4 */ \ |
| (M) += (M[0] & 0x0F) * 4; \ |
| (L) -= (M[0] & 0x0F) * 4; \ |
| } else \ |
| if (((M)[0] & 0xF0) == 0x60) { /* IPv6 */ \ |
| (M) += 40; \ |
| (L) -= 40; \ |
| } |
| |
| #define SKIP_UDP_HEADER(M,L) { \ |
| (M) += 8; \ |
| (L) -= 8; \ |
| } |
| |
| void |
| handle_packet(const u_char *packet, int length) { |
| static int n = 0; |
| static unsigned char initial_hello[] = { |
| 0x16, 0xfe, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 |
| }; |
| uint8 *data; |
| size_t data_length, rlen; |
| int i, res; |
| #if DTLS_VERSION == 0xfeff |
| #ifndef SHA1_DIGEST_LENGTH |
| #define SHA1_DIGEST_LENGTH 20 |
| #endif |
| uint8 hash_buf[16 + SHA1_DIGEST_LENGTH]; |
| #elif DTLS_VERSION == 0xfefd |
| uint8 hash_buf[DTLS_SHA256_DIGEST_LENGTH]; |
| #endif |
| #define verify_data_length 12 |
| int is_client; |
| n++; |
| |
| SKIP_ETH_HEADER(packet, length); |
| SKIP_IP_HEADER(packet, length); |
| |
| /* determine from port if this is a client */ |
| is_client = dtls_uint16_to_int(packet) != 20220; |
| |
| SKIP_UDP_HEADER(packet, length); |
| |
| while (length) { |
| rlen = dtls_uint16_to_int(packet + 11) + sizeof(dtls_record_header_t); |
| |
| if (!rlen) { |
| fprintf(stderr, "invalid length!\n"); |
| return; |
| } |
| |
| /* skip packet if it is from a different epoch */ |
| if (dtls_uint16_to_int(packet + 3) != epoch[is_client]) |
| goto next; |
| |
| res = decrypt_verify(is_client, packet, rlen, |
| &data, &data_length); |
| |
| if (res <= 0) |
| goto next; |
| |
| printf("packet %d (from %s):\n", n, is_client ? "client" : "server"); |
| hexdump(packet, sizeof(dtls_record_header_t)); |
| printf("\n"); |
| hexdump(data, data_length); |
| printf("\n"); |
| |
| if (packet[0] == 22 && data[0] == 1) { /* ClientHello */ |
| if (memcmp(packet, initial_hello, sizeof(initial_hello)) == 0) |
| goto next; |
| |
| memcpy(dtls_kb_client_iv(OTHER_CONFIG), data + 14, 32); |
| |
| clear_hash(); |
| #if DTLS_VERSION == 0xfeff |
| hs_hash[0] = dtls_new_hash(HASH_MD5); |
| hs_hash[1] = dtls_new_hash(HASH_SHA1); |
| |
| hs_hash[0]->init(hs_hash[0]->data); |
| hs_hash[1]->init(hs_hash[1]->data); |
| #elif DTLS_VERSION == 0xfefd |
| dtls_hash_init(hs_hash[0]); |
| #endif |
| } |
| |
| if (packet[0] == 22 && data[0] == 2) { /* ServerHello */ |
| memcpy(dtls_kb_server_iv(OTHER_CONFIG), data + 14, 32); |
| /* FIXME: search in ciphers */ |
| OTHER_CONFIG->cipher = TLS_PSK_WITH_AES_128_CCM_8; |
| } |
| |
| if (packet[0] == 20 && data[0] == 1) { /* ChangeCipherSpec */ |
| printf("client random: "); |
| dump(dtls_kb_client_iv(OTHER_CONFIG), 32); |
| printf("\nserver random: "); |
| dump(dtls_kb_server_iv(OTHER_CONFIG), 32); |
| printf("\n"); |
| master_secret_len = |
| dtls_prf(pre_master_secret, pre_master_len, |
| (unsigned char *)"master secret", 13, |
| dtls_kb_client_iv(OTHER_CONFIG), 32, |
| dtls_kb_server_iv(OTHER_CONFIG), 32, |
| master_secret, DTLS_MASTER_SECRET_LENGTH); |
| |
| printf("master_secret:\n "); |
| for(i = 0; i < master_secret_len; i++) |
| printf("%02x", master_secret[i]); |
| printf("\n"); |
| |
| /* create key_block from master_secret |
| * key_block = PRF(master_secret, |
| "key expansion" + server_random + client_random) */ |
| dtls_prf(master_secret, master_secret_len, |
| (unsigned char *)"key expansion", 13, |
| dtls_kb_server_iv(OTHER_CONFIG), 32, |
| dtls_kb_client_iv(OTHER_CONFIG), 32, |
| OTHER_CONFIG->key_block, |
| dtls_kb_size(OTHER_CONFIG)); |
| |
| OTHER_CONFIG->read_cipher = |
| dtls_cipher_new(OTHER_CONFIG->cipher, |
| dtls_kb_client_write_key(OTHER_CONFIG), |
| dtls_kb_key_size(OTHER_CONFIG)); |
| |
| if (!OTHER_CONFIG->read_cipher) { |
| warn("cannot create read cipher\n"); |
| } else { |
| dtls_cipher_set_iv(OTHER_CONFIG->read_cipher, |
| dtls_kb_client_iv(OTHER_CONFIG), |
| dtls_kb_iv_size(OTHER_CONFIG)); |
| } |
| |
| OTHER_CONFIG->write_cipher = |
| dtls_cipher_new(OTHER_CONFIG->cipher, |
| dtls_kb_server_write_key(OTHER_CONFIG), |
| dtls_kb_key_size(OTHER_CONFIG)); |
| |
| if (!OTHER_CONFIG->write_cipher) { |
| warn("cannot create write cipher\n"); |
| } else { |
| dtls_cipher_set_iv(OTHER_CONFIG->write_cipher, |
| dtls_kb_server_iv(OTHER_CONFIG), |
| dtls_kb_iv_size(OTHER_CONFIG)); |
| } |
| |
| /* if (is_client) */ |
| SWITCH_CONFIG; |
| epoch[is_client]++; |
| |
| printf("key_block:\n"); |
| printf(" client_MAC_secret:\t"); |
| dump(dtls_kb_client_mac_secret(CURRENT_CONFIG), |
| dtls_kb_mac_secret_size(CURRENT_CONFIG)); |
| printf("\n"); |
| |
| printf(" server_MAC_secret:\t"); |
| dump(dtls_kb_server_mac_secret(CURRENT_CONFIG), |
| dtls_kb_mac_secret_size(CURRENT_CONFIG)); |
| printf("\n"); |
| |
| printf(" client_write_key:\t"); |
| dump(dtls_kb_client_write_key(CURRENT_CONFIG), |
| dtls_kb_key_size(CURRENT_CONFIG)); |
| printf("\n"); |
| |
| printf(" server_write_key:\t"); |
| dump(dtls_kb_server_write_key(CURRENT_CONFIG), |
| dtls_kb_key_size(CURRENT_CONFIG)); |
| printf("\n"); |
| |
| printf(" client_IV:\t\t"); |
| dump(dtls_kb_client_iv(CURRENT_CONFIG), |
| dtls_kb_iv_size(CURRENT_CONFIG)); |
| printf("\n"); |
| |
| printf(" server_IV:\t\t"); |
| dump(dtls_kb_server_iv(CURRENT_CONFIG), |
| dtls_kb_iv_size(CURRENT_CONFIG)); |
| printf("\n"); |
| |
| } |
| |
| if (packet[0] == 22) { |
| if (data[0] == 20) { /* Finished */ |
| finalize_hash(hash_buf); |
| /* clear_hash(); */ |
| |
| update_hash((unsigned char *)packet, sizeof(dtls_record_header_t), |
| data, data_length); |
| |
| dtls_prf(master_secret, master_secret_len, |
| is_client |
| ? (unsigned char *)"client finished" |
| : (unsigned char *)"server finished" |
| , 15, |
| hash_buf, sizeof(hash_buf), |
| NULL, 0, |
| data + sizeof(dtls_handshake_header_t), |
| verify_data_length); |
| printf("verify_data:\n"); |
| dump(data, data_length); |
| printf("\n"); |
| } else { |
| update_hash((unsigned char *)packet, sizeof(dtls_record_header_t), |
| data, data_length); |
| } |
| } |
| |
| if (packet[0] == 23) { /* Application Data */ |
| printf("Application Data:\n"); |
| dump(data, data_length); |
| printf("\n"); |
| } |
| |
| next: |
| length -= rlen; |
| packet += rlen; |
| } |
| } |
| |
| void init() { |
| memset(security_params, 0, sizeof(security_params)); |
| CURRENT_CONFIG->cipher = -1; |
| |
| memset(hs_hash, 0, sizeof(hs_hash)); |
| |
| /* set pre_master_secret to default if no PSK was given */ |
| if (!pre_master_len) { |
| /* unsigned char psk[] = { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06 }; */ |
| pre_master_len = |
| dtls_pre_master_secret((unsigned char *)"secretPSK", 9, |
| pre_master_secret); |
| } |
| } |
| |
| int main(int argc, char **argv) { |
| pcap_t *pcap; |
| char errbuf[PCAP_ERRBUF_SIZE]; |
| struct pcap_pkthdr *pkthdr; |
| const u_char *packet; |
| int res = 0; |
| int c, option_index = 0; |
| |
| static struct option opts[] = { |
| { "psk", 1, 0, 'p' }, |
| { 0, 0, 0, 0 } |
| }; |
| |
| /* handle command line options */ |
| while (1) { |
| c = getopt_long(argc, argv, "p:", opts, &option_index); |
| if (c == -1) |
| break; |
| |
| switch (c) { |
| case 'p': |
| pre_master_len = dtls_pre_master_secret((unsigned char *)optarg, |
| strlen(optarg), pre_master_secret); |
| break; |
| } |
| } |
| |
| if (argc <= optind) { |
| fprintf(stderr, "usage: %s [-p|--psk PSK] pcapfile\n", argv[0]); |
| return -1; |
| } |
| |
| init(); |
| |
| pcap = pcap_open_offline(argv[optind], errbuf); |
| if (!pcap) { |
| fprintf(stderr, "pcap_open_offline: %s\n", errbuf); |
| return -2; |
| } |
| |
| for (;;) { |
| res = pcap_next_ex(pcap, &pkthdr, &packet); |
| |
| switch(res) { |
| case -2: goto done; |
| case -1: pcap_perror(pcap, "read packet"); break; |
| case 1: handle_packet(packet, pkthdr->caplen); break; |
| default: |
| ; |
| } |
| } |
| done: |
| |
| pcap_close(pcap); |
| |
| return 0; |
| } |