#include #include #include #include #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; }