├── .ackrc ├── .gitignore ├── .jshintrc ├── LICENSE.txt ├── Makefile ├── README.md ├── captures ├── README.md ├── caps │ ├── 01-hello-c │ ├── 02-hello-s │ ├── 03-encext-shs-prot │ ├── 04-cert-shs-prot │ ├── 05-cverify-shs-prot │ ├── 06-fin-shs-prot │ ├── 07-fin-chs-prot │ ├── 08-ack-sap-prot │ ├── 09-data-cap-prot │ ├── 10-data-sap-prot │ ├── 11-bye-sap-prot │ ├── Makefile │ ├── decrypt.sh │ ├── record-cdata │ ├── record-cert │ ├── record-cfin │ ├── record-chello │ ├── record-cverify │ ├── record-encext │ ├── record-sack │ ├── record-sbye │ ├── record-sdata │ ├── record-sfin │ ├── record-shello │ └── sn_removal.sh ├── capture.pcap └── keylog.txt ├── client ├── Makefile └── client.c ├── generate ├── Makefile ├── certificate.html.template ├── index-00-header.html.template ├── index-01-chello.html.template ├── index-02-shello.html.template ├── index-02-zzz-handkeys.html.template ├── index-03-encext.html.template ├── index-04-cert.html.template ├── index-05-cverify.html.template ├── index-06-sfin.html.template ├── index-07-cfin.html.template ├── index-07-zzz-appkeys.html.template ├── index-08-sack.html.template ├── index-09-cdata.html.template ├── index-10-sdata.html.template ├── index-11-bye.html.template └── index-99-footer.html.template ├── server ├── Makefile ├── server.c ├── server.crt └── server.key ├── site ├── certificate.html ├── favicon.ico ├── favicon │ ├── android-chrome-144x144.png │ ├── apple-touch-icon.png │ ├── browserconfig.xml │ ├── favicon-16x16.png │ ├── favicon-32x32.png │ ├── mstile-150x150.png │ ├── safari-pinned-tab.svg │ └── site.webmanifest ├── files │ ├── aes_128_gcm_decrypt.c │ ├── aes_128_gcm_encrypt.c │ ├── client-ephemeral-private.key │ ├── client-ephemeral-public.key │ ├── curve25519-mult.c │ ├── hkdf-dtls.sh │ ├── server-ephemeral-private.key │ ├── server-ephemeral-public.key │ ├── server.crt │ └── server.key ├── frombootstrap.css ├── illustrated.css ├── illustrated.js ├── images │ ├── key1.png │ ├── key2.png │ ├── key3.png │ ├── key4.png │ ├── key5.png │ ├── key6.png │ ├── key7.png │ ├── key8.png │ ├── key9.png │ └── og.png ├── index.html └── printmode.css └── wolfssl ├── Makefile └── instruments.patch /.ackrc: -------------------------------------------------------------------------------- 1 | --ignore-dir=.deps 2 | --ignore-dir=ill12 3 | --ignore-dir=ill13 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | wolfssl/bin 2 | wolfssl/lib 3 | wolfssl/include 4 | wolfssl/share 5 | wolfssl/wolfssl 6 | server/server 7 | client/client 8 | captures/caps/sn_removal 9 | captures/caps/aes_128_gcm_decrypt* 10 | captures/caps/*-unprot 11 | captures/caps/*-dec 12 | generate/hkdf/ 13 | generate/ill-generator 14 | generate/ill12/ 15 | generate/ill13/ 16 | /.idea 17 | */bak 18 | -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "esversion": 11, 3 | "bitwise": true, 4 | "browser": true, 5 | "curly": true, 6 | "eqeqeq": true, 7 | "eqnull": true, 8 | "forin": true, 9 | "freeze": true, 10 | "futurehostile": true, 11 | "latedef": true, 12 | "noarg": true, 13 | "nocomma": true, 14 | "nonbsp": true, 15 | "nonew": true, 16 | "quotmark": true, 17 | "singleGroups": true, 18 | "strict": true, 19 | "undef": true, 20 | "unused": true, 21 | "varstmt": true, 22 | "globals": { "ill": true } 23 | } 24 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Michael Driscoll 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | all: 2 | 3 | dist: 4 | @if [[ -z "${DISTROOT}" ]]; then echo "Must set \$$DISTROOT variable"; exit 1; fi 5 | rsync -rlpvhc site/ ${DISTROOT}/dtls/ 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # The Illustrated DTLS Connection 2 | 3 | Published at https://dtls.xargs.org 4 | 5 | - `site/`: page source for the finished product 6 | - `server/server.c`: server code 7 | - `client/client.c`: client code 8 | - `wolfssl/`: patch and build of wolfSSL that removes any random aspects of the documented connection 9 | - `captures/`: PCAP and keylog files 10 | 11 | See also https://github.com/syncsynchalt/illustrated-tls13 for a TLS 1.3 version of this project. 12 | 13 | ### Build instructions 14 | 15 | If you'd like a working example that reproduces the exact handshake documented on the site: 16 | 17 | ``` 18 | git clone https://github.com/syncsynchalt/illustrated-dtls.git 19 | cd illustrated-dtls/ 20 | cd wolfssl/ 21 | make 22 | cd ../server/ 23 | make 24 | cd ../client/ 25 | make 26 | ``` 27 | 28 | Then open two terminals and run `./server` in the server/ subdir and `./client` in the client/ subdir. 29 | 30 | This has been shown to work on MacOS 12 and various Linuxes and 31 | only has a few easy-to-find dependencies: gcc or clang, make, patch, 32 | etc. 33 | -------------------------------------------------------------------------------- /captures/README.md: -------------------------------------------------------------------------------- 1 | # Packet captures 2 | 3 | In this directory is [a packet capture](./capture.pcap) 4 | that can be loaded by programs such as [Wireshark](https://www.wireshark.org). 5 | 6 | The [keylog](./keylog.txt) 7 | (a [NSS key log](https://firefox-source-docs.mozilla.org/security/nss/legacy/key_log_format/)) 8 | for this packet capture can be loaded in Wireshark by right-clicking 9 | a TLS packet and selecting `Protocol Preferences → Transport Layer Security -> Pre-Master-Secret log filename`. 10 | This allows Wireshark to decrypt and show TLS connection details. 11 | -------------------------------------------------------------------------------- /captures/caps/01-hello-c: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/syncsynchalt/illustrated-dtls/cb49c285131efd9e2d4252806420bda936134a48/captures/caps/01-hello-c -------------------------------------------------------------------------------- /captures/caps/02-hello-s: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/syncsynchalt/illustrated-dtls/cb49c285131efd9e2d4252806420bda936134a48/captures/caps/02-hello-s -------------------------------------------------------------------------------- /captures/caps/03-encext-shs-prot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/syncsynchalt/illustrated-dtls/cb49c285131efd9e2d4252806420bda936134a48/captures/caps/03-encext-shs-prot -------------------------------------------------------------------------------- /captures/caps/04-cert-shs-prot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/syncsynchalt/illustrated-dtls/cb49c285131efd9e2d4252806420bda936134a48/captures/caps/04-cert-shs-prot -------------------------------------------------------------------------------- /captures/caps/05-cverify-shs-prot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/syncsynchalt/illustrated-dtls/cb49c285131efd9e2d4252806420bda936134a48/captures/caps/05-cverify-shs-prot -------------------------------------------------------------------------------- /captures/caps/06-fin-shs-prot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/syncsynchalt/illustrated-dtls/cb49c285131efd9e2d4252806420bda936134a48/captures/caps/06-fin-shs-prot -------------------------------------------------------------------------------- /captures/caps/07-fin-chs-prot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/syncsynchalt/illustrated-dtls/cb49c285131efd9e2d4252806420bda936134a48/captures/caps/07-fin-chs-prot -------------------------------------------------------------------------------- /captures/caps/08-ack-sap-prot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/syncsynchalt/illustrated-dtls/cb49c285131efd9e2d4252806420bda936134a48/captures/caps/08-ack-sap-prot -------------------------------------------------------------------------------- /captures/caps/09-data-cap-prot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/syncsynchalt/illustrated-dtls/cb49c285131efd9e2d4252806420bda936134a48/captures/caps/09-data-cap-prot -------------------------------------------------------------------------------- /captures/caps/10-data-sap-prot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/syncsynchalt/illustrated-dtls/cb49c285131efd9e2d4252806420bda936134a48/captures/caps/10-data-sap-prot -------------------------------------------------------------------------------- /captures/caps/11-bye-sap-prot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/syncsynchalt/illustrated-dtls/cb49c285131efd9e2d4252806420bda936134a48/captures/caps/11-bye-sap-prot -------------------------------------------------------------------------------- /captures/caps/Makefile: -------------------------------------------------------------------------------- 1 | all: targets 2 | 3 | TARGETS=record-chello record-shello record-encext record-cert record-cverify \ 4 | record-sfin record-cfin record-sack record-cdata record-sdata record-sbye 5 | targets: aes_128_gcm_decrypt $(TARGETS) 6 | 7 | OSSL_DIR=/usr/local/Cellar/openssl@3/3.0.3 8 | 9 | aes_128_gcm_decrypt.c: 10 | curl -sO https://quic.xargs.org/files/aes_128_gcm_decrypt.c 11 | 12 | aes_128_gcm_decrypt: aes_128_gcm_decrypt.c 13 | cc -Wall -I $(OSSL_DIR)/include -o $@ $^ -L $(OSSL_DIR)/lib -lssl -lcrypto 14 | 15 | S_HSN_KEY=7173fac51194e775001d625ef69d7c9f 16 | C_HSN_KEY=beed6218676635c2cb46a45694144fec 17 | 18 | S_HS_KEY=004e03e64ab6cba6b542775ec230e20a 19 | S_HS_IV=6d9924be044ee97c624913f2 20 | C_HS_KEY=6caa2633d5e48f10051e69dc45549c97 21 | C_HS_IV=106dc6e393b7a9ea8ef29dd7 22 | 23 | S_ASN_KEY=57ba02596c6a1352d7fe8416c7e17d5a 24 | C_ASN_KEY=5cb5bd8bac29777c650c0dde22d16d47 25 | 26 | S_AP_KEY=2b65fffbbc8189474aa2003c43c32d4d 27 | S_AP_IV=582f5a11bdaf973fe3ffeb4e 28 | C_AP_KEY=9ba90dbce8857bc1fcb81d41a0465cfe 29 | C_AP_IV=682219974631fa0656ee4eff 30 | 31 | %-unprot: %-prot 32 | @if echo $^ | grep -q -- -chs-; then ./sn_removal.sh $(C_HSN_KEY) $^ > $@ || exit 1; fi 33 | @if echo $^ | grep -q -- -shs-; then ./sn_removal.sh $(S_HSN_KEY) $^ > $@ || exit 1; fi 34 | @if echo $^ | grep -q -- -cap-; then ./sn_removal.sh $(C_ASN_KEY) $^ > $@ || exit 1; fi 35 | @if echo $^ | grep -q -- -sap-; then ./sn_removal.sh $(S_ASN_KEY) $^ > $@ || exit 1; fi 36 | 37 | %-dec: %-unprot 38 | @if echo $^ | grep -q -- -chs-; then ./decrypt.sh $(C_HS_KEY) $(C_HS_IV) 5 $^ $@ || exit 1; fi 39 | @if echo $^ | grep -q -- -shs-; then ./decrypt.sh $(S_HS_KEY) $(S_HS_IV) 5 $^ $@ || exit 1; fi 40 | @if echo $^ | grep -q -- -cap-; then ./decrypt.sh $(C_AP_KEY) $(C_AP_IV) 5 $^ $@ || exit 1; fi 41 | @if echo $^ | grep -q -- -sap-; then ./decrypt.sh $(S_AP_KEY) $(S_AP_IV) 5 $^ $@ || exit 1; fi 42 | 43 | record-chello: 01-hello-c 44 | cp $^ $@ 45 | 46 | record-shello: 02-hello-s 47 | cp $^ $@ 48 | 49 | record-encext: 03-encext-shs-dec 50 | cp $^ $@ 51 | 52 | record-cert: 04-cert-shs-dec 53 | cp $^ $@ 54 | 55 | record-cverify: 05-cverify-shs-dec 56 | cp $^ $@ 57 | 58 | record-sfin: 06-fin-shs-dec 59 | cp $^ $@ 60 | 61 | record-cfin: 07-fin-chs-dec 62 | cp $^ $@ 63 | 64 | record-sack: 08-ack-sap-dec 65 | cp $^ $@ 66 | 67 | record-cdata: 09-data-cap-dec 68 | cp $^ $@ 69 | 70 | record-sdata: 10-data-sap-dec 71 | cp $^ $@ 72 | 73 | record-sbye: 11-bye-sap-dec 74 | cp $^ $@ 75 | 76 | clean: 77 | rm -f *-unprot *-dec 78 | rm -f aes_128_gcm_decrypt aes_128_gcm_decrypt.c 79 | rm -f record-* 80 | 81 | .PHONY: clean targets all 82 | -------------------------------------------------------------------------------- /captures/caps/decrypt.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash -e 2 | 3 | if test $# -ne 5; then 4 | echo "Usage: $0 key iv aadlen infile outfile" 1>&2 5 | exit 1 6 | fi 7 | 8 | key=$1 9 | iv=$2 10 | adlen=$3 11 | infile=$4 12 | outfile=$5 13 | 14 | recdata=$(head -c ${adlen} ${infile} | xxd -p) 15 | authtag=$(perl -p0777 -e 's/(\0\0)*$//gs; s/.*(.{16})$/\1/s' ${infile} | xxd -p) 16 | recordnum=$(perl -p0777 -e 's/.(..).*/\1/s; print(unpack("n", $_))' ${infile}) 17 | rm -f /tmp/working.$$ 18 | perl -p0777 -e 's/(\0\0)*$//gs; s/.{'${adlen}'}(.*).{16}/\1/s' < ${infile} > /tmp/working.$$ 19 | cat /tmp/working.$$ | ./aes_128_gcm_decrypt ${iv} ${recordnum} ${key} ${recdata} ${authtag} > /tmp/out.$$ 20 | cat /tmp/out.$$ > ${outfile} 21 | 22 | filesize=$(du -k /tmp/working.$$ | cut -f1) 23 | if [[ $filesize -lt 7 ]]; then 24 | msg=$(cat /tmp/working.$$ | xxd -p | perl -pe 's/../& /g') 25 | else 26 | start=$(head -c 3 /tmp/working.$$ | xxd -p | perl -pe 's/../& /g') 27 | end=$(tail -c 3 /tmp/working.$$ | xxd -p | perl -pe 's/../& /g') 28 | msg="$start ... $end" 29 | fi 30 | 31 | echo --------- start copy into template ------- 32 | echo '$ key='$key 33 | echo '$ iv='$iv 34 | echo "### from this record" 35 | echo '$ recdata='$recdata 36 | echo '$ authtag='$authtag 37 | echo '$ recordnum='$recordnum 38 | echo "### may need to add -I and -L flags for include and lib dirs" 39 | echo '$ cc -o aes_128_gcm_decrypt aes_128_gcm_decrypt.c -lssl -lcrypto' 40 | echo '$ echo '"$msg" | xxd -r -p > /tmp/msg1 41 | echo '$ cat /tmp/msg1 \' 42 | echo ' | ./aes_128_gcm_decrypt $iv $recordnum $key $recdata $authtag \' 43 | echo ' | hexdump -C' 44 | echo 45 | hexdump -C /tmp/out.$$ | head 46 | echo --------- end copy into template ------- 47 | 48 | rm -f /tmp/out.$$ /tmp/working.$$ 49 | -------------------------------------------------------------------------------- /captures/caps/record-cdata: -------------------------------------------------------------------------------- 1 | ping -------------------------------------------------------------------------------- /captures/caps/record-cert: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/syncsynchalt/illustrated-dtls/cb49c285131efd9e2d4252806420bda936134a48/captures/caps/record-cert -------------------------------------------------------------------------------- /captures/caps/record-cfin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/syncsynchalt/illustrated-dtls/cb49c285131efd9e2d4252806420bda936134a48/captures/caps/record-cfin -------------------------------------------------------------------------------- /captures/caps/record-chello: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/syncsynchalt/illustrated-dtls/cb49c285131efd9e2d4252806420bda936134a48/captures/caps/record-chello -------------------------------------------------------------------------------- /captures/caps/record-cverify: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/syncsynchalt/illustrated-dtls/cb49c285131efd9e2d4252806420bda936134a48/captures/caps/record-cverify -------------------------------------------------------------------------------- /captures/caps/record-encext: -------------------------------------------------------------------------------- 1 |  2 | 3 |  -------------------------------------------------------------------------------- /captures/caps/record-sack: -------------------------------------------------------------------------------- 1 |  -------------------------------------------------------------------------------- /captures/caps/record-sbye: -------------------------------------------------------------------------------- 1 |  -------------------------------------------------------------------------------- /captures/caps/record-sdata: -------------------------------------------------------------------------------- 1 | pong -------------------------------------------------------------------------------- /captures/caps/record-sfin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/syncsynchalt/illustrated-dtls/cb49c285131efd9e2d4252806420bda936134a48/captures/caps/record-sfin -------------------------------------------------------------------------------- /captures/caps/record-shello: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/syncsynchalt/illustrated-dtls/cb49c285131efd9e2d4252806420bda936134a48/captures/caps/record-shello -------------------------------------------------------------------------------- /captures/caps/sn_removal.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash -e 2 | 3 | file=$2 4 | key=$1 5 | if [[ -z "$file" || -z "$key" ]]; then 6 | echo "Usage: $0 hexkey input > output" 1>&2 7 | exit 1 8 | fi 9 | sample=$(head -c 21 $file | tail -c 16 | xxd -p) 10 | 11 | mask=$(echo $sample | xxd -r -p | openssl aes-128-ecb -K $key | head -c 2 | xxd -p) 12 | mask1=$(echo $mask | sed -e 's/..$//') 13 | mask2=$(echo $mask | sed -e 's/..//') 14 | byte1=$(head -c2 $file | tail -c1 | xxd -p) 15 | byte2=$(head -c3 $file | tail -c1 | xxd -p) 16 | xor1=$(printf "%02x\n" $((0x${byte1} ^ 0x${mask1}))) 17 | xor2=$(printf "%02x\n" $((0x${byte2} ^ 0x${mask2}))) 18 | 19 | # output first byte as-is 20 | head -c 1 $file 21 | 22 | # xor the next two bytes 23 | echo "$xor1 $xor2" | xxd -r -p 24 | 25 | tail -c +4 $file 26 | -------------------------------------------------------------------------------- /captures/capture.pcap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/syncsynchalt/illustrated-dtls/cb49c285131efd9e2d4252806420bda936134a48/captures/capture.pcap -------------------------------------------------------------------------------- /captures/keylog.txt: -------------------------------------------------------------------------------- 1 | CLIENT_HANDSHAKE_TRAFFIC_SECRET e0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff 33e472fb8d821b0193314626bebee307ccbd1aeb3d3a17ba468888ffc5246da1 2 | SERVER_HANDSHAKE_TRAFFIC_SECRET e0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff 8ad7990b9d249bcbaa0805d8d3f3ad2259e75f3a42c5d84db3ea3c6ee57b3d38 3 | CLIENT_TRAFFIC_SECRET e0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff a9185352f61134f1d24eaa4a930fff2edca40ce8c06420848deb27699e9baf2c 4 | SERVER_TRAFFIC_SECRET e0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff 4ab12ae4022fc013eca21abb071e13aa24a150e3876c660fe0ed10a8eebd8f17 5 | -------------------------------------------------------------------------------- /client/Makefile: -------------------------------------------------------------------------------- 1 | CFLAGS += -I../wolfssl/include -Wall 2 | LDFLAGS += -L../wolfssl/lib -lwolfssl 3 | 4 | client: client.c ../wolfssl/lib/libwolfssl.a 5 | $(CC) $(CFLAGS) -o client client.c -Wall $(LDFLAGS) 6 | 7 | clean: 8 | rm -f client 9 | -------------------------------------------------------------------------------- /client/client.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | void die(const char *str) 7 | { 8 | fprintf(stderr, "%s: %s\n", str, strerror(errno)); 9 | exit(1); 10 | } 11 | 12 | void die_ssl(WOLFSSL *ssl, const char *str) 13 | { 14 | int err = wolfSSL_get_error(ssl, 0); 15 | fprintf(stderr, "%s: %s (err:%d)\n", str, wolfSSL_ERR_reason_error_string(err), err); 16 | exit(1); 17 | } 18 | 19 | int secret_callback(WOLFSSL *ssl, int id, const unsigned char *secret, int secret_sz, void *unused) 20 | { 21 | unsigned char crand[32]; 22 | int crand_sz = (int)wolfSSL_get_client_random(ssl, crand, sizeof(crand)); 23 | if (crand_sz <= 0) 24 | die("error getting random size"); 25 | 26 | const char *str = NULL; 27 | if (id == CLIENT_EARLY_TRAFFIC_SECRET) { 28 | str = "CLIENT_EARLY_TRAFFIC_SECRET"; 29 | } else if (id == EARLY_EXPORTER_SECRET) { 30 | str = "EARLY_EXPORTER_SECRET"; 31 | } else if (id == CLIENT_HANDSHAKE_TRAFFIC_SECRET) { 32 | str = "CLIENT_HANDSHAKE_TRAFFIC_SECRET"; 33 | } else if (id == SERVER_HANDSHAKE_TRAFFIC_SECRET) { 34 | str = "SERVER_HANDSHAKE_TRAFFIC_SECRET"; 35 | } else if (id == CLIENT_TRAFFIC_SECRET) { 36 | str = "CLIENT_TRAFFIC_SECRET"; 37 | } else if (id == SERVER_TRAFFIC_SECRET) { 38 | str = "SERVER_TRAFFIC_SECRET"; 39 | } else if (id == EXPORTER_SECRET) { 40 | str = "EXPORTER_SECRET"; 41 | } else { 42 | printf("unknown secret %d\n", id); 43 | str = "UNKNOWN_SECRET"; 44 | } 45 | 46 | FILE *fp = stdout; 47 | fprintf(fp, "%s ", str); 48 | for (size_t i = 0; i < crand_sz; i++) { 49 | fprintf(fp, "%02x", crand[i]); 50 | } 51 | fprintf(fp, " "); 52 | for (size_t i = 0; i < secret_sz; i++) { 53 | fprintf(fp, "%02x", secret[i]); 54 | } 55 | fprintf(fp, "\n"); 56 | 57 | return 0; 58 | } 59 | 60 | void setup_keylog(WOLFSSL *ssl) 61 | { 62 | wolfSSL_KeepArrays(ssl); 63 | wolfSSL_set_tls13_secret_cb(ssl, secret_callback, 0); 64 | } 65 | 66 | void setup_ctx(WOLFSSL_CTX *ctx) 67 | { 68 | int groups[] = { WOLFSSL_ECC_X25519 }; 69 | if (wolfSSL_CTX_set_groups(ctx, groups, 1) != SSL_SUCCESS) 70 | die("can't set groups list"); 71 | if (wolfSSL_CTX_set_cipher_list(ctx, 72 | "TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256") != SSL_SUCCESS) 73 | die("can't set cipher list"); 74 | wolfSSL_CTX_set_verify(ctx, SSL_VERIFY_NONE, 0); 75 | } 76 | 77 | int main(int argc, char **argv) 78 | { 79 | // set up ssl 80 | setenv("SERVER", "0", 1); 81 | wolfSSL_Init(); 82 | // wolfSSL_Debugging_ON(); 83 | WOLFSSL_CTX *ctx = wolfSSL_CTX_new(wolfDTLSv1_3_client_method()); 84 | if (ctx == NULL) 85 | die("CTX_new error"); 86 | setup_ctx(ctx); 87 | 88 | // create new session 89 | WOLFSSL *ssl = wolfSSL_new(ctx); 90 | if (ssl == NULL) 91 | die("can't create ssl object"); 92 | setup_keylog(ssl); 93 | 94 | // connect to localhost:8400 95 | struct sockaddr_in addr; 96 | memset(&addr, 0, sizeof(addr)); 97 | addr.sin_family = AF_INET; 98 | addr.sin_port = htons(8400); 99 | if (inet_pton(AF_INET, "127.0.0.1", &addr.sin_addr) < 1) 100 | die("can't inet_pton"); 101 | 102 | if (wolfSSL_dtls_set_peer(ssl, &addr, sizeof(addr)) != SSL_SUCCESS) 103 | die("can't set peer"); 104 | 105 | int fd = socket(AF_INET, SOCK_DGRAM, 0); 106 | if (fd < 0) 107 | die("can't create socket"); 108 | 109 | fflush(stdout); 110 | wolfSSL_set_fd(ssl, fd); 111 | if (wolfSSL_connect(ssl) != SSL_SUCCESS) 112 | die_ssl(ssl, "connect failed"); 113 | printf("Connected\n"); 114 | 115 | const char *servername = "example.ulfheim.net"; 116 | if (wolfSSL_UseSNI(ssl, WOLFSSL_SNI_HOST_NAME, servername, strlen(servername)) != SSL_SUCCESS) 117 | die_ssl(ssl, "SNI failed"); 118 | 119 | const char wbuf[] = "ping"; 120 | if (wolfSSL_write(ssl, wbuf, strlen(wbuf)) != strlen(wbuf)) 121 | die_ssl(ssl, "write failed"); 122 | printf("Wrote %s\n", wbuf); 123 | 124 | char rbuf[128] = { 0 }; 125 | size_t n = wolfSSL_read(ssl, rbuf, sizeof(rbuf)-1); 126 | if (n < 0) 127 | die_ssl(ssl, "read failed"); 128 | printf("Read [%s]\n", rbuf); 129 | 130 | wolfSSL_set_fd(ssl, 0); 131 | wolfSSL_shutdown(ssl); 132 | wolfSSL_free(ssl); 133 | close(fd); 134 | wolfSSL_CTX_free(ctx); 135 | wolfSSL_Cleanup(); 136 | 137 | return 0; 138 | } 139 | -------------------------------------------------------------------------------- /generate/Makefile: -------------------------------------------------------------------------------- 1 | all: site 2 | 3 | ill-generator: 4 | go install github.com/syncsynchalt/ill-generator@latest 5 | cp ~/go/bin/ill-generator . 6 | 7 | hkdf: 8 | git clone git@github.com:syncsynchalt/hkdf 9 | 10 | ill12: 11 | git clone git@github.com:syncsynchalt/illustrated-tls ill12 12 | 13 | ill13: 14 | git clone git@github.com:syncsynchalt/illustrated-tls13 ill13 15 | 16 | site: ill-generator hkdf ill12 ill13 17 | mkdir -p ../site/ 18 | /bin/echo -n '' > ../site/index.html 19 | for i in index-*.template; do \ 20 | ./ill-generator < $$i >> ../site/index.html || exit 1; \ 21 | done 22 | ./ill-generator < certificate.html.template > ../site/certificate.html 23 | mkdir -p ../site/files/ 24 | cp -a ill13/archive/tools/aes_128_gcm_decrypt.c ../site/files/ 25 | cp -a ../server/server.key ../server/server.crt ../site/files/ 26 | cp -a hkdf/hkdf-dtls ../site/files/hkdf-dtls.sh 27 | cp -a ill12/site/files/curve25519-mult.c ../site/files/ 28 | 29 | dist: 30 | cd .. && $(MAKE) dist 31 | 32 | clean: 33 | rm -rf ill-generator ill12 ill13 hkdf 34 | -------------------------------------------------------------------------------- /generate/index-00-header.html.template: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | The Illustrated DTLS Connection: Every Byte Explained 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 |
45 | QUIC 46 | DTLS 47 | TLS 1.3 48 | TLS 1.2 49 |
50 | 51 |

The Illustrated DTLS Connection

52 |
53 | 54 |

Every byte explained and reproduced

55 |
56 | 57 |

In this demonstration a client connects to a server, 58 | negotiates a DTLS 1.3 connection, sends "ping", 59 | receives "pong", then terminates the connection. 60 | Click below to begin exploring. 61 |

62 | 63 |
64 | 67 |
68 | -------------------------------------------------------------------------------- /generate/index-01-chello.html.template: -------------------------------------------------------------------------------- 1 |
2 |
3 |
Client Key Exchange Generation
4 | 5 | 6 |
7 |

The connection begins with the client generating a private/public keypair 8 | for key exchange. Key exchange is a technique 9 | where two parties can agree on the same number without 10 | an eavesdropper being able to tell what the number is. 11 |

12 | An explanation of the key exchange can be found on my 13 | X25519 site, 14 | but doesn't need to be understood in depth for the rest 15 | of this page. 16 |

17 | The private key is chosen by selecting an integer between 18 | 0 and 2256-1. The client does this by generating 32 19 | bytes (256 bits) of random data. The 20 | private key 21 | selected is: 22 | 23 |

202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f
25 | 26 | The public key 27 | is created from the private key as explained on the X25519 site. 28 | The public key calculated is: 29 | 30 |
358072d6365880d1aeea329adf9121383851ed21a28e3b75e965d0d2cd166254
32 | 33 | The public key calculation can be confirmed at the command line: 34 | 35 |
### requires openssl 1.1.0 or higher
 36 | $ openssl pkey -noout -text < client-ephemeral-private.key
 37 | 
 38 | X25519 Private-Key:
 39 | priv:
 40 |     20:21:22:23:24:25:26:27:28:29:2a:2b:2c:2d:2e:
 41 |     2f:30:31:32:33:34:35:36:37:38:39:3a:3b:3c:3d:
 42 |     3e:3f
 43 | pub:
 44 |     35:80:72:d6:36:58:80:d1:ae:ea:32:9a:df:91:21:
 45 |     38:38:51:ed:21:a2:8e:3b:75:e9:65:d0:d2:cd:16:
 46 |     62:54
 47 | 
48 |
49 | At this point nothing has been sent over the network. Continue the connection below. 50 |
51 |
52 |
53 | 54 | %file ../captures/caps/record-chello 55 |
56 |
57 |
Client Hello Datagram
58 |
59 | The encrypted session begins with the client saying "Hello". 60 | The client provides information including the following: 61 |
    62 |
  • client random data (used later in the handshake) 63 |
  • a list of cipher suites that the client supports 64 |
  • a public key for key exchange 65 |
  • protocol versions that the client can support 66 |
67 |
68 | 69 | 70 | DTLS Record Header 71 | 72 | %next 13 73 | %bytes 74 | 75 |
76 | Each DTLS record starts with a type, some sequence info, and a length. 77 |
    78 |
  • %0 - TLS record type %d0 (Handshake) 79 |
  • %1 %2 - Protocol version (DTLS 1.2, see below) 80 |
  • %3 %4 - key epoch (incremented each time the encryption keys are updated) 81 |
  • %5 %6 %7 %8 %9 %10 - DTLS record sequence number %nnnnnn5 82 |
  • %11 %12 - length of following data in this record (%nn11 bytes) 83 |
84 |

85 | DTLS versions are encoded by breaking the protocol version 86 | into parts and then putting each part into a byte with the ones-complement value 87 | (thus "1.3" becomes {1,3} which becomes the bytes 0xFE 0xFC). 88 | This complement technique keeps DTLS versions distinct from TLS versions. 89 |

90 | Because middleboxes have been created and deployed 91 | that do not allow protocol versions that 92 | they do not recognize, all DTLS 1.3 sessions 93 | indicate version DTLS 1.2 (0xFE 0xFD) in unencrypted records. 94 |

95 |
96 | 97 | 98 | TLS Handshake Header 99 | 100 | %next 4 101 | %bytes 102 | 103 |
104 | Each TLS handshake record starts with a type and a length. 105 |
    106 |
  • %0 - handshake record type %d0 (ClientHello) 107 |
  • %1 %2 %3 - %nnn1 bytes of client hello data is in this handshake record. 108 |
109 |
110 |
111 | 112 | 113 | Handshake Reconstruction Data 114 | 115 | %next 8 116 | %bytes 117 | 118 |
119 | Because UDP does not guarantee delivery or ordering, and 120 | because UDP datagrams might be smaller than handshake records that need 121 | to be sent, values must be provided to support record re-construction 122 | in case of data loss, reordering, or fragmentation. 123 |
    124 |
  • %0 %1 - handshake message sequence number %nn0 125 |
  • %2 %3 %4 - fragment offset of %nnn2 bytes 126 |
  • %5 %6 %7 - fragment length of %nnn5 bytes 127 |
128 |

129 | In this case the entire handshake record fits within a single UDP datagram, 130 | indicated by offset of zero and length of the full handshake record length. 131 |

132 |
133 | 134 | 135 | Legacy Client Version 136 | 137 | %next 2 138 | %bytes 139 | 140 |
141 | DTLS versions are encoded by breaking the protocol version 142 | into parts and then putting each part into a byte with the ones-complement value 143 | (thus "1.3" becomes {1,3} which becomes the bytes 0xFE 0xFC). 144 | This complement technique keeps DTLS versions distinct from TLS versions. 145 |

146 | Because middleboxes have been created and deployed 147 | that do not allow protocol versions that 148 | they do not recognize, all DTLS 1.3 sessions 149 | indicate version DTLS 1.2 (0xFE 0xFD) in this field. 150 | Therefore this field is no longer used in version negotiation, 151 | which uses the "Supported Versions" extension below instead. 152 |

153 |
154 | 155 | 156 | Client Random 157 | 158 | %next 32 159 | %bytes 160 | 161 |
162 | The client provides 32 bytes of random data. This data will be used later in the session. 163 | In this example we've made the random data a predictable string. 164 |
165 |
166 | 167 | 168 | Legacy Session ID 169 | 170 | %next 1 171 | %bytes 172 | 173 |
174 | This is a legacy field and is not used in DTLS 1.3. 175 |
    176 |
  • %0 - %n0 bytes of session ID follow 177 |
178 |
179 |
180 | 181 | 182 | Legacy Cookie 183 | 184 | %next 1 185 | %bytes 186 | 187 |
188 | This is a legacy field and is not used in DTLS 1.3. 189 |
    190 |
  • %0 - %n0 bytes of round-trip confirmation follow 191 |
192 |
193 |
194 | 195 | 196 | Cipher Suites 197 | 198 | %next 8 199 | %bytes 200 | 201 |
202 | The client provides an ordered list of which 203 | cipher suites it will support for encryption. 204 | The list is in the order preferred by the 205 | client, with highest preference first. 206 |
    207 |
  • %0 %1 - %nn0 bytes of cipher suite data 208 |
  • %2 %3 - assigned value for TLS_AES_128_GCM_SHA256 209 |
  • %4 %5 - assigned value for TLS_AES_256_GCM_SHA384 210 |
  • %6 %7 - assigned value for TLS_CHACHA20_POLY1305_SHA256 211 |
212 |
213 |
214 | 215 | 216 | Compression Methods 217 | 218 | %next 2 219 | %bytes 220 | 221 |
222 | Previous versions of TLS (and therefore DTLS) supported 223 | compression, which was found to leak 224 | information about the encrypted data allowing 225 | it to be read (see CRIME). 226 |

227 | DTLS 1.3 no longer allows compression, so 228 | this field is always a single entry with 229 | the "null" compression method which performs 230 | no change to the data. 231 |
    232 |
  • %0 - %n0 byte of compression methods 233 |
  • %1 - assigned value for "null" compression 234 |
235 |
236 |
237 | 238 | 239 | Extensions Length 240 | 241 | %next 2 242 | %bytes 243 | 244 |
245 | The client has provided a list of optional 246 | extensions which the server can use to 247 | take action or enable new features. 248 |
    249 |
  • %0 %1 - the extensions will take %nn0 bytes of data 250 |
251 | Each extension will start with two bytes 252 | that indicate which extension it is, followed 253 | by a two-byte content length field, followed 254 | by the contents of the extension. 255 |
256 |
257 | 258 | 259 | Extension - Key Share 260 | 261 | %next 42 262 | %bytes 263 | 264 |
265 | The client sends one or more ephemeral public keys 266 | using algorithm(s) that it thinks the server 267 | will support. This allows the 268 | rest of the handshake after the ClientHello 269 | and ServerHello messages to be encrypted, 270 | unlike previous protocol versions where the 271 | handshake was sent in the clear. 272 |
    273 |
  • 00 33 - assigned value for extension "Key Share" 274 |
  • %2 %3 - %nn2 bytes of "Key Share" extension data follows 275 |
  • %4 %5 - %nn4 bytes of key share data follows 276 |
  • 00 1d - assigned value for x25519 (key exchange via curve25519) 277 |
  • %8 %9 - %nn8 bytes of public key follows 278 |
  • %10 %11 ... %-2 %-1 - public key from the step "Client Key Exchange Generation" 279 |
280 |
281 |
282 | 283 | 284 | Extension - Supported Versions 285 | 286 | %next 7 287 | %bytes 288 | 289 |
290 | The client indicates its support of DTLS 1.3. For compatibility reasons 291 | this is put into an extension instead of the Client Version field above. 292 |
    293 |
  • 00 2b - assigned value for extension "Supported Versions" 294 |
  • %2 %3 - %nn2 bytes of "Supported Versions" extension data follows 295 |
  • %4 - %n4 bytes of DTLS version follows 296 |
  • %5 %6 - assigned value for DTLS 1.3 297 |
298 |
299 |
300 | 301 | 302 | Extension - Signature Algorithms 303 | 304 | %next 36 305 | %bytes 306 | 307 |
308 | This extension indicates which signature 309 | algorithms the client supports. This can 310 | influence the certificate that the server 311 | presents to the client, as well as the 312 | signature that is sent by the server in 313 | the CertificateVerify record. 314 |

315 | This list is presented in descending order 316 | of the client's preference. 317 |

    318 |
  • 00 0d - assigned value for extension "Signature Algorithms" 319 |
  • %2 %3 - %nn2 bytes of "Signature Algorithms" extension data follows 320 |
  • %4 %5 - %nn4 bytes of data are in the following list of algorithms 321 |
  • %6 %7 - assigned value for ECDSA-SECP512r1-SHA512 322 |
  • %8 %9 - assigned value for ECDSA-SECP384r1-SHA384 323 |
  • %10 %11 - assigned value for ECDSA-SECP256r1-SHA256 324 |
  • %12 %13 - assigned value for ECDSA-SHA1 325 |
  • %14 %15 - assigned value for RSA-PSS-RSAE-SHA512 326 |
  • %16 %17 - assigned value for RSA-PSS-PSS-SHA512 327 |
  • %18 %19 - assigned value for RSA-PSS-RSAE-SHA384 328 |
  • %20 %21 - assigned value for RSA-PSS-PSS-SHA384 329 |
  • %22 %23 - assigned value for RSA-PSS-RSAE-SHA256 330 |
  • %24 %25 - assigned value for RSA-PSS-PSS-SHA256 331 |
  • %26 %27 - assigned value for RSA-PKCS1-SHA512 332 |
  • %28 %29 - assigned value for RSA-PKCS1-SHA384 333 |
  • %30 %31 - assigned value for RSA-PKCS1-SHA256 334 |
  • %32 %33 - assigned value for SHA224-RSA 335 |
  • %34 %35 - assigned value for RSA-PKCS1-SHA1 336 |
337 |
338 |
339 | 340 | 341 | Extension - Encrypt-then-MAC 342 | 343 | %next 4 344 | %bytes 345 | 346 |
347 | The client indicates it can support EtM, which prevents 348 | certain vulnerabilities 349 | in earlier versions of TLS and DTLS. In DTLS 1.3 this mechanism is always used, 350 | so this extension will have no effect in this session. 351 |
    352 |
  • %0 %1 - assigned value for extension "Encrypt-then-MAC" 353 |
  • %2 %3 - %nn2 bytes of extension data follows 354 |
355 |
356 |
357 | 358 | 359 | Extension - Supported Groups 360 | 361 | %next 8 362 | %bytes 363 | 364 |
365 | The client has indicated that it supports 366 | elliptic curve (EC) cryptography for one curve type. 367 | To make this extension more generic for 368 | other cryptography types it calls these 369 | "supported groups" instead of "supported curves". 370 |

371 | This list is presented in descending order 372 | of the client's preference. 373 |
    374 |
  • %0 %1 - assigned value for extension "supported groups" 375 |
  • %2 %3 - %nn2 bytes of "supported group" extension data follows 376 |
  • %4 %5 - %nn4 bytes of data are in the curves list 377 |
  • %6 %7 - assigned value for the curve "x25519" 378 |
379 |
380 |
381 |
382 |
383 |
384 | %empty 385 | -------------------------------------------------------------------------------- /generate/index-02-shello.html.template: -------------------------------------------------------------------------------- 1 |
2 |
3 |
Server Key Exchange Generation
4 | 5 | 6 |
7 |

The server creates its own private/public keypair 8 | for key exchange. Key exchange is a technique 9 | where two parties can agree on the same number without 10 | an eavesdropper being able to tell what the number is. 11 |

12 | An explanation of the key exchange can be found on my 13 | X25519 site, 14 | but doesn't need to be understood in depth for the rest 15 | of this page. 16 |

17 | The private key is chosen by selecting an integer between 18 | 0 and 2256-1. The server does this by generating 32 19 | bytes (256 bits) of random data. The 20 | private key 21 | selected is: 22 | 23 |

909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeaf
25 | 26 | The public key 27 | is created from the private key as explained on the X25519 site. 28 | The public key calculated is: 29 | 30 |
9fd7ad6dcff4298dd3f96d5b1b2af910a0535b1488d7f8fabb349a982880b615
32 | 33 | The public key calculation can be confirmed with command line tools: 34 | 35 |
### requires openssl 1.1.0 or higher
 36 | $ openssl pkey -noout -text < server-ephemeral-private.key
 37 | 
 38 | X25519 Private-Key:
 39 | priv:
 40 |     90:91:92:93:94:95:96:97:98:99:9a:9b:9c:9d:9e:
 41 |     9f:a0:a1:a2:a3:a4:a5:a6:a7:a8:a9:aa:ab:ac:ad:
 42 |     ae:af
 43 | pub:
 44 |     9f:d7:ad:6d:cf:f4:29:8d:d3:f9:6d:5b:1b:2a:f9:
 45 |     10:a0:53:5b:14:88:d7:f8:fa:bb:34:9a:98:28:80:
 46 |     b6:15
 47 | 
48 |
49 |
50 |
51 |
52 | 53 | %file ../captures/caps/record-shello 54 |
55 |
56 |
Server Hello Datagram
57 |
58 | The server says "Hello" back. The server provides information including the following: 59 |
    60 |
  • server random data (used later in the handshake) 61 |
  • a selected cipher suite 62 |
  • a public key for key exchange 63 |
  • the negotiated protocol version 64 |
65 |
66 | 67 | 68 | DTLS Record Header 69 | 70 | %next 13 71 | %bytes 72 | 73 |
74 | Each DTLS record starts with a type, some sequence info, and a length. 75 |
    76 |
  • %0 - TLS record type %d0 (Handshake) 77 |
  • %1 %2 - Protocol version (DTLS 1.2, see below) 78 |
  • %3 %4 - key epoch (incremented each time the encryption keys are updated) 79 |
  • %5 %6 %7 %8 %9 %10 - DTLS record sequence number %nnnnnn5 80 |
  • %11 %12 - length of following data in this record (%nn11 bytes) 81 |
82 |

83 | DTLS versions are encoded by breaking the protocol version 84 | into parts and then putting each part into a byte with the ones-complement value 85 | (thus "1.3" becomes {1,3} which becomes the bytes 0xFE 0xFC). 86 | This complement technique keeps DTLS versions distinct from TLS versions. 87 |

88 | Because middleboxes have been created and deployed 89 | that do not allow protocol versions that 90 | they do not recognize, all DTLS 1.3 sessions 91 | indicate version DTLS 1.2 (0xFE 0xFD) in unencrypted records. 92 |

93 |
94 | 95 | 96 | TLS Handshake Header 97 | 98 | %next 4 99 | %bytes 100 | 101 |
102 | Each TLS handshake record starts with a type and a length. 103 |
    104 |
  • %0 - handshake record type %d0 (ServerHello) 105 |
  • %1 %2 %3 - %nnn1 bytes of server hello data is in this handshake record. 106 |
107 |
108 |
109 | 110 | 111 | Handshake Reconstruction Data 112 | 113 | %next 8 114 | %bytes 115 | 116 |
117 | Because UDP does not guarantee delivery or ordering, and 118 | because UDP datagrams might be smaller than handshake records that need 119 | to be sent, values must be provided to support record re-construction 120 | in case of data loss, reordering, or fragmentation. 121 |
    122 |
  • %0 %1 - handshake message sequence number %nn0 123 |
  • %2 %3 %4 - fragment offset of %nnn2 bytes 124 |
  • %5 %6 %7 - fragment length of %nnn5 bytes 125 |
126 |

127 | In this case the entire handshake record fits within a single UDP datagram, 128 | indicated by offset of zero and length of the full handshake record length. 129 |

130 |
131 | 132 | 133 | Server Version (Legacy) 134 | 135 | %next 2 136 | %bytes 137 | 138 |
139 | DTLS versions are encoded by breaking the protocol version 140 | into parts and then putting each part into a byte with the ones-complement value 141 | (thus "1.3" becomes {1,3} which becomes the bytes 0xFE 0xFC). 142 | This complement technique keeps DTLS versions distinct from TLS versions. 143 |

144 | Because middleboxes have been created and deployed 145 | that do not allow protocol versions that 146 | they do not recognize, all DTLS 1.3 sessions 147 | indicate version DTLS 1.2 (0xFE 0xFD) in this field. 148 | Therefore this field is no longer used in version negotiation, 149 | which uses the "Supported Versions" extension below instead. 150 |

151 |
152 | 153 | 154 | Server Random 155 | 156 | %next 32 157 | %bytes 158 | 159 |
160 | The server provides 32 bytes of random data. This data will be used later in the session. 161 | In this example we've made the random data a predictable string. 162 |
163 |
164 | 165 | 166 | Legacy Session ID 167 | 168 | %next 1 169 | %bytes 170 | 171 |
172 | This is a legacy field and is not used in DTLS 1.3. 173 |
    174 |
  • %0 - %n0 bytes of session ID follow 175 |
176 |
177 |
178 | 179 | 180 | Cipher Suite 181 | 182 | %next 2 183 | %bytes 184 | 185 |
186 | The server has selected cipher suite 0x1301 187 | (TLS_AES_128_GCM_SHA256) from the list of options given by the client. 188 |
189 |
190 | 191 | 192 | Compression Method 193 | 194 | %next 1 195 | %bytes 196 | 197 |
198 | The server has selected compression method 199 | 0x00 ("Null", which performs no compression) 200 | from the list of options given by the client. 201 |
202 |
203 | 204 | 205 | Extensions Length 206 | 207 | %next 2 208 | %bytes 209 | 210 |
211 | The server has returned a list of extensions 212 | to the client. Because the server is 213 | forbidden from replying with an extension 214 | that the client did not send in its hello 215 | message, the server knows that the client 216 | will understand and support all extensions listed. 217 |
    218 |
  • %0 %1 - the extensions will take %nn0 bytes of data 219 |
220 |
221 |
222 | 223 | 224 | Extension - Key Share 225 | 226 | %next 40 227 | %bytes 228 | 229 |
230 | The server sends a public key using the algorithm 231 | of the public key sent by the client. Once this is sent 232 | encryption keys can be calculated and the rest of the 233 | handshake will be encrypted, 234 | unlike previous protocol versions where the 235 | handshake was sent in the clear. 236 |
    237 |
  • 00 33 - assigned value for extension "Key Share" 238 |
  • %2 %3 - %nn2 bytes of "Key Share" extension data follows 239 |
  • 00 1d - assigned value for x25519 (key exchange via curve25519) 240 |
  • %6 %7 - %nn6 bytes of public key follows 241 |
  • %8 %9 ... %-2 %-1 - public key from the step "Server Key Exchange Generation" 242 |
243 |
244 |
245 | 246 | 247 | Extension - Supported Versions 248 | 249 | %next 6 250 | %bytes 251 | 252 |
253 | The server indicates the negotiated DTLS version of 1.3. 254 |
    255 |
  • 00 2b - assigned value for extension "Supported Versions" 256 |
  • %2 %3 - %nn2 bytes of "Supported Versions" extension data follows 257 |
  • %4 %5 - assigned value for DTLS 1.3 258 |
259 |
260 |
261 |
262 |
263 |
264 | %empty 265 | -------------------------------------------------------------------------------- /generate/index-02-zzz-handkeys.html.template: -------------------------------------------------------------------------------- 1 |
2 |
3 |
Server Handshake Keys Calc
4 | 5 | 6 |
7 | The server now has the information needed to calculate the 8 | keys used to encrypt the rest of the handshake. It uses the following 9 | information in this calculation: 10 | 15 |

16 | First, the server finds the shared secret, which is the 17 | result of the key exchange that allows the client and server 18 | to agree on a number. The server multiplies the client's 19 | public key by the server's private key using the curve25519() 20 | algorithm. The 32-byte result is found to be: 21 |

df4a291baa1eb7cfa6934b29b474baad2697e29f1f920dcc77c8a0a088447624
23 | I've provided a tool 24 | to perform this calculation: 25 | 26 |
$ cc -o curve25519-mult curve25519-mult.c
 27 | $ ./curve25519-mult server-ephemeral-private.key \
 28 |                     client-ephemeral-public.key | hexdump
 29 | 
 30 | 0000000 df 4a 29 1b aa 1e b7 cf a6 93 4b 29 b4 74 ba ad
 31 | 0000010 26 97 e2 9f 1f 92 0d cc 77 c8 a0 a0 88 44 76 24
 32 | 
33 |
34 | 35 | It then calculates the SHA256 hash of all handshake messages 36 | to this point (ClientHello and ServerHello). The hash does 37 | not include DTLS-only bytes in the records, which are bytes 0-12 and 17-24. Ignoring these 38 | bytes allows implementations to share code between TLS and DTLS implementations. This "hello_hash" 39 | is aee8eba0d2ee87052fbbc6864c1514c5a927d6f0ffb4f7954c7f379d95f1b1d7: 41 | 42 |
$ (cat captures/caps/record-chello | perl -0777 -pe 's/.{13}(.{4}).{8}/$1/';
 43 |    cat captures/caps/record-shello | perl -0777 -pe 's/.{13}(.{4}).{8}/$1/') \
 44 |    | openssl sha256
 45 | 
 46 | aee8eba0d2ee87052fbbc6864c1514c5a927d6f0ffb4f7954c7f379d95f1b1d7
 47 | 
48 |
49 | 50 | We then feed the hash and the shared secret into a set of 51 | key derivation operations, designed to protect against known and 52 | possible attacks: 53 | 54 | 55 |
early_secret = HKDF-Extract(salt=00, key=00...)
 56 | empty_hash = SHA256("")
 57 | derived_secret = HKDF-Expand-Label(key: early_secret, label: "derived", ctx: empty_hash, len: 32)
 58 | handshake_secret = HKDF-Extract(salt: derived_secret, key: shared_secret)
 59 | client_secret = HKDF-Expand-Label(key: handshake_secret, label: "c hs traffic", ctx: hello_hash, len: 32)
 60 | server_secret = HKDF-Expand-Label(key: handshake_secret, label: "s hs traffic", ctx: hello_hash, len: 32)
 61 | client_key = HKDF-Expand-Label(key: client_secret, label: "key", ctx: "", len: 16)
 62 | server_key = HKDF-Expand-Label(key: server_secret, label: "key", ctx: "", len: 16)
 63 | client_iv = HKDF-Expand-Label(key: client_secret, label: "iv", ctx: "", len: 12)
 64 | server_iv = HKDF-Expand-Label(key: server_secret, label: "iv", ctx: "", len: 12)
 65 | client_sn_key = HKDF-Expand-Label(key: client_secret, label: "sn", ctx: "", len: 16)
 66 | server_sn_key = HKDF-Expand-Label(key: server_secret, label: "sn", ctx: "", len: 16)
 67 | 
68 |
69 | 70 | I've created an HKDF tool 71 | to perform these operations on the command line. 72 | The key derivation process is reproduced below: 73 | 74 |
$ hello_hash=aee8eba0d2ee87052fbbc6864c1514c5a927d6f0ffb4f7954c7f379d95f1b1d7
 75 | $ shared_secret=df4a291baa1eb7cfa6934b29b474baad2697e29f1f920dcc77c8a0a088447624
 76 | $ zero_key=0000000000000000000000000000000000000000000000000000000000000000
 77 | $ early_secret=$(./hkdf-dtls extract 00 $zero_key)
 78 | $ empty_hash=$(openssl sha256 < /dev/null | sed -e 's/.* //')
 79 | $ derived_secret=$(./hkdf-dtls expandlabel $early_secret "derived" $empty_hash 32)
 80 | $ handshake_secret=$(./hkdf-dtls extract $derived_secret $shared_secret)
 81 | $ csecret=$(./hkdf-dtls expandlabel $handshake_secret "c hs traffic" $hello_hash 32)
 82 | $ ssecret=$(./hkdf-dtls expandlabel $handshake_secret "s hs traffic" $hello_hash 32)
 83 | $ client_handshake_key=$(./hkdf-dtls expandlabel $csecret "key" "" 16)
 84 | $ server_handshake_key=$(./hkdf-dtls expandlabel $ssecret "key" "" 16)
 85 | $ client_handshake_iv=$(./hkdf-dtls expandlabel $csecret "iv" "" 12)
 86 | $ server_handshake_iv=$(./hkdf-dtls expandlabel $ssecret "iv" "" 12)
 87 | $ client_sn_key=$(./hkdf-dtls expandlabel $csecret "sn" "" 16)
 88 | $ server_sn_key=$(./hkdf-dtls expandlabel $ssecret "sn" "" 16)
 89 | $ echo client_key: $client_handshake_key
 90 | $ echo client_iv: $client_handshake_iv
 91 | $ echo server_key: $server_handshake_key
 92 | $ echo server_iv: $server_handshake_iv
 93 | $ echo client_sn_key: $client_sn_key
 94 | $ echo server_sn_key: $server_sn_key
 95 | 
 96 | client_key: 6caa2633d5e48f10051e69dc45549c97
 97 | client_iv: 106dc6e393b7a9ea8ef29dd7
 98 | server_key: 004e03e64ab6cba6b542775ec230e20a
 99 | server_iv: 6d9924be044ee97c624913f2
100 | client_sn_key: beed6218676635c2cb46a45694144fec
101 | server_sn_key: 7173fac51194e775001d625ef69d7c9f
102 | 
103 |
104 | 105 | From this we get the following encryption keys and IVs: 106 |
    107 |
  • client handshake key: 6caa2633d5e48f10051e69dc45549c97 108 |
  • client handshake IV: 106dc6e393b7a9ea8ef29dd7 109 |
  • server handshake key: 004e03e64ab6cba6b542775ec230e20a 110 |
  • server handshake IV: 6d9924be044ee97c624913f2 111 |
  • client record number key: beed6218676635c2cb46a45694144fec 112 |
  • server record number key: 7173fac51194e775001d625ef69d7c9f 113 |
114 |
115 |
116 |
117 | 118 |
119 |
120 |
Client Handshake Keys Calc
121 | 122 | 123 |
124 | The client now has the information to calculate the 125 | keys that used to encrypt the rest of the handshake. It uses the following 126 | information in this calculation: 127 | 132 | First, the client finds the shared secret, which is the 133 | result of the key exchange that allows the client and server 134 | to agree on a number. The client multiplies the server's 135 | public key by the client's private key using the curve25519() 136 | algorithm. The properties of elliptic curve multiplication will 137 | cause this to result in the same number found by the server in its 138 | multiplication. The 32-byte result is found to be: 139 |
df4a291baa1eb7cfa6934b29b474baad2697e29f1f920dcc77c8a0a088447624
141 | 142 | I've provided a tool 143 | to perform this calculation: 144 | 145 |
$ cc -o curve25519-mult curve25519-mult.c
146 | $ ./curve25519-mult client-ephemeral-private.key \
147 |                     server-ephemeral-public.key | hexdump
148 | 
149 | 0000000 df 4a 29 1b aa 1e b7 cf a6 93 4b 29 b4 74 ba ad
150 | 0000010 26 97 e2 9f 1f 92 0d cc 77 c8 a0 a0 88 44 76 24
151 | 
152 |
153 | Since the shared secret above is the same number calculated by the 154 | server in "Server Handshake Keys Calc", the rest of 155 | the calculation is identical and the same values are found: 156 |
    157 |
  • client handshake key: 6caa2633d5e48f10051e69dc45549c97 158 |
  • client handshake IV: 106dc6e393b7a9ea8ef29dd7 159 |
  • server handshake key: 004e03e64ab6cba6b542775ec230e20a 160 |
  • server handshake IV: 6d9924be044ee97c624913f2 161 |
  • client record number key: beed6218676635c2cb46a45694144fec 162 |
  • server record number key: 7173fac51194e775001d625ef69d7c9f 163 |
164 |
165 |
166 |
167 | -------------------------------------------------------------------------------- /generate/index-03-encext.html.template: -------------------------------------------------------------------------------- 1 |
2 |
3 |
Server Encrypted Extensions Datagram
4 | 5 | 6 |
7 |

8 | The connection (including the handshake) is encrypted from 9 | this point on. The encryption of handshake data is new in 10 | DTLS 1.3. 11 |

12 | Any extensions that aren't needed for negotiating encryption 13 | are given in this encrypted record so they can be hidden from eavesdroppers and middleboxes. 14 |

15 | %file ../captures/caps/03-encext-shs-prot 16 | 17 | 18 | Header Info Byte 19 | 20 | %next 1 21 | %bytes 22 | 23 |
24 |

25 | An encrypted DTLS packet starts with the "Unified Header". This first byte 26 | of the header gives information on the structure and decryption of 27 | the rest of the header and packet. 28 |

29 | The bits in the value %x0 have the following meaning: 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 |
ValueMeaning
MSB001Fixed bits
0Connection ID field not present in header
1Sequence number in header is 2 bytes long
1Length field is present in header
LSB10Encryption epoch 2 - handshake keys
42 |

43 |
44 | 45 | 46 | Record Number 47 | 48 | %next 2 49 | %bytes 50 | 51 | 52 | 00 00 53 | 54 |
55 |
56 | The record number of the packet is encrypted, to prevent middleware 57 | from interpreting or interfering with the sequencing of packets. 58 |

59 | This encryption is applied by encrypting a sample of each packet's 60 | payload with the "record number key", then XOR'ing certain bits 61 | and bytes in each packet with the resulting data. 62 |

63 | An example of how to compute record number encryption: 64 | 65 |
### "server record number key" from handshake keys calc step above
 66 | $ key=7173fac51194e775001d625ef69d7c9f
 67 | ### sample is taken from 16 bytes of payload starting 5 bytes into the record
 68 | $ sample=ee9dcff3f8679a4859fe68377fb34ada
 69 | $ echo $sample | xxd -r -p | openssl aes-128-ecb -K $key | head -c 2 | xxd -p
 70 | 
 71 | 79fa
 72 | 
 73 | ### the above bytes are xor'd one-for-one into the bytes of the record number
 74 | 
75 |
76 |
77 |
78 | 79 | 80 | Record Length 81 | 82 | %next 2 83 | %bytes 84 | 85 |
86 | Each record is assumed to consume the remainder of the datagram unless this 87 | optional length is given. This allows implementations to send several TLS records 88 | in a single datagram (though this connection does not take advantage of this). 89 |
    90 |
  • %0 %1 - Record length of %nn0 bytes 91 |
92 |
93 |
94 | 95 | 96 | Encrypted Data 97 | 98 | %next 31 99 | %bytes 100 | 101 |
102 | This data is encrypted with the server handshake key. 103 |
104 |
105 | 106 | Auth Tag 107 | 108 | %next 16 109 | %bytes 110 | 111 |
112 | This is the AEAD authentication tag 113 | that protects the integrity of the 114 | encrypted data and the record header. 115 |
116 |
117 | %empty 118 | 119 |
120 |
Decryption
121 |
122 | This data is encrypted using the server 123 | handshake key and the server handshake IV that were 124 | generated during the "Server Handshake Keys 125 | Calc" step. The IV will be modified 126 | by XOR'ing it by the count of records that 127 | have already been encrypted with this key, 128 | which in this case is 0. The process also 129 | takes as input the 5-byte record header, as authenticated 130 | data that must match for the decryption to 131 | succeed. 132 |

133 | Because the openssl command line 134 | tool does not yet support AEAD ciphers, 135 | I've written command line tools to both 136 | decrypt 137 | and encrypt 138 | this data. 139 | 140 |
### from the "Server Handshake Keys Calc" step
141 | $ key=004e03e64ab6cba6b542775ec230e20a
142 | $ iv=6d9924be044ee97c624913f2
143 | ### from this record
144 | $ recdata=2e0000002f
145 | $ authtag=f67fe442e7d7d2b8a3d5fa59574ffd00
146 | $ recordnum=0
147 | ### may need to add -I and -L flags for include and lib dirs
148 | $ cc -o aes_128_gcm_decrypt aes_128_gcm_decrypt.c -lssl -lcrypto
149 | $ cat /tmp/msg1 \
150 |   | ./aes_128_gcm_decrypt $iv $recordnum $key $recdata $authtag \
151 |   | hexdump -C
152 | 
153 | 00000000  08 00 00 12 00 01 00 00  00 00 00 12 00 10 00 0a  |................|
154 | 00000010  00 0c 00 0a 00 17 00 1d  00 18 00 19 01 00 16     |...............|
155 | 
156 |
157 |
158 |
159 | 160 | %file ../captures/caps/record-encext 161 | 162 | Handshake Header 163 | 164 | %next 4 165 | %bytes 166 | 167 |
168 | Each handshake message starts with a type and a length. 169 |
    170 |
  • %0 - handshake message type 0x08 (encrypted extensions) 171 |
  • %1 %2 %3 - %nnn1 bytes of handshake message data within 172 |
173 |
174 |
175 | 176 | 177 | Handshake Reconstruction Data 178 | 179 | %next 8 180 | %bytes 181 | 182 |
183 | Because UDP does not guarantee delivery or ordering, and 184 | because UDP datagrams might be smaller than handshake records that need 185 | to be sent, values must be provided to support record re-construction 186 | in case of data loss, reordering, or fragmentation. 187 |
    188 |
  • %0 %1 - handshake message sequence number %nn0 189 |
  • %2 %3 %4 - fragment offset of %nnn2 bytes 190 |
  • %5 %6 %7 - fragment length of %nnn5 bytes 191 |
192 |

193 | In this case the entire handshake record fits within a single UDP datagram, 194 | indicated by offset of zero and length of the full handshake record length. 195 |

196 |
197 | 198 | 199 | Extensions Length 200 | 201 | %next 2 202 | %bytes 203 | 204 |
205 | The server has provided a list of extensions to apply to the connection. 206 |
    207 |
  • %0 %1 - the extensions will take %nn0 bytes of data 208 |
209 |
210 |
211 | 212 | 213 | Extension - Supported Groups 214 | 215 | %next 16 216 | %bytes 217 | 218 |
219 | The server replies with the elliptic curve algorithms 220 | it supports. To make this extension more generic for 221 | other cryptography types it calls these "supported 222 | groups" instead of "supported curves". 223 |
    224 |
  • %0 %1 - assigned value for extension "Supported Groups" 225 |
  • %2 %3 - %nn2 bytes of "supported group" extension data follows 226 |
  • %4 %5 - %nn4 bytes of data are in the curves list 227 |
  • %6 %7 - assigned value for curve "secp256r1" 228 |
  • %8 %9 - assigned value for curve "x25519" 229 |
  • %10 %11 - assigned value for curve "secp384r1" 230 |
  • %12 %13 - assigned value for curve "secp521r1" 231 |
  • %14 %15 - assigned value for group "ffdhe2048" 232 |
233 |
234 |
235 | 236 | 237 | Record Type 238 | 239 | %next 1 240 | %bytes 241 | 242 |
243 | Each encrypted DTLS 1.3 record has a final byte which indicates its actual record type. 244 |
    245 |
  • %0 - type is 22 (handshake record) 246 |
247 |
248 |
249 |
250 |
251 |
252 | %empty 253 | -------------------------------------------------------------------------------- /generate/index-04-cert.html.template: -------------------------------------------------------------------------------- 1 |
2 |
3 |
Server Certificate Datagram
4 | 5 | 6 |
7 | The server sends one or more certificates: 8 |
    9 |
  • the certificate for this host, containing the hostname, 10 | a public key, and a signature from a third party asserting 11 | that the owner of the certificate's hostname holds the 12 | private key for this certificate 13 |
  • an optional list of further certificates, each of which signs 14 | the previous certificate, and which form a chain of trust 15 | leading from the host certificate to a trusted certificate 16 | that has been pre-installed on the client 17 |
18 | In an effort to keep this example small we only send a 19 | host certificate. Certificates are in a binary format 20 | called DER which 21 | you can explore here. 22 |
23 | %file ../captures/caps/04-cert-shs-prot 24 | 25 | 26 | Header Info Byte 27 | 28 | %next 1 29 | %bytes 30 | 31 |
32 |

33 | An encrypted DTLS packet starts with the "Unified Header". This first byte 34 | of the header gives information on the structure and decryption of 35 | the rest of the header and packet. 36 |

37 | The bits in the value %x0 have the following meaning: 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 |
ValueMeaning
MSB001Fixed bits
0Connection ID field not present in header
1Sequence number in header is 2 bytes long
1Length field is present in header
LSB10Encryption epoch 2 - handshake keys
50 |

51 |
52 | 53 | 54 | Record Number 55 | 56 | %next 2 57 | %bytes 58 | 59 | 60 | 00 01 61 | 62 |
63 |
64 | The record number of the packet is encrypted, to prevent middleware 65 | from interpreting or interfering with the sequencing of packets. 66 |

67 | This encryption is applied by encrypting a sample of each packet's 68 | payload with the "record number key", then XOR'ing certain bits 69 | and bytes in each packet with the resulting data. 70 |

71 | An example of how to compute record number encryption: 72 | 73 |
### "server record number key" from handshake keys calc step above
 74 | $ key=7173fac51194e775001d625ef69d7c9f
 75 | ### sample is taken from 16 bytes of payload starting 5 bytes into the record
 76 | $ sample=d3777e1adf9e98c8c4ffa072c2c3b6bb
 77 | $ echo $sample | xxd -r -p | openssl aes-128-ecb -K $key | head -c 2 | xxd -p
 78 | 
 79 | ed2a
 80 | 
 81 | ### the above bytes are xor'd one-for-one into the bytes of the record number
 82 | 
83 |
84 |
85 |
86 | 87 | 88 | Record Length 89 | 90 | %next 2 91 | %bytes 92 | 93 |
94 | Each record is assumed to consume the remainder of the datagram unless this 95 | optional length is given. This allows implementations to send several TLS records 96 | in a single datagram (though this connection does not take advantage of this). 97 |
    98 |
  • %0 %1 - Record length of %nn0 bytes 99 |
100 |
101 |
102 | 103 | 104 | Encrypted Data 105 | 106 | %next 827 107 | %bytes 108 | 109 |
110 | This data is encrypted with the server handshake key. 111 |
112 |
113 | 114 | Auth Tag 115 | 116 | %next 16 117 | %bytes 118 | 119 |
120 | This is the AEAD authentication tag 121 | that protects the integrity of the 122 | encrypted data and the record header. 123 |
124 |
125 | %empty 126 | 127 |
128 |
Decryption
129 |
130 | This data is encrypted using the server 131 | handshake key and the server handshake IV that were 132 | generated during the "Server Handshake Keys 133 | Calc" step. The IV will be modified 134 | by XOR'ing it by the count of records that 135 | have already been encrypted with this key, 136 | which in this case is 1. The process also 137 | takes as input the 5-byte record header, as authenticated 138 | data that must match for the decryption to 139 | succeed. 140 |

141 | Because the openssl command line 142 | tool does not yet support AEAD ciphers, 143 | I've written command line tools to both 144 | decrypt 145 | and encrypt 146 | this data. 147 | 148 |
### from the "Server Handshake Keys Calc" step
149 | $ key=004e03e64ab6cba6b542775ec230e20a
150 | $ iv=6d9924be044ee97c624913f2
151 | ### from this record
152 | $ recdata=2e0001034b
153 | $ authtag=a43070214522938c0e66829ef133349b
154 | $ recordnum=1
155 | ### may need to add -I and -L flags for include and lib dirs
156 | $ cc -o aes_128_gcm_decrypt aes_128_gcm_decrypt.c -lssl -lcrypto
157 | $ cat /tmp/msg1 \
158 |   | ./aes_128_gcm_decrypt $iv $recordnum $key $recdata $authtag \
159 |   | hexdump -C
160 | 
161 | 00000000  0b 00 03 2e 00 02 00 00  00 00 03 2e 00 00 03 2a  |...............*|
162 | 00000010  00 03 25 30 82 03 21 30  82 02 09 a0 03 02 01 02  |..%0..!0...?....|
163 | 00000020  02 08 15 5a 92 ad c2 04  8f 90 30 0d 06 09 2a 86  |...Z.??...0...*.|
164 | 00000030  48 86 f7 0d 01 01 0b 05  00 30 22 31 0b 30 09 06  |H.?......0"1.0..|
165 | 00000040  03 55 04 06 13 02 55 53  31 13 30 11 06 03 55 04  |.U....US1.0...U.|
166 | 00000050  0a 13 0a 45 78 61 6d 70  6c 65 20 43 41 30 1e 17  |...Example CA0..|
167 | 00000060  0d 31 38 31 30 30 35 30  31 33 38 31 37 5a 17 0d  |.181005013817Z..|
168 | 00000070  31 39 31 30 30 35 30 31  33 38 31 37 5a 30 2b 31  |191005013817Z0+1|
169 | 00000080  0b 30 09 06 03 55 04 06  13 02 55 53 31 1c 30 1a  |.0...U....US1.0.|
170 | 00000090  06 03 55 04 03 13 13 65  78 61 6d 70 6c 65 2e 75  |..U....example.u|
171 | ... snip ...
172 | 
173 |
174 |
175 |
176 | 177 | %file ../captures/caps/record-cert 178 | 179 | Handshake Header 180 | 181 | %next 4 182 | %bytes 183 | 184 |
185 | Each handshake message starts with a type and a length. 186 |
    187 |
  • %0 - handshake message type 0x0b (certificate) 188 |
  • %1 %2 %3 - %nnn1 bytes of handshake message data within 189 |
190 |
191 |
192 | 193 | 194 | Handshake Reconstruction Data 195 | 196 | %next 8 197 | %bytes 198 | 199 |
200 | Because UDP does not guarantee delivery or ordering, and 201 | because UDP datagrams might be smaller than handshake records that need 202 | to be sent, values must be provided to support record re-construction 203 | in case of data loss, reordering, or fragmentation. 204 |
    205 |
  • %0 %1 - handshake message sequence number %nn0 206 |
  • %2 %3 %4 - fragment offset of %nnn2 bytes 207 |
  • %5 %6 %7 - fragment length of %nnn5 bytes 208 |
209 |

210 | In this case the entire handshake record fits within a single UDP datagram, 211 | indicated by offset of zero and length of the full handshake record length. 212 |

213 |
214 | 215 | 216 | Request Context 217 | 218 | %next 1 219 | %bytes 220 | 221 |
222 | This record is empty because this certificate was not sent in 223 | response to a Certificate Request. 224 |
    225 |
  • %0 - %n0 bytes of request context follows 226 |
227 |
228 |
229 | 230 | 231 | Certificates Length 232 | 233 | %next 3 234 | %bytes 235 | 236 |
237 |
    238 |
  • %0 %1 %2 - %nnn0 bytes of certificates follow 239 |
240 |
241 |
242 | 243 | 244 | Certificate Length 245 | 246 | %next 3 247 | %bytes 248 | 249 |
250 | The length of the first (and only) certificate. 251 |
    252 |
  • %0 %1 %2 - %nnn0 bytes of certificate follows 253 |
254 |
255 |
256 | 257 | 258 | Certificate 259 | 260 | %next 805 261 | %bytes 262 | 263 |
264 | The certificate is in ASN.1 DER 265 | encoding. The details of this format and 266 | the content of this binary payload are 267 | documented on another page. 268 | The certificate 269 | can be converted to the binary data in this message 270 | at the command line: 271 | 272 |
$ openssl x509 -outform der < server.crt | hexdump
273 | 
274 | 0000000 30 82 03 21 30 82 02 09 a0 03 02 01 02 02 08 15
275 | 0000010 5a 92 ad c2 04 8f 90 30 0d 06 09 2a 86 48 86 f7
276 | ... snip ...
277 | 
278 |
279 |
280 |
281 | 282 | 283 | Certificate Extensions 284 | 285 | %next 2 286 | %bytes 287 | 288 |
289 | The server can provide extension data for the certificate. 290 |
    291 |
  • %0 %1 - %nn0 bytes of extension data follows 292 |
293 |
294 |
295 | 296 | 297 | Record Type 298 | 299 | %next 1 300 | %bytes 301 | 302 |
303 | Each encrypted DTLS 1.3 record has a final byte which indicates its actual record type. 304 |
    305 |
  • %0 - type is 22 (handshake record) 306 |
307 |
308 |
309 |
310 |
311 |
312 | %empty 313 | -------------------------------------------------------------------------------- /generate/index-05-cverify.html.template: -------------------------------------------------------------------------------- 1 |
2 |
3 |
Server Cert Verify Datagram
4 | 5 | 6 |
7 | The server provides information that ties the public key 8 | generated during Server Key Exchange Generation to the 9 | ownership of the certificate's private key. 10 |
11 | %file ../captures/caps/05-cverify-shs-prot 12 | 13 | 14 | Header Info Byte 15 | 16 | %next 1 17 | %bytes 18 | 19 |
20 |

21 | An encrypted DTLS packet starts with the "Unified Header". This first byte 22 | of the header gives information on the structure and decryption of 23 | the rest of the header and packet. 24 |

25 | The bits in the value %x0 have the following meaning: 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 |
ValueMeaning
MSB001Fixed bits
0Connection ID field not present in header
1Sequence number in header is 2 bytes long
1Length field is present in header
LSB10Encryption epoch 2 - handshake keys
38 |

39 |
40 | 41 | 42 | Record Number 43 | 44 | %next 2 45 | %bytes 46 | 47 | 48 | 00 02 49 | 50 |
51 |
52 | The record number of the packet is encrypted, to prevent middleware 53 | from interpreting or interfering with the sequencing of packets. 54 |

55 | This encryption is applied by encrypting a sample of each packet's 56 | payload with the "record number key", then XOR'ing certain bits 57 | and bytes in each packet with the resulting data. 58 |

59 | An example of how to compute record number encryption: 60 | 61 |
### "server record number key" from handshake keys calc step above
 62 | $ key=7173fac51194e775001d625ef69d7c9f
 63 | ### sample is taken from 16 bytes of payload starting 5 bytes into the record
 64 | $ sample=83bedfea0f4aa578453af4f4a4be4106
 65 | $ echo $sample | xxd -r -p | openssl aes-128-ecb -K $key | head -c 2 | xxd -p
 66 | 
 67 | a43c
 68 | 
 69 | ### the above bytes are xor'd one-for-one into the bytes of the record number
 70 | 
71 |
72 |
73 |
74 | 75 | 76 | Record Length 77 | 78 | %next 2 79 | %bytes 80 | 81 |
82 | Each record is assumed to consume the remainder of the datagram unless this 83 | optional length is given. This allows implementations to send several TLS records 84 | in a single datagram (though this connection does not take advantage of this). 85 |
    86 |
  • %0 %1 - Record length of %nn0 bytes 87 |
88 |
89 |
90 | 91 | 92 | Encrypted Data 93 | 94 | %next 273 95 | %bytes 96 | 97 |
98 | This data is encrypted with the server handshake key. 99 |
100 |
101 | 102 | Auth Tag 103 | 104 | %next 16 105 | %bytes 106 | 107 |
108 | This is the AEAD authentication tag 109 | that protects the integrity of the 110 | encrypted data and the record header. 111 |
112 |
113 | %empty 114 | 115 |
116 |
Decryption
117 |
118 | This data is encrypted using the server 119 | handshake key and the server handshake IV that were 120 | generated during the "Server Handshake Keys 121 | Calc" step. The IV will be modified 122 | by XOR'ing it by the count of records that 123 | have already been encrypted with this key, 124 | which in this case is 2. The process also 125 | takes as input the 5-byte record header, as authenticated 126 | data that must match for the decryption to 127 | succeed. 128 |

129 | Because the openssl command line 130 | tool does not yet support AEAD ciphers, 131 | I've written command line tools to both 132 | decrypt 133 | and encrypt 134 | this data. 135 | 136 |
### from the "Server Handshake Keys Calc" step
137 | $ key=004e03e64ab6cba6b542775ec230e20a
138 | $ iv=6d9924be044ee97c624913f2
139 | ### from this record
140 | $ recdata=2e00020121
141 | $ authtag=bdf0f6f0060db4711971387c2189394f
142 | $ recordnum=2
143 | ### may need to add -I and -L flags for include and lib dirs
144 | $ cc -o aes_128_gcm_decrypt aes_128_gcm_decrypt.c -lssl -lcrypto
145 | $ cat /tmp/msg1 \
146 |   | ./aes_128_gcm_decrypt $iv $recordnum $key $recdata $authtag \
147 |   | hexdump -C
148 | 
149 | 00000000  0f 00 01 04 00 03 00 00  00 00 01 04 08 04 01 00  |................|
150 | 00000010  2c 76 3d 6a d3 d8 af 7f  a3 7d a6 d8 d9 0e 73 7c  |,v=j?د.?}???.s||
151 | 00000020  ea 53 ee 7a ff a5 61 48  74 cc 68 48 9c 73 a2 f3  |?S?z??aHt?hH.s??|
152 | 00000030  a0 43 cb ba e6 c2 7a 41  91 0e de 9a df c7 22 23  |?C˺??zA..?.??"#|
153 | 00000040  58 26 12 ec 96 79 fe 1f  9f a5 f4 a4 b6 12 f8 6f  |X&.?.y?..????.?o|
154 | ... snip ...
155 | 
156 |
157 |
158 |
159 | 160 | %file ../captures/caps/record-cverify 161 | 162 | Handshake Header 163 | 164 | %next 4 165 | %bytes 166 | 167 |
168 | Each handshake message starts with a type and a length. 169 |
    170 |
  • %0 - handshake message type 0x0f (certificate verify) 171 |
  • %1 %2 %3 - %nnn1 bytes of handshake message data within 172 |
173 |
174 |
175 | 176 | 177 | Handshake Reconstruction Data 178 | 179 | %next 8 180 | %bytes 181 | 182 |
183 | Because UDP does not guarantee delivery or ordering, and 184 | because UDP datagrams might be smaller than handshake records that need 185 | to be sent, values must be provided to support record re-construction 186 | in case of data loss, reordering, or fragmentation. 187 |
    188 |
  • %0 %1 - handshake message sequence number %nn0 189 |
  • %2 %3 %4 - fragment offset of %nnn2 bytes 190 |
  • %5 %6 %7 - fragment length of %nnn5 bytes 191 |
192 |

193 | In this case the entire handshake record fits within a single UDP datagram, 194 | indicated by offset of zero and length of the full handshake record length. 195 |

196 |
197 | 198 | 199 | Signature 200 | 201 | %next 260 202 | %bytes 203 | 204 |
205 | Because the server is generating ephemeral 206 | keys for each session (optional in TLS 1.2, 207 | mandatory in TLS 1.3) the session is not 208 | inherently tied to the certificate as it 209 | was in previous versions of TLS, when the 210 | certificate's public/private key were used 211 | for key exchange. 212 |

213 | To prove 214 | that the server owns the server certificate 215 | (giving the certificate validity in this 216 | TLS session), it signs a hash of the handshake 217 | messages using the private key associated with the certificate. 218 | The signature can be proven valid by 219 | the client by using the public key included in the certificate. 220 |
    221 |
  • 08 04 - reserved value for RSA-PSS-RSAE-SHA256 signature 222 |
  • %2 %3 - %nn2 bytes of signature data follows 223 |
  • %4 %5 %6 ... %-3 %-2 %-1 - a signature over this handshake's hash 224 |
225 | The signing process can't be reproduced byte-for-byte 226 | at the command line because the signing tool introduces 227 | random or changing data into the signature. 228 |

229 | We can verify the signature using the 230 | server's certificate 231 | at the command line: 232 | 233 |
### find the hash of the conversation to this point, excluding
234 | ### cleartext record headers, DTLS-only record headers,
235 | ### or 1-byte decrypted record trailers
236 | $ handshake_hash=$((
237 |    cat record-chello | perl -0777 -pe 's/.{13}(.{4}).{8}/$1/s';
238 |    cat record-shello | perl -0777 -pe 's/.{13}(.{4}).{8}/$1/s';
239 |    cat record-encext | perl -0777 -pe 's/(.{4}).{8}(.*).$/$1$2/s';
240 |    cat record-cert   | perl -0777 -pe 's/(.{4}).{8}(.*).$/$1$2/s';
241 |    )| openssl sha256)
242 | 
243 | ### build the data that was signed:
244 | ### 1. add 64 space characters
245 | $ echo -n '                                ' > /tmp/tosign
246 | $ echo -n '                                ' >> /tmp/tosign
247 | ### 2. add this fixed string
248 | $ echo -n 'TLS 1.3, server CertificateVerify' >> /tmp/tosign
249 | ### 3. add a single null character
250 | $ echo -en '\0' >> /tmp/tosign
251 | ### 4. add hash of handshake to this point
252 | $ echo $handshake_hash | xxd -r -p >> /tmp/tosign
253 | 
254 | ### copy the signature that we want to verify
255 | $ echo "2c 76 3d 6a d3 d8 af 7f a3 7d a6 d8 d9 0e 73 7c ea 53 ee 7a
256 |   ff a5 61 48 74 cc 68 48 9c 73 a2 f3 a0 43 cb ba e6 c2 7a 41 91 0e
257 |   de 9a df c7 22 23 58 26 12 ec 96 79 fe 1f 9f a5 f4 a4 b6 12 f8 6f
258 |   40 88 49 a3 29 f7 63 e0 4f be 95 9a 91 e8 d1 8d 4a ba 79 29 57 6f
259 |   a0 24 ec b2 37 d6 33 78 e9 8e e5 9d c9 59 49 b2 63 b3 06 53 0a 2e
260 |   6f b9 b2 2f a2 3c 64 32 33 43 03 89 33 01 fd 60 e2 05 82 6e b9 ec
261 |   41 4f ec 5f 9a 0d 6f 8f 3d 89 a0 9f 14 8e 0f 05 03 49 bc 1e 17 97
262 |   d9 28 1e ed f6 e7 66 9c e2 56 ae 79 d4 ee 8c 96 56 0d cf 07 6c 2a
263 |   45 a4 ee e8 d2 79 71 0f 0c e7 03 4a 3f 5c aa 94 41 4e ae df 61 08
264 |   48 66 e4 9e 81 88 3e e2 1a 12 59 3c cb 96 dd 11 76 9e 34 0f 1e 6c
265 |   c2 14 b0 57 95 e5 4a fc 94 79 84 5e 4d f2 bf 96 9f bb 21 8c b9 c4
266 |   b8 34 a8 51 be 34 75 a1 45 2f 4b 33 55 4f 9d 65" | xxd -r -p > /tmp/sig
267 | 
268 | ### extract the public key from the certificate
269 | $ openssl x509 -pubkey -noout -in server.crt > server.pub
270 | 
271 | ### verify the signature
272 | $ cat /tmp/tosign | openssl dgst -verify server.pub -sha256 \
273 |     -sigopt rsa_padding_mode:pss -sigopt rsa_pss_saltlen:-1 -signature /tmp/sig
274 | 
275 | Verified OK
276 | 
277 |
278 |
279 |
280 | 281 | 282 | Record Type 283 | 284 | %next 1 285 | %bytes 286 | 287 |
288 | Each encrypted DTLS 1.3 record has a final byte which indicates its actual record type. 289 |
    290 |
  • %0 - type is 22 (handshake record) 291 |
292 |
293 |
294 |
295 |
296 |
297 | %empty 298 | -------------------------------------------------------------------------------- /generate/index-06-sfin.html.template: -------------------------------------------------------------------------------- 1 |
2 |
3 |
Server Handshake Finished Datagram
4 |
5 | To verify that the handshake was successful and not tampered 6 | with, the server calculates verification data that client will agree on. 7 | The verification data is built from a hash of all handshake 8 | messages. 9 |
10 | %file ../captures/caps/06-fin-shs-prot 11 | 12 | 13 | Header Info Byte 14 | 15 | %next 1 16 | %bytes 17 | 18 |
19 |

20 | An encrypted DTLS packet starts with the "Unified Header". This first byte 21 | of the header gives information on the structure and decryption of 22 | the rest of the header and packet. 23 |

24 | The bits in the value %x0 have the following meaning: 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 |
ValueMeaning
MSB001Fixed bits
0Connection ID field not present in header
1Sequence number in header is 2 bytes long
1Length field is present in header
LSB10Encryption epoch 2 - handshake keys
37 |

38 |
39 | 40 | 41 | Record Number 42 | 43 | %next 2 44 | %bytes 45 | 46 | 47 | 00 03 48 | 49 |
50 |
51 | The record number of the packet is encrypted, to prevent middleware 52 | from interpreting or interfering with the sequencing of packets. 53 |

54 | This encryption is applied by encrypting a sample of each packet's 55 | payload with the "record number key", then XOR'ing certain bits 56 | and bytes in each packet with the resulting data. 57 |

58 | An example of how to compute record number encryption: 59 | 60 |
### "server record number key" from handshake keys calc step above
 61 | $ key=7173fac51194e775001d625ef69d7c9f
 62 | ### sample is taken from 16 bytes of payload starting 5 bytes into the record
 63 | $ sample=a44135732a099823b8a5f61a2b35ce92
 64 | $ echo $sample | xxd -r -p | openssl aes-128-ecb -K $key | head -c 2 | xxd -p
 65 | 
 66 | 0bbb
 67 | 
 68 | ### the above bytes are xor'd one-for-one into the bytes of the record number
 69 | 
70 |
71 |
72 |
73 | 74 | 75 | Record Length 76 | 77 | %next 2 78 | %bytes 79 | 80 |
81 | Each record is assumed to consume the remainder of the datagram unless this 82 | optional length is given. This allows implementations to send several TLS records 83 | in a single datagram (though this connection does not take advantage of this). 84 |
    85 |
  • %0 %1 - Record length of %nn0 bytes 86 |
87 |
88 |
89 | 90 | 91 | Encrypted Data 92 | 93 | %next 45 94 | %bytes 95 | 96 |
97 | This data is encrypted with the server handshake key. 98 |
99 |
100 | 101 | Auth Tag 102 | 103 | %next 16 104 | %bytes 105 | 106 |
107 | This is the AEAD authentication tag 108 | that protects the integrity of the 109 | encrypted data and the record header. 110 |
111 |
112 | %empty 113 | 114 |
115 |
Decryption
116 |
117 | This data is encrypted using the server 118 | handshake key and the server handshake IV that were 119 | generated during the "Server Handshake Keys 120 | Calc" step. The IV will be modified 121 | by XOR'ing it by the count of records that 122 | have already been encrypted with this key, 123 | which in this case is 3. The process also 124 | takes as input the 5-byte record header, as authenticated 125 | data that must match for the decryption to 126 | succeed. 127 |

128 | Because the openssl command line 129 | tool does not yet support AEAD ciphers, 130 | I've written command line tools to both 131 | decrypt 132 | and encrypt 133 | this data. 134 | 135 |
### from the "Server Handshake Keys Calc" step
136 | $ key=004e03e64ab6cba6b542775ec230e20a
137 | $ iv=6d9924be044ee97c624913f2
138 | ### from this record
139 | $ recdata=2e0003003d
140 | $ authtag=6b8b90ce9fe6454e0cef9efc40f2397a
141 | $ recordnum=3
142 | ### may need to add -I and -L flags for include and lib dirs
143 | $ cc -o aes_128_gcm_decrypt aes_128_gcm_decrypt.c -lssl -lcrypto
144 | $ cat /tmp/msg1 \
145 |   | ./aes_128_gcm_decrypt $iv $recordnum $key $recdata $authtag \
146 |   | hexdump -C
147 | 
148 | 00000000  14 00 00 20 00 04 00 00  00 00 00 20 1d 89 aa 62
149 | 00000010  e5 f8 8a 0f c9 52 88 47  15 d8 ac b3 79 86 59 af
150 | 00000020  b9 e7 78 9a 8d b2 b3 81  6b a4 52 46 16
151 | ... snip ...
152 | 
153 |
154 |
155 |
156 | 157 | %file ../captures/caps/record-sfin 158 | 159 | Handshake Header 160 | 161 | %next 4 162 | %bytes 163 | 164 |
165 | Each handshake message starts with a type and a length. 166 |
    167 |
  • %0 - handshake message type 0x14 (finished) 168 |
  • %1 %2 %3 - %nnn1 bytes of handshake message data within 169 |
170 |
171 |
172 | 173 | 174 | Handshake Reconstruction Data 175 | 176 | %next 8 177 | %bytes 178 | 179 |
180 | Because UDP does not guarantee delivery or ordering, and 181 | because UDP datagrams might be smaller than handshake records that need 182 | to be sent, values must be provided to support record re-construction 183 | in case of data loss, reordering, or fragmentation. 184 |
    185 |
  • %0 %1 - handshake message sequence number %nn0 186 |
  • %2 %3 %4 - fragment offset of %nnn2 bytes 187 |
  • %5 %6 %7 - fragment length of %nnn5 bytes 188 |
189 |

190 | In this case the entire handshake record fits within a single UDP datagram, 191 | indicated by offset of zero and length of the full handshake record length. 192 |

193 |
194 | 195 | 196 | Verify Data 197 | 198 | %next 32 199 | %bytes 200 | 201 |
202 | The verify_data is built using the 203 | server_secret from 204 | the "Server Handshake Keys Calc" step and 205 | a SHA256 hash of every handshake record 206 | before this point (Client Hello to Server Certificate 207 | Verify). 208 | 209 | 210 |
finished_key = HKDF-Expand-Label(key: server_secret, label: "finished", ctx: "", len: 32)
211 | finished_hash = SHA256(Client Hello ... Server Cert Verify)
212 | verify_data = HMAC-SHA256(key: finished_key, msg: finished_hash)
213 | 
214 |
215 | 216 | We can use the HKDF 217 | tool to reproduce this on the command line. 218 | 219 |
### find the hash of the conversation to this point, excluding
220 | ### cleartext record headers, DTLS-only record headers,
221 | ### or 1-byte decrypted record trailers
222 | $ fin_hash=$((
223 |     cat record-chello  | perl -0777 -pe 's/.{13}(.{4}).{8}/$1/s';
224 |     cat record-shello  | perl -0777 -pe 's/.{13}(.{4}).{8}/$1/s';
225 |     cat record-encext  | perl -0777 -pe 's/(.{4}).{8}(.*).$/$1$2/s';
226 |     cat record-cert    | perl -0777 -pe 's/(.{4}).{8}(.*).$/$1$2/s';
227 |     cat record-cverify | perl -0777 -pe 's/(.{4}).{8}(.*).$/$1$2/s';
228 |   ) | openssl sha256)
229 | $ sht_secret=8ad7990b9d249bcbaa0805d8d3f3ad2259e75f3a42c5d84db3ea3c6ee57b3d38
230 | $ fin_key=$(./hkdf-dtls expandlabel $sht_secret "finished" "" 32)
231 | $ echo $fin_hash | xxd -r -p \
232 |     | openssl dgst -sha256 -mac HMAC -macopt hexkey:$fin_key
233 | 
234 | 1d89aa62e5f88a0fc952884715d8acb3798659afb9e7789a8db2b3816ba45246
235 | 
236 |
237 |
238 |
239 | 240 | 241 | Record Type 242 | 243 | %next 1 244 | %bytes 245 | 246 |
247 | Each encrypted DTLS 1.3 record has a final byte which indicates its actual record type. 248 |
    249 |
  • %0 - type is 22 (handshake record) 250 |
251 |
252 |
253 |
254 |
255 |
256 | %empty 257 | -------------------------------------------------------------------------------- /generate/index-07-cfin.html.template: -------------------------------------------------------------------------------- 1 |
2 |
3 |
Client Handshake Finished Datagram
4 |
5 | To verify that the handshake was successful and not tampered 6 | with, the client calculates verification data that the 7 | server will agree on, and encrypts it with the client 8 | handshake key. The verification data is built from a hash 9 | of all handshake messages. 10 |
11 | %file ../captures/caps/07-fin-chs-prot 12 | 13 | 14 | Header Info Byte 15 | 16 | %next 1 17 | %bytes 18 | 19 |
20 |

21 | An encrypted DTLS packet starts with the "Unified Header". This first byte 22 | of the header gives information on the structure and decryption of 23 | the rest of the header and packet. 24 |

25 | The bits in the value %x0 have the following meaning: 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 |
ValueMeaning
MSB001Fixed bits
0Connection ID field not present in header
1Sequence number in header is 2 bytes long
1Length field is present in header
LSB10Encryption epoch 2 - handshake keys
38 |

39 |
40 | 41 | 42 | Record Number 43 | 44 | %next 2 45 | %bytes 46 | 47 | 48 | 00 00 49 | 50 |
51 |
52 | The record number of the packet is encrypted, to prevent middleware 53 | from interpreting or interfering with the sequencing of packets. 54 |

55 | This encryption is applied by encrypting a sample of each packet's 56 | payload with the "record number key", then XOR'ing certain bits 57 | and bytes in each packet with the resulting data. 58 |

59 | An example of how to compute record number encryption: 60 | 61 |
### "client record number key" from handshake keys calc step above
 62 | $ key=beed6218676635c2cb46a45694144fec
 63 | ### sample is taken from 16 bytes of payload starting 5 bytes into the record
 64 | $ sample=8a2cd52d5000f8786afb47cdf0b8f2b8
 65 | $ echo $sample | xxd -r -p | openssl aes-128-ecb -K $key | head -c 2 | xxd -p
 66 | 
 67 | c248
 68 | 
 69 | ### the above bytes are xor'd one-for-one into the bytes of the record number
 70 | 
71 |
72 |
73 |
74 | 75 | 76 | Record Length 77 | 78 | %next 2 79 | %bytes 80 | 81 |
82 | Each record is assumed to consume the remainder of the datagram unless this 83 | optional length is given. This allows implementations to send several TLS records 84 | in a single datagram (though this connection does not take advantage of this). 85 |
    86 |
  • %0 %1 - Record length of %nn0 bytes 87 |
88 |
89 |
90 | 91 | 92 | Encrypted Data 93 | 94 | %next 45 95 | %bytes 96 | 97 |
98 | This data is encrypted with the client handshake key. 99 |
100 |
101 | 102 | Auth Tag 103 | 104 | %next 16 105 | %bytes 106 | 107 |
108 | This is the AEAD authentication tag 109 | that protects the integrity of the 110 | encrypted data and the record header. 111 |
112 |
113 | %empty 114 | 115 |
116 |
Decryption
117 |
118 | This data is encrypted using the client 119 | handshake key and the client handshake IV that were 120 | generated during the "Client Handshake Keys 121 | Calc" step. The IV will be modified 122 | by XOR'ing it by the count of records that 123 | have already been encrypted with this key, 124 | which in this case is 0. The process also 125 | takes as input the 5-byte record header, as authenticated 126 | data that must match for the decryption to 127 | succeed. 128 |

129 | Because the openssl command line 130 | tool does not yet support AEAD ciphers, 131 | I've written command line tools to both 132 | decrypt 133 | and encrypt 134 | this data. 135 | 136 |
### from the "Client Handshake Keys Calc" step
137 | $ key=6caa2633d5e48f10051e69dc45549c97
138 | $ iv=106dc6e393b7a9ea8ef29dd7
139 | ### from this record
140 | $ recdata=2e0000003d
141 | $ authtag=07a2c5f75f7cffb7465bc01d23d8511f
142 | $ recordnum=0
143 | ### may need to add -I and -L flags for include and lib dirs
144 | $ cc -o aes_128_gcm_decrypt aes_128_gcm_decrypt.c -lssl -lcrypto
145 | $ cat /tmp/msg1 \
146 |   | ./aes_128_gcm_decrypt $iv $recordnum $key $recdata $authtag \
147 |   | hexdump -C
148 | 
149 | 00000000  14 00 00 20 00 01 00 00  00 00 00 20 6f 28 01 39
150 | 00000010  6b 0e 90 eb b4 a3 ba 38  4a bc fc 6b 24 20 1a bd
151 | 00000020  81 b3 16 b2 39 1d a3 78  37 7f ac f5 16
152 | 
153 |
154 |
155 |
156 | 157 | %file ../captures/caps/record-cfin 158 | 159 | Handshake Header 160 | 161 | %next 4 162 | %bytes 163 | 164 |
165 | Each handshake message starts with a type and a length. 166 |
    167 |
  • %0 - handshake message type 0x14 (finished) 168 |
  • %1 %2 %3 - %nnn1 bytes of handshake message data within 169 |
170 |
171 |
172 | 173 | 174 | Handshake Reconstruction Data 175 | 176 | %next 8 177 | %bytes 178 | 179 |
180 | Because UDP does not guarantee delivery or ordering, and 181 | because UDP datagrams might be smaller than handshake records that need 182 | to be sent, values must be provided to support record re-construction 183 | in case of data loss, reordering, or fragmentation. 184 |
    185 |
  • %0 %1 - handshake message sequence number %nn0 186 |
  • %2 %3 %4 - fragment offset of %nnn2 bytes 187 |
  • %5 %6 %7 - fragment length of %nnn5 bytes 188 |
189 |

190 | In this case the entire handshake record fits within a single UDP datagram, 191 | indicated by offset of zero and length of the full handshake record length. 192 |

193 |
194 | 195 | 196 | Verify Data 197 | 198 | %next 32 199 | %bytes 200 | 201 |
202 | The verify_data is built using the 203 | client_secret from 204 | the "Client Handshake Keys Calc" step and 205 | a SHA256 hash of every handshake record 206 | before this point (Client Hello to Server Handshake Finished). 207 | 208 | 209 |
finished_key = HKDF-Expand-Label(key: client_secret, label: "finished", ctx: "", len: 32)
210 | finished_hash = SHA256(Client Hello ... Server Handshake Finished)
211 | verify_data = HMAC-SHA256(key: finished_key, msg: finished_hash)
212 | 
213 |
214 | 215 | We can use the HKDF 216 | tool to reproduce this on the command line. 217 | 218 |
### find the hash of the conversation to this point, excluding
219 | ### cleartext record headers, DTLS-only record headers,
220 | ### or 1-byte decrypted record trailers
221 | $ fin_hash=$((
222 |     cat record-chello  | perl -0777 -pe 's/.{13}(.{4}).{8}/$1/s';
223 |     cat record-shello  | perl -0777 -pe 's/.{13}(.{4}).{8}/$1/s';
224 |     cat record-encext  | perl -0777 -pe 's/(.{4}).{8}(.*).$/$1$2/s';
225 |     cat record-cert    | perl -0777 -pe 's/(.{4}).{8}(.*).$/$1$2/s';
226 |     cat record-cverify | perl -0777 -pe 's/(.{4}).{8}(.*).$/$1$2/s';
227 |     cat record-sfin    | perl -0777 -pe 's/(.{4}).{8}(.*).$/$1$2/s';
228 |   ) | openssl sha256)
229 | $ cht_secret=33e472fb8d821b0193314626bebee307ccbd1aeb3d3a17ba468888ffc5246da1
230 | $ fin_key=$(./hkdf-dtls expandlabel $cht_secret "finished" "" 32)
231 | $ echo $fin_hash | xxd -r -p \
232 |     | openssl dgst -sha256 -mac HMAC -macopt hexkey:$fin_key
233 | 
234 | 6f2801396b0e90ebb4a3ba384abcfc6b24201abd81b316b2391da378377facf5
235 | 
236 |
237 |
238 |
239 | 240 | 241 | Record Type 242 | 243 | %next 1 244 | %bytes 245 | 246 |
247 | Each encrypted DTLS 1.3 record has a final byte which indicates its actual record type. 248 |
    249 |
  • %0 - type is 22 (handshake record) 250 |
251 |
252 |
253 |
254 |
255 |
256 | %empty 257 | -------------------------------------------------------------------------------- /generate/index-07-zzz-appkeys.html.template: -------------------------------------------------------------------------------- 1 |
2 |
3 |
Server Application Keys Calc
4 | 5 | 6 |
7 | The server now has the information to calculate the 8 | keys used to encrypt application traffic. 9 | It uses the following information in this calculation: 10 |
    11 |
  • The handshake secret (from "Server Handshake Key Calc") 12 |
  • The SHA256 hash of every handshake message from Client Hello to Server Handshake Finished
  • 13 |
14 | We calculate the SHA256 hash of all handshake messages to 15 | this point (Client Hello through Server Finished). 16 | The hash input does not include cleartext record headers, 17 | DTLS-only record headers, or 1-byte decrypted record trailers. 18 | 19 | This "handshake_hash" is 77ff5eee528abc269960b0ea316eb8578dc8325d86ec1336ffe4b2941e26d82b: 21 | 22 |
$ (
 23 |   cat record-chello  | perl -0777 -pe 's/.{13}(.{4}).{8}/$1/';
 24 |   cat record-shello  | perl -0777 -pe 's/.{13}(.{4}).{8}/$1/';
 25 |   cat record-encext  | perl -0777 -pe 's/(.{4}).{8}(.*).$/$1$2/s';
 26 |   cat record-cert    | perl -0777 -pe 's/(.{4}).{8}(.*).$/$1$2/s';
 27 |   cat record-cverify | perl -0777 -pe 's/(.{4}).{8}(.*).$/$1$2/s';
 28 |   cat record-sfin    | perl -0777 -pe 's/(.{4}).{8}(.*).$/$1$2/s';
 29 |   )| openssl sha256
 30 | 
 31 | 77ff5eee528abc269960b0ea316eb8578dc8325d86ec1336ffe4b2941e26d82b
 32 | 
33 |
34 | 35 | We then feed the hash and the handshake secret into a set of 36 | key derivation operations, designed to ensure the integrity 37 | of the handshake process and to protect against known and 38 | possible attacks: 39 | 40 | 41 |
empty_hash = SHA256("")
 42 | derived_secret = HKDF-Expand-Label(key: handshake_secret, label: "derived", ctx: empty_hash, len: 32)
 43 | master_secret = HKDF-Extract(salt: derived_secret, key: 00...)
 44 | client_secret = HKDF-Expand-Label(key: master_secret, label: "c ap traffic", ctx: handshake_hash, len: 32)
 45 | server_secret = HKDF-Expand-Label(key: master_secret, label: "s ap traffic", ctx: handshake_hash, len: 32)
 46 | client_application_key = HKDF-Expand-Label(key: client_secret, label: "key", ctx: "", len: 16)
 47 | server_application_key = HKDF-Expand-Label(key: server_secret, label: "key", ctx: "", len: 16)
 48 | client_application_iv = HKDF-Expand-Label(key: client_secret, label: "iv", ctx: "", len: 12)
 49 | server_application_iv = HKDF-Expand-Label(key: server_secret, label: "iv", ctx: "", len: 12)
 50 | 
51 |
52 | 53 | I've created an HKDF tool 54 | to perform these operations on the command line. 55 | 56 |
$ handshake_hash=77ff5eee528abc269960b0ea316eb8578dc8325d86ec1336ffe4b2941e26d82b
 57 | $ handshake_secret=d0d1397bb3c445d37f26f7ed00c83b73d2f67540de3761465ffe524f8f944e12
 58 | $ zero_key=0000000000000000000000000000000000000000000000000000000000000000
 59 | $ empty_hash=$(openssl sha256 < /dev/null | sed -e 's/.* //')
 60 | $ derived_secret=$(./hkdf-dtls expandlabel $handshake_secret "derived" $empty_hash 32)
 61 | $ master_secret=$(./hkdf-dtls extract $derived_secret $zero_key)
 62 | $ csecret=$(./hkdf-dtls expandlabel $master_secret "c ap traffic" $handshake_hash 32)
 63 | $ ssecret=$(./hkdf-dtls expandlabel $master_secret "s ap traffic" $handshake_hash 32)
 64 | $ client_application_key=$(./hkdf-dtls expandlabel $csecret "key" "" 16)
 65 | $ server_application_key=$(./hkdf-dtls expandlabel $ssecret "key" "" 16)
 66 | $ client_application_iv=$(./hkdf-dtls expandlabel $csecret "iv" "" 12)
 67 | $ server_application_iv=$(./hkdf-dtls expandlabel $ssecret "iv" "" 12)
 68 | $ client_sn_key=$(./hkdf-dtls expandlabel $csecret "sn" "" 16)
 69 | $ server_sn_key=$(./hkdf-dtls expandlabel $ssecret "sn" "" 16)
 70 | $ echo client_key: $client_application_key
 71 | $ echo client_iv: $client_application_iv
 72 | $ echo server_key: $server_application_key
 73 | $ echo server_iv: $server_application_iv
 74 | $ echo client_sn_key: $client_sn_key
 75 | $ echo server_sn_key: $server_sn_key
 76 | 
 77 | client_key: 9ba90dbce8857bc1fcb81d41a0465cfe
 78 | client_iv: 682219974631fa0656ee4eff
 79 | server_key: 2b65fffbbc8189474aa2003c43c32d4d
 80 | server_iv: 582f5a11bdaf973fe3ffeb4e
 81 | client_sn_key: 5cb5bd8bac29777c650c0dde22d16d47
 82 | server_sn_key: 57ba02596c6a1352d7fe8416c7e17d5a
 83 | 
84 |
85 | 86 | From this we get the following key data: 87 |
    88 |
  • server application key: 2b65fffbbc8189474aa2003c43c32d4d 89 |
  • server application IV: 582f5a11bdaf973fe3ffeb4e 90 |
  • client application key: 9ba90dbce8857bc1fcb81d41a0465cfe 91 |
  • client application IV: 682219974631fa0656ee4eff 92 |
  • client record number key: 5cb5bd8bac29777c650c0dde22d16d47 93 |
  • server record number key: 57ba02596c6a1352d7fe8416c7e17d5a 94 |
95 |
96 |
97 |
98 | 99 |
100 |
101 |
Client Application Keys Calc
102 |
103 | The client now has the information to calculate the 104 | keys used to encrypt application traffic. 105 | It performs the same calculation shown in "Server Application 106 | Keys Calc" and finds the same values: 107 |
    108 |
  • server application key: 2b65fffbbc8189474aa2003c43c32d4d 109 |
  • server application IV: 582f5a11bdaf973fe3ffeb4e 110 |
  • client application key: 9ba90dbce8857bc1fcb81d41a0465cfe 111 |
  • client application IV: 682219974631fa0656ee4eff 112 |
  • client record number key: 5cb5bd8bac29777c650c0dde22d16d47 113 |
  • server record number key: 57ba02596c6a1352d7fe8416c7e17d5a 114 |
115 |
116 |
117 |
118 | -------------------------------------------------------------------------------- /generate/index-08-sack.html.template: -------------------------------------------------------------------------------- 1 |
2 |
3 |
Server ACK Datagram
4 |
5 |

6 | Each peer must respond to or acknowledge data received from the other 7 | peer or it will be assumed lost and sent again. 8 |

9 | In this record the server acknowledges receipt of the Client Handshake Finished record. 10 |

11 | %file ../captures/caps/08-ack-sap-prot 12 | 13 | 14 | Header Info Byte 15 | 16 | %next 1 17 | %bytes 18 | 19 |
20 |

21 | An encrypted DTLS packet starts with the "Unified Header". This first byte 22 | of the header gives information on the structure and decryption of 23 | the rest of the header and packet. 24 |

25 | The bits in the value %x0 have the following meaning: 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 |
ValueMeaning
MSB001Fixed bits
0Connection ID field not present in header
1Sequence number in header is 2 bytes long
1Length field is present in header
LSB11Encryption epoch 3 - first application keys
38 |

39 |
40 | 41 | 42 | Record Number 43 | 44 | %next 2 45 | %bytes 46 | 47 | 48 | 00 00 49 | 50 |
51 |
52 | The record number of the packet is encrypted, to prevent middleware 53 | from interpreting or interfering with the sequencing of packets. 54 |

55 | This encryption is applied by encrypting a sample of each packet's 56 | payload with the "record number key", then XOR'ing certain bits 57 | and bytes in each packet with the resulting data. 58 |

59 | An example of how to compute record number encryption: 60 | 61 |
### "server record number key" from application keys calc step above
 62 | $ key=57ba02596c6a1352d7fe8416c7e17d5a
 63 | ### sample is taken from 16 bytes of payload starting 5 bytes into the record
 64 | $ sample=ea80ab8e08c93895418d243571ea6de7
 65 | $ echo $sample | xxd -r -p | openssl aes-128-ecb -K $key | head -c 2 | xxd -p
 66 | 
 67 | 3150
 68 | 
 69 | ### the above bytes are xor'd one-for-one into the bytes of the record number
 70 | 
71 |
72 |
73 |
74 | 75 | 76 | Record Length 77 | 78 | %next 2 79 | %bytes 80 | 81 |
82 | Each record is assumed to consume the remainder of the datagram unless this 83 | optional length is given. This allows implementations to send several TLS records 84 | in a single datagram (though this connection does not take advantage of this). 85 |
    86 |
  • %0 %1 - Record length of %nn0 bytes 87 |
88 |
89 |
90 | 91 | 92 | Encrypted Data 93 | 94 | %next 19 95 | %bytes 96 | 97 |
98 | This data is encrypted with the server application key. 99 |
100 |
101 | 102 | Auth Tag 103 | 104 | %next 16 105 | %bytes 106 | 107 |
108 | This is the AEAD authentication tag 109 | that protects the integrity of the 110 | encrypted data and the record header. 111 |
112 |
113 | %empty 114 | 115 |
116 |
Decryption
117 |
118 | This data is encrypted using the server 119 | application key and the server application IV that were 120 | generated during the "Server Application Keys 121 | Calc" step. The IV will be modified 122 | by XOR'ing it by the count of records that 123 | have already been encrypted with this key, 124 | which in this case is 0. The process also 125 | takes as input the 5-byte record header, as authenticated 126 | data that must match for the decryption to 127 | succeed. 128 |

129 | Because the openssl command line 130 | tool does not yet support AEAD ciphers, 131 | I've written command line tools to both 132 | decrypt 133 | and encrypt 134 | this data. 135 | 136 |
### from the "Server Application Keys Calc" step
137 | $ key=2b65fffbbc8189474aa2003c43c32d4d
138 | $ iv=582f5a11bdaf973fe3ffeb4e
139 | ### from this record
140 | $ recdata=2f00000023
141 | $ authtag=84230bb6043cb384df94b6da285a3bc4
142 | $ recordnum=0
143 | ### may need to add -I and -L flags for include and lib dirs
144 | $ cc -o aes_128_gcm_decrypt aes_128_gcm_decrypt.c -lssl -lcrypto
145 | $ cat /tmp/msg1 \
146 |   | ./aes_128_gcm_decrypt $iv $recordnum $key $recdata $authtag \
147 |   | hexdump -C
148 | 
149 | 00000000  00 10 00 00 00 00 00 00  00 02 00 00 00 00 00 00  |................|
150 | 00000010  00 00 1a                                          |...|
151 | 
152 |
153 |
154 |
155 | 156 | %file ../captures/caps/record-sack 157 | 158 | ACK Length 159 | 160 | %next 2 161 | %bytes 162 | 163 |
164 | Each ACK message starts with a payload length. 165 |
    166 |
  • %0 %1 - %nn0 bytes of ACK data within 167 |
168 |
169 |
170 | 171 | 172 | Record Acknowledgement 173 | 174 | %next 16 175 | %bytes 176 | 177 |
178 | The server acknowledges a record that it received. 179 |
    180 |
  • %1 %2 %3 %4 %5 %6 %7 - record epoch 2 (handshake keys) 181 |
  • %8 %9 %10 %11 %12 %13 %14 %15 - record number 0 182 |
183 |
184 |
185 | 186 | 187 | Record Type 188 | 189 | %next 1 190 | %bytes 191 | 192 |
193 | Each encrypted DTLS 1.3 record has a final byte which indicates its actual record type. 194 |
    195 |
  • %0 - type is %d0 (ACK record) 196 |
197 |
198 |
199 |
200 |
201 |
202 | %empty 203 | -------------------------------------------------------------------------------- /generate/index-09-cdata.html.template: -------------------------------------------------------------------------------- 1 |
2 |
3 |
Client Application Data Datagram
4 |
5 | The client sends the data "ping". 6 |
7 | %file ../captures/caps/09-data-cap-prot 8 | 9 | 10 | Header Info Byte 11 | 12 | %next 1 13 | %bytes 14 | 15 |
16 |

17 | An encrypted DTLS packet starts with the "Unified Header". This first byte 18 | of the header gives information on the structure and decryption of 19 | the rest of the header and packet. 20 |

21 | The bits in the value %x0 have the following meaning: 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 |
ValueMeaning
MSB001Fixed bits
0Connection ID field not present in header
1Sequence number in header is 2 bytes long
1Length field is present in header
LSB11Encryption epoch 3 - first application keys
34 |

35 |
36 | 37 | 38 | Record Number 39 | 40 | %next 2 41 | %bytes 42 | 43 | 44 | 00 00 45 | 46 |
47 |
48 | The record number of the packet is encrypted, to prevent middleware 49 | from interpreting or interfering with the sequencing of packets. 50 |

51 | This encryption is applied by encrypting a sample of each packet's 52 | payload with the "record number key", then XOR'ing certain bits 53 | and bytes in each packet with the resulting data. 54 |

55 | An example of how to compute record number encryption: 56 | 57 |
### "client record number key" from application keys calc step above
 58 | $ key=5cb5bd8bac29777c650c0dde22d16d47
 59 | ### sample is taken from 16 bytes of payload starting 5 bytes into the record
 60 | $ sample=7d72278b6c649f1e7b56b3cad411faf7
 61 | $ echo $sample | xxd -r -p | openssl aes-128-ecb -K $key | head -c 2 | xxd -p
 62 | 
 63 | 683f
 64 | 
 65 | ### the above bytes are xor'd one-for-one into the bytes of the record number
 66 | 
67 |
68 |
69 |
70 | 71 | 72 | Record Length 73 | 74 | %next 2 75 | %bytes 76 | 77 |
78 | Each record is assumed to consume the remainder of the datagram unless this 79 | optional length is given. This allows implementations to send several TLS records 80 | in a single datagram (though this connection does not take advantage of this). 81 |
    82 |
  • %0 %1 - Record length of %nn0 bytes 83 |
84 |
85 |
86 | 87 | 88 | Encrypted Data 89 | 90 | %next 5 91 | %bytes 92 | 93 |
94 | This data is encrypted with the client application key. 95 |
96 |
97 | 98 | Auth Tag 99 | 100 | %next 16 101 | %bytes 102 | 103 |
104 | This is the AEAD authentication tag 105 | that protects the integrity of the 106 | encrypted data and the record header. 107 |
108 |
109 | %empty 110 | 111 |
112 |
Decryption
113 |
114 | This data is encrypted using the client 115 | application key and the client application IV that were 116 | generated during the "Client Application Keys 117 | Calc" step. The IV will be modified 118 | by XOR'ing it by the count of records that 119 | have already been encrypted with this key, 120 | which in this case is 0. The process also 121 | takes as input the 5-byte record header, as authenticated 122 | data that must match for the decryption to 123 | succeed. 124 |

125 | Because the openssl command line 126 | tool does not yet support AEAD ciphers, 127 | I've written command line tools to both 128 | decrypt 129 | and encrypt 130 | this data. 131 | 132 |
### from the "Client Application Keys Calc" step
133 | $ key=9ba90dbce8857bc1fcb81d41a0465cfe
134 | $ iv=682219974631fa0656ee4eff
135 | ### from this record
136 | $ recdata=2f00000015
137 | $ authtag=649f1e7b56b3cad411faf7bd518bfb15
138 | $ recordnum=0
139 | ### may need to add -I and -L flags for include and lib dirs
140 | $ cc -o aes_128_gcm_decrypt aes_128_gcm_decrypt.c -lssl -lcrypto
141 | $ cat /tmp/msg1 \
142 |   | ./aes_128_gcm_decrypt $iv $recordnum $key $recdata $authtag \
143 |   | hexdump -C
144 | 
145 | 00000000  70 69 6e 67 17                                    |ping.|
146 | 
147 |
148 |
149 |
150 | 151 | %file ../captures/caps/record-cdata 152 | 153 | Application Data 154 | 155 | %next 4 156 | %bytes 157 | 158 |
159 | The bytes "ping". 160 |
161 |
162 | 163 | 164 | Record Type 165 | 166 | %next 1 167 | %bytes 168 | 169 |
170 | Each encrypted DTLS 1.3 record has a final byte which indicates its actual record type. 171 |
    172 |
  • %0 - type is 23 (application data) 173 |
174 |
175 |
176 |
177 |
178 |
179 | %empty 180 | -------------------------------------------------------------------------------- /generate/index-10-sdata.html.template: -------------------------------------------------------------------------------- 1 |
2 |
3 |
Server Application Data Datagram
4 |
5 | The server replies with the data "pong". 6 |
7 | %file ../captures/caps/10-data-sap-prot 8 | 9 | 10 | Header Info Byte 11 | 12 | %next 1 13 | %bytes 14 | 15 |
16 |

17 | An encrypted DTLS packet starts with the "Unified Header". This first byte 18 | of the header gives information on the structure and decryption of 19 | the rest of the header and packet. 20 |

21 | The bits in the value %x0 have the following meaning: 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 |
ValueMeaning
MSB001Fixed bits
0Connection ID field not present in header
1Sequence number in header is 2 bytes long
1Length field is present in header
LSB11Encryption epoch 3 - first application keys
34 |

35 |
36 | 37 | 38 | Record Number 39 | 40 | %next 2 41 | %bytes 42 | 43 | 44 | 00 01 45 | 46 |
47 |
48 | The record number of the packet is encrypted, to prevent middleware 49 | from interpreting or interfering with the sequencing of packets. 50 |

51 | This encryption is applied by encrypting a sample of each packet's 52 | payload with the "record number key", then XOR'ing certain bits 53 | and bytes in each packet with the resulting data. 54 |

55 | An example of how to compute record number encryption: 56 | 57 |
### "server record number key" from application keys calc step above
 58 | $ key=57ba02596c6a1352d7fe8416c7e17d5a
 59 | ### sample is taken from 16 bytes of payload starting 5 bytes into the record
 60 | $ sample=f5bd33f27b72780e351fa00703fb9f65
 61 | $ echo $sample | xxd -r -p | openssl aes-128-ecb -K $key | head -c 2 | xxd -p
 62 | 
 63 | a259
 64 | 
 65 | ### the above bytes are xor'd one-for-one into the bytes of the record number
 66 | 
67 |
68 |
69 |
70 | 71 | 72 | Record Length 73 | 74 | %next 2 75 | %bytes 76 | 77 |
78 | Each record is assumed to consume the remainder of the datagram unless this 79 | optional length is given. This allows implementations to send several TLS records 80 | in a single datagram (though this connection does not take advantage of this). 81 |
    82 |
  • %0 %1 - Record length of %nn0 bytes 83 |
84 |
85 |
86 | 87 | 88 | Encrypted Data 89 | 90 | %next 5 91 | %bytes 92 | 93 |
94 | This data is encrypted with the server application key. 95 |
96 |
97 | 98 | Auth Tag 99 | 100 | %next 16 101 | %bytes 102 | 103 |
104 | This is the AEAD authentication tag 105 | that protects the integrity of the 106 | encrypted data and the record header. 107 |
108 |
109 | %empty 110 | 111 |
112 |
Decryption
113 |
114 | This data is encrypted using the server 115 | application key and the server application IV that were 116 | generated during the "Server Application Keys 117 | Calc" step. The IV will be modified 118 | by XOR'ing it by the count of records that 119 | have already been encrypted with this key, 120 | which in this case is 1. The process also 121 | takes as input the 5-byte record header, as authenticated 122 | data that must match for the decryption to 123 | succeed. 124 |

125 | Because the openssl command line 126 | tool does not yet support AEAD ciphers, 127 | I've written command line tools to both 128 | decrypt 129 | and encrypt 130 | this data. 131 | 132 |
### from the "Server Application Keys Calc" step
133 | $ key=2b65fffbbc8189474aa2003c43c32d4d
134 | $ iv=582f5a11bdaf973fe3ffeb4e
135 | ### from this record
136 | $ recdata=2f00010015
137 | $ authtag=72780e351fa00703fb9f658c689f95ae
138 | $ recordnum=1
139 | ### may need to add -I and -L flags for include and lib dirs
140 | $ cc -o aes_128_gcm_decrypt aes_128_gcm_decrypt.c -lssl -lcrypto
141 | $ cat /tmp/msg1 \
142 |   | ./aes_128_gcm_decrypt $iv $recordnum $key $recdata $authtag \
143 |   | hexdump -C
144 | 
145 | 00000000  70 6f 6e 67 17                                    |pong.|
146 | 
147 |
148 |
149 |
150 | 151 | %file ../captures/caps/record-sdata 152 | 153 | Application Data 154 | 155 | %next 4 156 | %bytes 157 | 158 |
159 | The bytes "pong". 160 |
161 |
162 | 163 | 164 | Record Type 165 | 166 | %next 1 167 | %bytes 168 | 169 |
170 | Each encrypted DTLS 1.3 record has a final byte which indicates its actual record type. 171 |
    172 |
  • %0 - type is 23 (application data) 173 |
174 |
175 |
176 |
177 |
178 |
179 | %empty 180 | -------------------------------------------------------------------------------- /generate/index-11-bye.html.template: -------------------------------------------------------------------------------- 1 |
2 |
3 |
Server Alert Datagram
4 |
5 | The server sends an "alert" to indicate an orderly shutdown. 6 |
7 | %file ../captures/caps/11-bye-sap-prot 8 | 9 | 10 | Header Info Byte 11 | 12 | %next 1 13 | %bytes 14 | 15 |
16 |

17 | An encrypted DTLS packet starts with the "Unified Header". This first byte 18 | of the header gives information on the structure and decryption of 19 | the rest of the header and packet. 20 |

21 | The bits in the value %x0 have the following meaning: 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 |
ValueMeaning
MSB001Fixed bits
0Connection ID field not present in header
1Sequence number in header is 2 bytes long
1Length field is present in header
LSB11Encryption epoch 3 - first application keys
34 |

35 |
36 | 37 | 38 | Record Number 39 | 40 | %next 2 41 | %bytes 42 | 43 | 44 | 00 02 45 | 46 |
47 |
48 | The record number of the packet is encrypted, to prevent middleware 49 | from interpreting or interfering with the sequencing of packets. 50 |

51 | This encryption is applied by encrypting a sample of each packet's 52 | payload with the "record number key", then XOR'ing certain bits 53 | and bytes in each packet with the resulting data. 54 |

55 | An example of how to compute record number encryption: 56 | 57 |
### "server record number key" from application keys calc step above
 58 | $ key=57ba02596c6a1352d7fe8416c7e17d5a
 59 | ### sample is taken from 16 bytes of payload starting 5 bytes into the record
 60 | $ sample=dd8cd07daa964fd1ab508825378fc96f
 61 | $ echo $sample | xxd -r -p | openssl aes-128-ecb -K $key | head -c 2 | xxd -p
 62 | 
 63 | 690e
 64 | 
 65 | ### the above bytes are xor'd one-for-one into the bytes of the record number
 66 | 
67 |
68 |
69 |
70 | 71 | 72 | Record Length 73 | 74 | %next 2 75 | %bytes 76 | 77 |
78 | Each record is assumed to consume the remainder of the datagram unless this 79 | optional length is given. This allows implementations to send several TLS records 80 | in a single datagram (though this connection does not take advantage of this). 81 |
    82 |
  • %0 %1 - Record length of %nn0 bytes 83 |
84 |
85 |
86 | 87 | 88 | Encrypted Data 89 | 90 | %next 3 91 | %bytes 92 | 93 |
94 | This data is encrypted with the server application key. 95 |
96 |
97 | 98 | Auth Tag 99 | 100 | %next 16 101 | %bytes 102 | 103 |
104 | This is the AEAD authentication tag 105 | that protects the integrity of the 106 | encrypted data and the record header. 107 |
108 |
109 | %empty 110 | 111 |
112 |
Decryption
113 |
114 | This data is encrypted using the server 115 | application key and the server application IV that were 116 | generated during the "Server Application Keys 117 | Calc" step. The IV will be modified 118 | by XOR'ing it by the count of records that 119 | have already been encrypted with this key, 120 | which in this case is 1. The process also 121 | takes as input the 5-byte record header, as authenticated 122 | data that must match for the decryption to 123 | succeed. 124 |

125 | Because the openssl command line 126 | tool does not yet support AEAD ciphers, 127 | I've written command line tools to both 128 | decrypt 129 | and encrypt 130 | this data. 131 | 132 |
### from the "Server Application Keys Calc" step
133 | $ key=2b65fffbbc8189474aa2003c43c32d4d
134 | $ iv=582f5a11bdaf973fe3ffeb4e
135 | ### from this record
136 | $ recdata=2f00020013
137 | $ authtag=7daa964fd1ab508825378fc96fa8b1e8
138 | $ recordnum=2
139 | ### may need to add -I and -L flags for include and lib dirs
140 | $ cc -o aes_128_gcm_decrypt aes_128_gcm_decrypt.c -lssl -lcrypto
141 | $ cat /tmp/msg1 \
142 |   | ./aes_128_gcm_decrypt $iv $recordnum $key $recdata $authtag \
143 |   | hexdump -C
144 | 
145 | 00000000  01 00 15
146 | 
147 |
148 |
149 |
150 | 151 | %file ../captures/caps/record-sbye 152 | 153 | Alert 154 | 155 | %next 2 156 | %bytes 157 | 158 |
159 | The server sends a "close notify" alert, indicating an orderly shutdown of the connection. 160 |
    161 |
  • %0 - alert level 1 (warning) - unused 162 |
  • %1 - alert number 0 (close_notify) 163 |
164 |
165 |
166 | 167 | 168 | Record Type 169 | 170 | %next 1 171 | %bytes 172 | 173 |
174 | Each encrypted DTLS 1.3 record has a final byte which indicates its actual record type. 175 |
    176 |
  • %0 - type is 21 (alert) 177 |
178 |
179 |
180 |
181 |
182 |
183 | %empty 184 | -------------------------------------------------------------------------------- /generate/index-99-footer.html.template: -------------------------------------------------------------------------------- 1 |
2 |

The code for this project, including packet captures, can be found 3 | on GitHub.

4 |
5 | 6 |
7 |

You may also be interested in a breakdown of 8 | TLS 1.3.

9 |
10 | 11 |
12 |

If you found this page useful or interesting let me know via Twitter 13 | @XargsNotBombs.

14 |
15 | 16 |
17 | 18 | 24 | 25 | 26 | 27 | [print] 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /server/Makefile: -------------------------------------------------------------------------------- 1 | CFLAGS += -I../wolfssl/include -Wall 2 | LDFLAGS += -L../wolfssl/lib -lwolfssl 3 | 4 | server: server.c ../wolfssl/lib/libwolfssl.a 5 | $(CC) $(CFLAGS) -o server server.c -Wall $(LDFLAGS) 6 | 7 | clean: 8 | rm -f server 9 | -------------------------------------------------------------------------------- /server/server.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | void die(const char *str) 9 | { 10 | fprintf(stderr, "%s: %s\n", str, strerror(errno)); 11 | exit(1); 12 | } 13 | 14 | void die_ssl(WOLFSSL *ssl, const char *str) 15 | { 16 | int err = wolfSSL_get_error(ssl, 0); 17 | fprintf(stderr, "%s: %s (err:%d)\n", str, wolfSSL_ERR_reason_error_string(err), err); 18 | exit(1); 19 | } 20 | 21 | int secret_callback(WOLFSSL *ssl, int id, const unsigned char *secret, int secret_sz, void *unused) 22 | { 23 | unsigned char crand[32]; 24 | int crand_sz = (int)wolfSSL_get_client_random(ssl, crand, sizeof(crand)); 25 | if (crand_sz <= 0) 26 | die("error getting random size"); 27 | 28 | const char *str = NULL; 29 | if (id == CLIENT_EARLY_TRAFFIC_SECRET) { 30 | str = "CLIENT_EARLY_TRAFFIC_SECRET"; 31 | } else if (id == EARLY_EXPORTER_SECRET) { 32 | str = "EARLY_EXPORTER_SECRET"; 33 | } else if (id == CLIENT_HANDSHAKE_TRAFFIC_SECRET) { 34 | str = "CLIENT_HANDSHAKE_TRAFFIC_SECRET"; 35 | } else if (id == SERVER_HANDSHAKE_TRAFFIC_SECRET) { 36 | str = "SERVER_HANDSHAKE_TRAFFIC_SECRET"; 37 | } else if (id == CLIENT_TRAFFIC_SECRET) { 38 | str = "CLIENT_TRAFFIC_SECRET"; 39 | } else if (id == SERVER_TRAFFIC_SECRET) { 40 | str = "SERVER_TRAFFIC_SECRET"; 41 | } else if (id == EXPORTER_SECRET) { 42 | str = "EXPORTER_SECRET"; 43 | } else { 44 | printf("unknown secret %d\n", id); 45 | str = "UNKNOWN_SECRET"; 46 | } 47 | 48 | FILE *fp = stdout; 49 | fprintf(fp, "%s ", str); 50 | for (size_t i = 0; i < crand_sz; i++) { 51 | fprintf(fp, "%02x", crand[i]); 52 | } 53 | fprintf(fp, " "); 54 | for (size_t i = 0; i < secret_sz; i++) { 55 | fprintf(fp, "%02x", secret[i]); 56 | } 57 | fprintf(fp, "\n"); 58 | 59 | return 0; 60 | } 61 | 62 | void setup_keylog(WOLFSSL *ssl) 63 | { 64 | wolfSSL_KeepArrays(ssl); 65 | wolfSSL_set_tls13_secret_cb(ssl, secret_callback, 0); 66 | } 67 | 68 | int create_bind(int port) 69 | { 70 | struct sockaddr_in addr; 71 | addr.sin_family = AF_INET; 72 | addr.sin_port = htons(port); 73 | addr.sin_addr.s_addr = htonl(INADDR_ANY); 74 | 75 | int s = socket(AF_INET, SOCK_DGRAM, 0); 76 | if (s < 0) { 77 | die("Unable to create socket"); 78 | } 79 | int enable = 1; 80 | if (setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &enable, sizeof(int)) < 0) { 81 | die("SO_REUSEADDR failed"); 82 | } 83 | if (bind(s, (struct sockaddr*)&addr, sizeof(addr)) < 0) { 84 | die("Unable to bind to port"); 85 | } 86 | printf("Bound to port %d\n", port); 87 | return s; 88 | } 89 | 90 | int wait_for_readable(int fd) 91 | { 92 | struct pollfd fds = {fd, POLLIN, 0}; 93 | if (poll(&fds, 1, -1) != 1) 94 | die("poll failed"); 95 | return fd; 96 | } 97 | 98 | void setup_ctx(WOLFSSL_CTX *ctx) 99 | { 100 | if (wolfSSL_CTX_use_certificate_file(ctx, "server.crt", SSL_FILETYPE_PEM) != SSL_SUCCESS) 101 | die("can't use server.crt"); 102 | if (wolfSSL_CTX_use_PrivateKey_file(ctx, "server.key", SSL_FILETYPE_PEM) != SSL_SUCCESS) 103 | die("can't use server.key"); 104 | wolfSSL_CTX_no_ticket_TLSv13(ctx); 105 | } 106 | 107 | int main(int argc, char **argv) 108 | { 109 | // set up ssl 110 | setenv("SERVER", "1", 1); 111 | wolfSSL_Init(); 112 | // wolfSSL_Debugging_ON(); 113 | WOLFSSL_CTX *ctx = wolfSSL_CTX_new(wolfDTLSv1_3_server_method()); 114 | if (ctx == NULL) 115 | die("CTX_new error"); 116 | setup_ctx(ctx); 117 | 118 | int sock = create_bind(8400); 119 | 120 | struct sockaddr_storage addr; 121 | socklen_t addrlen = sizeof(addr); 122 | char packet[5000]; 123 | 124 | printf("Waiting for client to send\n"); 125 | wait_for_readable(sock); 126 | ssize_t sret = recvfrom(sock, (char *)&packet, sizeof(packet), MSG_PEEK, (struct sockaddr *)&addr, &addrlen); 127 | if (sret <= 0) 128 | die("Recvfrom failed"); 129 | 130 | char host[NI_MAXHOST]; 131 | char port[NI_MAXSERV]; 132 | getnameinfo((struct sockaddr *)&addr, addrlen, host, sizeof(host), 133 | port, sizeof(port), NI_NUMERICHOST|NI_NUMERICSERV); 134 | printf("Receiving data from %s:%s\n", host, port); 135 | 136 | if (connect(sock, (const struct sockaddr *)&addr, addrlen) != 0) 137 | die("connect failed"); 138 | 139 | WOLFSSL *ssl = wolfSSL_new(ctx); 140 | if (!ssl) 141 | die_ssl(ssl, "can't make new ssl"); 142 | setup_keylog(ssl); 143 | wolfSSL_set_fd(ssl, sock); 144 | 145 | if (wolfSSL_accept(ssl) <= 0) 146 | die_ssl(ssl, "can't accept ssl connection"); 147 | 148 | char rbuf[128]; 149 | int ret = wolfSSL_read(ssl, rbuf, sizeof(rbuf)-1); 150 | if (ret <= 0) 151 | die_ssl(ssl, "Unable to read from connection"); 152 | 153 | rbuf[ret] = '\0'; 154 | printf("Read [%s]\n", rbuf); 155 | 156 | const char reply[] = "pong"; 157 | if (wolfSSL_write(ssl, reply, strlen(reply)) <= 0) 158 | die_ssl(ssl, "Unable to write to connection"); 159 | printf("Wrote [%s]\n", reply); 160 | 161 | // wolfSSL_set_fd(ssl, 0); 162 | wolfSSL_shutdown(ssl); 163 | wolfSSL_free(ssl); 164 | close(sock); 165 | wolfSSL_CTX_free(ctx); 166 | wolfSSL_Cleanup(); 167 | 168 | return 0; 169 | } 170 | -------------------------------------------------------------------------------- /server/server.crt: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIDITCCAgmgAwIBAgIIFVqSrcIEj5AwDQYJKoZIhvcNAQELBQAwIjELMAkGA1UE 3 | BhMCVVMxEzARBgNVBAoTCkV4YW1wbGUgQ0EwHhcNMTgxMDA1MDEzODE3WhcNMTkx 4 | MDA1MDEzODE3WjArMQswCQYDVQQGEwJVUzEcMBoGA1UEAxMTZXhhbXBsZS51bGZo 5 | ZWltLm5ldDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMSANga650dr 6 | CJQE7Ke2kQQ/95K8Ge77fXTXqA0AHntLOkrmD+jAcfxz5wJMDbz0vdEdOWu6cEZK 7 | E+lK+D3z4QlZVHvJVftBLaN2UhHh89x3bKpTN27KOuy+w6q3OzHVbLZSnICYvMng 8 | KBjiC/f4oDr9FwRQns55vZ858epp7EeXLoMPtcqV3pWh5gQi1e6+UnlUoee/iob2 9 | Rm0NnxaVGkz3oEaSWVwTUvJUnlr7Tr/XejeVAUTkwCaHTGU+QH19IwdEAfSE/9CP 10 | eh+gUhDR9PDVznlwKTLiyr5wH9+ta0u3EQH0S61mahETD+Lugp5NAp3JHN1nFtu5 11 | BhiG7cG6lCECAwEAAaNSMFAwDgYDVR0PAQH/BAQDAgWgMB0GA1UdJQQWMBQGCCsG 12 | AQUFBwMCBggrBgEFBQcDATAfBgNVHSMEGDAWgBSJT95bzGniUs8+owDfsZe4HeHB 13 | RjANBgkqhkiG9w0BAQsFAAOCAQEAWRZFppouN3nk9t0nGrocC/1s11WZtefDblM+ 14 | /zZZCEMkyeelBAedOeDUKYf/4+vdCcHPHZFEVYcLVx3Rm98dJPi7mhH+gP1ZK6A5 15 | jN4R4mUeYYzlmPqW5Tcu7z0kiv3hdGPrv6u45NGrUCpU7ABk6S94GWYNPyfPIJ5m 16 | f85a4uSsmcfJOBj4slEHIt/tl/MuPpNJ1MZsnqY5bXREYqBrQsbVumiOrDoBe938 17 | jiz8rSfLadPM3KKAQURl0640jODzSrL7nGGDcTErGRBBZBwjfxGl1lyETwQEhJk4 18 | cSuVntaFvFxd1kXtGZCUc0ApJty0DjRpoVlB6OLMqEu2CEY2oA== 19 | -----END CERTIFICATE----- 20 | -------------------------------------------------------------------------------- /server/server.key: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | MIIEowIBAAKCAQEAxIA2BrrnR2sIlATsp7aRBD/3krwZ7vt9dNeoDQAee0s6SuYP 3 | 6MBx/HPnAkwNvPS90R05a7pwRkoT6Ur4PfPhCVlUe8lV+0Eto3ZSEeHz3HdsqlM3 4 | bso67L7Dqrc7MdVstlKcgJi8yeAoGOIL9/igOv0XBFCeznm9nznx6mnsR5cugw+1 5 | ypXelaHmBCLV7r5SeVSh57+KhvZGbQ2fFpUaTPegRpJZXBNS8lSeWvtOv9d6N5UB 6 | ROTAJodMZT5AfX0jB0QB9IT/0I96H6BSENH08NXOeXApMuLKvnAf361rS7cRAfRL 7 | rWZqERMP4u6Cnk0Cnckc3WcW27kGGIbtwbqUIQIDAQABAoIBAGF7OVIdZp8Hejn0 8 | N3L8HvT8xtUEe9kS6ioM0lGgvX5s035Uo4/T6LhUx0VcdXRH9eLHnLTUyN4V4cra 9 | ZkxVsE3zAvZl60G6E+oDyLMWZOP6Wu4kWlub9597A5atT7BpMIVCdmFVZFLB4SJ3 10 | AXkC3nplFAYP+Lh1rJxRIrIn2g+pEeBboWbYA++oDNuMQffDZaokTkJ8Bn1JZYh0 11 | xEXKY8Bi2Egd5NMeZa1UFO6y8tUbZfwgVs6Enq5uOgtfayq79vZwyjj1kd29MBUD 12 | 8g8byV053ZKxbUOiOuUts97eb+fN3DIDRTcT2c+lXt/4C54M1FclJAbtYRK/qwsl 13 | pYWKQAECgYEA4ZUbqQnTo1ICvj81ifGrz+H4LKQqe92Hbf/W51D/Umk2kP702W22 14 | HP4CvrJRtALThJIG9m2TwUjl/WAuZIBrhSAbIvc3Fcoa2HjdRp+sO5U1ueDq7d/S 15 | Z+PxRI8cbLbRpEdIaoR46qr/2uWZ943PHMv9h4VHPYn1w8b94hwD6vkCgYEA3v87 16 | mFLzyM9ercnEv9zHMRlMZFQhlcUGQZvfb8BuJYl/WogyT6vRrUuM0QXULNEPlrin 17 | mBQTqc1nCYbgkFFsD2VVt1qIyiAJsB9MD1LNV6YuvE7T2KOSadmsA4fa9PUqbr71 18 | hf3lTTq+LeR09LebO7WgSGYY+5YKVOEGpYMR1GkCgYEAxPVQmk3HKHEhjgRYdaG5 19 | lp9A9ZE8uruYVJWtiHgzBTxx9TV2iST+fd/We7PsHFTfY3+wbpcMDBXfIVRKDVwH 20 | BMwchXH9+Ztlxx34bYJaegd0SmA0Hw9ugWEHNgoSEmWpM1s9wir5/ELjc7dGsFtz 21 | uzvsl9fpdLSxDYgAAdzeGtkCgYBAzKIgrVox7DBzB8KojhtD5ToRnXD0+H/M6OKQ 22 | srZPKhlb0V/tTtxrIx0UUEFLlKSXA6mPw6XDHfDnD86JoV9pSeUSlrhRI+Ysy6tq 23 | eIE7CwthpPZiaYXORHZ7wCqcK/HcpJjsCs9rFbrV0yE5S3FMdIbTAvgXg44VBB7O 24 | UbwIoQKBgDuY8gSrA5/A747wjjmsdRWK4DMTMEV4eCW1BEP7Tg7Cxd5n3xPJiYhr 25 | nhLGN+mMnVIcv2zEMS0/eNZr1j/0BtEdx+3IC6Eq+ONY0anZ4Irt57/5QeKgKn/L 26 | JPhfPySIPG4UmwE4gW8t79vfOKxnUu2fDD1ZXUYopan6EckACNH/ 27 | -----END RSA PRIVATE KEY----- 28 | -------------------------------------------------------------------------------- /site/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/syncsynchalt/illustrated-dtls/cb49c285131efd9e2d4252806420bda936134a48/site/favicon.ico -------------------------------------------------------------------------------- /site/favicon/android-chrome-144x144.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/syncsynchalt/illustrated-dtls/cb49c285131efd9e2d4252806420bda936134a48/site/favicon/android-chrome-144x144.png -------------------------------------------------------------------------------- /site/favicon/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/syncsynchalt/illustrated-dtls/cb49c285131efd9e2d4252806420bda936134a48/site/favicon/apple-touch-icon.png -------------------------------------------------------------------------------- /site/favicon/browserconfig.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | #da532c 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /site/favicon/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/syncsynchalt/illustrated-dtls/cb49c285131efd9e2d4252806420bda936134a48/site/favicon/favicon-16x16.png -------------------------------------------------------------------------------- /site/favicon/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/syncsynchalt/illustrated-dtls/cb49c285131efd9e2d4252806420bda936134a48/site/favicon/favicon-32x32.png -------------------------------------------------------------------------------- /site/favicon/mstile-150x150.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/syncsynchalt/illustrated-dtls/cb49c285131efd9e2d4252806420bda936134a48/site/favicon/mstile-150x150.png -------------------------------------------------------------------------------- /site/favicon/safari-pinned-tab.svg: -------------------------------------------------------------------------------- 1 | 2 | 4 | 7 | 8 | Created by potrace 1.14, written by Peter Selinger 2001-2017 9 | 10 | 12 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /site/favicon/site.webmanifest: -------------------------------------------------------------------------------- 1 | { 2 | "name": "", 3 | "short_name": "", 4 | "icons": [ 5 | { 6 | "src": "android-chrome-144x144.png", 7 | "sizes": "144x144", 8 | "type": "image/png" 9 | } 10 | ], 11 | "theme_color": "#ffffff", 12 | "background_color": "#ffffff", 13 | "display": "standalone" 14 | } 15 | -------------------------------------------------------------------------------- /site/files/aes_128_gcm_decrypt.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | typedef unsigned char uchar; 8 | 9 | static void die(const char *msg); 10 | static void read_hex(const char *hex, uchar *out, size_t outmax, size_t *outlen); 11 | static void build_iv(uchar *iv, uint64_t seq); 12 | 13 | const int gcm_ivlen = 12; 14 | const int gcm_taglen = 16; 15 | const int aes_keylen = 16; // aes-128 16 | 17 | int main(int argc, char **argv) 18 | { 19 | if (argc != 6) { 20 | fprintf(stderr, "Usage: %s hexiv seq hexkey hexaad hextag\n", argv[0]); 21 | fprintf(stderr, "\n"); 22 | fprintf(stderr, "Reads ciphertext on stdin and prints plaintext on stdout\n"); 23 | exit(1); 24 | } 25 | 26 | uchar iv[1024], key[1024], aad[1024], tag[1024]; 27 | size_t ivlen, keylen, aadlen, taglen; 28 | read_hex(argv[1], iv, sizeof(iv), &ivlen); 29 | read_hex(argv[3], key, sizeof(key), &keylen); 30 | read_hex(argv[4], aad, sizeof(aad), &aadlen); 31 | read_hex(argv[5], tag, sizeof(tag), &taglen); 32 | uint64_t seq = atoi(argv[2]); 33 | 34 | if (keylen != aes_keylen) 35 | die("Incorrect key length, expected 16 bytes"); 36 | if (ivlen != gcm_ivlen) 37 | die("Incorrect IV length, expected 12 bytes"); 38 | if (taglen != gcm_taglen) 39 | die("Incorrect IV length, expected 16 bytes"); 40 | build_iv(iv, seq); 41 | 42 | EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new(); 43 | if (!ctx) 44 | die("cipher ctx create failed"); 45 | 46 | if (!EVP_DecryptInit_ex(ctx, EVP_aes_128_gcm(), NULL, NULL, NULL)) 47 | die("init algorithm failed"); 48 | 49 | if (!EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_SET_IVLEN, ivlen, NULL)) 50 | die("set ivlen failed"); 51 | 52 | if (!EVP_DecryptInit_ex(ctx, NULL, NULL, key, iv)) 53 | die("set key/iv failed"); 54 | 55 | int len = 0; 56 | if (!EVP_DecryptUpdate(ctx, NULL, &len, aad, aadlen)) 57 | die("set aad failed"); 58 | 59 | uchar bufin[1024], bufout[1024]; 60 | char *out = NULL; 61 | int outlen = 0; 62 | while (!feof(stdin)) { 63 | size_t num = fread(bufin, 1, sizeof(bufin), stdin); 64 | if (!EVP_DecryptUpdate(ctx, bufout, &len, bufin, num)) 65 | die("decrypt failed"); 66 | out = realloc(out, outlen + len); 67 | memcpy(out + outlen, bufout, len); 68 | outlen += len; 69 | } 70 | 71 | if (!EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_SET_TAG, taglen, tag)) 72 | die("set expected tag failed"); 73 | 74 | // positive is success 75 | int final = EVP_DecryptFinal_ex(ctx, bufout, &len); 76 | out = realloc(out, outlen + len); 77 | memcpy(out + outlen, bufout, len); 78 | outlen += len; 79 | 80 | EVP_CIPHER_CTX_free(ctx); 81 | 82 | if (final > 0) { 83 | fwrite(out, 1, outlen, stdout); 84 | free(out); 85 | } else { 86 | free(out); 87 | die("decrypt failed; tag value didn't match"); 88 | } 89 | } 90 | 91 | static void die(const char *msg) 92 | { 93 | fprintf(stderr, "%s\n", msg); 94 | exit(1); 95 | } 96 | 97 | static void read_hex(const char *hex, uchar *out, size_t outmax, size_t *outlen) 98 | { 99 | *outlen = 0; 100 | if (strlen(hex) > 2*outmax) 101 | die("read_hex overflow"); 102 | size_t i; 103 | for (i = 0; hex[i] && hex[i+1]; i += 2) { 104 | unsigned int value = 0; 105 | if (!sscanf(hex + i, "%02x", &value)) 106 | die("sscanf failure"); 107 | out[(*outlen)++] = value; 108 | } 109 | } 110 | 111 | static void build_iv(uchar *iv, uint64_t seq) 112 | { 113 | size_t i; 114 | for (i = 0; i < 8; i++) { 115 | iv[gcm_ivlen-1-i] ^= ((seq>>(i*8))&0xFF); 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /site/files/aes_128_gcm_encrypt.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | typedef unsigned char uchar; 8 | 9 | static void die(const char *msg); 10 | static void read_hex(const char *hex, uchar *out, size_t outmax, size_t *outlen); 11 | static void build_iv(uchar *iv, uint64_t seq); 12 | 13 | const int gcm_ivlen = 12; 14 | const int gcm_taglen = 16; 15 | const int aes_keylen = 16; // aes-128 16 | 17 | int main(int argc, char **argv) 18 | { 19 | if (argc != 5) { 20 | fprintf(stderr, "Usage: %s hexiv seq hexkey hexaad\n", argv[0]); 21 | fprintf(stderr, "\n"); 22 | fprintf(stderr, "Reads plaintext on stdin and prints ciphertext and tag on stdout\n"); 23 | exit(1); 24 | } 25 | 26 | uchar iv[1024], key[1024], aad[1024]; 27 | size_t ivlen, keylen, aadlen; 28 | read_hex(argv[1], iv, sizeof(iv), &ivlen); 29 | uint64_t seq = atoi(argv[2]); 30 | read_hex(argv[3], key, sizeof(key), &keylen); 31 | read_hex(argv[4], aad, sizeof(aad), &aadlen); 32 | 33 | if (keylen != aes_keylen) 34 | die("Incorrect key length, expected 16 bytes"); 35 | if (ivlen != gcm_ivlen) 36 | die("Incorrect IV length, expected 12 bytes"); 37 | build_iv(iv, seq); 38 | 39 | EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new(); 40 | if (!ctx) 41 | die("cipher ctx create failed"); 42 | 43 | if (!EVP_EncryptInit_ex(ctx, EVP_aes_128_gcm(), NULL, NULL, NULL)) 44 | die("init algorithm failed"); 45 | 46 | if (!EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_SET_IVLEN, ivlen, NULL)) 47 | die("set ivlen failed"); 48 | 49 | if (!EVP_EncryptInit_ex(ctx, NULL, NULL, key, iv)) 50 | die("set key/iv failed"); 51 | 52 | int len = 0; 53 | if (!EVP_EncryptUpdate(ctx, NULL, &len, aad, aadlen)) 54 | die("set aad failed"); 55 | 56 | uchar bufin[512], bufout[1024]; 57 | while (!feof(stdin)) { 58 | size_t num = fread(bufin, 1, sizeof(bufin), stdin); 59 | if (!EVP_EncryptUpdate(ctx, bufout, &len, bufin, num)) 60 | die("decrypt failed"); 61 | fwrite(bufout, 1, len, stdout); 62 | } 63 | 64 | EVP_EncryptFinal_ex(ctx, bufout, &len); 65 | fwrite(bufout, 1, len, stdout); 66 | 67 | if (!EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_GET_TAG, gcm_taglen, bufout)) 68 | die("generate tag failed"); 69 | fwrite(bufout, 1, gcm_taglen, stdout); 70 | 71 | EVP_CIPHER_CTX_free(ctx); 72 | } 73 | 74 | static void die(const char *msg) 75 | { 76 | fprintf(stderr, "%s\n", msg); 77 | exit(1); 78 | } 79 | 80 | static void read_hex(const char *hex, uchar *out, size_t outmax, size_t *outlen) 81 | { 82 | *outlen = 0; 83 | if (strlen(hex) > 2*outmax) 84 | die("read_hex overflow"); 85 | size_t i; 86 | for (i = 0; hex[i] && hex[i+1]; i += 2) { 87 | unsigned int value = 0; 88 | if (!sscanf(hex + i, "%02x", &value)) 89 | die("sscanf failure"); 90 | out[(*outlen)++] = value; 91 | } 92 | } 93 | 94 | static void build_iv(uchar *iv, uint64_t seq) 95 | { 96 | size_t i; 97 | for (i = 0; i < 8; i++) { 98 | iv[gcm_ivlen-1-i] ^= ((seq>>(i*8))&0xFF); 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /site/files/client-ephemeral-private.key: -------------------------------------------------------------------------------- 1 | -----BEGIN PRIVATE KEY----- 2 | MC4CAQAwBQYDK2VuBCIEICAhIiMkJSYnKCkqKywtLi8wMTIzNDU2Nzg5Ojs8PT4/ 3 | -----END PRIVATE KEY----- 4 | -------------------------------------------------------------------------------- /site/files/client-ephemeral-public.key: -------------------------------------------------------------------------------- 1 | -----BEGIN PUBLIC KEY----- 2 | MCowBQYDK2VuAyEANYBy1jZYgNGu6jKa35EhODhR7SGijjt16WXQ0s0WYlQ= 3 | -----END PUBLIC KEY----- 4 | -------------------------------------------------------------------------------- /site/files/hkdf-dtls.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Perform HKDF for DTLS 1.3 with SHA256 hashing - such as might be used for TLS_AES_128_GCM_SHA256 4 | 5 | op=$1 6 | if [[ "$op" = "extract" ]]; then 7 | 8 | ### RFC 5869 section 2.2 9 | # 10 | # HKDF-Extract(salt, IKM) -> PRK 11 | # 12 | # Options: 13 | # Hash a hash function; HashLen denotes the length of the 14 | # hash function output in octets 15 | # 16 | # Inputs: 17 | # salt optional salt value (a non-secret random value); 18 | # if not provided, it is set to a string of HashLen zeros. 19 | # IKM input keying material 20 | # 21 | # Output: 22 | # PRK a pseudorandom key (of HashLen octets) 23 | # 24 | # The output PRK is calculated as follows: 25 | # 26 | # PRK = HMAC-Hash(salt, IKM) 27 | 28 | hexsalt="$2" hexkeymaterial="$3" 29 | 30 | if [[ "$hexsalt" = "" ]]; then 31 | hexsalt="0000000000000000000000000000000000000000000000000000000000000000" 32 | fi 33 | echo -en "$hexkeymaterial" | xxd -r -p | openssl dgst -sha256 -mac HMAC -macopt hexkey:"$hexsalt" -hex \ 34 | | sed -e 's/.* //' 35 | 36 | elif [[ "$op" = "expand" || "$op" = "expandlabel" ]]; then 37 | 38 | if [[ "$op" = "expand" ]]; then 39 | 40 | ### RFC 5869 section 2.3 41 | # 42 | # HKDF-Expand(PRK, info, L) -> OKM 43 | # 44 | # Options: 45 | # Hash a hash function; HashLen denotes the length of the 46 | # hash function output in octets 47 | # 48 | # Inputs: 49 | # PRK a pseudorandom key of at least HashLen octets 50 | # (usually, the output from the extract step) 51 | # info optional context and application specific information 52 | # (can be a zero-length string) 53 | # L length of output keying material in octets 54 | # (<= 255*HashLen) 55 | # 56 | # Output: 57 | # OKM output keying material (of L octets) 58 | # 59 | # The output OKM is calculated as follows: 60 | # N = ceil(L/HashLen) 61 | # T = T(1) | T(2) | T(3) | ... | T(N) 62 | # OKM = first L octets of T 63 | # 64 | # where: 65 | # T(0) = empty string (zero length) 66 | # T(1) = HMAC-Hash(PRK, T(0) | info | 0x01) 67 | # T(2) = HMAC-Hash(PRK, T(1) | info | 0x02) 68 | # T(3) = HMAC-Hash(PRK, T(2) | info | 0x03) 69 | # ... 70 | # (where the constant concatenated to the end of each T(n) is a 71 | # single octet.) 72 | 73 | hexprk="$2" hexinfo="$3" length="$4" 74 | 75 | elif [[ "$op" = "expandlabel" ]]; then 76 | 77 | ### RFC 8446 section 7.1 78 | # 79 | # The key derivation process makes use of the HKDF-Extract and 80 | # HKDF-Expand functions as defined above, as well as the 81 | # functions defined below: 82 | # 83 | # HKDF-Expand-Label(Secret, Label, Context, Length) = 84 | # HKDF-Expand(Secret, HkdfLabel, Length) 85 | # 86 | # Where HkdfLabel is specified as: 87 | # 88 | # struct { 89 | # uint16 length = Length; 90 | # opaque label<7..255> = "tls13 " + Label; 91 | # opaque context<0..255> = Context; 92 | # } HkdfLabel; 93 | # 94 | # implementor's note: the above definition references variable-length vectors, 95 | # which in this case are preceded by a single-byte of length info. 96 | 97 | hexprk="$2" label="$3" hexcontext="$4" length="$5" 98 | labellen=$(echo -n "$label" | wc -c | awk '{print $1}') 99 | let labellen=labellen+6 100 | contextlen=$(echo -n "$hexcontext" | wc -c | awk '{print $1}') 101 | let contextlen=contextlen/2 102 | hkdflabel=$(printf "%04x" "$length")$(printf "%02x" "$labellen") 103 | hkdflabel=${hkdflabel}$(echo -en "dtls13$label" | xxd -p ) 104 | hkdflabel=${hkdflabel}$(printf "%02x" "$contextlen")"$hexcontext" 105 | hexinfo=${hkdflabel} 106 | fi 107 | 108 | let hexlength="$length"*2 109 | hexoutput= 110 | hexlast= 111 | i=1 112 | while [[ $(echo -n "$hexoutput" | wc -c) -lt $hexlength ]]; do 113 | hexin=${hexlast}${hexinfo}$(printf "%02x" "$i") 114 | hexlast=$(echo -en "$hexin" | xxd -r -p | openssl dgst -sha256 -mac HMAC -macopt hexkey:"$hexprk" -hex \ 115 | | sed -e 's/.* //') 116 | hexoutput=${hexoutput}${hexlast} 117 | let i++ 118 | done 119 | echo -n "$hexoutput" | head -c "$hexlength" 120 | echo 121 | else 122 | cat <&2 123 | Usage: 124 | 125 | $0 extract hexsalt hexkey 126 | - perform the HKDF-Extract function to concentrate key material 127 | $0 expand hexprk hexinfo length 128 | - perform the HKDF-Expand function to generate keys 129 | $0 expandlabel hexprk label hexcontext length 130 | - perform the HKDF-Expand-Label function as defined in RFC 8446 (TLS 1.3) 131 | 132 | EOF 133 | fi 134 | -------------------------------------------------------------------------------- /site/files/server-ephemeral-private.key: -------------------------------------------------------------------------------- 1 | -----BEGIN PRIVATE KEY----- 2 | MC4CAQAwBQYDK2VuBCIEIJCRkpOUlZaXmJmam5ydnp+goaKjpKWmp6ipqqusra6v 3 | -----END PRIVATE KEY----- 4 | -------------------------------------------------------------------------------- /site/files/server-ephemeral-public.key: -------------------------------------------------------------------------------- 1 | -----BEGIN PUBLIC KEY----- 2 | MCowBQYDK2VuAyEAn9etbc/0KY3T+W1bGyr5EKBTWxSI1/j6uzSamCiAthU= 3 | -----END PUBLIC KEY----- 4 | -------------------------------------------------------------------------------- /site/files/server.crt: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIDITCCAgmgAwIBAgIIFVqSrcIEj5AwDQYJKoZIhvcNAQELBQAwIjELMAkGA1UE 3 | BhMCVVMxEzARBgNVBAoTCkV4YW1wbGUgQ0EwHhcNMTgxMDA1MDEzODE3WhcNMTkx 4 | MDA1MDEzODE3WjArMQswCQYDVQQGEwJVUzEcMBoGA1UEAxMTZXhhbXBsZS51bGZo 5 | ZWltLm5ldDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMSANga650dr 6 | CJQE7Ke2kQQ/95K8Ge77fXTXqA0AHntLOkrmD+jAcfxz5wJMDbz0vdEdOWu6cEZK 7 | E+lK+D3z4QlZVHvJVftBLaN2UhHh89x3bKpTN27KOuy+w6q3OzHVbLZSnICYvMng 8 | KBjiC/f4oDr9FwRQns55vZ858epp7EeXLoMPtcqV3pWh5gQi1e6+UnlUoee/iob2 9 | Rm0NnxaVGkz3oEaSWVwTUvJUnlr7Tr/XejeVAUTkwCaHTGU+QH19IwdEAfSE/9CP 10 | eh+gUhDR9PDVznlwKTLiyr5wH9+ta0u3EQH0S61mahETD+Lugp5NAp3JHN1nFtu5 11 | BhiG7cG6lCECAwEAAaNSMFAwDgYDVR0PAQH/BAQDAgWgMB0GA1UdJQQWMBQGCCsG 12 | AQUFBwMCBggrBgEFBQcDATAfBgNVHSMEGDAWgBSJT95bzGniUs8+owDfsZe4HeHB 13 | RjANBgkqhkiG9w0BAQsFAAOCAQEAWRZFppouN3nk9t0nGrocC/1s11WZtefDblM+ 14 | /zZZCEMkyeelBAedOeDUKYf/4+vdCcHPHZFEVYcLVx3Rm98dJPi7mhH+gP1ZK6A5 15 | jN4R4mUeYYzlmPqW5Tcu7z0kiv3hdGPrv6u45NGrUCpU7ABk6S94GWYNPyfPIJ5m 16 | f85a4uSsmcfJOBj4slEHIt/tl/MuPpNJ1MZsnqY5bXREYqBrQsbVumiOrDoBe938 17 | jiz8rSfLadPM3KKAQURl0640jODzSrL7nGGDcTErGRBBZBwjfxGl1lyETwQEhJk4 18 | cSuVntaFvFxd1kXtGZCUc0ApJty0DjRpoVlB6OLMqEu2CEY2oA== 19 | -----END CERTIFICATE----- 20 | -------------------------------------------------------------------------------- /site/files/server.key: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | MIIEowIBAAKCAQEAxIA2BrrnR2sIlATsp7aRBD/3krwZ7vt9dNeoDQAee0s6SuYP 3 | 6MBx/HPnAkwNvPS90R05a7pwRkoT6Ur4PfPhCVlUe8lV+0Eto3ZSEeHz3HdsqlM3 4 | bso67L7Dqrc7MdVstlKcgJi8yeAoGOIL9/igOv0XBFCeznm9nznx6mnsR5cugw+1 5 | ypXelaHmBCLV7r5SeVSh57+KhvZGbQ2fFpUaTPegRpJZXBNS8lSeWvtOv9d6N5UB 6 | ROTAJodMZT5AfX0jB0QB9IT/0I96H6BSENH08NXOeXApMuLKvnAf361rS7cRAfRL 7 | rWZqERMP4u6Cnk0Cnckc3WcW27kGGIbtwbqUIQIDAQABAoIBAGF7OVIdZp8Hejn0 8 | N3L8HvT8xtUEe9kS6ioM0lGgvX5s035Uo4/T6LhUx0VcdXRH9eLHnLTUyN4V4cra 9 | ZkxVsE3zAvZl60G6E+oDyLMWZOP6Wu4kWlub9597A5atT7BpMIVCdmFVZFLB4SJ3 10 | AXkC3nplFAYP+Lh1rJxRIrIn2g+pEeBboWbYA++oDNuMQffDZaokTkJ8Bn1JZYh0 11 | xEXKY8Bi2Egd5NMeZa1UFO6y8tUbZfwgVs6Enq5uOgtfayq79vZwyjj1kd29MBUD 12 | 8g8byV053ZKxbUOiOuUts97eb+fN3DIDRTcT2c+lXt/4C54M1FclJAbtYRK/qwsl 13 | pYWKQAECgYEA4ZUbqQnTo1ICvj81ifGrz+H4LKQqe92Hbf/W51D/Umk2kP702W22 14 | HP4CvrJRtALThJIG9m2TwUjl/WAuZIBrhSAbIvc3Fcoa2HjdRp+sO5U1ueDq7d/S 15 | Z+PxRI8cbLbRpEdIaoR46qr/2uWZ943PHMv9h4VHPYn1w8b94hwD6vkCgYEA3v87 16 | mFLzyM9ercnEv9zHMRlMZFQhlcUGQZvfb8BuJYl/WogyT6vRrUuM0QXULNEPlrin 17 | mBQTqc1nCYbgkFFsD2VVt1qIyiAJsB9MD1LNV6YuvE7T2KOSadmsA4fa9PUqbr71 18 | hf3lTTq+LeR09LebO7WgSGYY+5YKVOEGpYMR1GkCgYEAxPVQmk3HKHEhjgRYdaG5 19 | lp9A9ZE8uruYVJWtiHgzBTxx9TV2iST+fd/We7PsHFTfY3+wbpcMDBXfIVRKDVwH 20 | BMwchXH9+Ztlxx34bYJaegd0SmA0Hw9ugWEHNgoSEmWpM1s9wir5/ELjc7dGsFtz 21 | uzvsl9fpdLSxDYgAAdzeGtkCgYBAzKIgrVox7DBzB8KojhtD5ToRnXD0+H/M6OKQ 22 | srZPKhlb0V/tTtxrIx0UUEFLlKSXA6mPw6XDHfDnD86JoV9pSeUSlrhRI+Ysy6tq 23 | eIE7CwthpPZiaYXORHZ7wCqcK/HcpJjsCs9rFbrV0yE5S3FMdIbTAvgXg44VBB7O 24 | UbwIoQKBgDuY8gSrA5/A747wjjmsdRWK4DMTMEV4eCW1BEP7Tg7Cxd5n3xPJiYhr 25 | nhLGN+mMnVIcv2zEMS0/eNZr1j/0BtEdx+3IC6Eq+ONY0anZ4Irt57/5QeKgKn/L 26 | JPhfPySIPG4UmwE4gW8t79vfOKxnUu2fDD1ZXUYopan6EckACNH/ 27 | -----END RSA PRIVATE KEY----- 28 | -------------------------------------------------------------------------------- /site/frombootstrap.css: -------------------------------------------------------------------------------- 1 | /* everything we wanted from bootstrap but nothing more */ 2 | 3 | *, 4 | *::before, 5 | *::after { 6 | box-sizing: border-box; 7 | } 8 | 9 | html { 10 | line-height: 1.15; 11 | -webkit-text-size-adjust: 100%; 12 | -ms-text-size-adjust: 100%; 13 | -ms-overflow-style: scrollbar; 14 | -webkit-tap-highlight-color: rgba(0, 0, 0, 0); 15 | } 16 | 17 | body { 18 | margin: 0; 19 | font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif, 20 | "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"; 21 | font-size: 1rem; 22 | font-weight: 400; 23 | line-height: 1.5; 24 | color: #212529; 25 | text-align: left; 26 | background-color: #fff; 27 | } 28 | 29 | p { 30 | margin-top: 0; 31 | margin-bottom: 1rem; 32 | } 33 | 34 | a { 35 | color: #007bff; 36 | text-decoration: none; 37 | background-color: transparent; 38 | -webkit-text-decoration-skip: objects; 39 | } 40 | a:hover { 41 | color: #0056b3; 42 | text-decoration: underline; 43 | } 44 | 45 | pre, 46 | code, 47 | kbd, 48 | samp { 49 | font-family: SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; 50 | font-size: 1em; 51 | } 52 | 53 | h1, h2, h3, h4, h5, h6, 54 | .h1, .h2, .h3, .h4, .h5, .h6 { 55 | margin-top: 0; 56 | margin-bottom: 0.5rem; 57 | font-weight: 500; 58 | line-height: 1.2; 59 | } 60 | h1, .h1 { 61 | font-size: 2.5rem; 62 | } 63 | h2, .h2 { 64 | font-size: 2rem; 65 | } 66 | h3, .h3 { 67 | font-size: 1.75rem; 68 | } 69 | h4, .h4 { 70 | font-size: 1.5rem; 71 | } 72 | h5, .h5 { 73 | font-size: 1.25rem; 74 | } 75 | h6, .h6 { 76 | font-size: 1rem; 77 | } 78 | 79 | pre { 80 | display: block; 81 | font-size: 87.5%; 82 | color: #212529; 83 | } 84 | 85 | .container { 86 | width: 100%; 87 | padding-right: 15px; 88 | padding-left: 15px; 89 | margin-right: auto; 90 | margin-left: auto; 91 | } 92 | 93 | @media (min-width: 400px) { 94 | .container { 95 | padding-left: 5px; 96 | padding-right: 5px; 97 | } 98 | } 99 | 100 | @media (min-width: 768px) { 101 | .container { 102 | max-width: 720px; 103 | } 104 | } 105 | 106 | @media (min-width: 992px) { 107 | .container { 108 | max-width: 960px; 109 | } 110 | } 111 | 112 | @media (min-width: 1200px) { 113 | .container { 114 | max-width: 1140px; 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /site/illustrated.css: -------------------------------------------------------------------------------- 1 | :root { 2 | --chunky-text: #777; 3 | --server-bg: hsl(190, 60%, 80%); 4 | --client-bg: hsl(142, 61%, 82%); 5 | --server-bg-hover: hsl(288, 28%, 83%); 6 | --client-bg-hover: hsl(288, 31%, 80%); 7 | 8 | --prot-text: #555; 9 | --prot-text-shadow: #555; 10 | 11 | --sam-btn0: hsl(190, 30%, 45%); 12 | --sam-btn9: hsl(190, 25%, 40%); 13 | --sam-hov-btn0: hsl(190, 30%, 40%); 14 | --sam-hov-btn9: hsl(190, 25%, 35%); 15 | --sam-btn-shadow: hsl(180, 41%, 28%); 16 | --sam-btn-inset: hsl(180, 45%, 40%); 17 | --sam-btn-border: hsl(161, 10%, 37%); 18 | 19 | --act-btn0: hsl(37, 96%, 54%); 20 | --act-btn9: hsl(25, 91%, 54%); 21 | --act-hov-btn0: hsl(32, 94%, 52%); 22 | --act-hov-btn9: hsl(21, 88%, 51%); 23 | --act-btn-shadow: hsl(25, 91%, 40%); 24 | --act-btn-border: hsl(33, 90%, 45%); 25 | } 26 | 27 | /* remove focus rings for non-keyboard users */ 28 | body:not(.user-is-tabbing) button:focus, 29 | body:not(.user-is-tabbing) input:focus, 30 | body:not(.user-is-tabbing) select:focus, 31 | body:not(.user-is-tabbing) textarea:focus { 32 | outline: none; 33 | } 34 | 35 | body { 36 | /* printmode */ 37 | position: relative; 38 | } 39 | 40 | .container { 41 | /* printmode */ 42 | overflow: auto; 43 | } 44 | 45 | @media (max-width: 600px) { 46 | .illustration { 47 | /* hide keys when there is no room for them */ 48 | display: none !important; 49 | } 50 | .print-mode { 51 | display: none; 52 | } 53 | } 54 | 55 | h1:after { 56 | content: "\00a0❧"; 57 | } 58 | h1:before { 59 | content: "❧\00a0"; 60 | } 61 | h1, h3, h5 { 62 | text-align: center; 63 | padding: 10px; 64 | max-width: 800px; 65 | margin: 0 auto; 66 | } 67 | h5 { 68 | font-style: italic; 69 | font-family: serif; 70 | } 71 | 72 | pre { 73 | margin: 1em 0; 74 | overflow: scroll; 75 | } 76 | 77 | pre code { 78 | font-size: inherit; 79 | word-break: normal; 80 | display: block; 81 | color: white; 82 | background-color: black; 83 | padding: 0.5em; 84 | border-radius: 5px; 85 | overflow: scroll; 86 | } 87 | 88 | ol, ul, dl { 89 | margin: 1rem 0; 90 | } 91 | 92 | tt.longboi { 93 | word-break: break-all; 94 | } 95 | 96 | .ind1 { 97 | padding-left: 1em; 98 | } 99 | .ind2 { 100 | padding-left: 2em; 101 | } 102 | 103 | .outerblock { 104 | max-width: 600px; 105 | margin: 1em auto; 106 | } 107 | .outerblock p { 108 | text-align: center; 109 | } 110 | 111 | .server { 112 | background-color: var(--server-bg); 113 | } 114 | 115 | .client { 116 | background-color: var(--client-bg); 117 | } 118 | 119 | xtt { 120 | font-family: monospace; 121 | } 122 | 123 | /***** .record and .calculation *****/ 124 | 125 | .rec-outer { 126 | max-width: 800px; 127 | margin: 0.8em auto; 128 | } 129 | 130 | .record, .calculation { 131 | cursor: pointer; 132 | max-width: 800px; 133 | padding: 1em; 134 | border-radius: 1em; 135 | border: 2px solid transparent; 136 | box-shadow: 2px 2px 2px rgba(0,0,0,0.3); 137 | } 138 | 139 | .illustrated .server.record .rec-label:before { 140 | content: "❰ "; 141 | color: var(--chunky-text); 142 | } 143 | .illustrated .client.record .rec-label:before { 144 | content: "❱ "; 145 | color: var(--chunky-text); 146 | } 147 | .illustrated .calculation .rec-label:before { 148 | content: "± "; 149 | font-weight: bold; 150 | color: var(--chunky-text); 151 | } 152 | 153 | .rec-label { 154 | cursor: pointer; 155 | font-size: 1.3em; 156 | -webkit-user-select: none; 157 | user-select: none; 158 | overflow: hidden; 159 | text-overflow: ellipsis; 160 | 161 | white-space: nowrap; 162 | text-align: center; 163 | width: 100%; 164 | transition: all 0.3s; 165 | } 166 | 167 | .record.selected, 168 | .calculation.selected { 169 | cursor: inherit; 170 | box-shadow: 2px 2px 2px rgba(0,0,0,0.3); 171 | } 172 | 173 | .record.selected .rec-label, 174 | .calculation.selected .rec-label { 175 | padding: 0 0 10px 0; 176 | width: 0; 177 | overflow: inherit; 178 | } 179 | .illustrated .record.selected .rec-label:after, 180 | .illustrated .calculation.selected .rec-label:after { 181 | color: var(--chunky-text); 182 | content: " ×"; 183 | font-weight: bold; 184 | cursor: pointer; 185 | } 186 | 187 | .record .record-data { 188 | font-family: Menlo, Monaco, Consolas, "Courier New", monospace; 189 | display: none; 190 | position: relative; 191 | } 192 | 193 | .record.selected .record-data { 194 | display: block; 195 | } 196 | 197 | .record:hover, 198 | .calculation:hover { 199 | border: 2px solid #666; 200 | } 201 | .record.selected:hover, 202 | .calculation.selected:hover { 203 | border: 2px solid transparent; 204 | } 205 | 206 | .rec-explanation { 207 | display: none; 208 | margin-bottom: 1em; 209 | } 210 | .selected .rec-explanation { 211 | display: block; 212 | } 213 | 214 | .record .illustration, 215 | .calculation .illustration { 216 | margin: -40px 0 0 0; 217 | display: none; 218 | float: right; 219 | } 220 | .record.selected .illustration, 221 | .calculation.selected .illustration { 222 | display: block; 223 | } 224 | 225 | .record > button.annotate-toggle { 226 | display: none; 227 | } 228 | .record.selected > button.annotate-toggle { 229 | display: block; 230 | } 231 | 232 | /***** .record-data *****/ 233 | 234 | .client .record-data .string .bytes:hover { 235 | background-color: var(--client-bg-hover); 236 | } 237 | .server .record-data .string .bytes:hover { 238 | background-color: var(--server-bg-hover); 239 | } 240 | .record.annotate .record-data .string:hover { 241 | color: inherit; 242 | } 243 | 244 | .record-data .string { 245 | position: relative; 246 | } 247 | 248 | .record-data .string .label { 249 | display: none; 250 | position: absolute; 251 | background-color: #FAF7DC; 252 | border-radius: 5px; 253 | margin: 2px 0; 254 | padding: 2px 7px; 255 | line-height: 1.2; 256 | white-space: nowrap; 257 | -webkit-user-select: none; 258 | user-select: none; 259 | top: -28px; 260 | box-shadow: 2px 2px 2px rgba(0,0,0,0.3); 261 | } 262 | .record-data .string:hover > .label { 263 | display: inline; 264 | } 265 | 266 | .record-data .string .label:after { 267 | content: ""; 268 | position: absolute; 269 | box-shadow: 2px 2px 2px rgba(0,0,0,0.3); 270 | transform: rotate(45deg); 271 | bottom: -3px; 272 | left: 10px; 273 | border-width: 3px; 274 | border-style: solid; 275 | border-color: transparent #FAF7DC #FAF7DC transparent; 276 | } 277 | 278 | .record.annotate .string > .explanation, 279 | .record.annotate .decryption > .explanation { 280 | position: relative; 281 | display: block; 282 | font-size: 0.9em; 283 | color: black; 284 | margin: 1em 0; 285 | padding: 1em; 286 | background-color: #FAF7DC; 287 | border: 2px solid #e0d998; 288 | border-radius: 1em; 289 | font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; 290 | box-shadow: 2px 2px 2px rgba(0,0,0,0.3); 291 | } 292 | 293 | .record.annotate .string > .explanation:before { 294 | content: " "; 295 | position: absolute; 296 | padding: 5px; 297 | transform: rotate(45deg); 298 | top: -7px; 299 | left: 20px; 300 | border: 2px solid; 301 | background-color: #FAF7DC; 302 | border-color: #e0d998 transparent transparent #e0d998; 303 | } 304 | 305 | .record.annotate .string > .label { 306 | display: none; 307 | } 308 | 309 | .record .string > .explanation, 310 | .record .decryption > .explanation { 311 | display: none; 312 | } 313 | 314 | .record .decryption > .label:before { 315 | content: "⬇ "; 316 | font-weight: bold; 317 | color: var(--chunky-text); 318 | } 319 | .record .decryption > .label:after { 320 | content: " ⬇"; 321 | font-weight: bold; 322 | color: var(--chunky-text); 323 | } 324 | 325 | .string .bytes { 326 | padding: 0.2em 0; 327 | } 328 | 329 | .string.encrypted .bytes { 330 | text-shadow: 1px 1px 0 red; 331 | } 332 | 333 | .string.decrypted .bytes { 334 | text-shadow: 1px 1px 0 green; 335 | } 336 | 337 | .string .bytes.protected { 338 | color: var(--prot-text); 339 | text-shadow: 1px 1px 3px var(--prot-text-shadow); 340 | } 341 | .string .bytes.unprotected { 342 | display: none; 343 | } 344 | .string .bytes.protected { 345 | display: inline; 346 | } 347 | .string .bytes.unprotected.hp-disabled { 348 | display: inline; 349 | } 350 | .string .bytes.protected.hp-disabled { 351 | display: none; 352 | } 353 | 354 | .record-data .decryption { 355 | margin: 1em 0; 356 | } 357 | 358 | .record-data .decryption .label { 359 | text-align: center; 360 | } 361 | 362 | .bits { 363 | border-collapse: collapse; 364 | } 365 | .bits td, .bits th { 366 | padding: 0 0.5em; 367 | } 368 | .bits td { 369 | border-top: 1px solid #aaa; 370 | } 371 | /***** processblock *****/ 372 | 373 | processblock { 374 | display: block; 375 | position: relative; 376 | margin: 1em; 377 | padding-left: 1em; 378 | overflow: hidden; 379 | transition: all 0.3s; 380 | cursor: pointer; 381 | font-size: 0.8em; 382 | } 383 | 384 | processblock:before { 385 | content: " "; 386 | width: 100%; 387 | height: 100%; 388 | position: absolute; 389 | left: 0; 390 | top: 0; 391 | pointer-events: none; 392 | } 393 | 394 | processblock pre { 395 | margin: 0; 396 | } 397 | 398 | /***** codesample *****/ 399 | 400 | codesample { 401 | display: block; 402 | margin: 1em 0; 403 | } 404 | 405 | codesample pre { 406 | margin: 0; 407 | height: 0; 408 | } 409 | 410 | codesample button.show-code { 411 | display: block; 412 | clear: both; 413 | box-shadow: inset 0 0 5px 0 var(--sam-btn-inset); 414 | background: linear-gradient(to bottom, var(--sam-btn0) 1%, var(--sam-btn9) 100%); 415 | border-radius:5px; 416 | border: 1px solid var(--sam-btn-border); 417 | cursor: pointer; 418 | color: white; 419 | font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; 420 | font-size: 15px; 421 | font-weight: bold; 422 | padding: 11px 23px; 423 | text-decoration: none; 424 | text-shadow: 0 1px 0 var(--sam-btn-shadow); 425 | } 426 | 427 | codesample button.show-code:hover:hover { 428 | background: linear-gradient(to bottom, var(--sam-hov-btn0) 1%, var(--sam-hov-btn9) 100%); 429 | } 430 | codesample button.show-code:active { 431 | position: relative; 432 | top: 1px; 433 | } 434 | codesample.show button.show-code { 435 | display: none; 436 | } 437 | codesample.show pre { 438 | height: auto; 439 | } 440 | 441 | /***** annotation toggle button *****/ 442 | 443 | button.annotate-toggle { 444 | margin-bottom: 1em; 445 | 446 | display: inline-block; 447 | outline: none; 448 | cursor: pointer; 449 | text-align: center; 450 | text-decoration: none; 451 | font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; 452 | font-size: 1em; 453 | font-weight: bold; 454 | padding: .4em 1.7em .45em; 455 | text-shadow: 0 1px 0 var(--act-btn-shadow); 456 | border-radius: .5em; 457 | box-shadow: 1px 1px 1px rgba(0,0,0,0.3); 458 | 459 | color: #fff; 460 | border: solid 1px var(--act-btn-border); 461 | background: linear-gradient(180deg, var(--act-btn0) 1%, var(--act-btn9) 100%); 462 | } 463 | button.annotate-toggle:hover { 464 | text-decoration: none; 465 | background: linear-gradient(180deg, var(--act-hov-btn0) 1%, var(--act-hov-btn9) 100%); 466 | } 467 | button.annotate-toggle:active { 468 | position: relative; 469 | top: 1px; 470 | } 471 | 472 | /***** header protection toggle button *****/ 473 | 474 | button.hp-toggle { 475 | margin-bottom: 1em; 476 | 477 | display: inline-block; 478 | outline: none; 479 | cursor: pointer; 480 | text-align: center; 481 | text-decoration: none; 482 | font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; 483 | font-size: 1em; 484 | font-weight: bold; 485 | padding: .4em 1.7em .45em; 486 | text-shadow: 0 1px 0 var(--act-btn-shadow); 487 | border-radius: .5em; 488 | box-shadow: 1px 1px 1px rgba(0,0,0,0.3); 489 | 490 | color: #fff; 491 | border: solid 1px var(--act-btn-border); 492 | background: linear-gradient(180deg, var(--act-btn0) 1%, var(--act-btn9) 100%); 493 | } 494 | button.hp-toggle:hover { 495 | text-decoration: none; 496 | background: linear-gradient(180deg, var(--act-hov-btn0) 1%, var(--act-hov-btn9) 100%); 497 | } 498 | button.hp-toggle:active { 499 | position: relative; 500 | top: 1px; 501 | } 502 | 503 | 504 | /***** datagram borders *****/ 505 | .datagram { 506 | max-width: 800px; 507 | border-radius: 1.2em; 508 | border: 1px solid black; 509 | padding: 5px 15px; 510 | margin: 0.8em auto; 511 | } 512 | 513 | .datagram > .label { 514 | font-size: 0.8em; 515 | font-weight: bold; 516 | } 517 | 518 | button#openCloseAll { 519 | margin-bottom: 0; 520 | min-width: 8em; 521 | } 522 | 523 | /***** print mode *****/ 524 | .print-mode { 525 | cursor: pointer; 526 | position: absolute; 527 | padding: 5px; 528 | bottom: 0; 529 | right: 0; 530 | } 531 | 532 | /***** header ******/ 533 | 534 | .header { 535 | width: 100%; 536 | margin: 0; 537 | padding: 2px; 538 | border-bottom: 1px solid grey; 539 | background-color: bisque; 540 | color: #444; 541 | line-height: 20px; 542 | font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; 543 | font-size: 14px; 544 | text-align: right; 545 | } 546 | .header a, .header a:visited, .header a:hover { 547 | text-decoration: none; 548 | color: #444; 549 | } 550 | .header a.this-page:before { 551 | content: "❧\00a0"; 552 | } 553 | .header a, .header span { 554 | margin-right: 0.5em; 555 | } 556 | .header a.this-page { 557 | font-weight: bold; 558 | } 559 | -------------------------------------------------------------------------------- /site/illustrated.js: -------------------------------------------------------------------------------- 1 | (() => { 2 | "use strict"; 3 | 4 | let ill = { 5 | anchors: {} 6 | }; 7 | 8 | // viewports etc 9 | 10 | ill.elementIsVisible = (el) => { 11 | let rect = el.getBoundingClientRect(), 12 | viewHeight = Math.max(document.documentElement.clientHeight, window.innerHeight); 13 | return !(rect.bottom < 0 || rect.top - viewHeight >= 0); 14 | }; 15 | 16 | ill.ensureElementInView = (el) => { 17 | if (!ill.elementIsVisible(el)) { 18 | el.scrollIntoView({behavior: "smooth"}); 19 | } 20 | }; 21 | 22 | // events 23 | 24 | ill.unselectAllRecords = () => { 25 | document.querySelectorAll( 26 | ".illustrated .record.selected, .illustrated .calculation.selected" 27 | ).forEach(el => { 28 | el.classList.remove("selected"); 29 | }); 30 | ill.normalizeOpenCloseAll(); 31 | }; 32 | 33 | ill.toggleRecord = (element, event) => { 34 | ill.cancel(event); 35 | let selected = element.classList.contains("selected"); 36 | if (!element.classList.contains("selected")) { 37 | element.classList.add("selected"); 38 | if (event) { ill.changeHash(element.dataset.anchor); } 39 | ill.ensureElementInView(element); 40 | } else { 41 | element.classList.remove("selected"); 42 | ill.closeAllCode(); 43 | if (event) { ill.changeHash(""); } 44 | } 45 | ill.normalizeOpenCloseAll(); 46 | }; 47 | 48 | ill.selectRecord = (element, event) => { 49 | ill.unselectAllRecords(); 50 | element.classList.add("selected"); 51 | if (event) { ill.changeHash(element.dataset.anchor); } 52 | ill.cancel(event); 53 | ill.ensureElementInView(element); 54 | }; 55 | 56 | ill.showCode = (element, event) => { 57 | element.classList.add("show"); 58 | ill.cancel(event); 59 | }; 60 | 61 | ill.closeAllCode = () => { 62 | document.querySelectorAll("codesample.show").forEach(el => { 63 | el.classList.remove("show"); 64 | }); 65 | }; 66 | 67 | ill.getAncestorAnchor = (el) => { 68 | while (el && !el.dataset.anchor) { 69 | el = el.parentElement; 70 | } 71 | return el?.dataset?.anchor; 72 | }; 73 | 74 | ill.toggleAnnotate = (el, event) => { 75 | let anchor = ill.getAncestorAnchor(el); 76 | if (el.classList.toggle("annotate")) { 77 | anchor = `${anchor}/annotated`; 78 | } 79 | if (event) { ill.changeHash(anchor); } 80 | ill.cancel(event); 81 | }; 82 | 83 | ill.cancel = (event) => { 84 | if (event) { event.stopPropagation(); } 85 | }; 86 | 87 | // injections 88 | 89 | ill.addShowCode = (el) => { 90 | el.innerHTML = document.getElementById("showCodeTmpl").innerHTML + el.innerHTML; 91 | }; 92 | 93 | function htmlToElement(html) { 94 | let outer = document.createElement("template"); 95 | outer.innerHTML = html.trim(); 96 | return outer.content.firstChild; 97 | } 98 | 99 | ill.addAnchors = (record) => { 100 | let label = record.getElementsByClassName("rec-label"); 101 | label = label && label[0].textContent; 102 | let count = 1; 103 | if (label) { 104 | label = label.toLowerCase().replaceAll(/[^a-z\d]/g, "-"); 105 | while (ill.anchors[label]) { 106 | label = label.replaceAll(/-\d+$/g, ""); 107 | label = `${label}-${++count}`; 108 | } 109 | record.dataset.anchor = label; 110 | ill.anchors[label] = record; 111 | ill.anchors[`${label}/annotated`] = record; 112 | record.insertBefore( 113 | htmlToElement(``), record.firstChild); 114 | record.insertBefore( 115 | htmlToElement(``), record.firstChild); 116 | } 117 | }; 118 | 119 | ill.resolveHash = () => { 120 | let hash = window.location.hash.replace(/^#/, ""); 121 | if (hash === 'open-all') { 122 | let btn = document.getElementById('openCloseAll'); 123 | if (btn) btn.click(); 124 | } 125 | const rec = ill.anchors[hash]; 126 | if (!rec) { 127 | return; 128 | } 129 | ill.selectRecord(rec, null); 130 | if (hash.endsWith("/annotated")) { 131 | const b = rec.getElementsByClassName("annotate-toggle"); 132 | if (b && b.length) { 133 | ill.toggleAnnotate(b[0].parentElement); 134 | } 135 | } 136 | }; 137 | 138 | ill.addToggleAnnotations = (record) => { 139 | let expl = record.querySelector(".rec-explanation"); 140 | let copy = document.getElementById("annotateTmpl").cloneNode(true); 141 | // always true (IDE warning) 142 | if (copy instanceof Element) { expl.insertAdjacentElement("afterend", copy); } 143 | copy.addEventListener("click", (e) => { ill.toggleAnnotate(record, e); }); 144 | }; 145 | 146 | ill.injectLabels = () => { 147 | let els = document.querySelectorAll(".string > .explanation, .decryption > .explanation"); 148 | els.forEach(expl => { 149 | let label = expl.parentNode.querySelector(".label"), 150 | h4 = document.createElement("h4"); 151 | h4.appendChild(document.createTextNode(label.textContent)); 152 | expl.insertAdjacentElement("afterbegin", h4); 153 | }); 154 | }; 155 | 156 | ill.toggleHeaderProtection = () => { 157 | let els = document.querySelectorAll(".bytes.protected"); 158 | els.forEach(el => { el.classList.toggle("hp-disabled"); }); 159 | els = document.querySelectorAll(".bytes.unprotected"); 160 | els.forEach(el => { el.classList.toggle("hp-disabled"); }); 161 | let btns = document.querySelectorAll("button.hp-toggle"); 162 | const b = btns[0]; 163 | const text = b.textContent === b.dataset.declbl ? b.dataset.enclbl : b.dataset.declbl; 164 | btns.forEach(el => { el.textContent = text; }); 165 | }; 166 | 167 | /** 168 | * Open or close all elements on the page 169 | * @param {string} openOrClose - "open" or "close" 170 | */ 171 | let actionAll = (openOrClose) => { 172 | let classOperation = openOrClose === 'open' ? document.body.classList.add : document.body.classList.remove; 173 | [].forEach.call(document.querySelectorAll(".record, .calculation"), (el) => { 174 | classOperation.call(el.classList, "selected", "annotate"); 175 | }); 176 | [].forEach.call(document.querySelectorAll("codesample"), (el) => { 177 | classOperation.call(el.classList, "show"); 178 | }); 179 | if (openOrClose !== 'open') { 180 | ill.closeAllCode(); 181 | }; 182 | }; 183 | 184 | ill.openCloseAll = (btn, event) => { 185 | ill.cancel(event); 186 | btn = btn || document.getElementById('openCloseAll'); 187 | if (!btn) return; 188 | 189 | let action = btn.dataset['lblState']; 190 | actionAll(action); 191 | let nextState = action === 'open' ? 'close' : 'open'; 192 | ill.changeHash(action === 'open' ? 'open-all' : ''); 193 | ill.normalizeOpenCloseAll(); 194 | }; 195 | 196 | ill.normalizeOpenCloseAll = () => { 197 | let allCount = document.querySelectorAll('.record, .calculation').length; 198 | let openCount = document.querySelectorAll('.record.selected, .calculation.selected').length; 199 | let closedCount = allCount - openCount; 200 | 201 | let newButtonState = 'open'; 202 | if (closedCount === 0) { 203 | newButtonState = 'close'; 204 | } 205 | 206 | let btn = document.getElementById('openCloseAll'); 207 | if (btn && btn.dataset['lblState'] !== newButtonState) { 208 | // swap text w/ lbl-toggle, then swap state 209 | let tmp = btn.textContent; 210 | btn.textContent = btn.dataset['lblToggle']; 211 | btn.dataset['lblToggle'] = tmp; 212 | btn.dataset['lblState'] = newButtonState; 213 | } 214 | }; 215 | 216 | ill.printMode = () => { 217 | // add printmode css 218 | let inject = document.createElement("link"); 219 | inject.setAttribute("rel", "stylesheet"); 220 | inject.setAttribute("href", "printmode.css"); 221 | document.head.appendChild(inject); 222 | actionAll('open'); 223 | document.querySelectorAll("*").forEach(el => { 224 | el.onclick = null; 225 | }); 226 | }; 227 | 228 | ill.changeHash = (hash) => { 229 | let href = window.location.href.replace(/#.*/, ""); 230 | if (hash) { 231 | window.history.replaceState({}, "", `${href}#${hash}`); 232 | } else { 233 | window.history.replaceState({}, "", `${href}`); 234 | } 235 | }; 236 | 237 | 238 | window.onload = () => { 239 | document.querySelectorAll(".record, .calculation").forEach(el => { 240 | ill.addAnchors(el); 241 | el.onclick = (event) => { 242 | if (!el.classList.contains("selected") || (el === event.target && event.offsetY < 60)) { 243 | ill.toggleRecord(el, event); 244 | } 245 | }; 246 | }); 247 | document.querySelectorAll(".rec-label").forEach(el => { 248 | el.onclick = (event) => { 249 | ill.toggleRecord(el.parentNode, event); 250 | }; 251 | }); 252 | document.querySelectorAll("button.hp-toggle").forEach(el => { 253 | el.dataset.enclbl = "Encrypt record numbers"; 254 | el.dataset.declbl = "Decrypt record numbers"; 255 | el.innerText = el.dataset.declbl; 256 | el.onclick = (event) => { 257 | ill.toggleHeaderProtection(); 258 | ill.cancel(event); 259 | }; 260 | }); 261 | document.querySelectorAll(".record").forEach(el => { 262 | ill.addToggleAnnotations(el); 263 | }); 264 | document.querySelectorAll("codesample").forEach(el => { 265 | ill.addShowCode(el); 266 | el.addEventListener("click", (event) => { ill.showCode(el, event); }); 267 | }); 268 | document.querySelectorAll(".bytes.protected").forEach(el => { 269 | el.setAttribute("title", "encrypted header number"); 270 | }); 271 | ill.resolveHash(); 272 | ill.injectLabels(); 273 | }; 274 | 275 | window.onkeyup = (e) => { 276 | let els; 277 | if (e.key === "Escape" || e.key === "Esc") { 278 | els = document.querySelectorAll(".record.annotate"); 279 | if (els.length) { 280 | els.forEach(rec => { ill.toggleAnnotate(rec, e); }); 281 | } else { 282 | ill.unselectAllRecords(); 283 | ill.changeHash(""); 284 | } 285 | } 286 | }; 287 | 288 | window.ill = ill; 289 | })(); 290 | -------------------------------------------------------------------------------- /site/images/key1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/syncsynchalt/illustrated-dtls/cb49c285131efd9e2d4252806420bda936134a48/site/images/key1.png -------------------------------------------------------------------------------- /site/images/key2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/syncsynchalt/illustrated-dtls/cb49c285131efd9e2d4252806420bda936134a48/site/images/key2.png -------------------------------------------------------------------------------- /site/images/key3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/syncsynchalt/illustrated-dtls/cb49c285131efd9e2d4252806420bda936134a48/site/images/key3.png -------------------------------------------------------------------------------- /site/images/key4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/syncsynchalt/illustrated-dtls/cb49c285131efd9e2d4252806420bda936134a48/site/images/key4.png -------------------------------------------------------------------------------- /site/images/key5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/syncsynchalt/illustrated-dtls/cb49c285131efd9e2d4252806420bda936134a48/site/images/key5.png -------------------------------------------------------------------------------- /site/images/key6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/syncsynchalt/illustrated-dtls/cb49c285131efd9e2d4252806420bda936134a48/site/images/key6.png -------------------------------------------------------------------------------- /site/images/key7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/syncsynchalt/illustrated-dtls/cb49c285131efd9e2d4252806420bda936134a48/site/images/key7.png -------------------------------------------------------------------------------- /site/images/key8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/syncsynchalt/illustrated-dtls/cb49c285131efd9e2d4252806420bda936134a48/site/images/key8.png -------------------------------------------------------------------------------- /site/images/key9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/syncsynchalt/illustrated-dtls/cb49c285131efd9e2d4252806420bda936134a48/site/images/key9.png -------------------------------------------------------------------------------- /site/images/og.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/syncsynchalt/illustrated-dtls/cb49c285131efd9e2d4252806420bda936134a48/site/images/og.png -------------------------------------------------------------------------------- /site/printmode.css: -------------------------------------------------------------------------------- 1 | /* print mode */ 2 | 3 | @media (min-width: 0) { 4 | .container { 5 | max-width: none !important; 6 | margin: 5px 0 !important; 7 | } 8 | } 9 | 10 | *, *:hover { 11 | color: #000 !important; 12 | background-color: #fff !important; 13 | text-shadow: none !important; 14 | } 15 | 16 | .illustration { 17 | display: none !important; 18 | } 19 | 20 | button { 21 | display: none !important; 22 | } 23 | 24 | .client, .server { 25 | background-color: #fff !important; 26 | border: 1px solid black !important; 27 | box-shadow: none !important; 28 | max-width: none !important; 29 | margin: 1em 0; 30 | } 31 | 32 | .rec-label:after { 33 | content: "" !important; 34 | } 35 | 36 | .string > .explanation, .decryption > .explanation { 37 | background-color: #fff !important; 38 | box-shadow: none !important; 39 | border: 2px solid black !important; 40 | } 41 | 42 | .record.annotate .string > .explanation:before { 43 | display: none; 44 | } 45 | 46 | pre code { 47 | border-radius: 0 !important; 48 | border: 2px solid black !important; 49 | white-space: pre-wrap !important; 50 | } 51 | 52 | a:after { 53 | content: " [link]"; 54 | } 55 | 56 | a.no-show:after { 57 | content: ""; 58 | } 59 | 60 | .print-mode { 61 | display: none; 62 | } 63 | 64 | .header { 65 | display: none; 66 | } 67 | -------------------------------------------------------------------------------- /wolfssl/Makefile: -------------------------------------------------------------------------------- 1 | all: lib 2 | 3 | wolfssl/autogen.sh: 4 | rm -rf wolfssl/ 5 | mkdir wolfssl 6 | cd wolfssl && git init 7 | cd wolfssl && git remote add origin git@github.com:wolfSSL/wolfssl.git 8 | cd wolfssl && git fetch --depth 1 origin e8e35c9a9226b6952745a7609d12d0cacda03bbf 9 | cd wolfssl && git checkout FETCH_HEAD 10 | 11 | wolfssl/stamp.patched: wolfssl/autogen.sh 12 | cd wolfssl && patch -p1 < ../instruments.patch 13 | touch $@ 14 | 15 | wolfssl/configure: wolfssl/stamp.patched 16 | cd wolfssl && ./autogen.sh 17 | 18 | wolfssl/Makefile: wolfssl/configure 19 | cd wolfssl && ./configure --prefix=`cd .. && pwd` \ 20 | --disable-shared --enable-static --enable-debug \ 21 | --enable-sni --enable-dtls --enable-dtls13 --enable-curve25519 \ 22 | CFLAGS="-DHAVE_SECRET_CALLBACK" 23 | 24 | lib: wolfssl/Makefile 25 | cd wolfssl && make 26 | cd wolfssl && make install 27 | 28 | clean: 29 | rm -rf wolfssl include lib bin share 30 | 31 | rebuild: lib 32 | 33 | build: wolfssl/stamp.built 34 | 35 | checkout: wolfssl/autogen.sh 36 | 37 | patch: wolfssl/stamp.patched 38 | 39 | reconfig: 40 | rm -f wolfssl/Makefile 41 | $(MAKE) lib 42 | 43 | .PHONY: all link clean lib 44 | -------------------------------------------------------------------------------- /wolfssl/instruments.patch: -------------------------------------------------------------------------------- 1 | diff --git a/.gitignore b/.gitignore 2 | index c42f7c3..6ce8df1 100644 3 | --- a/.gitignore 4 | +++ b/.gitignore 5 | @@ -390,3 +390,4 @@ cmake_install.cmake 6 | # GDB Settings 7 | \.gdbinit 8 | 9 | +/stamp.* 10 | diff --git a/src/ssl.c b/src/ssl.c 11 | index 202577d..1d25a61 100644 12 | --- a/src/ssl.c 13 | +++ b/src/ssl.c 14 | @@ -11728,8 +11728,13 @@ int wolfSSL_DTLS_SetCookieSecret(WOLFSSL* ssl, 15 | 16 | /* If the supplied secret is NULL, randomly generate a new secret. */ 17 | if (secret == NULL) { 18 | +#if 0 19 | ret = wc_RNG_GenerateBlock(ssl->rng, 20 | ssl->buffers.dtlsCookieSecret.buffer, secretSz); 21 | +#else 22 | + for (size_t i = 0; i < secretSz; i++) ssl->buffers.dtlsCookieSecret.buffer[i] = 'A' + i; 23 | + ret = 0; 24 | +#endif 25 | } 26 | else 27 | XMEMCPY(ssl->buffers.dtlsCookieSecret.buffer, secret, secretSz); 28 | @@ -38064,6 +38069,7 @@ int wolfSSL_RAND_pseudo_bytes(unsigned char* buf, int num) 29 | int hash; 30 | byte secret[DRBG_SEED_LEN]; /* secret length arbitraily choosen */ 31 | 32 | +abort(); 33 | #ifndef WOLFSSL_NO_OPENSSL_RAND_CB 34 | if (wolfSSL_RAND_InitMutex() == 0 && wc_LockMutex(&gRandMethodMutex) == 0) { 35 | if (gRandMethods && gRandMethods->pseudorand) { 36 | @@ -38129,6 +38135,7 @@ int wolfSSL_RAND_bytes(unsigned char* buf, int num) 37 | /* return code compliant with OpenSSL */ 38 | return 0; 39 | 40 | +abort(); 41 | /* if a RAND callback has been set try and use it */ 42 | #ifndef WOLFSSL_NO_OPENSSL_RAND_CB 43 | if (wolfSSL_RAND_InitMutex() == 0 && wc_LockMutex(&gRandMethodMutex) == 0) { 44 | diff --git a/src/tls13.c b/src/tls13.c 45 | index 15936fa..6f40fba 100644 46 | --- a/src/tls13.c 47 | +++ b/src/tls13.c 48 | @@ -3428,9 +3428,13 @@ int SendTls13ClientHello(WOLFSSL* ssl) 49 | } 50 | /* Client Random */ 51 | if (ssl->options.connectState == CONNECT_BEGIN) { 52 | +#if 0 53 | ret = wc_RNG_GenerateBlock(ssl->rng, args->output + args->idx, RAN_LEN); 54 | if (ret != 0) 55 | return ret; 56 | +#else 57 | + for (size_t i = 0; i < RAN_LEN; i++) *(args->output + args->idx + i) = 0xe0 + i; 58 | +#endif 59 | 60 | /* Store random for possible second ClientHello. */ 61 | XMEMCPY(ssl->arrays->clientRandom, args->output + args->idx, RAN_LEN); 62 | @@ -5455,8 +5459,12 @@ int SendTls13ServerHello(WOLFSSL* ssl, byte extMsgType) 63 | 64 | if (extMsgType == server_hello) { 65 | /* Generate server random. */ 66 | +#if 0 67 | if ((ret = wc_RNG_GenerateBlock(ssl->rng, output + idx, RAN_LEN)) != 0) 68 | return ret; 69 | +#else 70 | + for (size_t i = 0; i < RAN_LEN; i++) *(output + idx + i) = 0x70 + i; 71 | +#endif 72 | } 73 | else { 74 | /* HelloRetryRequest message has fixed value for random. */ 75 | diff --git a/wolfcrypt/src/curve25519.c b/wolfcrypt/src/curve25519.c 76 | index 9f64234..29dcf2c 100644 77 | --- a/wolfcrypt/src/curve25519.c 78 | +++ b/wolfcrypt/src/curve25519.c 79 | @@ -200,7 +200,16 @@ int wc_curve25519_make_priv(WC_RNG* rng, int keysize, byte* key) 80 | return ECC_BAD_ARG_E; 81 | 82 | /* random number for private key */ 83 | +#if 0 84 | ret = wc_RNG_GenerateBlock(rng, key, keysize); 85 | +#else 86 | + if (getenv("SERVER") && strcmp(getenv("SERVER"), "1") == 0) { 87 | + for (size_t i = 0; i < 32; i++) key[i] = 0x90 + i; 88 | + } else { 89 | + for (size_t i = 0; i < 32; i++) key[i] = 0x20 + i; 90 | + } 91 | + ret = 0; 92 | +#endif 93 | if (ret == 0) { 94 | /* Clamp the private key */ 95 | ret = curve25519_priv_clamp(key); 96 | diff --git a/wolfcrypt/src/random.c b/wolfcrypt/src/random.c 97 | index 03f5167..1506e23 100644 98 | --- a/wolfcrypt/src/random.c 99 | +++ b/wolfcrypt/src/random.c 100 | @@ -78,12 +78,14 @@ int wc_InitRng(WC_RNG* rng) 101 | 102 | int wc_RNG_GenerateBlock(WC_RNG* rng, byte* b, word32 sz) 103 | { 104 | +// abort(); 105 | return RNG_GenerateBlock_fips(rng, b, sz); 106 | } 107 | 108 | 109 | int wc_RNG_GenerateByte(WC_RNG* rng, byte* b) 110 | { 111 | +// abort(); 112 | return RNG_GenerateByte(rng, b); 113 | } 114 | 115 | @@ -981,6 +983,7 @@ int wc_InitRngNonce_ex(WC_RNG* rng, byte* nonce, word32 nonceSz, 116 | WOLFSSL_ABI 117 | int wc_RNG_GenerateBlock(WC_RNG* rng, byte* output, word32 sz) 118 | { 119 | +// abort(); 120 | int ret; 121 | 122 | if (rng == NULL || output == NULL) 123 | @@ -1081,6 +1084,7 @@ int wc_RNG_GenerateBlock(WC_RNG* rng, byte* output, word32 sz) 124 | 125 | int wc_RNG_GenerateByte(WC_RNG* rng, byte* b) 126 | { 127 | +// abort(); 128 | return wc_RNG_GenerateBlock(rng, b, 1); 129 | } 130 | 131 | @@ -2880,6 +2884,7 @@ int wc_GenerateSeed(OS_Seed* os, byte* output, word32 sz) 132 | #include 133 | int wc_hwrng_generate_block(byte *output, word32 sz) 134 | { 135 | +// abort(); 136 | int fd; 137 | int len; 138 | int ret = 0; 139 | diff --git a/wolfcrypt/src/rsa.c b/wolfcrypt/src/rsa.c 140 | index 3e626b0..6396d45 100644 141 | --- a/wolfcrypt/src/rsa.c 142 | +++ b/wolfcrypt/src/rsa.c 143 | @@ -1376,7 +1376,12 @@ static int RsaPad_PSS(const byte* input, word32 inputLen, byte* pkcsBlock, 144 | m += inputLen; 145 | o = 0; 146 | if (saltLen > 0) { 147 | +#if 0 148 | ret = wc_RNG_GenerateBlock(rng, salt, saltLen); 149 | +#else 150 | + if (rng) for (ssize_t ii = 0; ii < saltLen; ii++) salt[ii] = 0x12; 151 | + ret = 0; 152 | +#endif 153 | if (ret == 0) { 154 | XMEMCPY(m, salt, saltLen); 155 | m += saltLen; 156 | diff --git a/wolfcrypt/src/wolfmath.c b/wolfcrypt/src/wolfmath.c 157 | index d6772ea..0a06b9e 100644 158 | --- a/wolfcrypt/src/wolfmath.c 159 | +++ b/wolfcrypt/src/wolfmath.c 160 | @@ -178,7 +178,12 @@ int mp_rand(mp_int* a, int digits, WC_RNG* rng) 161 | #endif 162 | /* fill the data with random bytes */ 163 | if (ret == MP_OKAY) { 164 | +#if 0 165 | ret = wc_RNG_GenerateBlock(rng, (byte*)a->dp, cnt); 166 | +#else 167 | + for (int ii = 0; ii < cnt; ii++) ((byte*)a->dp)[ii] = 0x13; 168 | + ret = 0; 169 | +#endif 170 | } 171 | if (ret == MP_OKAY) { 172 | #if !defined(USE_FAST_MATH) && !defined(WOLFSSL_SP_MATH) 173 | --------------------------------------------------------------------------------