├── .babelrc ├── .editorconfig ├── .eslintignore ├── .github └── FUNDING.yml ├── .gitignore ├── .travis.yml ├── .vscode └── launch.json ├── CHANGELOG.md ├── LICENSE ├── example.js ├── fixtures └── terminalizer │ ├── dtls.yml │ ├── gnutls.yml │ ├── render1533622385765.gif │ └── render1533622791504.gif ├── package-lock.json ├── package.json ├── readme.md ├── src ├── index.js └── node_modules │ ├── cipher │ ├── abstract.js │ ├── aead.js │ ├── chacha20-poly1305.js │ ├── create.js │ ├── key-exchange.js │ ├── null.js │ └── utils.js │ ├── filter │ ├── decoder.js │ ├── defragmentation.js │ └── reordering.js │ ├── lib │ ├── constants.js │ ├── cookie-manager.js │ ├── protocol.js │ ├── retransmitter.js │ ├── sender.js │ ├── server.js │ ├── sliding-window.js │ └── socket.js │ ├── protocol │ ├── client │ │ ├── handlers.js │ │ └── index.js │ ├── flow.js │ └── packet.js │ ├── session │ ├── abstract.js │ ├── client.js │ └── utils.js │ └── utils │ ├── cipher-suite.js │ └── debug.js └── test ├── integrated └── test.js └── unit └── filter └── reordering.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "test": { 4 | "plugins": [ 5 | ["istanbul", { 6 | "exclude": [ 7 | "!**/node_modules/**", 8 | "**/test/**" 9 | ] 10 | }] 11 | ] 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # http://editorconfig.org 2 | root = true 3 | 4 | 5 | [*] 6 | indent_size = 2 7 | end_of_line = lf 8 | charset = utf-8 9 | trim_trailing_whitespace = true 10 | insert_final_newline = true 11 | 12 | [Makefile] 13 | indent_style = tab 14 | 15 | [*.js] 16 | indent_style = space 17 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | /node_modules 2 | /fixtures 3 | /.jest-cache 4 | /coverage 5 | 6 | # IDE 7 | .idea 8 | /.vscode 9 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | custom: ['https://www.buymeacoffee.com/reklatsmasters'] 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | 6 | # testing 7 | /coverage 8 | 9 | # production 10 | /build 11 | 12 | # misc 13 | .DS_Store 14 | npm-debug.log* 15 | yarn-debug.log* 16 | yarn-error.log* 17 | .idea 18 | *.txt 19 | /.jest-cache 20 | .vscode 21 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "10" 4 | - "8" 5 | 6 | services: 7 | - docker 8 | 9 | env: 10 | global: 11 | - DEBUG=dtls,dtls:* 12 | - PRIORITY=NORMAL:+AEAD:+ECDHE-ECDSA:+ECDHE-RSA:+RSA:+PSK:+ECDHE-PSK:+VERS-DTLS1.2 13 | matrix: 14 | - CIPHER=TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 KEYFILE=key-rsa.pem CERTFILE=cert-rsa.pem 15 | - CIPHER=TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 KEYFILE=key-rsa.pem CERTFILE=cert-rsa.pem 16 | - CIPHER=TLS_RSA_WITH_AES_128_GCM_SHA256 KEYFILE=key-rsa.pem CERTFILE=cert-rsa.pem 17 | - CIPHER=TLS_RSA_WITH_AES_256_GCM_SHA384 KEYFILE=key-rsa.pem CERTFILE=cert-rsa.pem 18 | - CIPHER=TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256 KEYFILE=key-ecdsa.pem CERTFILE=cert-ecdsa.pem 19 | - CIPHER=TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384 KEYFILE=key-ecdsa.pem CERTFILE=cert-ecdsa.pem 20 | - CIPHER=TLS_PSK_WITH_AES_128_GCM_SHA256 KEYFILE=key-ecdsa.pem CERTFILE=cert-ecdsa.pem 21 | - CIPHER=TLS_PSK_WITH_AES_256_GCM_SHA384 KEYFILE=key-ecdsa.pem CERTFILE=cert-ecdsa.pem 22 | 23 | matrix: 24 | include: 25 | - node_js: "11" 26 | env: CIPHER=TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256 KEYFILE=key-rsa.pem CERTFILE=cert-rsa.pem 27 | - node_js: "11" 28 | env: CIPHER=TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256 KEYFILE=key-ecdsa.pem CERTFILE=cert-ecdsa.pem 29 | - node_js: "11" 30 | env: CIPHER=TLS_PSK_WITH_CHACHA20_POLY1305_SHA256 KEYFILE=key-ecdsa.pem CERTFILE=cert-ecdsa.pem 31 | - node_js: "11" 32 | env: CIPHER=TLS_ECDHE_PSK_WITH_CHACHA20_POLY1305_SHA256 KEYFILE=key-ecdsa.pem CERTFILE=cert-ecdsa.pem 33 | 34 | before_script: 35 | - docker run -d --name dtlsd -p 4444:4444/udp -e "GNUTLS_DEBUG_LEVEL=2" -e "KEYFILE=$KEYFILE" -e "CERTFILE=$CERTFILE" -e "PRIORITY=$PRIORITY" nodertc/dtls-server:1 36 | 37 | script: 38 | - npm test 39 | - npm run integrated-test 40 | 41 | after_script: 42 | - docker logs dtlsd 43 | - docker stop dtlsd 44 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Используйте IntelliSense, чтобы узнать о возможных атрибутах. 3 | // Наведите указатель мыши, чтобы просмотреть описания существующих атрибутов. 4 | // Для получения дополнительной информации посетите: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "type": "node", 9 | "request": "launch", 10 | "name": "Debug DTLS", 11 | "program": "${workspaceFolder}/start.js" 12 | }, 13 | { 14 | "type": "node", 15 | "request": "launch", 16 | "name": "Debug DTLS via NPM", 17 | "runtimeExecutable": "npm", 18 | "runtimeArgs": [ 19 | "start" 20 | ], 21 | "port": 9229 22 | } 23 | ] 24 | } 25 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | All notable changes to the "dtls" package will be documented in this file. 3 | 4 | ## [0.6.0] - 2019-05-05 5 | - Update binary-data@0.6.0 6 | - Added PSK key exchange, [RFC4279](https://tools.ietf.org/html/rfc4279), [RFC5487](https://tools.ietf.org/html/rfc5487). Ciphers: 7 | * TLS_PSK_WITH_AES_128_GCM_SHA256 8 | * TLS_PSK_WITH_AES_256_GCM_SHA384 9 | - Added CHACHA20-POLY1305 ciphers: 10 | * TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256 (_nodejs v11+ only_) 11 | * TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256 (_nodejs v11+ only_) 12 | * TLS_PSK_WITH_CHACHA20_POLY1305_SHA256 (_nodejs v11+ only_) 13 | - Users may change ciphers list sent to the server using `options.cipherSuites` 14 | - Security fixes 15 | - Added ECDHE_PSK key exchange [#16](https://github.com/nodertc/dtls/issues/16). Ciphers: 16 | * TLS_ECDHE_PSK_WITH_AES_128_GCM_SHA256 17 | * TLS_ECDHE_PSK_WITH_AES_256_GCM_SHA384 18 | * TLS_ECDHE_PSK_WITH_CHACHA20_POLY1305_SHA256 19 | 20 | ## [0.5.0] - 2018-11-17 21 | - Drop AES-CCM block cipher 22 | - Added `timeout` event to detect inactive connections 23 | - Added `ALPN` ([RFC 7301](https://tools.ietf.org/html/rfc7301)) extension 24 | - Update dependencies 25 | - Bug fixes 26 | 27 | ## [0.4.0] - 2018-09-10 28 | - Added ECDHE_ECDSA key exchange. Ciphers: 29 | * TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256 30 | * TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384 31 | * TLS_ECDHE_ECDSA_WITH_AES_128_CCM 32 | * TLS_ECDHE_ECDSA_WITH_AES_256_CCM 33 | - Support for message reordering. 34 | - Added ability to verify server (incoming) certificate. 35 | - Added support for client-side certificate. 36 | - [bug] retransmitted messages don't ignore it's epoch. 37 | 38 | ## [0.3.0] - 2018-08-22 39 | - Added [AES CCM](https://tools.ietf.org/html/rfc6655) cipers 40 | * TLS_RSA_WITH_AES_128_CCM 41 | * TLS_RSA_WITH_AES_256_CCM 42 | - Added [Extended Master Secret](https://tools.ietf.org/html/rfc7627) tls extension 43 | - Added [ECDHE_RSA](https://tools.ietf.org/html/draft-ietf-tls-rfc4492bis-17#section-2.2) key exchange. 44 | * TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 45 | * TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 46 | 47 | ## [0.2.0] - 2018-08-11 48 | - support set / get MTU, 1200 bytes by default. 49 | - handshake retransmission, follow RFC's rules. 50 | - merge outgoing handshakes to speed up handshake process. 51 | 52 | ## [0.1.0] - 2018-08-05 53 | - First release. Client-side implementation only with limited ciphers: 54 | * TLS_RSA_WITH_AES_128_GCM_SHA256 55 | * TLS_RSA_WITH_AES_256_GCM_SHA384 56 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Dmitriy Tsvettsikh 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 | -------------------------------------------------------------------------------- /example.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const dgram = require('dgram'); 4 | const dtls = require('.'); 5 | 6 | const serverSocket = dgram.createSocket('udp4'); 7 | serverSocket.bind(4444); 8 | 9 | const socket = dtls.connect({ 10 | type: 'udp4', 11 | remotePort: 4444, 12 | remoteAddress: '127.0.0.1', 13 | maxHandshakeRetransmissions: 4, 14 | }); 15 | 16 | socket.on('error', err => { 17 | console.error('client:', err); 18 | }); 19 | 20 | socket.on('data', data => { 21 | console.log('client: got message "%s"', data.toString('ascii')); 22 | socket.close(); 23 | }); 24 | 25 | socket.once('connect', () => { 26 | socket.write('Hello from Node.js!'); 27 | }); 28 | 29 | socket.once('timeout', () => { 30 | console.log('client: got timeout'); 31 | }); 32 | 33 | socket.setTimeout(5e3); 34 | 35 | const server = dtls.createServer({ socket: serverSocket }); 36 | 37 | server.on('error', err => { 38 | console.error('server:', err); 39 | }); 40 | 41 | server.on('data', data => { 42 | console.log('server: got message "%s"', data.toString('ascii')); 43 | server.write(data, () => server.close()); 44 | }); 45 | 46 | socket.once('connect', () => { 47 | console.log('server: got connecton'); 48 | }); 49 | -------------------------------------------------------------------------------- /fixtures/terminalizer/dtls.yml: -------------------------------------------------------------------------------- 1 | # The configurations that used for the recording, feel free to edit them 2 | config: 3 | 4 | # Specify a command to be executed 5 | # like `/bin/bash -l`, `ls`, or any other commands 6 | # the default is bash for Linux 7 | # or powershell.exe for Windows 8 | command: bash -l 9 | 10 | # Specify the current working directory path 11 | # the default is the current working directory path 12 | cwd: /home/dtsvet/develop/projects/dtls 13 | 14 | # Export additional ENV variables 15 | env: 16 | recording: true 17 | 18 | # Explicitly set the number of columns 19 | # or use `auto` to take the current 20 | # number of columns of your shell 21 | cols: 141 22 | 23 | # Explicitly set the number of rows 24 | # or use `auto` to take the current 25 | # number of rows of your shell 26 | rows: 24 27 | 28 | # Amount of times to repeat GIF 29 | # If value is -1, play once 30 | # If value is 0, loop indefinitely 31 | # If value is a positive number, loop n times 32 | repeat: 0 33 | 34 | # Quality 35 | # 1 - 100 36 | quality: 100 37 | 38 | # Delay between frames in ms 39 | # If the value is `auto` use the actual recording delays 40 | frameDelay: auto 41 | 42 | # Maximum delay between frames in ms 43 | # Ignored if the `frameDelay` isn't set to `auto` 44 | # Set to `auto` to prevent limiting the max idle time 45 | maxIdleTime: 2000 46 | 47 | # The surrounding frame box 48 | # The `type` can be null, window, floating, or solid` 49 | # To hide the title use the value null 50 | # Don't forget to add a backgroundColor style with a null as type 51 | frameBox: 52 | type: floating 53 | title: Terminalizer 54 | style: 55 | border: 0px black solid 56 | # boxShadow: none 57 | # margin: 0px 58 | 59 | # Add a watermark image to the rendered gif 60 | # You need to specify an absolute path for 61 | # the image on your machine or a url, and you can also 62 | # add your own CSS styles 63 | watermark: 64 | imagePath: null 65 | style: 66 | position: absolute 67 | right: 15px 68 | bottom: 15px 69 | width: 100px 70 | opacity: 0.9 71 | 72 | # Cursor style can be one of 73 | # `block`, `underline`, or `bar` 74 | cursorStyle: block 75 | 76 | # Font family 77 | # You can use any font that is installed on your machine 78 | # in CSS-like syntax 79 | fontFamily: "Monaco, Lucida Console, Ubuntu Mono, Monospace" 80 | 81 | # The size of the font 82 | fontSize: 12 83 | 84 | # The height of lines 85 | lineHeight: 1 86 | 87 | # The spacing between letters 88 | letterSpacing: 0 89 | 90 | # Theme 91 | theme: 92 | background: "transparent" 93 | foreground: "#afafaf" 94 | cursor: "#c7c7c7" 95 | black: "#232628" 96 | red: "#fc4384" 97 | green: "#b3e33b" 98 | yellow: "#ffa727" 99 | blue: "#75dff2" 100 | magenta: "#ae89fe" 101 | cyan: "#708387" 102 | white: "#d5d5d0" 103 | brightBlack: "#626566" 104 | brightRed: "#ff7fac" 105 | brightGreen: "#c8ed71" 106 | brightYellow: "#ebdf86" 107 | brightBlue: "#75dff2" 108 | brightMagenta: "#ae89fe" 109 | brightCyan: "#b1c6ca" 110 | brightWhite: "#f9f9f4" 111 | 112 | # Records, feel free to edit them 113 | records: 114 | - delay: 263 115 | content: "\e]0;dtsvet@dtsvet:~/develop/projects/dtls\a\e]7;file://dtsvet/home/dtsvet/develop/projects/dtls\a\e]0;dtsvet@dtsvet: ~/develop/projects/dtls\a\e[01;32mdtsvet@dtsvet\e[00m:\e[01;34m~/develop/projects/dtls\e[00m$ " 116 | - delay: 275 117 | content: 'n' 118 | - delay: 121 119 | content: p 120 | - delay: 100 121 | content: m 122 | - delay: 103 123 | content: ' ' 124 | - delay: 527 125 | content: s 126 | - delay: 119 127 | content: t 128 | - delay: 123 129 | content: a 130 | - delay: 79 131 | content: r 132 | - delay: 180 133 | content: t 134 | - delay: 688 135 | content: "\r\n" 136 | - delay: 237 137 | content: "\r\n> @nodertc/dtls@0.1.0 start /home/dtsvet/develop/projects/dtls\r\n> DEBUG=dtls,dtls:* DEBUG_DEPTH=10 node example.js\r\n\r\n" 138 | - delay: 219 139 | content: " \e[34;1mdtls:session \e[0mstart handshake \e[34m+0ms\e[0m\r\n \e[32;1mdtls:protocol-reader \e[0mprepare client hello \e[32m+0ms\e[0m\r\n \e[35;1mdtls:sender \e[0msend Client Hello \e[35m+0ms\e[0m\r\n \e[34;1mdtls:session \e[0mpacket added to handshake queue, type = 1 \e[34m+6ms\e[0m\r\n \e[35;1mdtls:sender \e[0mencrypt, cipher = NULL \e[35m+2ms\e[0m\r\n \e[35;1mdtls:sender \e[0msuccess \e[35m+0ms\e[0m\r\n" 140 | - delay: 5 141 | content: " \e[33;1mdtls:socket \e[0mgot message, is dtls = true \e[33m+0ms\e[0m\r\n \e[31;1mdtls:decoder \e[0mprocess new chunk \e[31m+0ms\e[0m\r\n \e[31;1mdtls:decoder \e[0mdecoded 44 bytes \e[31m+1ms\e[0m\r\n \e[31;1mdtls:decoder \e[0mdecrypt record layer \e[31m+0ms\e[0m\r\n \e[31;1mdtls:decoder \e[0mdecryption success \e[31m+0ms\e[0m\r\n \e[33;1mdtls:reorder \e[0msuccess \e[33m+0ms\e[0m\r\n \e[32;1mdtls:fragment \e[0mgot full handshake \e[32m+0ms\e[0m\r\n \e[32;1mdtls:protocol-reader \e[0mgot hello verify request \e[32m+11ms\e[0m\r\n \e[34;1mdtls:session \e[0mreset handshake queue \e[34m+8ms\e[0m\r\n \e[32;1mdtls:protocol-reader \e[0mgot cookie d96e13835a38ef4e6e2bdfcd3a70ad9b \e[32m+0ms\e[0m\r\n \e[32;1mdtls:protocol-reader \e[0mprepare client hello \e[32m+0ms\e[0m\r\n \e[35;1mdtls:sender \e[0msend Client Hello \e[35m+8ms\e[0m\r\n \e[34;1mdtls:session \e[0mpacket added to handshake queue, type = 1 \e[34m+0ms\e[0m\r\n \e[35;1mdtls:sender \e[0mencrypt, cipher = NULL \e[35m+0ms\e[0m\r\n \e[35;1mdtls:sender \e[0msuccess \e[35m+0ms\e[0m\r\n" 142 | - delay: 53 143 | content: " \e[33;1mdtls:socket \e[0mgot message, is dtls = true \e[33m+56ms\e[0m\r\n \e[31;1mdtls:decoder \e[0mprocess new chunk \e[31m+55ms\e[0m\r\n \e[31;1mdtls:decoder \e[0mdecoded 95 bytes \e[31m+0ms\e[0m\r\n \e[31;1mdtls:decoder \e[0mdecrypt record layer \e[31m+0ms\e[0m\r\n \e[31;1mdtls:decoder \e[0mdecryption success \e[31m+0ms\e[0m\r\n \e[33;1mdtls:reorder \e[0msuccess \e[33m+56ms\e[0m\r\n \e[32;1mdtls:fragment \e[0mgot full handshake \e[32m+56ms\e[0m\r\n \e[32;1mdtls:protocol-reader \e[0mgot server hello \e[32m+56ms\e[0m\r\n \e[32;1mdtls:protocol-reader \e[0mserver selected TLS_RSA_WITH_AES_128_GCM_SHA256 cipher \e[32m+1ms\e[0m\r\n \e[34;1mdtls:session \e[0mpacket added to handshake queue, type = 2 \e[34m+58ms\e[0m\r\n \e[33;1mdtls:socket \e[0mgot message, is dtls = true \e[33m+5ms\e[0m\r\n \e[31;1mdtls:decoder \e[0mprocess new chunk \e[31m+5ms\e[0m\r\n \e[31;1mdtls:decoder \e[0mdecoded 860 bytes \e[31m+0ms\e[0m\r\n \e[31;1mdtls:decoder \e[0mdecrypt record layer \e[31m+0ms\e[0m\r\n \e[31;1mdtls:decoder \e[0mdecryption success \e[31m+0ms\e[0m\r\n \e[33;1mdtls:reorder \e[0msuccess \e[33m+4ms\e[0m\r\n \e[32;1mdtls:fragment \e[0mgot full handshake \e[32m+5ms\e[0m\r\n \e[32;1mdtls:protocol-reader \e[0mgot server certificate \e[32m+3ms\e[0m\r\n" 144 | - delay: 37 145 | content: " \e[34;1mdtls:session \e[0mpacket added to handshake queue, type = 11 \e[34m+39ms\e[0m\r\n \e[33;1mdtls:socket \e[0mgot message, is dtls = true \e[33m+39ms\e[0m\r\n \e[31;1mdtls:decoder \e[0mprocess new chunk \e[31m+38ms\e[0m\r\n \e[31;1mdtls:decoder \e[0mdecoded 25 bytes \e[31m+0ms\e[0m\r\n \e[31;1mdtls:decoder \e[0mdecrypt record layer \e[31m+0ms\e[0m\r\n \e[31;1mdtls:decoder \e[0mdecryption success \e[31m+0ms\e[0m\r\n \e[33;1mdtls:reorder \e[0msuccess \e[33m+39ms\e[0m\r\n \e[32;1mdtls:fragment \e[0mgot full handshake \e[32m+38ms\e[0m\r\n \e[32;1mdtls:protocol-reader \e[0mgot server hello done \e[32m+38ms\e[0m\r\n \e[34;1mdtls:session \e[0mpacket added to handshake queue, type = 14 \e[34m+1ms\e[0m\r\n \e[32;1mdtls:protocol-reader \e[0mprepare client key exchange \e[32m+0ms\e[0m\r\n \e[35;1mdtls:sender \e[0msend Client Key Exchange \e[35m+98ms\e[0m\r\n \e[34;1mdtls:session \e[0mpacket added to handshake queue, type = 16 \e[34m+1ms\e[0m\r\n \e[35;1mdtls:sender \e[0mencrypt, cipher = NULL \e[35m+1ms\e[0m\r\n \e[35;1mdtls:sender \e[0msuccess \e[35m+0ms\e[0m\r\n \e[32;1mdtls:protocol-reader \e[0mprepare change cipher spec \e[32m+1ms\e[0m\r\n \e[35;1mdtls:sender \e[0msend Change Cipher Spec \e[35m+1ms\e[0m\r\n \e[34;1mdtls:cipher:aead \e[0mclient iv \e[34m+0ms\e[0m\r\n \e[34;1mdtls:cipher:aead \e[0mserver iv \e[34m+1ms\e[0m\r\n \e[32;1mdtls:protocol-reader \e[0mprepare client finished \e[32m+3ms\e[0m\r\n \e[32;1mdtls:protocol-reader \e[0mclient finished 4c333ee2ceeb8520d753225c \e[32m+0ms\e[0m\r\n \e[35;1mdtls:sender \e[0msend Finished \e[35m+2ms\e[0m\r\n \e[34;1mdtls:session \e[0mpacket added to handshake queue, type = 20 \e[34m+3ms\e[0m\r\n \e[35;1mdtls:sender \e[0mencrypt, cipher = aes-128-gcm \e[35m+0ms\e[0m\r\n \e[35;1mdtls:sender \e[0msuccess \e[35m+1ms\e[0m\r\n" 146 | - delay: 52 147 | content: " \e[33;1mdtls:socket \e[0mgot message, is dtls = true \e[33m+58ms\e[0m\r\n \e[31;1mdtls:decoder \e[0mprocess new chunk \e[31m+59ms\e[0m\r\n \e[31;1mdtls:decoder \e[0mdecoded 14 bytes \e[31m+0ms\e[0m\r\n \e[33;1mdtls:reorder \e[0msuccess \e[33m+58ms\e[0m\r\n \e[32;1mdtls:protocol-reader \e[0mgot change cipher spec \e[32m+54ms\e[0m\r\n \e[33;1mdtls:socket \e[0mgot message, is dtls = true \e[33m+2ms\e[0m\r\n \e[31;1mdtls:decoder \e[0mprocess new chunk \e[31m+1ms\e[0m\r\n \e[31;1mdtls:decoder \e[0mdecoded 61 bytes \e[31m+0ms\e[0m\r\n \e[31;1mdtls:decoder \e[0mdecrypt record layer \e[31m+0ms\e[0m\r\n \e[31;1mdtls:decoder \e[0mdecryption success \e[31m+2ms\e[0m\r\n \e[33;1mdtls:reorder \e[0msuccess \e[33m+3ms\e[0m\r\n \e[32;1mdtls:fragment \e[0mgot full handshake \e[32m+61ms\e[0m\r\n \e[32;1mdtls:protocol-reader \e[0mgot finished \e[32m+4ms\e[0m\r\n \e[32;1mdtls:protocol-reader \e[0mserver finished c01deaab5f83c2e191bb0d55 \e[32m+0ms\e[0m\r\n \e[34;1mdtls:session \e[0mstop handshake \e[34m+58ms\e[0m\r\n \e[34;1mdtls:session \e[0mreset handshake queue \e[34m+0ms\e[0m\r\n \e[35;1mdtls:sender \e[0mencrypt, cipher = aes-128-gcm \e[35m+58ms\e[0m\r\n \e[35;1mdtls:sender \e[0msuccess \e[35m+1ms\e[0m\r\n \e[33;1mdtls:socket \e[0mgot message, is dtls = true \e[33m+6ms\e[0m\r\n \e[31;1mdtls:decoder \e[0mprocess new chunk \e[31m+5ms\e[0m\r\n \e[31;1mdtls:decoder \e[0mdecoded 56 bytes \e[31m+0ms\e[0m\r\n \e[31;1mdtls:decoder \e[0mdecrypt record layer \e[31m+0ms\e[0m\r\n \e[31;1mdtls:decoder \e[0mdecryption success \e[31m+1ms\e[0m\r\n \e[33;1mdtls:reorder \e[0msuccess \e[33m+6ms\e[0m\r\n \e[32;1mdtls:protocol-reader \e[0mgot application data \e[32m+5ms\e[0m\r\n \e[32;1mdtls:protocol-reader \e[0mpacket: 48656c6c6f2066726f6d204e6f64652e6a7321 \e[32m+0ms\e[0m\r\ngot message \"Hello from Node.js!\"\r\n" 148 | - delay: 37 149 | content: "\e]0;dtsvet@dtsvet:~/develop/projects/dtls\a\e]7;file://dtsvet/home/dtsvet/develop/projects/dtls\a\e]0;dtsvet@dtsvet: ~/develop/projects/dtls\a\e[01;32mdtsvet@dtsvet\e[00m:\e[01;34m~/develop/projects/dtls\e[00m$ " 150 | - delay: 1350 151 | content: "^C\r\n" 152 | - delay: 10 153 | content: "\e]0;dtsvet@dtsvet:~/develop/projects/dtls\a\e]7;file://dtsvet/home/dtsvet/develop/projects/dtls\a\e]0;dtsvet@dtsvet: ~/develop/projects/dtls\a\e[01;32mdtsvet@dtsvet\e[00m:\e[01;34m~/develop/projects/dtls\e[00m$ " 154 | -------------------------------------------------------------------------------- /fixtures/terminalizer/gnutls.yml: -------------------------------------------------------------------------------- 1 | # The configurations that used for the recording, feel free to edit them 2 | config: 3 | 4 | # Specify a command to be executed 5 | # like `/bin/bash -l`, `ls`, or any other commands 6 | # the default is bash for Linux 7 | # or powershell.exe for Windows 8 | command: bash -l 9 | 10 | # Specify the current working directory path 11 | # the default is the current working directory path 12 | cwd: /home/dtsvet/develop/projects/dtls 13 | 14 | # Export additional ENV variables 15 | env: 16 | recording: true 17 | 18 | # Explicitly set the number of columns 19 | # or use `auto` to take the current 20 | # number of columns of your shell 21 | cols: 114 22 | 23 | # Explicitly set the number of rows 24 | # or use `auto` to take the current 25 | # number of rows of your shell 26 | rows: 24 27 | 28 | # Amount of times to repeat GIF 29 | # If value is -1, play once 30 | # If value is 0, loop indefinitely 31 | # If value is a positive number, loop n times 32 | repeat: 0 33 | 34 | # Quality 35 | # 1 - 100 36 | quality: 100 37 | 38 | # Delay between frames in ms 39 | # If the value is `auto` use the actual recording delays 40 | frameDelay: auto 41 | 42 | # Maximum delay between frames in ms 43 | # Ignored if the `frameDelay` isn't set to `auto` 44 | # Set to `auto` to prevent limiting the max idle time 45 | maxIdleTime: 2000 46 | 47 | # The surrounding frame box 48 | # The `type` can be null, window, floating, or solid` 49 | # To hide the title use the value null 50 | # Don't forget to add a backgroundColor style with a null as type 51 | frameBox: 52 | type: floating 53 | title: Terminalizer 54 | style: 55 | border: 0px black solid 56 | # boxShadow: none 57 | # margin: 0px 58 | 59 | # Add a watermark image to the rendered gif 60 | # You need to specify an absolute path for 61 | # the image on your machine or a url, and you can also 62 | # add your own CSS styles 63 | watermark: 64 | imagePath: null 65 | style: 66 | position: absolute 67 | right: 15px 68 | bottom: 15px 69 | width: 100px 70 | opacity: 0.9 71 | 72 | # Cursor style can be one of 73 | # `block`, `underline`, or `bar` 74 | cursorStyle: block 75 | 76 | # Font family 77 | # You can use any font that is installed on your machine 78 | # in CSS-like syntax 79 | fontFamily: "Monaco, Lucida Console, Ubuntu Mono, Monospace" 80 | 81 | # The size of the font 82 | fontSize: 12 83 | 84 | # The height of lines 85 | lineHeight: 1 86 | 87 | # The spacing between letters 88 | letterSpacing: 0 89 | 90 | # Theme 91 | theme: 92 | background: "transparent" 93 | foreground: "#afafaf" 94 | cursor: "#c7c7c7" 95 | black: "#232628" 96 | red: "#fc4384" 97 | green: "#b3e33b" 98 | yellow: "#ffa727" 99 | blue: "#75dff2" 100 | magenta: "#ae89fe" 101 | cyan: "#708387" 102 | white: "#d5d5d0" 103 | brightBlack: "#626566" 104 | brightRed: "#ff7fac" 105 | brightGreen: "#c8ed71" 106 | brightYellow: "#ebdf86" 107 | brightBlue: "#75dff2" 108 | brightMagenta: "#ae89fe" 109 | brightCyan: "#b1c6ca" 110 | brightWhite: "#f9f9f4" 111 | 112 | # Records, feel free to edit them 113 | records: 114 | - delay: 289 115 | content: "\e]0;dtsvet@dtsvet:~/develop/projects/dtls\a\e]7;file://dtsvet/home/dtsvet/develop/projects/dtls\a\e]0;dtsvet@dtsvet: ~/develop/projects/dtls\a\e[01;32mdtsvet@dtsvet\e[00m:\e[01;34m~/develop/projects/dtls\e[00m$ " 116 | - delay: 1259 117 | content: 'n' 118 | - delay: 162 119 | content: p 120 | - delay: 160 121 | content: m 122 | - delay: 60 123 | content: ' ' 124 | - delay: 378 125 | content: r 126 | - delay: 366 127 | content: u 128 | - delay: 258 129 | content: 'n' 130 | - delay: 239 131 | content: ' ' 132 | - delay: 488 133 | content: g 134 | - delay: 209 135 | content: 'n' 136 | - delay: 271 137 | content: u 138 | - delay: 351 139 | content: 't' 140 | - delay: 209 141 | content: 'l' 142 | - delay: 271 143 | content: s 144 | - delay: 701 145 | content: '-' 146 | - delay: 295 147 | content: s 148 | - delay: 240 149 | content: e 150 | - delay: 100 151 | content: r 152 | - delay: 256 153 | content: v 154 | - delay: 118 155 | content: e 156 | - delay: 102 157 | content: r 158 | - delay: 1054 159 | content: "\r\n" 160 | - delay: 234 161 | content: "\r\n> @nodertc/dtls@0.1.0 gnutls-server /home/dtsvet/develop/projects/dtls\r\n> cd $PWD/fixtures && GNUTLS_DEBUG_LEVEL=100 ./dtls\r\n\r\n" 162 | - delay: 8 163 | content: "gnutls[2]: Enabled GnuTLS 3.6.2 logging...\r\ngnutls[2]: getrandom random generator was detected\r\ngnutls[2]: Intel SSSE3 was detected\r\ngnutls[2]: Intel AES accelerator was detected\r\ngnutls[2]: Intel GCM accelerator (AVX) was detected\r\ngnutls[2]: unable to access: /etc/gnutls/default-priorities: 2\r\n" 164 | - delay: 41 165 | content: "gnutls[3]: ASSERT: common.c[_gnutls_x509_get_raw_field2]:1558\r\ngnutls[3]: ASSERT: x509.c[gnutls_x509_crt_get_subject_unique_id]:3819\r\ngnutls[3]: ASSERT: x509.c[gnutls_x509_crt_get_issuer_unique_id]:3869\r\n" 166 | - delay: 5 167 | content: "gnutls[3]: ASSERT: attributes.c[_x509_parse_attribute]:103\r\ngnutls[3]: ASSERT: attributes.c[_x509_parse_attribute]:174\r\ngnutls[3]: ASSERT: x509_ext.c[gnutls_subject_alt_names_get]:110\r\ngnutls[3]: ASSERT: x509.c[get_alt_name]:1811\r\ngnutls[2]: added 53 ciphersuites, 11 sig algos and 8 groups into priority list\r\nUDP server ready. Listening to port '4444'.\r\n\r\nWaiting for connection...\r\n" 168 | - delay: 6975 169 | content: "gnutls[3]: ASSERT: dtls.c[gnutls_dtls_cookie_verify]:988\r\nSending hello verify request to IPv4 127.0.0.1 port 47079\r\nWaiting for connection...\r\nAccepted connection from IPv4 127.0.0.1 port 47079\r\ngnutls[5]: REC[0x14be4e0]: Allocating epoch #0\r\ngnutls[5]: REC[0x14be4e0]: Allocating epoch #1\r\n" 170 | - delay: 50 171 | content: "gnutls[3]: ASSERT: buffers.c[get_last_packet]:1161\r\ngnutls[10]: READ: Got 85 bytes from 0x7fffb8fbefc0\r\ngnutls[10]: READ: read 85 bytes from 0x7fffb8fbefc0\r\ngnutls[10]: RB: Have 0 bytes into buffer. Adding 85 bytes.\r\ngnutls[10]: RB: Requested 13 bytes\r\ngnutls[5]: REC[0x14be4e0]: SSL 254.253 Handshake packet received. Epoch 0, length: 72\r\ngnutls[5]: REC[0x14be4e0]: Expected Packet Handshake(22)\r\ngnutls[5]: REC[0x14be4e0]: Received Packet Handshake(22) with length: 72\r\ngnutls[5]: REC[0x14be4e0]: Decrypted Packet[0.2] Handshake(22) with length: 72\r\ngnutls[13]: BUF[REC]: Inserted 72 bytes of Data(22)\r\ngnutls[4]: HSK[0x14be4e0]: CLIENT HELLO (1) was received. Length 60[60], frag offset 0, frag length: 60, sequence: 1\r\ngnutls[4]: HSK[0x14be4e0]: Client's version: 254.253\r\ngnutls[4]: HSK[0x14be4e0]: Selected version DTLS1.2\r\ngnutls[3]: ASSERT: db.c[_gnutls_server_restore_session]:276\r\ngnutls[2]: checking 00.9c (GNUTLS_RSA_AES_128_GCM_SHA256) for compatibility\r\ngnutls[3]: ASSERT: server_name.c[gnutls_server_name_get]:259\r\ngnutls[4]: HSK[0x14be4e0]: Requested server name: ''\r\ngnutls[4]: HSK[0x14be4e0]: checking compat of GNUTLS_RSA_AES_128_GCM_SHA256 with certificate[0] (RSA/X.509)\r\ngnutls[2]: Selected (RSA) cert based on ciphersuite 0.9c: GNUTLS_RSA_AES_128_GCM_SHA256\r\ngnutls[4]: HSK[0x14be4e0]: Selected cipher suite: GNUTLS_RSA_AES_128_GCM_SHA256\r\ngnutls[4]: HSK[0x14be4e0]: Allowing unsafe initial negotiation\r\ngnutls[4]: HSK[0x14be4e0]: SessionID: d468d846cc9f369cf8aef1233e39783905f17dbc69eb424875a3374f471c9c86\r\ngnutls[4]: HSK[0x14be4e0]: SERVER HELLO was queued [82 bytes]\r\ngnutls[11]: HWRITE: enqueued [SERVER HELLO] 82. Total 82 bytes.\r\ngnutls[4]: HSK[0x14be4e0]: CERTIFICATE was queued [847 bytes]\r\ngnutls[11]: HWRITE: enqueued [CERTIFICATE] 847. Total 929 bytes.\r\ngnutls[4]: HSK[0x14be4e0]: SERVER HELLO DONE was queued [12 bytes]\r\ngnutls[11]: HWRITE: enqueued [SERVER HELLO DONE] 12. Total 941 bytes.\r\ngnutls[11]: HWRITE FLUSH: 941 bytes in buffer.\r\ngnutls[6]: DTLS[0x14be4e0]: Start of flight transmission.\r\ngnutls[6]: DTLS[0x14be4e0]: Sending Packet[1] fragment SERVER HELLO(2) with length: 70, offset: 0, fragment length: 70, mtu: 1375\r\ngnutls[5]: REC[0x14be4e0]: Preparing Packet Handshake(22) with length: 82 and min pad: 0\r\ngnutls[9]: ENC[0x14be4e0]: cipher: NULL, MAC: MAC-NULL, Epoch: 0\r\ngnutls[11]: WRITE: enqueued 95 bytes for 0x7fffb8fbefc0. Total 95 bytes.\r\ngnutls[5]: REC[0x14be4e0]: Sent Packet[3] Handshake(22) in epoch 0 and length: 95\r\ngnutls[6]: DTLS[0x14be4e0]: Sending Packet[2] fragment CERTIFICATE(11) with length: 835, offset: 0, fragment length: 835, mtu: 1375\r\ngnutls[5]: REC[0x14be4e0]: Preparing Packet Handshake(22) with length: 847 and min pad: 0\r\ngnutls[9]: ENC[0x14be4e0]: cipher: NULL, MAC: MAC-NULL, Epoch: 0\r\ngnutls[11]: WRITE: enqueued 860 bytes for 0x7fffb8fbefc0. Total 955 bytes.\r\ngnutls[5]: REC[0x14be4e0]: Sent Packet[4] Handshake(22) in epoch 0 and length: 860\r\ngnutls[6]: DTLS[0x14be4e0]: Sending Packet[3] fragment SERVER HELLO DONE(14) with length: 0, offset: 0, fragment length: 0, mtu: 1375\r\ngnutls[5]: REC[0x14be4e0]: Preparing Packet Handshake(22) with length: 12 and min pad: 0\r\ngnutls[9]: ENC[0x14be4e0]: cipher: NULL, MAC: MAC-NULL, Epoch: 0\r\ngnutls[11]: WRITE: enqueued 25 bytes for 0x7fffb8fbefc0. Total 980 bytes.\r\ngnutls[5]: REC[0x14be4e0]: Sent Packet[5] Handshake(22) in epoch 0 and length: 25\r\ngnutls[11]: WRITE FLUSH: 980 bytes in buffer.\r\ngnutls[11]: WRITE: wrote 980 bytes, 0 bytes left.\r\n" 172 | - delay: 50 173 | content: "gnutls[10]: READ: Got 283 bytes from 0x7fffb8fbefc0\r\ngnutls[10]: READ: read 283 bytes from 0x7fffb8fbefc0\r\ngnutls[10]: RB: Have 0 bytes into buffer. Adding 283 bytes.\r\ngnutls[10]: RB: Requested 13 bytes\r\ngnutls[5]: REC[0x14be4e0]: SSL 254.253 Handshake packet received. Epoch 0, length: 270\r\ngnutls[5]: REC[0x14be4e0]: Expected Packet Handshake(22)\r\ngnutls[5]: REC[0x14be4e0]: Received Packet Handshake(22) with length: 270\r\ngnutls[5]: REC[0x14be4e0]: Decrypted Packet[0.3] Handshake(22) with length: 270\r\ngnutls[13]: BUF[REC]: Inserted 270 bytes of Data(22)\r\ngnutls[4]: HSK[0x14be4e0]: CLIENT KEY EXCHANGE (16) was received. Length 258[258], frag offset 0, frag length: 258, sequence: 2\r\ngnutls[6]: DTLS[0x14be4e0]: End of flight transmission.\r\ngnutls[3]: ASSERT: buffers.c[_gnutls_handshake_io_recv_int]:1377\r\ngnutls[10]: READ: Got 14 bytes from 0x7fffb8fbefc0\r\ngnutls[10]: READ: read 14 bytes from 0x7fffb8fbefc0\r\ngnutls[10]: RB: Have 0 bytes into buffer. Adding 14 bytes.\r\ngnutls[10]: RB: Requested 13 bytes\r\ngnutls[5]: REC[0x14be4e0]: SSL 254.253 ChangeCipherSpec packet received. Epoch 0, length: 1\r\ngnutls[5]: REC[0x14be4e0]: Expected Packet ChangeCipherSpec(20)\r\ngnutls[5]: REC[0x14be4e0]: Received Packet ChangeCipherSpec(20) with length: 1\r\ngnutls[5]: REC[0x14be4e0]: Decrypted Packet[0.4] ChangeCipherSpec(20) with length: 1\r\ngnutls[13]: BUF[REC]: Inserted 1 bytes of Data(20)\r\ngnutls[9]: INT: PREMASTER SECRET[48]: fefd1948c486f01cf69c395872cbcf726406c620806456c66a7888ac65c6974bcbe21ad32b7d494d2935a48677d1dbec\r\ngnutls[9]: INT: CLIENT RANDOM[32]: 5b6934f01d671279588834ff46e2066952fac58d2a1ef046765327f5ab8ab50b\r\ngnutls[9]: INT: SERVER RANDOM[32]: 5b6934c2b88be93f6ef438d5d029e3c12271fc697cf209d4ca8af1f5a68cec45\r\ngnutls[9]: INT: MASTER SECRET: 06f125edfc19df4daee151548782c914a633923c10dfe905346264b59fc18ea69cafcc70c23a970e14decefeed36e10a\r\ngnutls[5]: REC[0x14be4e0]: Initializing epoch #1\r\ngnutls[9]: INT: KEY BLOCK[40]: 406758aa99ebbb252c8a9fc2319af6e4dcb80a68a15bdba3534bcf1016616a79\r\ngnutls[9]: INT: CLIENT WRITE KEY [16]: 406758aa99ebbb252c8a9fc2319af6e4\r\ngnutls[9]: INT: SERVER WRITE KEY [16]: dcb80a68a15bdba3534bcf1016616a79\r\ngnutls[9]: INT: CLIENT WRITE IV [4]: 8f6ec8cb\r\ngnutls[9]: INT: SERVER WRITE IV [4]: e08c8261\r\ngnutls[5]: REC[0x14be4e0]: Epoch #1 ready\r\ngnutls[4]: HSK[0x14be4e0]: Cipher Suite: GNUTLS_RSA_AES_128_GCM_SHA256\r\n" 174 | - delay: 50 175 | content: "gnutls[3]: ASSERT: buffers.c[get_last_packet]:1161\r\ngnutls[10]: READ: Got 61 bytes from 0x7fffb8fbefc0\r\ngnutls[10]: READ: read 61 bytes from 0x7fffb8fbefc0\r\ngnutls[10]: RB: Have 0 bytes into buffer. Adding 61 bytes.\r\ngnutls[10]: RB: Requested 13 bytes\r\ngnutls[5]: REC[0x14be4e0]: SSL 254.253 Handshake packet received. Epoch 1, length: 48\r\ngnutls[5]: REC[0x14be4e0]: Expected Packet Handshake(22)\r\ngnutls[5]: REC[0x14be4e0]: Received Packet Handshake(22) with length: 48\r\ngnutls[5]: REC[0x14be4e0]: Decrypted Packet[1.5] Handshake(22) with length: 24\r\ngnutls[13]: BUF[REC]: Inserted 24 bytes of Data(22)\r\ngnutls[4]: HSK[0x14be4e0]: FINISHED (20) was received. Length 12[12], frag offset 0, frag length: 12, sequence: 3\r\ngnutls[3]: ASSERT: safe_renegotiation.c[_gnutls_ext_sr_finished]:63\r\ngnutls[4]: HSK[0x14be4e0]: recording tls-unique CB (recv)\r\ngnutls[11]: HWRITE: enqueued [CHANGE CIPHER SPEC] 1. Total 1 bytes.\r\ngnutls[4]: REC[0x14be4e0]: Sent ChangeCipherSpec\r\ngnutls[4]: HSK[0x14be4e0]: Cipher Suite: GNUTLS_RSA_AES_128_GCM_SHA256\r\ngnutls[4]: HSK[0x14be4e0]: Initializing internal [write] cipher sessions\r\ngnutls[3]: ASSERT: safe_renegotiation.c[_gnutls_ext_sr_finished]:63\r\ngnutls[4]: HSK[0x14be4e0]: FINISHED was queued [24 bytes]\r\ngnutls[11]: HWRITE: enqueued [FINISHED] 24. Total 25 bytes.\r\ngnutls[11]: HWRITE FLUSH: 25 bytes in buffer.\r\ngnutls[6]: DTLS[0x14be4e0]: Start of flight transmission.\r\ngnutls[6]: DTLS[0x14be4e0]: Sending Packet[3] fragment CHANGE CIPHER SPEC(254), mtu 1351\r\ngnutls[5]: REC[0x14be4e0]: Preparing Packet ChangeCipherSpec(20) with length: 1 and min pad: 0\r\ngnutls[9]: ENC[0x14be4e0]: cipher: NULL, MAC: MAC-NULL, Epoch: 0\r\ngnutls[11]: WRITE: enqueued 14 bytes for 0x7fffb8fbefc0. Total 14 bytes.\r\ngnutls[5]: REC[0x14be4e0]: Sent Packet[6] ChangeCipherSpec(20) in epoch 0 and length: 14\r\ngnutls[6]: DTLS[0x14be4e0]: Sending Packet[4] fragment FINISHED(20) with length: 12, offset: 0, fragment length: 12, mtu: 1351\r\ngnutls[5]: REC[0x14be4e0]: Preparing Packet Handshake(22) with length: 24 and min pad: 0\r\ngnutls[9]: ENC[0x14be4e0]: cipher: AES-128-GCM, MAC: AEAD, Epoch: 1\r\ngnutls[11]: WRITE: enqueued 61 bytes for 0x7fffb8fbefc0. Total 75 bytes.\r\ngnutls[5]: REC[0x14be4e0]: Sent Packet[1] Handshake(22) in epoch 1 and length: 61\r\ngnutls[11]: WRITE FLUSH: 75 bytes in buffer.\r\ngnutls[11]: WRITE: wrote 75 bytes, 0 bytes left.\r\ngnutls[6]: DTLS[0x14be4e0]: Initializing timer for handshake state.\r\ngnutls[5]: REC[0x14be4e0]: Start of epoch cleanup\r\ngnutls[5]: REC[0x14be4e0]: Note inactive epoch 0 has 1 users\r\ngnutls[5]: REC[0x14be4e0]: End of epoch cleanup\r\n- Handshake was completed\r\n" 176 | - delay: 8 177 | content: "gnutls[10]: READ: Got 56 bytes from 0x7fffb8fbefc0\r\ngnutls[10]: READ: read 56 bytes from 0x7fffb8fbefc0\r\ngnutls[10]: RB: Have 0 bytes into buffer. Adding 56 bytes.\r\ngnutls[10]: RB: Requested 13 bytes\r\ngnutls[5]: REC[0x14be4e0]: SSL 254.253 Application Data packet received. Epoch 1, length: 43\r\ngnutls[5]: REC[0x14be4e0]: Expected Packet Application Data(23)\r\ngnutls[5]: REC[0x14be4e0]: Received Packet Application Data(23) with length: 43\r\ngnutls[5]: REC[0x14be4e0]: Decrypted Packet[1.6] Application Data(23) with length: 19\r\ngnutls[13]: BUF[REC]: Inserted 19 bytes of Data(23)\r\ngnutls[6]: DTLS[0x14be4e0]: Deinitializing previous handshake state.\r\ngnutls[5]: REC[0x14be4e0]: Start of epoch cleanup\r\ngnutls[5]: REC[0x14be4e0]: Epoch #0 freed\r\ngnutls[5]: REC[0x14be4e0]: End of epoch cleanup\r\nreceived[0001000000000006]: Hello from Node.js!\r\ngnutls[5]: REC[0x14be4e0]: Preparing Packet Application Data(23) with length: 19 and min pad: 0\r\ngnutls[9]: ENC[0x14be4e0]: cipher: AES-128-GCM, MAC: AEAD, Epoch: 1\r\ngnutls[11]: WRITE: enqueued 56 bytes for 0x7fffb8fbefc0. Total 56 bytes.\r\ngnutls[11]: WRITE FLUSH: 56 bytes in buffer.\r\ngnutls[11]: WRITE: wrote 56 bytes, 0 bytes left.\r\ngnutls[5]: REC[0x14be4e0]: Sent Packet[2] Application Data(23) in epoch 1 and length: 56\r\n" 178 | - delay: 756 179 | content: ^C 180 | - delay: 12 181 | content: "\r\n" 182 | -------------------------------------------------------------------------------- /fixtures/terminalizer/render1533622385765.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nodertc/dtls/ee9a8e1821fd3c18dbe89056ee599d04ca17d244/fixtures/terminalizer/render1533622385765.gif -------------------------------------------------------------------------------- /fixtures/terminalizer/render1533622791504.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nodertc/dtls/ee9a8e1821fd3c18dbe89056ee599d04ca17d244/fixtures/terminalizer/render1533622791504.gif -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@nodertc/dtls", 3 | "version": "0.6.0", 4 | "description": "Secure UDP communications using DTLS.", 5 | "main": "src/index.js", 6 | "scripts": { 7 | "test": "npm run lint && npm run unit-test", 8 | "lint": "npx eslint --quiet .", 9 | "unit-test": "npx jest", 10 | "integrated-test": "node test/integrated/test.js", 11 | "start": "DEBUG=dtls,dtls:* DEBUG_DEPTH=10 node example.js", 12 | "gnutls-server": "docker run -it --name dtlsd --rm -e GNUTLS_DEBUG_LEVEL=2 -e PRIORITY=NORMAL:+AEAD:+ECDHE-RSA:+VERS-DTLS1.2 -e KEYFILE=key-rsa.pem -e CERTFILE=cert-rsa.pem -p 4444:4444/udp nodertc/dtls-server:1" 13 | }, 14 | "keywords": [ 15 | "dtls", 16 | "tls", 17 | "secure", 18 | "udp", 19 | "dgram", 20 | "webrtc", 21 | "nodertc" 22 | ], 23 | "repository": { 24 | "type": "git", 25 | "url": "git+https://github.com/nodertc/dtls.git" 26 | }, 27 | "author": "Dmitriy Tsvettsikh ", 28 | "license": "MIT", 29 | "bugs": { 30 | "url": "https://github.com/nodertc/dtls/issues" 31 | }, 32 | "homepage": "https://github.com/nodertc/dtls#readme", 33 | "engines": { 34 | "node": ">=8.3" 35 | }, 36 | "files": [ 37 | "src" 38 | ], 39 | "dependencies": { 40 | "@fidm/asn1": "^1.0.3", 41 | "@fidm/x509": "^1.2.0", 42 | "binary-data": "^0.6.0", 43 | "buffer-xor": "^2.0.2", 44 | "debug": "^4.1.0", 45 | "is-chacha20-poly1305-supported": "^1.0.0", 46 | "is-dtls": "^2.0.0", 47 | "is-stream": "^1.1.0", 48 | "ms": "^2.1.2", 49 | "next-state": "^1.0.0", 50 | "readable-stream": "^3.0.6", 51 | "sorted-array-functions": "^1.2.0", 52 | "streamfilter": "^3.0.0", 53 | "timeout-refresh": "^1.0.0", 54 | "unicast": "^1.2.0" 55 | }, 56 | "devDependencies": { 57 | "@nodertc/eslint-config": "^0.2.1", 58 | "@types/node": "^12.6.2", 59 | "eslint": "^5.16.0", 60 | "jest": "^23.6.0", 61 | "prettier": "^1.14.3" 62 | }, 63 | "jest": { 64 | "modulePaths": [ 65 | "", 66 | "/src", 67 | "/src/node_modules" 68 | ], 69 | "testMatch": [ 70 | "**/test/unit/**/*.js" 71 | ], 72 | "testPathIgnorePatterns": [ 73 | "/node_modules/" 74 | ], 75 | "coverageDirectory": "/coverage", 76 | "collectCoverageFrom": [ 77 | "**/src/*.js", 78 | "**/src/node_modules/**/*.js" 79 | ], 80 | "coveragePathIgnorePatterns": [ 81 | "/node_modules/" 82 | ], 83 | "cacheDirectory": ".jest-cache", 84 | "haste": { 85 | "providesModuleNodeModules": [ 86 | "cipher", 87 | "filter", 88 | "fsm", 89 | "lib", 90 | "session", 91 | "utils" 92 | ] 93 | } 94 | }, 95 | "eslintConfig": { 96 | "extends": "@nodertc", 97 | "rules": { 98 | "no-underscore-dangle": "off" 99 | }, 100 | "overrides": [ 101 | { 102 | "files": [ 103 | "test/**/*.js" 104 | ], 105 | "env": { 106 | "jest": true 107 | }, 108 | "settings": { 109 | "import/resolver": { 110 | "node": { 111 | "moduleDirectory": [ 112 | "node_modules", 113 | "src", 114 | "src/node_modules" 115 | ] 116 | } 117 | } 118 | }, 119 | "rules": { 120 | "require-jsdoc": "off", 121 | "security/detect-non-literal-fs-filename": "off", 122 | "no-process-exit": "off", 123 | "unicorn/no-process-exit": "off" 124 | } 125 | }, 126 | { 127 | "files": [ 128 | "example.js" 129 | ], 130 | "rules": { 131 | "no-console": "off", 132 | "security/detect-non-literal-fs-filename": "off" 133 | } 134 | } 135 | ] 136 | } 137 | } 138 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # @nodertc/dtls 2 | 3 | [![stability-experimental](https://img.shields.io/badge/stability-experimental-orange.svg)](https://github.com/emersion/stability-badges#experimental) 4 | [![Build Status](https://travis-ci.org/nodertc/dtls.svg?branch=master)](https://travis-ci.org/nodertc/dtls) 5 | [![npm](https://img.shields.io/npm/v/@nodertc/dtls.svg)](https://www.npmjs.com/package/@nodertc/dtls) 6 | [![node](https://img.shields.io/node/v/@nodertc/dtls.svg)](https://www.npmjs.com/package/@nodertc/dtls) 7 | [![license](https://img.shields.io/npm/l/@nodertc/dtls.svg)](https://www.npmjs.com/package/@nodertc/dtls) 8 | [![downloads](https://img.shields.io/npm/dm/@nodertc/dtls.svg)](https://www.npmjs.com/package/@nodertc/dtls) 9 | [![Gitter chat](https://badges.gitter.im/nodertc.png)](https://gitter.im/nodertc/community) 10 | 11 | Secure UDP communications using Datagram Transport Layer Security protocol version 1.2 in **pure js**. Follow [RFC6347](https://tools.ietf.org/html/rfc6347), [RFC7627](https://tools.ietf.org/html/rfc7627). 12 | 13 | [![asciicast](fixtures/terminalizer/render1533622791504.gif)](https://asciinema.org/a/195096) 14 | 15 | ### Support 16 | 17 | [![Buy Me A Coffee](https://www.buymeacoffee.com/assets/img/custom_images/purple_img.png)](https://www.buymeacoffee.com/reklatsmasters) 18 | 19 | ### Features 20 | 21 | * **no native dependecies!** 22 | * modern secure ciphers (by default) 23 | * in-out fragmentation / in-out retransmission 24 | * merge outgoing handshakes 25 | 26 | ### Usage 27 | 28 | ``` 29 | npm i @nodertc/dtls 30 | ``` 31 | 32 | ```js 33 | const dtls = require('@nodertc/dtls'); 34 | 35 | const socket = dtls.connect({ 36 | type: 'udp4', 37 | remotePort: 4444, 38 | remoteAddress: '127.0.0.1', 39 | }); 40 | 41 | socket.on('error', err => { 42 | console.error(err); 43 | }); 44 | 45 | socket.on('data', data => { 46 | console.log('got message "%s"', data.toString('ascii')); 47 | socket.close(); 48 | }); 49 | 50 | socket.once('connect', () => { 51 | socket.write('Hello from Node.js!'); 52 | }); 53 | ``` 54 | 55 | ### Suppored ciphers: 56 | 57 | * TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256 (_nodejs v11.2+ only_) 58 | * TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256 (_nodejs v11.2+ only_) 59 | * TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256 60 | * TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384 61 | * TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 62 | * TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 63 | * TLS_RSA_WITH_AES_128_GCM_SHA256 64 | * TLS_RSA_WITH_AES_256_GCM_SHA384 65 | * TLS_PSK_WITH_CHACHA20_POLY1305_SHA256 (_nodejs v11.2+ only_) 66 | * TLS_PSK_WITH_AES_128_GCM_SHA256 67 | * TLS_PSK_WITH_AES_256_GCM_SHA384 68 | * TLS_ECDHE_PSK_WITH_AES_128_GCM_SHA256 69 | * TLS_ECDHE_PSK_WITH_AES_256_GCM_SHA384 70 | * TLS_ECDHE_PSK_WITH_CHACHA20_POLY1305_SHA256 71 | 72 | ### API 73 | 74 | * `dtls.connect(options: Options [, callback: function]) : Socket` 75 | 76 | Creates an esteblished connection to remote dtls server. A `connect()` function also accept all options for [`unicast.createSocket()`](https://www.npmjs.com/package/unicast) or [`dgram.createSocket()`](https://nodejs.org/dist/latest-v8.x/docs/api/dgram.html#dgram_dgram_createsocket_options_callback). If `options.socket` is provided, these options will be ignored. 77 | 78 | The `callback` function, if specified, will be added as a listener for the 'connect' event. 79 | 80 | * `options.socket` 81 | 82 | A [duplex stream](https://nodejs.org/api/stream.html#stream_class_stream_duplex) in a common case. It is also [unicast](https://www.npmjs.com/package/unicast) or [dgram](https://nodejs.org/dist/latest-v8.x/docs/api/dgram.html) socket instance. Used if you want a low level control of your connection. 83 | 84 | * `options.extendedMasterSecret: bool, [default=true]` 85 | 86 | This option enable the use [Extended Master Secret](https://tools.ietf.org/html/rfc7627) extension. Enabled by default. 87 | 88 | * `options.checkServerIdentity: function(certificate): bool` 89 | 90 | Optional certificate verify function. 91 | 92 | * `options.certificate: Buffer` 93 | 94 | PEM-encoded client certificate, optional. Supports RSASSA-PKCS1-v1_5 and ECDSA certificates. 95 | 96 | * `options.certificatePrivateKey: Buffer` 97 | 98 | PEM-encoded private key for client certificate. 99 | 100 | * `options.maxHandshakeRetransmissions: number` 101 | 102 | The number of retransmissions during on handshake stage. 103 | 104 | * `options.alpn: string | string[]` 105 | 106 | The list of the supported ALPN protocols. 107 | * `options.pskIdentity: String|Buffer` 108 | 109 | Identity string for PSK key exchange, see [RFC4279](https://tools.ietf.org/html/rfc4279). 110 | 111 | * `options.pskSecret: String|Buffer` 112 | 113 | Secret data for the identity string of PSK key exchange. 114 | 115 | * `options.ignorePSKIdentityHint: boolean, default=true` 116 | 117 | Both clients and servers may have pre-shared keys with several different parties. The client indicates which key to use by including a "PSK identity" (_see `options.pskIdentity` above_) in the ClientKeyExchange message. To help the client in selecting which identity to use, the server can provide a "PSK identity hint" in the ServerKeyExchange message. 118 | 119 | * `options.cipherSuites: number[]|string[]` 120 | 121 | List of supported by client cipher suites. Default cipher suites: 122 | - TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256 (_in nodejs v11+ only_) 123 | - TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256 (_in nodejs v11+ only_) 124 | - TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256 125 | - TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384 126 | - TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 127 | - TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 128 | 129 | See above a full list of supported cipher suites. 130 | 131 | * `class Socket` 132 | 133 | A `Socket` is also a [duplex stream](https://nodejs.org/api/stream.html#stream_class_stream_duplex), so it can be both readable and writable, and it is also a [EventEmitter](https://nodejs.org/api/events.html#events_class_eventemitter). 134 | 135 | * `Socket.setMTU(mtu: number): void` 136 | 137 | Set MTU (minimal transfer unit) for this socket, 1420 bytes maximal. 138 | 139 | * `Socket.getMTU(): number` 140 | 141 | Return MTU (minimal transfer unit) for this socket, 1200 bytes by default. 142 | 143 | * `Socket.setTimeout(timeout: number[, callback: function()])` 144 | 145 | Sets the socket to timeout after timeout milliseconds of inactivity on the socket. By default `dtls.Socket` do not have a timeout. 146 | 147 | The optional callback parameter will be added as a one-time listener for the 'timeout' event. 148 | 149 | * `Socket.close(): void` 150 | 151 | Close socket, stop listening for socket. Do not emit `data` events anymore. 152 | 153 | * `Socket.alpnProtocol: string` 154 | 155 | Get a string that contains the selected ALPN protocol. 156 | 157 | * `Event: connect` 158 | 159 | The 'connect' event is emitted after the handshaking process for a new connection has successfully completed. 160 | 161 | * `Event: timeout` 162 | 163 | Emitted if the socket times out from inactivity. This is only to notify that the socket has been idle. 164 | 165 | * `dtls.constants: Object` 166 | - `cipherSuites: Object` 167 | A full list supported cipher suites. See above for detailes. 168 | 169 | ### How to debug? 170 | 171 | Start dtls server: 172 | 173 | ```sh 174 | docker run -it --name dtlsd --rm -e "GNUTLS_DEBUG_LEVEL=2" -e "PRIORITY=NORMAL:+AEAD:+ECDHE-RSA:+VERS-DTLS1.2" -e "KEYFILE=key-rsa.pem" -e "CERTFILE=cert-rsa.pem" -p 4444:4444/udp nodertc/dtls-server:1 175 | ``` 176 | 177 | Start default client: 178 | 179 | ```sh 180 | npm start 181 | ``` 182 | 183 | ## License 184 | 185 | MIT, 2018 - 2019 © Dmitriy Tsvettsikh 186 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const { connect } = require('lib/socket'); 4 | const { cipherSuites } = require('lib/constants'); 5 | const CookieManager = require('lib/cookie-manager'); 6 | const { createServer } = require('lib/server'); 7 | 8 | module.exports = { 9 | connect, 10 | createServer, 11 | constants: { 12 | cipherSuites: Object.assign({}, cipherSuites), 13 | }, 14 | CookieManager, 15 | }; 16 | -------------------------------------------------------------------------------- /src/node_modules/cipher/abstract.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /* eslint-disable class-methods-use-this */ 4 | 5 | /** 6 | * This class implements abstract cipher cuite. 7 | */ 8 | module.exports = class AbstractCipher { 9 | /** 10 | * @class AbstractCipher 11 | */ 12 | constructor() { 13 | this.id = 0; 14 | this.name = null; 15 | this.hash = null; 16 | this.verifyDataLength = 12; 17 | 18 | this.blockAlgorithm = null; 19 | this.kx = null; 20 | } 21 | 22 | /** 23 | * Init cipher. 24 | * @abstract 25 | */ 26 | init() {} 27 | 28 | /** 29 | * Encrypts data. 30 | * @abstract 31 | */ 32 | encrypt() { 33 | throw new Error('not implemented'); 34 | } 35 | 36 | /** 37 | * Decrypts data. 38 | * @abstract 39 | */ 40 | decrypt() { 41 | throw new Error('not implemented'); 42 | } 43 | 44 | /** 45 | * @returns {string} 46 | */ 47 | toString() { 48 | return this.name; 49 | } 50 | }; 51 | -------------------------------------------------------------------------------- /src/node_modules/cipher/aead.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const crypto = require('crypto'); 4 | const { createDecode, encode } = require('binary-data'); 5 | const debug = require('utils/debug')('dtls:cipher:aead'); 6 | const { sessionType } = require('lib/constants'); 7 | const { AEADAdditionalData } = require('lib/protocol'); 8 | const { phash } = require('cipher/utils'); 9 | const Cipher = require('cipher/abstract'); 10 | 11 | /** 12 | * This class implements AEAD cipher family. 13 | */ 14 | module.exports = class AEADCipher extends Cipher { 15 | /** 16 | * @class AEADCipher 17 | */ 18 | constructor() { 19 | super(); 20 | 21 | this.keyLength = 0; 22 | this.nonceLength = 0; 23 | this.ivLength = 0; 24 | this.authTagLength = 0; 25 | 26 | this.nonceImplicitLength = 0; 27 | this.nonceExplicitLength = 0; 28 | 29 | this.clientWriteKey = null; 30 | this.serverWriteKey = null; 31 | 32 | this.clientNonce = null; 33 | this.serverNonce = null; 34 | } 35 | 36 | /** 37 | * Initialize encryption and decryption parts. 38 | * @param {Session} session 39 | */ 40 | init(session) { 41 | const size = this.keyLength * 2 + this.ivLength * 2; 42 | const secret = session.masterSecret; 43 | const seed = Buffer.concat([session.serverRandom, session.clientRandom]); 44 | const keyBlock = this.prf(size, secret, 'key expansion', seed); 45 | const stream = createDecode(keyBlock); 46 | 47 | this.clientWriteKey = stream.readBuffer(this.keyLength); 48 | this.serverWriteKey = stream.readBuffer(this.keyLength); 49 | 50 | debug('CLIENT WRITE KEY %h', this.clientWriteKey); 51 | debug('SERVER WRITE KEY %h', this.serverWriteKey); 52 | 53 | const clientNonceImplicit = stream.readBuffer(this.ivLength); 54 | const serverNonceImplicit = stream.readBuffer(this.ivLength); 55 | 56 | debug('CLIENT WRITE IV %h', clientNonceImplicit); 57 | debug('SERVER WRITE IV %h', serverNonceImplicit); 58 | 59 | this.clientNonce = Buffer.alloc(this.nonceLength, 0); 60 | this.serverNonce = Buffer.alloc(this.nonceLength, 0); 61 | 62 | clientNonceImplicit.copy(this.clientNonce, 0); 63 | serverNonceImplicit.copy(this.serverNonce, 0); 64 | } 65 | 66 | /** 67 | * Encrypt message. 68 | * @param {Session} session 69 | * @param {Buffer} data Message to encrypt. 70 | * @param {Object} header Record layer message header. 71 | * @returns {Buffer} 72 | */ 73 | encrypt(session, data, header) { 74 | const isClient = session.type === sessionType.CLIENT; 75 | const iv = isClient ? this.clientNonce : this.serverNonce; 76 | 77 | const writeKey = isClient ? this.clientWriteKey : this.serverWriteKey; 78 | 79 | iv.writeUInt16BE(header.epoch, this.nonceImplicitLength); 80 | iv.writeUIntBE(header.sequenceNumber, this.nonceImplicitLength + 2, 6); 81 | 82 | const explicitNonce = iv.slice(this.nonceImplicitLength); 83 | 84 | const additionalData = { 85 | epoch: header.epoch, 86 | sequence: header.sequenceNumber, 87 | type: header.type, 88 | version: header.version, 89 | length: data.length, 90 | }; 91 | 92 | const additionalBuffer = encode(additionalData, AEADAdditionalData).slice(); 93 | 94 | const cipher = crypto.createCipheriv(this.blockAlgorithm, writeKey, iv, { 95 | authTagLength: this.authTagLength, 96 | }); 97 | 98 | cipher.setAAD(additionalBuffer, { 99 | plaintextLength: data.length, 100 | }); 101 | 102 | const headPart = cipher.update(data); 103 | const finalPart = cipher.final(); 104 | const authtag = cipher.getAuthTag(); 105 | 106 | return Buffer.concat([explicitNonce, headPart, finalPart, authtag]); 107 | } 108 | 109 | /** 110 | * Decrypt message. 111 | * @param {Buffer} session 112 | * @param {Buffer} data Encrypted message. 113 | * @param {Object} header Record layer headers. 114 | * @returns {Buffer} 115 | */ 116 | decrypt(session, data, header) { 117 | const isClient = session.type === sessionType.CLIENT; 118 | const iv = isClient ? this.serverNonce : this.clientNonce; 119 | const final = createDecode(data); 120 | 121 | const explicitNonce = final.readBuffer(this.nonceExplicitLength); 122 | explicitNonce.copy(iv, this.nonceImplicitLength); 123 | 124 | const encryted = final.readBuffer(final.length - this.authTagLength); 125 | const authTag = final.readBuffer(this.authTagLength); 126 | const writeKey = isClient ? this.serverWriteKey : this.clientWriteKey; 127 | 128 | const additionalData = { 129 | epoch: header.epoch, 130 | sequence: header.sequenceNumber, 131 | type: header.type, 132 | version: header.version, 133 | length: encryted.length, 134 | }; 135 | 136 | const additionalBuffer = encode(additionalData, AEADAdditionalData).slice(); 137 | 138 | const decipher = crypto.createDecipheriv( 139 | this.blockAlgorithm, 140 | writeKey, 141 | iv, 142 | { 143 | authTagLength: this.authTagLength, 144 | } 145 | ); 146 | 147 | decipher.setAuthTag(authTag); 148 | decipher.setAAD(additionalBuffer, { 149 | plaintextLength: encryted.length, 150 | }); 151 | 152 | const headPart = decipher.update(encryted); 153 | const finalPart = decipher.final(); 154 | 155 | return finalPart.length > 0 156 | ? Buffer.concat([headPart, finalPart]) 157 | : headPart; 158 | } 159 | 160 | /** 161 | * Pseudorandom Function. 162 | * @param {number} size - The number of required bytes. 163 | * @param {Buffer} secret - Hmac secret. 164 | * @param {string} label - Identifying label. 165 | * @param {Buffer} seed - Input data. 166 | * @returns {Buffer} 167 | */ 168 | prf(size, secret, label, seed) { 169 | const isLabelString = typeof label === 'string'; 170 | const name = isLabelString ? Buffer.from(label, 'ascii') : label; 171 | 172 | return phash(size, this.hash, secret, Buffer.concat([name, seed])); 173 | } 174 | }; 175 | -------------------------------------------------------------------------------- /src/node_modules/cipher/chacha20-poly1305.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const crypto = require('crypto'); 4 | const xor = require('buffer-xor/inplace'); 5 | const { createDecode, encode } = require('binary-data'); 6 | const AEADCipher = require('cipher/aead'); 7 | const { AEAD_CHACHA20_POLY1305 } = require('lib/constants'); 8 | const debug = require('utils/debug')('dtls:cipher:aead'); 9 | const { sessionType } = require('lib/constants'); 10 | const { AEADAdditionalData } = require('lib/protocol'); 11 | 12 | /** 13 | * This class implements chacha20-poly1305 cipher which is 14 | * part of AEAD cipher family. 15 | */ 16 | module.exports = class Chacha20Poly1305Cipher extends AEADCipher { 17 | /** 18 | * @class Chacha20Poly1305Cipher 19 | */ 20 | constructor() { 21 | super(); 22 | 23 | this.keyLength = AEAD_CHACHA20_POLY1305.K_LEN; 24 | this.nonceLength = AEAD_CHACHA20_POLY1305.N_MIN; 25 | this.ivLength = AEAD_CHACHA20_POLY1305.N_MIN; 26 | this.authTagLength = 16; 27 | } 28 | 29 | /** 30 | * Initialize encryption and decryption parts. 31 | * @param {Session} session 32 | */ 33 | init(session) { 34 | const size = this.keyLength * 2 + this.ivLength * 2; 35 | const secret = session.masterSecret; 36 | const seed = Buffer.concat([session.serverRandom, session.clientRandom]); 37 | const keyBlock = this.prf(size, secret, 'key expansion', seed); 38 | const stream = createDecode(keyBlock); 39 | 40 | this.clientWriteKey = stream.readBuffer(this.keyLength); 41 | this.serverWriteKey = stream.readBuffer(this.keyLength); 42 | 43 | debug('CLIENT WRITE KEY %h', this.clientWriteKey); 44 | debug('SERVER WRITE KEY %h', this.serverWriteKey); 45 | 46 | this.clientNonce = stream.readBuffer(this.ivLength); 47 | this.serverNonce = stream.readBuffer(this.ivLength); 48 | 49 | debug('CLIENT WRITE IV %h', this.clientNonce); 50 | debug('SERVER WRITE IV %h', this.serverNonce); 51 | } 52 | 53 | /** 54 | * Encrypt message. 55 | * @param {Session} session 56 | * @param {Buffer} data Message to encrypt. 57 | * @param {Object} header Record layer message header. 58 | * @returns {Buffer} 59 | */ 60 | encrypt(session, data, header) { 61 | const isClient = session.type === sessionType.CLIENT; 62 | const iv = isClient ? this.clientNonce : this.serverNonce; 63 | 64 | const writeKey = isClient ? this.clientWriteKey : this.serverWriteKey; 65 | 66 | // 1. The 64-bit record sequence number is serialized as an 8-byte, 67 | // big-endian value and padded on the left with four 0x00 bytes. 68 | const nonce = Buffer.alloc(this.nonceLength); 69 | nonce.writeUInt16BE(header.epoch, 4); 70 | nonce.writeUIntBE(header.sequenceNumber, 6, 6); 71 | 72 | // 2. The padded sequence number is XORed with the client_write_IV 73 | // (when the client is sending) or server_write_IV (when the server 74 | // is sending). 75 | xor(nonce, iv); 76 | 77 | const additionalData = { 78 | epoch: header.epoch, 79 | sequence: header.sequenceNumber, 80 | type: header.type, 81 | version: header.version, 82 | length: data.length, 83 | }; 84 | 85 | const additionalBuffer = encode(additionalData, AEADAdditionalData).slice(); 86 | 87 | const cipher = crypto.createCipheriv(this.blockAlgorithm, writeKey, nonce, { 88 | authTagLength: this.authTagLength, 89 | }); 90 | 91 | cipher.setAAD(additionalBuffer, { 92 | plaintextLength: data.length, 93 | }); 94 | 95 | const headPart = cipher.update(data); 96 | const finalPart = cipher.final(); 97 | const authtag = cipher.getAuthTag(); 98 | 99 | return Buffer.concat([headPart, finalPart, authtag]); 100 | } 101 | 102 | /** 103 | * Decrypt message. 104 | * @param {Buffer} session 105 | * @param {Buffer} data Encrypted message. 106 | * @param {Object} header Record layer headers. 107 | * @returns {Buffer} 108 | */ 109 | decrypt(session, data, header) { 110 | const isClient = session.type === sessionType.CLIENT; 111 | const iv = isClient ? this.serverNonce : this.clientNonce; 112 | const final = createDecode(data); 113 | 114 | const encryted = final.readBuffer(final.length - this.authTagLength); 115 | const authTag = final.readBuffer(this.authTagLength); 116 | const writeKey = isClient ? this.serverWriteKey : this.clientWriteKey; 117 | 118 | // 1. The 64-bit record sequence number is serialized as an 8-byte, 119 | // big-endian value and padded on the left with four 0x00 bytes. 120 | const nonce = Buffer.alloc(this.nonceLength); 121 | nonce.writeUInt16BE(header.epoch, 4); 122 | nonce.writeUIntBE(header.sequenceNumber, 6, 6); 123 | 124 | // 2. The padded sequence number is XORed with the client_write_IV 125 | // (when the client is sending) or server_write_IV (when the server 126 | // is sending). 127 | xor(nonce, iv); 128 | 129 | const additionalData = { 130 | epoch: header.epoch, 131 | sequence: header.sequenceNumber, 132 | type: header.type, 133 | version: header.version, 134 | length: encryted.length, 135 | }; 136 | 137 | const additionalBuffer = encode(additionalData, AEADAdditionalData).slice(); 138 | 139 | const decipher = crypto.createDecipheriv( 140 | this.blockAlgorithm, 141 | writeKey, 142 | nonce, 143 | { 144 | authTagLength: this.authTagLength, 145 | } 146 | ); 147 | 148 | decipher.setAuthTag(authTag); 149 | decipher.setAAD(additionalBuffer, { 150 | plaintextLength: encryted.length, 151 | }); 152 | 153 | const headPart = decipher.update(encryted); 154 | const finalPart = decipher.final(); 155 | 156 | return finalPart.length > 0 157 | ? Buffer.concat([headPart, finalPart]) 158 | : headPart; 159 | } 160 | }; 161 | -------------------------------------------------------------------------------- /src/node_modules/cipher/create.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const { 4 | cipherSuites, 5 | AEAD_AES_128_GCM, 6 | AEAD_AES_256_GCM, 7 | } = require('lib/constants'); 8 | const AEADCipher = require('cipher/aead'); 9 | const Chacha20Poly1305Cipher = require('cipher/chacha20-poly1305'); 10 | const { 11 | createRSAKeyExchange, 12 | createECDHERSAKeyExchange, 13 | createECDHEECDSAKeyExchange, 14 | createPSKKeyExchange, 15 | createECDHEPSKKeyExchange, 16 | } = require('cipher/key-exchange'); 17 | 18 | module.exports = { 19 | createCipher, 20 | }; 21 | 22 | const RSA_KEY_EXCHANGE = createRSAKeyExchange(); 23 | const ECDHE_RSA_KEY_EXCHANGE = createECDHERSAKeyExchange(); 24 | const ECDHE_ECDSA_KEY_EXCHANGE = createECDHEECDSAKeyExchange(); 25 | const PSK_KEY_EXCHANGE = createPSKKeyExchange(); 26 | const ECDHE_PSK_KEY_EXCHANGE = createECDHEPSKKeyExchange(); 27 | 28 | /** 29 | * Convert cipher value to cipher instance. 30 | * @param {number} cipher 31 | * @returns {AEADCipher} 32 | */ 33 | function createCipher(cipher) { 34 | switch (cipher) { 35 | case cipherSuites.TLS_RSA_WITH_AES_128_GCM_SHA256: 36 | return createAEADCipher( 37 | cipherSuites.TLS_RSA_WITH_AES_128_GCM_SHA256, 38 | 'TLS_RSA_WITH_AES_128_GCM_SHA256', 39 | 'aes-128-gcm', 40 | RSA_KEY_EXCHANGE, 41 | AEAD_AES_128_GCM 42 | ); 43 | case cipherSuites.TLS_RSA_WITH_AES_256_GCM_SHA384: 44 | return createAEADCipher( 45 | cipherSuites.TLS_RSA_WITH_AES_256_GCM_SHA384, 46 | 'TLS_RSA_WITH_AES_256_GCM_SHA384', 47 | 'aes-256-gcm', 48 | RSA_KEY_EXCHANGE, 49 | AEAD_AES_256_GCM, 50 | 'sha384' 51 | ); 52 | case cipherSuites.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256: 53 | return createAEADCipher( 54 | cipherSuites.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, 55 | 'TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256', 56 | 'aes-128-gcm', 57 | ECDHE_RSA_KEY_EXCHANGE, 58 | AEAD_AES_128_GCM 59 | ); 60 | case cipherSuites.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384: 61 | return createAEADCipher( 62 | cipherSuites.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384, 63 | 'TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384', 64 | 'aes-256-gcm', 65 | ECDHE_RSA_KEY_EXCHANGE, 66 | AEAD_AES_256_GCM, 67 | 'sha384' 68 | ); 69 | case cipherSuites.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256: 70 | return createAEADCipher( 71 | cipherSuites.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, 72 | 'TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256', 73 | 'aes-128-gcm', 74 | ECDHE_ECDSA_KEY_EXCHANGE, 75 | AEAD_AES_128_GCM 76 | ); 77 | case cipherSuites.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384: 78 | return createAEADCipher( 79 | cipherSuites.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384, 80 | 'TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384', 81 | 'aes-256-gcm', 82 | ECDHE_ECDSA_KEY_EXCHANGE, 83 | AEAD_AES_256_GCM, 84 | 'sha384' 85 | ); 86 | case cipherSuites.TLS_PSK_WITH_AES_128_GCM_SHA256: 87 | return createAEADCipher( 88 | cipherSuites.TLS_PSK_WITH_AES_128_GCM_SHA256, 89 | 'TLS_PSK_WITH_AES_128_GCM_SHA256', 90 | 'aes-128-gcm', 91 | PSK_KEY_EXCHANGE, 92 | AEAD_AES_128_GCM, 93 | 'sha256' 94 | ); 95 | case cipherSuites.TLS_PSK_WITH_AES_256_GCM_SHA384: 96 | return createAEADCipher( 97 | cipherSuites.TLS_PSK_WITH_AES_256_GCM_SHA384, 98 | 'TLS_PSK_WITH_AES_256_GCM_SHA384', 99 | 'aes-256-gcm', 100 | PSK_KEY_EXCHANGE, 101 | AEAD_AES_256_GCM, 102 | 'sha384' 103 | ); 104 | case cipherSuites.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256: 105 | return createChacha20Cipher( 106 | cipherSuites.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256, 107 | 'TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256', 108 | 'chacha20-poly1305', 109 | ECDHE_ECDSA_KEY_EXCHANGE, 110 | 'sha256' 111 | ); 112 | case cipherSuites.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256: 113 | return createChacha20Cipher( 114 | cipherSuites.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256, 115 | 'TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256', 116 | 'chacha20-poly1305', 117 | ECDHE_RSA_KEY_EXCHANGE, 118 | 'sha256' 119 | ); 120 | case cipherSuites.TLS_PSK_WITH_CHACHA20_POLY1305_SHA256: 121 | return createChacha20Cipher( 122 | cipherSuites.TLS_PSK_WITH_CHACHA20_POLY1305_SHA256, 123 | 'TLS_PSK_WITH_CHACHA20_POLY1305_SHA256', 124 | 'chacha20-poly1305', 125 | PSK_KEY_EXCHANGE, 126 | 'sha256' 127 | ); 128 | case cipherSuites.TLS_ECDHE_PSK_WITH_AES_128_GCM_SHA256: 129 | return createAEADCipher( 130 | cipherSuites.TLS_ECDHE_PSK_WITH_AES_128_GCM_SHA256, 131 | 'TLS_ECDHE_PSK_WITH_AES_128_GCM_SHA256', 132 | 'aes-128-gcm', 133 | ECDHE_PSK_KEY_EXCHANGE, 134 | AEAD_AES_128_GCM, 135 | 'sha256' 136 | ); 137 | case cipherSuites.TLS_ECDHE_PSK_WITH_AES_256_GCM_SHA384: 138 | return createAEADCipher( 139 | cipherSuites.TLS_ECDHE_PSK_WITH_AES_256_GCM_SHA384, 140 | 'TLS_ECDHE_PSK_WITH_AES_256_GCM_SHA384', 141 | 'aes-256-gcm', 142 | ECDHE_PSK_KEY_EXCHANGE, 143 | AEAD_AES_256_GCM, 144 | 'sha384' 145 | ); 146 | case cipherSuites.TLS_ECDHE_PSK_WITH_CHACHA20_POLY1305_SHA256: 147 | return createChacha20Cipher( 148 | cipherSuites.TLS_ECDHE_PSK_WITH_CHACHA20_POLY1305_SHA256, 149 | 'TLS_ECDHE_PSK_WITH_CHACHA20_POLY1305_SHA256', 150 | 'chacha20-poly1305', 151 | ECDHE_PSK_KEY_EXCHANGE, 152 | 'sha256' 153 | ); 154 | default: 155 | break; 156 | } 157 | 158 | return null; 159 | } 160 | 161 | /** 162 | * @param {number} id An internal id of cipher suite. 163 | * @param {string} name A valid cipher suite name. 164 | * @param {string} block A valid nodejs cipher name. 165 | * @param {KeyExchange} kx Key exchange type. 166 | * @param {Object} constants Cipher specific constants. 167 | * @param {string} hash 168 | * @returns {AEADCipher} 169 | */ 170 | function createAEADCipher(id, name, block, kx, constants, hash = 'sha256') { 171 | const cipher = new AEADCipher(); 172 | 173 | cipher.id = id; 174 | cipher.name = name; 175 | cipher.blockAlgorithm = block; 176 | cipher.kx = kx; 177 | cipher.hash = hash; 178 | 179 | cipher.keyLength = constants.K_LEN; 180 | cipher.nonceLength = constants.N_MAX; 181 | 182 | // RFC5288, sec. 3 183 | cipher.nonceImplicitLength = 4; 184 | cipher.nonceExplicitLength = 8; 185 | 186 | cipher.ivLength = cipher.nonceImplicitLength; 187 | 188 | cipher.authTagLength = 16; 189 | 190 | return cipher; 191 | } 192 | 193 | /** 194 | * @param {number} id An internal id of cipher suite. 195 | * @param {string} name A valid cipher suite name. 196 | * @param {string} block A valid nodejs cipher name. 197 | * @param {KeyExchange} kx Key exchange type. 198 | * @param {string} hash 199 | * @returns {AEADCipher} 200 | */ 201 | function createChacha20Cipher(id, name, block, kx, hash = 'sha256') { 202 | const cipher = new Chacha20Poly1305Cipher(); 203 | 204 | cipher.id = id; 205 | cipher.name = name; 206 | cipher.blockAlgorithm = block; 207 | cipher.kx = kx; 208 | cipher.hash = hash; 209 | 210 | return cipher; 211 | } 212 | -------------------------------------------------------------------------------- /src/node_modules/cipher/key-exchange.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const { signTypes, keyTypes, kxTypes } = require('lib/constants'); 4 | 5 | module.exports = { 6 | createRSAKeyExchange, 7 | createECDHERSAKeyExchange, 8 | createECDHEECDSAKeyExchange, 9 | createNULLKeyExchange, 10 | createPSKKeyExchange, 11 | createECDHEPSKKeyExchange, 12 | }; 13 | 14 | /** 15 | * This class represent type of key exchange mechanism. 16 | */ 17 | class KeyExchange { 18 | /** 19 | * @class KeyExchange 20 | */ 21 | constructor() { 22 | this.id = 0; 23 | this.name = null; 24 | 25 | this.signType = null; 26 | this.keyType = null; 27 | } 28 | 29 | /** 30 | * @returns {string} 31 | */ 32 | toString() { 33 | return this.name; 34 | } 35 | } 36 | 37 | /** 38 | * Creates `RSA` key exchange. 39 | * @returns {KeyExchange} 40 | */ 41 | function createRSAKeyExchange() { 42 | const exchange = new KeyExchange(); 43 | 44 | exchange.id = kxTypes.RSA; 45 | exchange.name = 'RSA'; 46 | 47 | exchange.keyType = keyTypes.RSA; 48 | 49 | return exchange; 50 | } 51 | 52 | /** 53 | * Creates `ECDHE_RSA` key exchange. 54 | * @returns {KeyExchange} 55 | */ 56 | function createECDHERSAKeyExchange() { 57 | const exchange = new KeyExchange(); 58 | 59 | exchange.id = kxTypes.ECDHE_RSA; 60 | exchange.name = 'ECDHE_RSA'; 61 | 62 | exchange.signType = signTypes.ECDHE; 63 | exchange.keyType = keyTypes.RSA; 64 | 65 | return exchange; 66 | } 67 | 68 | /** 69 | * Creates `ECDHE_ECDSA` key exchange. 70 | * @returns {KeyExchange} 71 | */ 72 | function createECDHEECDSAKeyExchange() { 73 | const exchange = new KeyExchange(); 74 | 75 | exchange.id = kxTypes.ECDHE_ECDSA; 76 | exchange.name = 'ECDHE_ECDSA'; 77 | 78 | exchange.signType = signTypes.ECDHE; 79 | exchange.keyType = keyTypes.ECDSA; 80 | 81 | return exchange; 82 | } 83 | 84 | /** 85 | * Creates `NULL` key exchange. 86 | * @returns {KeyExchange} 87 | */ 88 | function createNULLKeyExchange() { 89 | const exchange = new KeyExchange(); 90 | 91 | exchange.id = kxTypes.NULL; 92 | exchange.name = 'NULL'; 93 | 94 | exchange.signType = signTypes.NULL; 95 | exchange.keyType = keyTypes.NULL; 96 | 97 | return exchange; 98 | } 99 | 100 | /** 101 | * Creates `PSK` key exchange. 102 | * @returns {KeyExchange} 103 | */ 104 | function createPSKKeyExchange() { 105 | const exchange = new KeyExchange(); 106 | 107 | exchange.id = kxTypes.PSK; 108 | exchange.name = 'PSK'; 109 | 110 | exchange.signType = signTypes.NULL; 111 | exchange.keyType = keyTypes.PSK; 112 | 113 | return exchange; 114 | } 115 | 116 | /** 117 | * Creates `ECDHE_PSK` key exchange. 118 | * @returns {KeyExchange} 119 | */ 120 | function createECDHEPSKKeyExchange() { 121 | const exchange = new KeyExchange(); 122 | 123 | exchange.id = kxTypes.ECDHE_PSK; 124 | exchange.name = 'ECDHE_PSK'; 125 | 126 | exchange.signType = signTypes.ECDHE; 127 | exchange.keyType = keyTypes.PSK; 128 | 129 | return exchange; 130 | } 131 | -------------------------------------------------------------------------------- /src/node_modules/cipher/null.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /* eslint-disable class-methods-use-this */ 4 | 5 | const Cipher = require('cipher/abstract'); 6 | const { createNULLKeyExchange } = require('cipher/key-exchange'); 7 | 8 | /** 9 | * Default passthrough cipher. 10 | */ 11 | module.exports = class NullCipher extends Cipher { 12 | /** 13 | * @class NullCipher 14 | */ 15 | constructor() { 16 | super(); 17 | 18 | this.name = 'NULL_NULL_NULL'; // key, mac, hash 19 | this.blockAlgorithm = 'NULL'; 20 | this.kx = createNULLKeyExchange(); 21 | this.hash = 'NULL'; 22 | } 23 | 24 | /** 25 | * Encrypts data. 26 | * @param {AbstractSession} session 27 | * @param {Buffer} data Content to encryption. 28 | * @returns {Buffer} 29 | */ 30 | encrypt(session, data) { 31 | return data; 32 | } 33 | 34 | /** 35 | * Decrypts data. 36 | * @param {AbstractSession} session 37 | * @param {Buffer} data Content to encryption. 38 | * @returns {Buffer} 39 | */ 40 | decrypt(session, data) { 41 | return data; 42 | } 43 | }; 44 | -------------------------------------------------------------------------------- /src/node_modules/cipher/utils.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const crypto = require('crypto'); 4 | 5 | module.exports = { 6 | hmac, 7 | phash, 8 | }; 9 | 10 | /** 11 | * Culculates HMAC using provided hash. 12 | * @param {string} algorithm - Hash algorithm. 13 | * @param {Buffer} secret - Hmac seed. 14 | * @param {Buffer} data - Input data. 15 | * @returns {Buffer} 16 | */ 17 | function hmac(algorithm, secret, data) { 18 | const hash = crypto.createHmac(algorithm, secret); 19 | hash.update(data); 20 | return hash.digest(); 21 | } 22 | 23 | /** 24 | * A data expansion function for PRF. 25 | * @param {number} bytes - The number of bytes required by PRF. 26 | * @param {string} algorithm - Hmac hash algorithm. 27 | * @param {Buffer} secret - Hmac secret. 28 | * @param {Buffer} seed - Input data. 29 | * @returns {Buffer} 30 | */ 31 | function phash(bytes, algorithm, secret, seed) { 32 | const totalLength = bytes; 33 | const bufs = []; 34 | let Ai = seed; // A0 35 | 36 | do { 37 | Ai = hmac(algorithm, secret, Ai); // A(i) = HMAC(secret, A(i-1)) 38 | const output = hmac(algorithm, secret, Buffer.concat([Ai, seed])); 39 | 40 | bufs.push(output); 41 | bytes -= output.length; // eslint-disable-line no-param-reassign 42 | } while (bytes > 0); 43 | 44 | return Buffer.concat(bufs, totalLength); 45 | } 46 | -------------------------------------------------------------------------------- /src/node_modules/filter/decoder.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const { Transform } = require('readable-stream'); 4 | const { decode, createDecode } = require('binary-data'); 5 | const { contentType } = require('lib/constants'); 6 | const { DTLSPlaintext, Handshake } = require('lib/protocol'); 7 | const debug = require('utils/debug')('dtls:decoder'); 8 | 9 | const _session = Symbol('_session'); 10 | 11 | /** 12 | * Decode record and handshake layer messages into objects. 13 | */ 14 | module.exports = class Decoder extends Transform { 15 | /** 16 | * @class Decoder 17 | * @param {AbstractSession} session 18 | */ 19 | constructor(session) { 20 | super({ 21 | writableObjectMode: false, 22 | readableObjectMode: true, 23 | }); 24 | 25 | this[_session] = session; 26 | } 27 | 28 | /** 29 | * @returns {AbstractSession} 30 | */ 31 | get session() { 32 | return this[_session]; 33 | } 34 | 35 | /** 36 | * @private 37 | * @param {*} chunk 38 | * @param {*} enc 39 | * @param {*} callback 40 | */ 41 | _transform(chunk, enc, callback) { 42 | const stream = createDecode(chunk); 43 | 44 | while (stream.length > 0) { 45 | debug('process new chunk'); 46 | const record = decode(stream, DTLSPlaintext); 47 | debug('decoded %s bytes', decode.bytes); 48 | 49 | const isHandshake = record.type === contentType.HANDSHAKE; 50 | const isAlert = record.type === contentType.ALERT; 51 | const isCipher = record.type === contentType.CHANGE_CIPHER_SPEC; 52 | 53 | if (!isAlert && !isCipher) { 54 | const isPreviousEpoch = 55 | this.session.clientEpoch - this.session.serverEpoch === 1; 56 | const cipher = isPreviousEpoch 57 | ? this.session.prevCipher 58 | : this.session.cipher; 59 | try { 60 | debug('decrypt record layer'); 61 | this.session.decrypt(cipher, record); 62 | debug('decryption success'); 63 | } catch (error) { 64 | debug('decryption error, ignore'); 65 | } 66 | } 67 | 68 | if (isHandshake) { 69 | record.fragment = decode(record.fragment, Handshake); 70 | } 71 | 72 | this.push(record); 73 | } 74 | 75 | callback(); 76 | } 77 | }; 78 | -------------------------------------------------------------------------------- /src/node_modules/filter/defragmentation.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /* eslint-disable class-methods-use-this */ 4 | 5 | const { Transform } = require('readable-stream'); 6 | const { contentType } = require('lib/constants'); 7 | const debug = require('utils/debug')('dtls:fragment'); 8 | 9 | const _queue = Symbol('_queue'); 10 | 11 | /** 12 | * This class drops incoming handshake defragmentation. 13 | */ 14 | module.exports = class Defragmentation extends Transform { 15 | /** 16 | * @class Defragmentation 17 | */ 18 | constructor() { 19 | super({ objectMode: true }); 20 | 21 | this[_queue] = []; 22 | } 23 | 24 | /** 25 | * @private 26 | * @param {Object} record Record / handshake message. 27 | * @param {string} enc 28 | * @param {Function} callback 29 | */ 30 | _transform(record, enc, callback) { 31 | if (record.type !== contentType.HANDSHAKE) { 32 | callback(null, record); 33 | return; 34 | } 35 | 36 | const handshake = record.fragment; 37 | const endOffset = handshake.fragment.offset + handshake.fragment.length; 38 | 39 | // Check for invalid fragment. 40 | const isInvalidLength = endOffset > handshake.length; 41 | 42 | if (handshake.length > 0 && isInvalidLength) { 43 | debug('Unexpected packet length'); 44 | callback(); 45 | return; 46 | } 47 | 48 | // Handle fragments. 49 | if (handshake.length > handshake.fragment.length) { 50 | // Incomplete message, waiting for next fragment. 51 | if (handshake.length > endOffset) { 52 | debug('got incomplete fragment'); 53 | 54 | this[_queue].push(handshake); 55 | callback(); 56 | return; 57 | } 58 | 59 | debug('got final fragment'); 60 | 61 | // Reassembly handshake. 62 | this[_queue].push(handshake); 63 | const queue = this[_queue].map(packet => packet.body); 64 | const fragment = Buffer.concat(queue); 65 | 66 | handshake.fragment.offset = 0; 67 | handshake.fragment.length = fragment.length; 68 | handshake.body = fragment; 69 | 70 | if (handshake.length !== fragment.length) { 71 | debug(new Error('Invalid fragment.')); 72 | callback(); 73 | return; 74 | } 75 | 76 | // Reset queue. 77 | this[_queue].length = 0; 78 | 79 | debug('complete handshake fragment, length = %s', handshake.length); 80 | record.fragment = handshake; 81 | this.push(record); 82 | } else { 83 | debug('got full handshake'); 84 | this.push(record); 85 | } 86 | 87 | callback(); 88 | } 89 | }; 90 | -------------------------------------------------------------------------------- /src/node_modules/filter/reordering.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const { Transform } = require('readable-stream'); 4 | const sorter = require('sorted-array-functions'); 5 | const { contentType } = require('lib/constants'); 6 | const debug = require('utils/debug')('dtls:reorder'); 7 | 8 | const _session = Symbol('_session'); 9 | const _queue = Symbol('_queue'); 10 | 11 | /** 12 | * Insert comparator for ordered records queue. 13 | * @param {Object} recordLeft 14 | * @param {Object} recordRight 15 | * @returns {number} 16 | */ 17 | function comparator(recordLeft, recordRight) { 18 | return recordLeft.sequenceNumber > recordRight.sequenceNumber ? 1 : -1; 19 | } 20 | 21 | /** 22 | * Handles reordering of a handshake message. 23 | */ 24 | module.exports = class Reordering extends Transform { 25 | /** 26 | * @class Reordering 27 | * @param {AbstractSession} session 28 | */ 29 | constructor(session) { 30 | super({ objectMode: true }); 31 | 32 | this[_session] = session; 33 | this[_queue] = []; 34 | 35 | // 3.2 36 | session.retransmitter.on('timeout', () => { 37 | this[_queue].length = 0; 38 | }); 39 | } 40 | 41 | /** 42 | * @returns {AbstractSession} 43 | */ 44 | get session() { 45 | return this[_session]; 46 | } 47 | 48 | /** 49 | * @returns {number} 50 | */ 51 | get queueSize() { 52 | return this[_queue].length; 53 | } 54 | 55 | /** 56 | * @private 57 | * @param {Object} record Record / handshake message. 58 | * @param {string} enc 59 | * @param {Function} callback 60 | */ 61 | _transform(record, enc, callback) { 62 | // 1. check epoch 63 | // 2. check sliding window 64 | // 3. if handshake and got handshake, check last received handshake number 65 | // 3.1 if handshake sequence number <= lastRvHandshake, drop 66 | // 3.2 if retransmit timeout have got, reset queue 67 | // 3.3 if expected handshake number equals to sequence number 68 | // 3.3.1 if packet without fragmentation, use it 69 | // 3.3.2 if fragmentation, add to the sorted queue 70 | // 3.3.3 if fragmentation and queue is not empty, check if we have full framented packet in queue 71 | // 3.3.4 if queue is not empty again, drain (future) messages 72 | // 3.4 if handshake sequence number > expected handshake number, add to the sorted queue 73 | // 3.5 check queue 74 | // 4. if handshake and got not handshake, use packet 75 | // the queue will be always empty in the last flight [CCS, FINISHED]. 76 | // if we got Finished before CCS, it will be silently discarded due to mismatch epoches 77 | // 5. if not handshake, use packet 78 | // 6. reset queue after complete handshake 79 | // 7. ヘ(>_<ヘ) 80 | 81 | const isHandshake = record.type === contentType.HANDSHAKE; 82 | const isReplay = 83 | isHandshake && record.fragment.sequence <= this.session.lastRvHandshake; 84 | const expectedHandshake = this.session.lastRvHandshake + 1; 85 | 86 | // 1 87 | if (this.session.peerEpoch !== record.epoch) { 88 | debug( 89 | 'mismatch epoch: got %s, expected %s', 90 | record.epoch, 91 | this.session.peerEpoch 92 | ); 93 | callback(); 94 | return; 95 | } 96 | 97 | // 2 98 | if (!this.session.window.check(record.sequenceNumber)) { 99 | const seq = record.sequenceNumber; 100 | 101 | debug('record layer replay probably, seq = %s', seq, this.session.window); 102 | callback(); 103 | return; 104 | } 105 | 106 | // 3 107 | if (this.session.isHandshakeInProcess) { 108 | if (isHandshake) { 109 | // 3.1 110 | if (isReplay) { 111 | debug('handshake replay detected, drop'); 112 | callback(); 113 | return; 114 | } 115 | 116 | // 3.3 117 | if (expectedHandshake === record.fragment.sequence) { 118 | debug('success, matched handshake seq number'); 119 | this.push(record); 120 | } 121 | // 3.4 122 | else if (expectedHandshake < record.fragment.sequence) { 123 | debug('save record to the queue, waiting for next packet'); 124 | sorter.add(this[_queue], record, comparator); 125 | } 126 | 127 | // 3.5 128 | if (this[_queue].length > 0) { 129 | let i = 0; 130 | let touched = false; 131 | 132 | for (; i < this[_queue].length; i += 1) { 133 | const packet = this[_queue][i]; 134 | const nextHandshake = this.session.lastRvHandshake + 1; 135 | 136 | if (nextHandshake === packet.fragment.sequence) { 137 | this.push(packet); 138 | touched = true; 139 | } 140 | } 141 | 142 | if (touched) { 143 | this[_queue].splice(0, i + 1); 144 | } 145 | } 146 | } 147 | // 4 148 | else { 149 | // !isHandshake 150 | debug('success, got message type = %s', record.type); 151 | this.push(record); 152 | } 153 | } 154 | // 5 155 | else { 156 | // !this.session.isHandshakeInProcess 157 | debug('success'); 158 | this.push(record); 159 | } 160 | 161 | callback(); 162 | } 163 | }; 164 | -------------------------------------------------------------------------------- /src/node_modules/lib/constants.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const isChachaSupported = require('is-chacha20-poly1305-supported'); 4 | 5 | /** 6 | * Alert protocol. 7 | * @link https://tools.ietf.org/html/rfc5246#section-7.2 8 | */ 9 | const alertLevel = { 10 | WARNING: 1, 11 | FATAL: 2, 12 | }; 13 | 14 | const alertDescription = { 15 | CLOSE_NOTIFY: 0, 16 | UNEXPECTED_MESSAGE: 10, 17 | BAD_RECORD_MAC: 20, 18 | DECRYPTION_FAILED_RESERVED: 21, 19 | RECORD_OVERFLOW: 22, 20 | DECOMPRESSION_FAILURE: 30, 21 | HANDSHAKE_FAILURE: 40, 22 | NO_CERTIFICATE_RESERVED: 41, 23 | BAD_CERTIFICATE: 42, 24 | UNSUPPORTED_CERTIFICATE: 43, 25 | CERTIFICATE_REVOKED: 44, 26 | CERTIFICATE_EXPIRED: 45, 27 | CERTIFICATE_UNKNOWN: 46, 28 | ILLEGAL_PARAMETER: 47, 29 | UNKNOWN_CA: 48, 30 | ACCESS_DENIED: 49, 31 | DECODE_ERROR: 50, 32 | DECRYPT_ERROR: 51, 33 | EXPORT_RESTRICTION_RESERVED: 60, 34 | PROTOCOL_VERSION: 70, 35 | INSUFFICIENT_SECURITY: 71, 36 | INTERNAL_ERROR: 80, 37 | USER_CANCELED: 90, 38 | NO_RENEGOTIATION: 100, 39 | UNSUPPORTED_EXTENSION: 110, 40 | }; 41 | 42 | const sessionType = { 43 | CLIENT: 1, 44 | SERVER: 2, 45 | }; 46 | 47 | /** 48 | * Handshake Protocol 49 | * @link https://tools.ietf.org/html/rfc6347#section-4.3.2 50 | */ 51 | const handshakeType = { 52 | HELLO_REQUEST: 0, 53 | CLIENT_HELLO: 1, 54 | SERVER_HELLO: 2, 55 | HELLO_VERIFY_REQUEST: 3, 56 | CERTIFICATE: 11, 57 | SERVER_KEY_EXCHANGE: 12, 58 | CERTIFICATE_REQUEST: 13, 59 | SERVER_HELLO_DONE: 14, 60 | CERTIFICATE_VERIFY: 15, 61 | CLIENT_KEY_EXCHANGE: 16, 62 | FINISHED: 20, 63 | }; 64 | 65 | const contentType = { 66 | CHANGE_CIPHER_SPEC: 20, 67 | ALERT: 21, 68 | HANDSHAKE: 22, 69 | APPLICATION_DATA: 23, 70 | }; 71 | 72 | const protocolVersion = { 73 | DTLS_1_0: 0xfeff, 74 | DTLS_1_2: 0xfefd, 75 | }; 76 | 77 | const cipherSuites = { 78 | TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256: 0xc02b, 79 | TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384: 0xc02c, 80 | TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256: 0xc02f, 81 | TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384: 0xc030, 82 | TLS_RSA_WITH_AES_128_GCM_SHA256: 0x009c, 83 | TLS_RSA_WITH_AES_256_GCM_SHA384: 0x009d, 84 | TLS_PSK_WITH_AES_128_GCM_SHA256: 0x00a8, 85 | TLS_PSK_WITH_AES_256_GCM_SHA384: 0x00a9, 86 | TLS_ECDHE_PSK_WITH_AES_128_GCM_SHA256: 0xd001, 87 | TLS_ECDHE_PSK_WITH_AES_256_GCM_SHA384: 0xd002, 88 | TLS_ECDHE_PSK_WITH_CHACHA20_POLY1305_SHA256: 0xccac, 89 | TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256: 0xcca9, 90 | TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256: 0xcca8, 91 | TLS_PSK_WITH_CHACHA20_POLY1305_SHA256: 0xccab, 92 | }; 93 | 94 | const defaultCipherSuites = [ 95 | cipherSuites.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, 96 | cipherSuites.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384, 97 | cipherSuites.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, 98 | cipherSuites.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384, 99 | ]; 100 | 101 | if (isChachaSupported) { 102 | defaultCipherSuites.unshift( 103 | cipherSuites.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256, 104 | cipherSuites.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256 105 | ); 106 | } 107 | 108 | const compressionMethod = { 109 | NULL: 0, 110 | }; 111 | 112 | const extensionTypes = { 113 | EXTENDED_MASTER_SECRET: 23, 114 | ELLIPTIC_CURVES: 10, 115 | EC_POINT_FORMATS: 11, 116 | APPLICATION_LAYER_PROTOCOL_NEGOTIATION: 16, 117 | }; 118 | 119 | const AEAD_AES_128_GCM = { 120 | K_LEN: 16, // Length of a key. 121 | N_MIN: 12, // Min nonce length. 122 | N_MAX: 12, // Max nonce length. 123 | P_MAX: 2 ** 36 - 31, // Max length of a plaintext. 124 | 125 | // Max safe int in js is 2 ** 53. So, use this value 126 | // instead of 2 ** 61 as described in rfc5116. 127 | A_MAX: 2 ** 53 - 1, // Max length of an additional data. 128 | C_MAX: 2 ** 36 - 15, // Cipher text length. 129 | }; 130 | 131 | const AEAD_AES_256_GCM = { 132 | K_LEN: 32, // Length of a key. 133 | N_MIN: 12, // Min nonce length. 134 | N_MAX: 12, // Max nonce length. 135 | P_MAX: 2 ** 36 - 31, // Max length of a plaintext. 136 | 137 | // Note: see above. 138 | A_MAX: 2 ** 53 - 1, // Max length of an additional data. 139 | C_MAX: 2 ** 36 - 15, // Cipher text length. 140 | }; 141 | 142 | const AEAD_CHACHA20_POLY1305 = { 143 | K_LEN: 32, // Length of a key. 144 | N_MIN: 12, // Min nonce length. 145 | N_MAX: 12, // Max nonce length. 146 | P_MAX: 247877906880, // Max length of a plaintext (~256 GB). 147 | 148 | // Max safe int in js is 2 ** 53. So, use this value 149 | // instead of 2 ** 64 - 1 as described in rfc7539. 150 | A_MAX: 2 ** 53 - 1, // Max length of an additional data. 151 | C_MAX: 247877906896, // Cipher text length. 152 | }; 153 | 154 | const randomSize = 32; 155 | const maxSessionIdSize = 32; 156 | 157 | const namedCurves = { 158 | // curves 1 - 22 was deprecated 159 | // secp256r1: 23, do not support by nodejs 160 | secp384r1: 24, 161 | secp521r1: 25, 162 | // x25519: 29, do not support by nodejs 163 | // x448: 30, do not support by nodejs 164 | }; 165 | 166 | const ecCurveTypes = { 167 | namedCurve: 3, 168 | }; 169 | 170 | const signTypes = { 171 | NULL: 0, 172 | ECDHE: 1, 173 | }; 174 | 175 | const keyTypes = { 176 | NULL: 0, 177 | RSA: 1, 178 | ECDSA: 2, 179 | PSK: 3, 180 | }; 181 | 182 | const kxTypes = { 183 | NULL: 0, 184 | RSA: 1, 185 | ECDHE_RSA: 2, 186 | ECDHE_ECDSA: 3, 187 | PSK: 4, 188 | ECDHE_PSK: 5, 189 | }; 190 | 191 | // TLS 1.3 signature algorithms 192 | // https://tools.ietf.org/html/rfc8446#section-4.2.3 193 | const signatureScheme = { 194 | /* RSASSA-PKCS1-v1_5 algorithms */ 195 | rsa_pkcs1_sha256: 0x0401, 196 | rsa_pkcs1_sha384: 0x0501, 197 | rsa_pkcs1_sha512: 0x0601, 198 | 199 | /* ECDSA algorithms */ 200 | ecdsa_secp256r1_sha256: 0x0403, 201 | ecdsa_secp384r1_sha384: 0x0503, 202 | ecdsa_secp521r1_sha512: 0x0603, 203 | 204 | /* RSASSA-PSS algorithms with public key OID rsaEncryption */ 205 | rsa_pss_rsae_sha256: 0x0804, 206 | rsa_pss_rsae_sha384: 0x0805, 207 | rsa_pss_rsae_sha512: 0x0806, 208 | 209 | /* EdDSA algorithms */ 210 | ed25519: 0x0807, 211 | ed448: 0x0808, 212 | 213 | /* RSASSA-PSS algorithms with public key OID RSASSA-PSS */ 214 | rsa_pss_pss_sha256: 0x0809, 215 | rsa_pss_pss_sha384: 0x080a, 216 | rsa_pss_pss_sha512: 0x080b, 217 | 218 | /* Legacy algorithms */ 219 | rsa_pkcs1_sha1: 0x0201, 220 | ecdsa_sha1: 0x0203, 221 | }; 222 | 223 | const certificateType = { 224 | // rfc5246-defined types 225 | rsa_sign: 1, 226 | dss_sign: 2, 227 | 228 | // rfc8422-defined types 229 | ecdsa_sign: 64, 230 | }; 231 | 232 | module.exports = { 233 | alertLevel, 234 | alertDescription, 235 | sessionType, 236 | handshakeType, 237 | contentType, 238 | protocolVersion, 239 | cipherSuites, 240 | compressionMethod, 241 | extensionTypes, 242 | AEAD_AES_128_GCM, 243 | AEAD_AES_256_GCM, 244 | AEAD_CHACHA20_POLY1305, 245 | randomSize, 246 | maxSessionIdSize, 247 | namedCurves, 248 | ecCurveTypes, 249 | signTypes, 250 | keyTypes, 251 | kxTypes, 252 | signatureScheme, 253 | certificateType, 254 | defaultCipherSuites, 255 | isChachaSupported, 256 | }; 257 | -------------------------------------------------------------------------------- /src/node_modules/lib/cookie-manager.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const Events = require('events'); 4 | const crypto = require('crypto'); 5 | const ms = require('ms'); 6 | const timeout = require('timeout-refresh'); 7 | const debug = require('utils/debug')('dtls:cookie'); 8 | 9 | const _renewTimer = Symbol('renewTimer'); 10 | const _expireTimer = Symbol('expireTimer'); 11 | const _cookie = Symbol('cookie'); 12 | const _cookieSize = Symbol('cookieSize'); 13 | const _prevCookie = Symbol('prevCookie'); 14 | 15 | const defaultOptions = { 16 | renew: ms('30m'), 17 | expire: ms('30s'), 18 | size: 10, 19 | }; 20 | 21 | /** 22 | * @typedef {Object} CookieManagerOptions 23 | * @property {number} renew Time to update cookie 24 | * @property {number} expire Time to expire previous cookie 25 | * @property {number} size Cookie size 26 | */ 27 | 28 | /** 29 | * This class manage server cookies. 30 | */ 31 | module.exports = class CookieManager extends Events { 32 | /** 33 | * @class CookieManager 34 | * @param {CookieManagerOptions} options 35 | */ 36 | constructor(options = defaultOptions) { 37 | super(); 38 | 39 | if (options !== defaultOptions) { 40 | options = Object.assign({}, defaultOptions, options); // eslint-disable-line no-param-reassign 41 | } 42 | 43 | debug('start cookie manager'); 44 | 45 | // this[_timeout] = timeout; 46 | this[_prevCookie] = null; 47 | this[_cookieSize] = options.size; 48 | 49 | // Sync for first time. 50 | this[_cookie] = crypto.randomBytes(options.size); 51 | 52 | const expireTimer = timeout(options.expire, () => { 53 | this[_prevCookie] = null; 54 | debug('drop previous cookie'); 55 | }); 56 | 57 | const renewTimer = timeout(options.renew, () => { 58 | crypto.randomBytes(options.size, (err, cookie) => { 59 | if (!err) { 60 | this[_prevCookie] = this[_cookie]; 61 | this[_cookie] = cookie; 62 | 63 | debug('cookie was updated'); 64 | this.emit('renew', cookie); 65 | } else { 66 | debug('got error', err); 67 | this.emit('error', err); 68 | } 69 | 70 | expireTimer.refresh(); 71 | renewTimer.refresh(); 72 | }); 73 | }); 74 | 75 | this[_renewTimer] = renewTimer; 76 | this[_expireTimer] = expireTimer; 77 | } 78 | 79 | /** 80 | * Validate the cookie. 81 | * @param {Buffer} cookie Recived cookie. 82 | * @returns {boolean} 83 | */ 84 | validate(cookie) { 85 | if (!Buffer.isBuffer(cookie)) { 86 | throw new TypeError('Expected a Buffer'); 87 | } 88 | 89 | if (Buffer.compare(cookie, this.current) === 0) { 90 | return true; 91 | } 92 | 93 | if ( 94 | Buffer.isBuffer(this.previous) && 95 | Buffer.compare(cookie, this.previous) === 0 96 | ) { 97 | return true; 98 | } 99 | 100 | return false; 101 | } 102 | 103 | /** 104 | * Get current cookie. 105 | * @returns {Buffer} 106 | */ 107 | get current() { 108 | return this[_cookie]; 109 | } 110 | 111 | /** 112 | * Get previous cookie until it's fully expired. 113 | * @returns {Buffer} Get `null` if expired. 114 | */ 115 | get previous() { 116 | return this[_prevCookie]; 117 | } 118 | 119 | /** 120 | * Destroy cookie manager. 121 | */ 122 | destroy() { 123 | this[_renewTimer].destroy(); 124 | this[_expireTimer].destroy(); 125 | } 126 | }; 127 | -------------------------------------------------------------------------------- /src/node_modules/lib/protocol.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const { 4 | types: { 5 | uint8, 6 | uint16be, 7 | uint48be, 8 | uint24be, 9 | buffer, 10 | array, 11 | when, 12 | select, 13 | string, 14 | }, 15 | } = require('binary-data'); 16 | const { ecCurveTypes } = require('lib/constants'); 17 | 18 | /** 19 | * Internal type for trivial errors check. 20 | * @private 21 | * @param {string} errorMessage 22 | * @returns {Object} The `binary-data` compatible type. 23 | */ 24 | function assertType(errorMessage) { 25 | return { 26 | encode: () => { 27 | throw new Error(errorMessage); 28 | }, 29 | decode: () => { 30 | throw new Error(errorMessage); 31 | }, 32 | encodingLength: () => { 33 | throw new Error(errorMessage); 34 | }, 35 | }; 36 | } 37 | 38 | // Record layer. 39 | 40 | const ProtocolVersion = uint16be; 41 | 42 | const ContentType = uint8; 43 | 44 | const DTLSPlaintextHeader = { 45 | type: ContentType, 46 | version: ProtocolVersion, 47 | epoch: uint16be, 48 | sequenceNumber: uint48be, 49 | length: uint16be, 50 | }; 51 | 52 | const DTLSPlaintext = { 53 | ...DTLSPlaintextHeader, 54 | fragment: buffer(context => context.current.length), 55 | }; 56 | 57 | const AlertLevel = uint8; 58 | const AlertDescription = uint8; 59 | 60 | const Alert = { 61 | level: AlertLevel, 62 | description: AlertDescription, 63 | }; 64 | 65 | // Handshake Protocol 66 | 67 | const HandshakeType = uint8; 68 | 69 | const HandshakeHeader = { 70 | type: HandshakeType, 71 | length: uint24be, 72 | sequence: uint16be, 73 | fragment: { 74 | offset: uint24be, 75 | length: uint24be, 76 | }, 77 | }; 78 | 79 | const Handshake = { 80 | ...HandshakeHeader, 81 | body: buffer(({ current }) => current.fragment.length), 82 | }; 83 | 84 | const Random = buffer(32); 85 | 86 | const SessionID = buffer(uint8); 87 | 88 | const CipherSuite = uint16be; 89 | 90 | const CompressionMethod = uint8; 91 | 92 | const ClientHello = { 93 | clientVersion: ProtocolVersion, 94 | random: Random, // Unixtime + 28 random bytes., 95 | sessionId: SessionID, 96 | cookie: buffer(uint8), 97 | cipherSuites: array(CipherSuite, uint16be, 'bytes'), 98 | compressionMethods: array(CompressionMethod, uint8, 'bytes'), 99 | }; 100 | 101 | const HelloVerifyRequest = { 102 | serverVersion: ProtocolVersion, 103 | cookie: buffer(uint8), 104 | }; 105 | 106 | const ExtensionType = uint16be; 107 | 108 | const Extension = { 109 | type: ExtensionType, 110 | data: buffer(uint16be), 111 | }; 112 | 113 | const ExtensionList = array(Extension, uint16be, 'bytes'); 114 | 115 | const ServerHello = { 116 | serverVersion: ProtocolVersion, 117 | random: Random, // Unixtime + 28 random bytes. 118 | sessionId: SessionID, 119 | cipherSuite: CipherSuite, 120 | compressionMethod: CompressionMethod, 121 | }; 122 | 123 | const ASN11Cert = buffer(uint24be); 124 | 125 | const Certificate = { 126 | certificateList: array(ASN11Cert, uint24be, 'bytes'), 127 | }; 128 | 129 | const EncryptedPreMasterSecret = buffer(uint16be); 130 | 131 | const AEADAdditionalData = { 132 | epoch: uint16be, 133 | sequence: uint48be, 134 | type: ContentType, 135 | version: ProtocolVersion, 136 | length: uint16be, 137 | }; 138 | 139 | const NamedCurve = uint16be; 140 | 141 | const NamedCurveList = array(NamedCurve, uint16be, 'bytes'); 142 | 143 | const SignatureAlgorithm = uint16be; 144 | 145 | const ECPublicKey = buffer(uint8); 146 | 147 | const ServerECDHParams = { 148 | curveType: uint8, 149 | curve: select( 150 | when( 151 | ({ current }) => current.curveType === ecCurveTypes.namedCurve, 152 | NamedCurve 153 | ), 154 | assertType('Invalid curve type') 155 | ), 156 | pubkey: ECPublicKey, 157 | }; 158 | 159 | // RFC5246, section-4.7 160 | const DigitallySigned = { 161 | algorithm: SignatureAlgorithm, 162 | signature: buffer(uint16be), 163 | }; 164 | 165 | const ClientCertificateType = uint8; 166 | const DistinguishedName = string(uint16be); 167 | 168 | const CertificateRequest = { 169 | certificateTypes: array(ClientCertificateType, uint8, 'bytes'), 170 | signatures: array(SignatureAlgorithm, uint16be, 'bytes'), 171 | authorities: array(DistinguishedName, uint16be, 'bytes'), 172 | }; 173 | 174 | const ALPNProtocolName = string(uint8); 175 | const ALPNProtocolNameList = array(ALPNProtocolName, uint16be, 'bytes'); 176 | 177 | // RFC4279, section-2 178 | const ServerPSKIdentityHint = buffer(uint16be); 179 | 180 | module.exports = { 181 | DTLSPlaintextHeader, 182 | DTLSPlaintext, 183 | Alert, 184 | Handshake, 185 | HandshakeHeader, 186 | ClientHello, 187 | HelloVerifyRequest, 188 | ExtensionList, 189 | ServerHello, 190 | Certificate, 191 | EncryptedPreMasterSecret, 192 | AEADAdditionalData, 193 | NamedCurve, 194 | NamedCurveList, 195 | ECPublicKey, 196 | SignatureAlgorithm, 197 | ServerECDHParams, 198 | DigitallySigned, 199 | ClientCertificateType, 200 | CertificateRequest, 201 | ALPNProtocolNameList, 202 | ServerPSKIdentityHint, 203 | }; 204 | -------------------------------------------------------------------------------- /src/node_modules/lib/retransmitter.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const assert = require('assert'); 4 | const Emitter = require('events'); 5 | const debug = require('utils/debug')('dtls:retransmitter'); 6 | const { createMachine, createState } = require('next-state'); 7 | 8 | const PREPARING = 'preparing'; 9 | const SENDING = 'sending'; 10 | const WAITING = 'waiting'; 11 | const FINISHED = 'finished'; 12 | 13 | const _timer = Symbol('timer'); 14 | const _ms = Symbol('ms'); 15 | const _queue = Symbol('queue'); 16 | const _onTimeout = Symbol('onTimeout'); 17 | const _tries = Symbol('tries'); 18 | const _machine = Symbol('machine'); 19 | 20 | /** 21 | * Allowed state transitions. 22 | * @see https://tools.ietf.org/html/rfc6347#section-4.2.4 23 | */ 24 | const transitions = { 25 | [PREPARING]: createState(SENDING), 26 | [SENDING]: createState(WAITING, FINISHED), 27 | [WAITING]: createState(PREPARING, SENDING, FINISHED), 28 | [FINISHED]: createState(PREPARING), 29 | }; 30 | 31 | const INIT_TIMEOUT = 1e3; // Initial timer is 1s 32 | 33 | /** 34 | * Timeout handler. 35 | * @param {Timer} self 36 | */ 37 | function ontimeout(self) { 38 | self.emit('timeout'); 39 | } 40 | 41 | /** 42 | * This class implements convenient timers. 43 | */ 44 | class Timer extends Emitter { 45 | /** 46 | * @class Timer 47 | */ 48 | constructor() { 49 | super(); 50 | 51 | this[_onTimeout] = null; 52 | } 53 | 54 | /** 55 | * Stop active timeout. 56 | */ 57 | stop() { 58 | if (this[_onTimeout] !== null) { 59 | clearTimeout(this[_onTimeout]); 60 | this[_onTimeout] = null; 61 | } 62 | } 63 | 64 | /** 65 | * Restart current timeout. 66 | * @param {number} ms 67 | */ 68 | restart(ms) { 69 | this.stop(); 70 | 71 | this[_onTimeout] = setTimeout(ontimeout, ms, this).unref(); 72 | } 73 | } 74 | 75 | /** 76 | * Timeout and Retransmission State Machine. 77 | */ 78 | class RetransmitMachine extends Emitter { 79 | /** 80 | * @class RetransmitMachine 81 | * @param {string} initialState 82 | */ 83 | constructor(initialState) { 84 | super(); 85 | 86 | const timer = new Timer(); 87 | const machine = createMachine(transitions, initialState); 88 | const queue = []; 89 | 90 | this[_timer] = timer; 91 | this[_ms] = INIT_TIMEOUT; 92 | this[_tries] = 0; 93 | this[_machine] = machine; 94 | this[_queue] = queue; 95 | 96 | // Implementations SHOULD use an initial timer value 97 | // of 1 second and double the value at each retransmission, 98 | // up to no less than the RFC 6298 maximum of 60 seconds. 99 | this.maxTries = Math.log2(64) + 1; 100 | 101 | timer.on('timeout', () => { 102 | this[_tries] += 1; 103 | 104 | if (this[_tries] > this.maxTries) { 105 | debug('got timeout, max tries (%s) is reached, close', this.maxTries); 106 | this.close(); 107 | return; 108 | } 109 | 110 | /** 111 | * Double the value at each retransmission 112 | * @see https://tools.ietf.org/html/rfc6347#section-4.2.4.1 113 | */ 114 | this[_ms] = Math.min(this[_ms] * 2, 60e3); 115 | 116 | debug('got timeout, next time is %s ms', this[_ms]); 117 | this.emit('timeout'); 118 | }); 119 | 120 | this.on('timeout', () => { 121 | if (queue.length === 0) { 122 | debug('empty queue, ignore'); 123 | return; 124 | } 125 | 126 | if (this.state !== WAITING) { 127 | return; 128 | } 129 | 130 | debug('send stored messages again'); 131 | queue.forEach(item => this.emit('data', item)); 132 | this.send(); 133 | }); 134 | 135 | [PREPARING, SENDING, WAITING, FINISHED].forEach(event => 136 | machine.on(event, () => this.emit(event)) 137 | ); 138 | } 139 | 140 | /** 141 | * Get the current state of the State Machine. 142 | */ 143 | get state() { 144 | return this[_machine].state; 145 | } 146 | 147 | /** 148 | * @returns {Timer} 149 | */ 150 | get timer() { 151 | return this[_timer]; 152 | } 153 | 154 | /** 155 | * Change state to `FINISHED`. 156 | */ 157 | finish() { 158 | this.timer.stop(); 159 | this[_queue].length = 0; 160 | this[_machine].next(FINISHED); 161 | } 162 | 163 | /** 164 | * Change state to `WAITING`. 165 | */ 166 | wait() { 167 | const ms = this[_ms]; 168 | 169 | this.timer.restart(ms); 170 | this[_machine].next(WAITING); 171 | } 172 | 173 | /** 174 | * Change state to `SENDING`. 175 | */ 176 | send() { 177 | this[_machine].next(SENDING); 178 | } 179 | 180 | /** 181 | * Change state to `PREPARING`. 182 | */ 183 | prepare() { 184 | this.timer.stop(); 185 | this[_queue].length = 0; 186 | 187 | // After every success data transfer reset timer to 188 | // it's initial value. 189 | this[_ms] = INIT_TIMEOUT; 190 | this[_machine].next(PREPARING); 191 | } 192 | 193 | /** 194 | * Create a new flight and buffer it. 195 | * @param {number} type 196 | * @param {number} epoch 197 | * @param {Buffer} packet Handshake messages. 198 | */ 199 | append(type, epoch, packet) { 200 | assert(this.state === PREPARING); 201 | debug('save packet'); 202 | 203 | this[_queue].push({ type, epoch, packet }); 204 | } 205 | 206 | /** 207 | * Close the State Machine. 208 | */ 209 | close() { 210 | this.timer.stop(); 211 | this.emit('close'); 212 | } 213 | } 214 | 215 | /** 216 | * Create Timeout and Retransmission State Machine 217 | * for the clients. 218 | * @returns {RetransmitMachine} 219 | */ 220 | function createRetransmitClient() { 221 | return new RetransmitMachine(PREPARING); 222 | } 223 | 224 | /** 225 | * Create Timeout and Retransmission State Machine 226 | * for the servers. 227 | * @returns {RetransmitMachine} 228 | */ 229 | function createRetransmitServer() { 230 | return new RetransmitMachine(WAITING); 231 | } 232 | 233 | module.exports = { 234 | createRetransmitClient, 235 | createRetransmitServer, 236 | RetransmitMachine, 237 | states: { 238 | PREPARING, 239 | SENDING, 240 | WAITING, 241 | FINISHED, 242 | }, 243 | }; 244 | -------------------------------------------------------------------------------- /src/node_modules/lib/sender.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const { Readable } = require('readable-stream'); 4 | const debug = require('utils/debug')('dtls:sender'); 5 | const { createEncode, encode, BinaryStream } = require('binary-data'); 6 | const { 7 | contentType, 8 | handshakeType, 9 | compressionMethod, 10 | extensionTypes, 11 | namedCurves, 12 | kxTypes, 13 | signTypes, 14 | keyTypes, 15 | } = require('lib/constants'); 16 | const { 17 | Alert, 18 | ALPNProtocolNameList, 19 | Certificate, 20 | DTLSPlaintext, 21 | Handshake, 22 | ClientHello, 23 | EncryptedPreMasterSecret, 24 | ExtensionList, 25 | ECPublicKey, 26 | DigitallySigned, 27 | ServerPSKIdentityHint, 28 | } = require('lib/protocol'); 29 | const { encryptPreMasterSecret } = require('session/utils'); 30 | const { constants: states, getProtocol } = require('protocol/packet'); 31 | const { 32 | states: { SENDING }, 33 | } = require('lib/retransmitter'); 34 | 35 | const changeCipherSpecMessage = Buffer.alloc(1, 1); 36 | const defaultCompressionMethods = [compressionMethod.NULL]; 37 | 38 | const { 39 | CLIENT_HELLO, 40 | FINISHED, 41 | CERTIFICATE, 42 | CHANGE_CIPHER_SPEC, 43 | CLIENT_KEY_EXCHANGE, 44 | HANDSHAKE, 45 | CERTIFICATE_VERIFY, 46 | } = states; 47 | 48 | const EMPTY_BUFFER = Buffer.alloc(0); 49 | 50 | const namedCurvesExtension = Buffer.from([ 51 | 0, 52 | 4, // length in bytes 53 | 0, 54 | namedCurves.secp384r1, 55 | 0, 56 | namedCurves.secp521r1, 57 | ]); 58 | 59 | const ecPointFormatExtension = Buffer.from([ 60 | 1, // length in bytes 61 | 0, // uncompressed points format 62 | ]); 63 | 64 | const DTLS_RECORD_SIZE = 13; 65 | const DTLS_HANDSHAKE_SIZE = 12; 66 | 67 | const _output = Symbol('_output'); 68 | const _session = Symbol('_session'); 69 | const _drain = Symbol('_drain'); 70 | const _bufferDrain = Symbol('_buffer_drain'); 71 | const _queue = Symbol('_queue'); 72 | const _nextPacketQueue = Symbol('_next_packet_queue'); 73 | const _clientHello = Symbol('_client_hello'); 74 | const _finished = Symbol('_finished'); 75 | const _certificate = Symbol('_certificate'); 76 | const _changeCipherSpec = Symbol('_change_cipher_spec'); 77 | const _clientKeyExchange = Symbol('_client_key_exchange'); 78 | const _certificateVerify = Symbol('_certificate_verify'); 79 | const _alert = Symbol('_alert'); 80 | const _applicationData = Symbol('_application_data'); 81 | 82 | const senders = { 83 | [CLIENT_HELLO]: _clientHello, 84 | [FINISHED]: _finished, 85 | [CERTIFICATE]: _certificate, 86 | [CHANGE_CIPHER_SPEC]: _changeCipherSpec, 87 | [CLIENT_KEY_EXCHANGE]: _clientKeyExchange, 88 | [CERTIFICATE_VERIFY]: _certificateVerify, 89 | }; 90 | 91 | module.exports = class Sender extends Readable { 92 | /** 93 | * @param {AbstractSession} session 94 | */ 95 | constructor(session) { 96 | super(); 97 | 98 | const output = { 99 | alert: createEncode(Alert), 100 | record: createEncode(DTLSPlaintext), 101 | handshake: createEncode(Handshake), 102 | }; 103 | 104 | output.alert.on('data', packet => { 105 | this.sendRecord(packet, contentType.ALERT); 106 | }); 107 | 108 | output.handshake.on('data', packet => { 109 | session.retransmitter.append(HANDSHAKE, this.session.clientEpoch, packet); 110 | this.sendRecord(packet, contentType.HANDSHAKE); 111 | }); 112 | 113 | output.record.on('data', packet => this[_bufferDrain](packet)); 114 | 115 | this[_output] = output; 116 | this[_session] = session; 117 | this[_queue] = []; 118 | this[_nextPacketQueue] = new BinaryStream(); 119 | 120 | session.on('send', state => this[senders[state]]()); 121 | 122 | session.on('send:appdata', payload => this[_applicationData](payload)); 123 | session.on('send:alert', (description, level) => 124 | this[_alert](level, description) 125 | ); 126 | 127 | // Merge outgoing handshake packets before send. 128 | session.retransmitter.on(SENDING, () => this[_drain]()); 129 | 130 | // Send stored handshake message again. 131 | session.retransmitter.on('data', ({ type, epoch, packet }) => 132 | this.sendRecord(packet, getProtocol(type), epoch) 133 | ); 134 | } 135 | 136 | /** 137 | * @returns {{alert, record, handshake}} 138 | */ 139 | get output() { 140 | return this[_output]; 141 | } 142 | 143 | /** 144 | * @returns {AbstractSession} 145 | */ 146 | get session() { 147 | return this[_session]; 148 | } 149 | 150 | /** 151 | * @private 152 | */ 153 | _read() {} // eslint-disable-line class-methods-use-this 154 | 155 | /** 156 | * @param {Buffer} message 157 | * @param {contentType} type 158 | * @param {number} [epoch] 159 | */ 160 | sendRecord(message, type, epoch = null) { 161 | const outgoingEpoch = Number.isInteger(epoch) 162 | ? epoch 163 | : this.session.clientEpoch; 164 | 165 | const record = { 166 | type, 167 | version: this.session.version, 168 | epoch: outgoingEpoch, 169 | sequenceNumber: this.session.nextRecordNumber(), 170 | length: message.length, 171 | fragment: message, 172 | }; 173 | 174 | if (type !== contentType.ALERT && type !== contentType.CHANGE_CIPHER_SPEC) { 175 | const isPreviousEpoch = this.session.clientEpoch - outgoingEpoch === 1; 176 | const cipher = isPreviousEpoch 177 | ? this.session.prevCipher 178 | : this.session.cipher; 179 | 180 | debug('encrypt, cipher = %s', cipher.blockAlgorithm); 181 | this.session.encrypt(cipher, record); 182 | debug('success'); 183 | } 184 | 185 | this.output.record.write(record); 186 | } 187 | 188 | /** 189 | * @param {Buffer} message Packet payload. 190 | * @param {handshakeType} type 191 | */ 192 | sendHandshake(message, type) { 193 | const { mtu } = this.session; 194 | const packetLength = this[_nextPacketQueue].length; 195 | 196 | const remainder = mtu - packetLength; 197 | const payloadRemainder = remainder - DTLS_RECORD_SIZE - DTLS_HANDSHAKE_SIZE; 198 | const isEnough = payloadRemainder - message.length; 199 | 200 | // Fragmented handshakes must have the same seq number. 201 | // Also, save this number between parts. 202 | const sequence = this.session.nextHandshakeNumber(); 203 | 204 | /** 205 | * @private 206 | * @param {Buffer} payload 207 | * @param {number} offset 208 | * @returns {Object} 209 | */ 210 | const createPacket = (payload, offset = 0) => ({ 211 | type, 212 | length: message.length, 213 | sequence, 214 | fragment: { 215 | offset, 216 | length: payload.length, 217 | }, 218 | body: payload, 219 | }); 220 | 221 | // Store unfragmented handshake message. 222 | this.session.appendHandshake(createPacket(message)); 223 | 224 | if (isEnough >= 0) { 225 | this.output.handshake.write(createPacket(message)); 226 | } else { 227 | debug( 228 | 'start handshake fragmentation, remainder = %s bytes, data = %s bytes', 229 | payloadRemainder, 230 | message.length 231 | ); 232 | let payloadLength = message.length; 233 | let offset = 0; 234 | 235 | // Send first part 236 | this.output.handshake.write( 237 | createPacket(message.slice(0, payloadRemainder)) 238 | ); 239 | offset += payloadRemainder; 240 | payloadLength -= payloadRemainder; 241 | debug( 242 | 'enqueue %s bytes, %s bytes remaind', 243 | payloadRemainder, 244 | payloadLength 245 | ); 246 | 247 | // Send next parts 248 | while (payloadLength > 0) { 249 | const dataLegth = mtu - DTLS_RECORD_SIZE - DTLS_HANDSHAKE_SIZE; 250 | 251 | this.output.handshake.write( 252 | createPacket(message.slice(offset, offset + dataLegth), offset) 253 | ); 254 | 255 | offset += dataLegth; 256 | payloadLength -= dataLegth; 257 | debug( 258 | 'enqueue %s bytes, %s bytes remaind', 259 | dataLegth, 260 | Math.max(payloadLength, 0) 261 | ); 262 | } 263 | } 264 | } 265 | 266 | /** 267 | * Send `Alert` message. 268 | * @param {number} level 269 | * @param {number} code 270 | */ 271 | sendAlert(level, code) { 272 | debug('send Alert'); 273 | 274 | const message = { 275 | level, 276 | description: code, 277 | }; 278 | 279 | this.output.alert.write(message); 280 | } 281 | 282 | /** 283 | * Send `Client Hello` message. 284 | */ 285 | [_clientHello]() { 286 | debug('send Client Hello'); 287 | 288 | const clientHello = { 289 | clientVersion: this.session.version, 290 | random: this.session.clientRandom, 291 | sessionId: EMPTY_BUFFER, // We do not support resuming session. So, send empty id. 292 | cookie: this.session.cookie || EMPTY_BUFFER, 293 | cipherSuites: this.session.cipherSuites, 294 | compressionMethods: defaultCompressionMethods, 295 | }; 296 | 297 | const output = createEncode(); 298 | encode(clientHello, output, ClientHello); 299 | 300 | const extensions = []; 301 | 302 | // Send Extended Master Secret Extension if need. 303 | if (this.session.extendedMasterSecret) { 304 | extensions.push({ 305 | type: extensionTypes.EXTENDED_MASTER_SECRET, 306 | data: EMPTY_BUFFER, 307 | }); 308 | } 309 | 310 | extensions.push({ 311 | type: extensionTypes.ELLIPTIC_CURVES, 312 | data: namedCurvesExtension, 313 | }); 314 | 315 | if (this.session.alpnProtocols.length > 0) { 316 | const alpnOutput = encode( 317 | this.session.alpnProtocols, 318 | ALPNProtocolNameList 319 | ); 320 | 321 | extensions.push({ 322 | type: extensionTypes.APPLICATION_LAYER_PROTOCOL_NEGOTIATION, 323 | data: alpnOutput, 324 | }); 325 | } 326 | 327 | extensions.push({ 328 | type: extensionTypes.EC_POINT_FORMATS, 329 | data: ecPointFormatExtension, 330 | }); 331 | 332 | if (extensions.length > 0) { 333 | encode(extensions, output, ExtensionList); 334 | } 335 | 336 | this.sendHandshake(output.slice(), handshakeType.CLIENT_HELLO); 337 | } 338 | 339 | /** 340 | * Send `Client Key Exchange` message. 341 | */ 342 | [_clientKeyExchange]() { 343 | debug('send Client Key Exchange'); 344 | 345 | const { nextCipher } = this.session; 346 | const output = createEncode(); 347 | 348 | if (nextCipher.kx.id === kxTypes.RSA) { 349 | const pubkey = this.session.serverPublicKey; 350 | const premaster = this.session.clientPremaster; 351 | const encryptedPremaster = encryptPreMasterSecret(pubkey, premaster); 352 | 353 | encode(encryptedPremaster, output, EncryptedPreMasterSecret); 354 | } 355 | 356 | if (nextCipher.kx.keyType === keyTypes.PSK) { 357 | const { 358 | serverPSKIdentityHint, 359 | ignorePSKIdentityHint, 360 | clientPSKIdentity, 361 | } = this.session; 362 | 363 | const useHint = 364 | !ignorePSKIdentityHint && 365 | Buffer.isBuffer(serverPSKIdentityHint) && 366 | serverPSKIdentityHint.length > 0; 367 | 368 | const pskIdentity = useHint ? serverPSKIdentityHint : clientPSKIdentity; 369 | encode(pskIdentity, output, ServerPSKIdentityHint); 370 | } 371 | 372 | // ECDHE_PSK send both ServerPSKIdentityHint and ECPublicKey 373 | if (nextCipher.kx.signType === signTypes.ECDHE) { 374 | const pubkey = this.session.ellipticPublicKey; 375 | 376 | encode(pubkey, output, ECPublicKey); 377 | } 378 | 379 | this.sendHandshake(output.slice(), handshakeType.CLIENT_KEY_EXCHANGE); 380 | } 381 | 382 | /** 383 | * Send `Change Cipher Spec` message. 384 | */ 385 | [_changeCipherSpec]() { 386 | debug('send Change Cipher Spec'); 387 | 388 | this.session.retransmitter.append( 389 | CHANGE_CIPHER_SPEC, 390 | this.session.clientEpoch, 391 | changeCipherSpecMessage 392 | ); 393 | this.sendRecord(changeCipherSpecMessage, contentType.CHANGE_CIPHER_SPEC); 394 | } 395 | 396 | /** 397 | * Send `Certificate` message. 398 | */ 399 | [_certificate]() { 400 | debug('send client certificate'); 401 | 402 | const packet = { 403 | certificateList: [], 404 | }; 405 | 406 | if (this.session.clientCertificate !== null) { 407 | packet.certificateList.push(this.session.clientCertificate.raw); 408 | } 409 | 410 | const output = encode(packet, Certificate); 411 | 412 | this.sendHandshake(output.slice(), handshakeType.CERTIFICATE); 413 | } 414 | 415 | /** 416 | * Send `Certificate Verify` message. 417 | */ 418 | [_certificateVerify]() { 419 | debug('send client certificate'); 420 | 421 | const digitalSignature = { 422 | algorithm: this.session.clientCertificateSignatureAlgorithm, 423 | signature: this.session.clientCertificateSignature, 424 | }; 425 | 426 | const output = encode(digitalSignature, DigitallySigned); 427 | 428 | this.sendHandshake(output.slice(), handshakeType.CERTIFICATE_VERIFY); 429 | } 430 | 431 | /** 432 | * Send `Finished` message. 433 | */ 434 | [_finished]() { 435 | debug('send Finished'); 436 | 437 | this.sendHandshake(this.session.clientFinished, handshakeType.FINISHED); 438 | } 439 | 440 | /** 441 | * Send `application data` message. 442 | * @param {Buffer} payload 443 | * @private 444 | */ 445 | [_applicationData](payload) { 446 | this.sendRecord(payload, contentType.APPLICATION_DATA); 447 | } 448 | 449 | /** 450 | * Send `alert` message. 451 | * @private 452 | * @param {number} level 453 | * @param {number} description 454 | */ 455 | [_alert](level, description) { 456 | this.output.alert.write({ 457 | level, 458 | description, 459 | }); 460 | } 461 | 462 | /** 463 | * Clears internal message buffer and sends packets. 464 | * @private 465 | */ 466 | [_drain]() { 467 | const nextPacketLength = this[_nextPacketQueue].length; 468 | 469 | if (nextPacketLength > 0) { 470 | this[_queue].push(this[_nextPacketQueue].slice()); 471 | this[_nextPacketQueue].consume(nextPacketLength); 472 | } 473 | 474 | if (this[_queue].length === 0) { 475 | debug('empty out queue'); 476 | return; 477 | } 478 | 479 | debug('drain queue'); 480 | this[_queue].forEach(packet => this.push(packet)); 481 | this[_queue].length = 0; 482 | 483 | this.session.retransmitter.wait(); 484 | } 485 | 486 | /** 487 | * @param {Buffer} packet Record layer message. 488 | * @private 489 | */ 490 | [_bufferDrain](packet) { 491 | if (this.session.isHandshakeInProcess) { 492 | debug('buffer packet'); 493 | 494 | const { mtu } = this.session; 495 | const queueLength = this[_nextPacketQueue].length; 496 | const probablyLength = mtu - queueLength - packet.length; 497 | 498 | if (probablyLength < 0) { 499 | this[_queue].push(this[_nextPacketQueue].slice()); 500 | this[_nextPacketQueue].consume(queueLength); 501 | } 502 | 503 | this[_nextPacketQueue].append(packet); 504 | } else { 505 | debug('send packet'); 506 | this.push(packet); 507 | } 508 | } 509 | }; 510 | -------------------------------------------------------------------------------- /src/node_modules/lib/server.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const dgram = require('dgram'); 4 | const Emitter = require('events'); 5 | const { Duplex } = require('readable-stream'); 6 | const debug = require('utils/debug')('dtls:server'); 7 | const CookieManager = require('lib/cookie-manager'); 8 | const { decode, encode } = require('binary-data'); 9 | const { 10 | contentType, 11 | handshakeType, 12 | protocolVersion, 13 | } = require('lib/constants'); 14 | const { 15 | DTLSPlaintext, 16 | Handshake, 17 | ClientHello, 18 | HelloVerifyRequest, 19 | } = require('lib/protocol'); 20 | 21 | const _session = Symbol('_session'); 22 | const _socket = Symbol('_socket'); 23 | const _storage = Symbol('storage'); 24 | 25 | const COOKIE_VALID_VERIFIED = 1; 26 | const COOKIE_VALID_UNVERIFIED = 0; 27 | const COOKIE_INVALID = -1; 28 | 29 | /** 30 | * @typedef {Object} DTLSServerOptions 31 | * @property {dgram.Socket} [socket] dgram socket 32 | * @property {string} [type] dgram type socket 33 | * @property {CookieManager} [cookie] shared cookie 34 | * @property {Buffer} [options.certificate] Server certificate 35 | * @property {Buffer} [options.certificatePrivateKey] Private key of the server certificate 36 | * @property {number[]} [options.cipherSuites] List of supported cipher suites 37 | */ 38 | 39 | /** 40 | * @typedef {DTLSServerOptions} DTLSSingleServerOptions 41 | * @property {string} [address] Host address to listening on, see defaults in dgram.Socket.bind. 42 | * @property {number} port Port to listening on. 43 | * @property {string} remoteAddress Wait for data from this remote address. 44 | * @property {number} remotePort Wait for data from this remote port. 45 | */ 46 | 47 | /** 48 | * Create a handler for incoming data. 49 | * @param {dgram.Socket} socket UDP socket used as transport. 50 | * @param {Map} storage Storage of connection mapped to ip+port. 51 | * @param {CookieManager} cookie Cookie manager instance. 52 | * @returns {Function} 53 | */ 54 | const createIncomingDataHandler = (socket, storage, cookie) => ( 55 | message, 56 | rinfo 57 | ) => { 58 | debug('got a new packet from %s:%s', rinfo.address, rinfo.port); 59 | 60 | const peer = `${rinfo.address}:${rinfo.port}`; 61 | 62 | if (storage.has(peer)) { 63 | // handle packet in session 64 | } else { 65 | const status = isCookieValid(cookie, message); 66 | 67 | if (status === COOKIE_VALID_UNVERIFIED) { 68 | const response = createHelloVerifyRequest(cookie); 69 | socket.send(response, rinfo.port, rinfo.address); 70 | } else if (status === COOKIE_VALID_VERIFIED) { 71 | debug('start new connection'); 72 | } 73 | } 74 | }; 75 | 76 | /** 77 | * DTLS server. 78 | */ 79 | class Server extends Emitter { 80 | /** 81 | * @class Server 82 | * @param {DTLSServerOptions} options 83 | */ 84 | constructor(options = {}) { 85 | super(); 86 | 87 | const { socket, cookie } = options; 88 | const sessionStorage = new Map(); 89 | 90 | this[_socket] = socket; 91 | this[_storage] = sessionStorage; 92 | 93 | socket.on( 94 | 'message', 95 | createIncomingDataHandler(socket, sessionStorage, cookie) 96 | ); 97 | } 98 | } 99 | 100 | /** 101 | * DTLS server, configured once only for specified client. 102 | */ 103 | class SingleServer extends Duplex { 104 | /** 105 | * @class SingleServer 106 | * @param {DTLSSingleServerOptions} options 107 | */ 108 | constructor(options = {}) { 109 | super({ objectMode: false, decodeStrings: false }); 110 | 111 | const { socket, cookie, remoteAddress, remotePort } = options; 112 | 113 | this[_session] = null; 114 | this[_socket] = socket; 115 | 116 | socket.on('message', (message, rinfo) => { 117 | debug('got a new packet from %s:%s', rinfo.address, rinfo.port); 118 | 119 | if (rinfo.address !== remoteAddress && rinfo.port !== remotePort) { 120 | debug('invalid sender'); 121 | return; 122 | } 123 | 124 | if (this[_session] === null) { 125 | const status = isCookieValid(cookie, message); 126 | 127 | if (status === COOKIE_VALID_UNVERIFIED) { 128 | const response = createHelloVerifyRequest(cookie); 129 | socket.send(response, rinfo.port, rinfo.address); 130 | } else if (status === COOKIE_VALID_VERIFIED) { 131 | debug('start new connection'); 132 | cookie.destroy(); 133 | } 134 | } else { 135 | debug('handle session'); 136 | } 137 | }); 138 | } 139 | } 140 | 141 | /** 142 | * Handle client's ClientHello message by checking cookie. 143 | * @param {CookieManager} cookie 144 | * @param {Buffer} message 145 | * @returns {number} 146 | */ 147 | function isCookieValid(cookie, message) { 148 | debug('process ClientHello'); 149 | const record = decode(message, DTLSPlaintext); 150 | 151 | if (record.type !== contentType.HANDSHAKE) { 152 | debug('not a handshake - %s, ignore', record.type); 153 | return COOKIE_INVALID; 154 | } 155 | 156 | const handshake = decode(record.fragment, Handshake); 157 | 158 | if (handshake.type !== handshakeType.CLIENT_HELLO) { 159 | debug('not a ClientHello - %s, ignore', handshake.type); 160 | return COOKIE_INVALID; 161 | } 162 | 163 | // Ignore fragmented (first) ClientHello due to potencial DoS. 164 | if (handshake.length !== handshake.fragment.length) { 165 | debug('got fragmented ClientHello, ignore'); 166 | return COOKIE_INVALID; 167 | } 168 | 169 | const clientHello = decode(handshake.body, ClientHello); 170 | 171 | // Send HelloVerifyRequest 172 | if (clientHello.cookie.length === 0) { 173 | return COOKIE_VALID_UNVERIFIED; 174 | } 175 | 176 | if (cookie.validate(clientHello.cookie)) { 177 | debug('cookie is valid'); 178 | return COOKIE_VALID_VERIFIED; 179 | } 180 | 181 | debug('invalid cookie, ignore'); 182 | return COOKIE_INVALID; 183 | } 184 | 185 | /** 186 | * Send HelloVerifyRequest message. 187 | * @param {CookieManager} cookie 188 | * @returns {Buffer} 189 | */ 190 | function createHelloVerifyRequest(cookie) { 191 | const helloVerifyRequest = { 192 | serverVersion: protocolVersion.DTLS_1_2, 193 | cookie: cookie.current, 194 | }; 195 | 196 | const handshakePayload = encode( 197 | helloVerifyRequest, 198 | HelloVerifyRequest 199 | ).slice(); 200 | 201 | const handshake = { 202 | type: handshakeType.HELLO_VERIFY_REQUEST, 203 | length: handshakePayload.length, 204 | sequence: 0, 205 | fragment: { 206 | offset: 0, 207 | length: handshakePayload.length, 208 | }, 209 | body: handshakePayload, 210 | }; 211 | 212 | const headerPayload = encode(handshake, Handshake).slice(); 213 | 214 | const record = { 215 | type: contentType.HANDSHAKE, 216 | version: protocolVersion.DTLS_1_2, 217 | epoch: 0, 218 | sequenceNumber: 0, 219 | length: headerPayload.length, 220 | fragment: headerPayload, 221 | }; 222 | 223 | return encode(record, DTLSPlaintext).slice(); 224 | } 225 | 226 | /** 227 | * Create DTLS server. 228 | * @param {DTLSServerOptions} options 229 | * @param {Function} callback 230 | * @returns {Server} 231 | */ 232 | function createServer(options, callback) { 233 | if (!options.socket) { 234 | options.socket = dgram.createSocket(options); 235 | } 236 | 237 | if (!options.cookie) { 238 | options.cookie = new CookieManager(); 239 | } 240 | 241 | const server = new Server(options); 242 | 243 | if (typeof callback === 'function') { 244 | server.once('connect', callback); 245 | } 246 | 247 | return server; 248 | } 249 | 250 | module.exports = { 251 | createServer, 252 | Server, 253 | SingleServer, 254 | }; 255 | -------------------------------------------------------------------------------- /src/node_modules/lib/sliding-window.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const debug = require('utils/debug')('dtls:window'); 4 | 5 | const _step = Symbol('_step'); 6 | const _left = Symbol('_left'); 7 | const _right = Symbol('_right'); 8 | 9 | /** 10 | * Record layer anti-replay protection. 11 | * @link https://tools.ietf.org/html/rfc6347#section-4.1.2.6 12 | * @link https://tools.ietf.org/html/rfc4303#section-3.4.3 13 | */ 14 | module.exports = class SlidingWindow { 15 | /** 16 | * @class SlidingWindow 17 | * @param {number} step 18 | */ 19 | constructor(step = 64) { 20 | this[_step] = step; 21 | 22 | this.reset(); 23 | } 24 | 25 | /** 26 | * Get left edge. 27 | * @returns {number} 28 | */ 29 | get left() { 30 | return this[_left]; 31 | } 32 | 33 | /** 34 | * Get right edge. 35 | * @returns {number} 36 | */ 37 | get right() { 38 | return this[_right]; 39 | } 40 | 41 | /** 42 | * Update edges. 43 | * @param {number} sequence 44 | * @returns {bool} 45 | */ 46 | accept(sequence) { 47 | if (this.check(sequence)) { 48 | this[_left] = sequence; 49 | this[_right] = sequence + this[_step]; 50 | 51 | return true; 52 | } 53 | 54 | return false; 55 | } 56 | 57 | /** 58 | * Validate number. 59 | * @param {number} sequence 60 | * @returns {bool} 61 | */ 62 | check(sequence) { 63 | if (sequence < this[_left]) { 64 | return false; 65 | } 66 | 67 | if (sequence >= this[_right]) { 68 | return false; 69 | } 70 | 71 | return true; 72 | } 73 | 74 | /** 75 | * Resets internals state of edges. 76 | */ 77 | reset() { 78 | debug('reset sliding window'); 79 | 80 | this[_left] = 0; 81 | this[_right] = this[_step]; 82 | } 83 | 84 | /** 85 | * @public 86 | * @returns {string} 87 | */ 88 | toString() { 89 | return `left = ${this.left}, right = ${this.right}, step = ${this[_step]}`; 90 | } 91 | }; 92 | -------------------------------------------------------------------------------- /src/node_modules/lib/socket.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const { Duplex, pipeline } = require('readable-stream'); 4 | const unicast = require('unicast'); 5 | const isDtls = require('is-dtls'); 6 | const streamfilter = require('streamfilter'); 7 | const debug = require('debug')('dtls:socket'); 8 | const ClientSession = require('session/client'); 9 | const ClientProtocol = require('protocol/client'); 10 | const Sender = require('lib/sender'); 11 | const Decoder = require('filter/decoder'); 12 | const Defragmentation = require('filter/defragmentation'); 13 | const Reordering = require('filter/reordering'); 14 | const x509 = require('@fidm/x509'); 15 | const { duplex: isDuplexStream } = require('is-stream'); 16 | const { toCipherSuite } = require('utils/cipher-suite'); 17 | 18 | const _session = Symbol('_session'); 19 | const _queue = Symbol('_queue'); 20 | const _protocol = Symbol('_protocol'); 21 | const _socket = Symbol('_socket'); 22 | const _timeout = Symbol('_timeout'); 23 | const _onTimeout = Symbol('_onTimeout'); 24 | const _resetTimer = Symbol('_resetTimer'); 25 | 26 | const DTLS_MAX_MTU = 1420; // 1500 - IP/UDP/DTLS headers 27 | const DTLS_MIN_MTU = 100; 28 | 29 | const isString = str => typeof str === 'string'; 30 | 31 | /** 32 | * DTLS socket. 33 | */ 34 | class Socket extends Duplex { 35 | /** 36 | * @class Socket 37 | * @param {Object} options 38 | * @param {number} [options.maxHandshakeRetransmissions] 39 | * @param {boolean} [options.extendedMasterSecret] 40 | * @param {Function} [options.checkServerIdentity] 41 | * @param {string|string[]} [options.alpn] 42 | * @param {Buffer} [options.certificate] 43 | * @param {Buffer} [options.certificatePrivateKey] 44 | * @param {string|Buffer} [options.pskIdentity] 45 | * @param {string|Buffer} [options.pskSecret] 46 | * @param {boolean} [options.ignorePSKIdentityHint] 47 | * @param {number[]} [options.cipherSuites] 48 | */ 49 | constructor(options = {}) { 50 | super({ objectMode: false, decodeStrings: false, allowHalfOpen: true }); 51 | 52 | const { socket } = options; 53 | 54 | const session = new ClientSession(); 55 | const protocol = new ClientProtocol(session); 56 | const writer = new Sender(session); 57 | const decoder = new Decoder(session); 58 | const defrag = new Defragmentation(); 59 | const reorder = new Reordering(session); 60 | 61 | // Disable Extended Master Secret Extension, RFC7627 62 | if (options.extendedMasterSecret === false) { 63 | session.extendedMasterSecret = false; 64 | } 65 | 66 | // Set up server certificate verify callback. 67 | if (typeof options.checkServerIdentity === 'function') { 68 | session.serverCertificateVerifyCallback = options.checkServerIdentity; 69 | } 70 | 71 | if (Buffer.isBuffer(options.certificate)) { 72 | session.clientCertificate = x509.Certificate.fromPEM(options.certificate); 73 | 74 | if (options.certificatePrivateKey !== undefined) { 75 | session.clientCertificatePrivateKey = options.certificatePrivateKey; 76 | } else { 77 | throw new Error('Expected private key'); 78 | } 79 | } 80 | 81 | if ( 82 | Number.isSafeInteger(options.maxHandshakeRetransmissions) && 83 | options.maxHandshakeRetransmissions > 0 84 | ) { 85 | session.retransmitter.maxTries = options.maxHandshakeRetransmissions; 86 | } 87 | 88 | if (isString(options.alpn)) { 89 | session.alpnProtocols.push(options.alpn); 90 | } 91 | 92 | if (Array.isArray(options.alpn)) { 93 | if (options.alpn.every(isString)) { 94 | session.alpnProtocols.push(...options.alpn); 95 | } else { 96 | throw new TypeError( 97 | 'Argument `options.alpn` accept a string or an array of a strings.' 98 | ); 99 | } 100 | } 101 | 102 | // Entering PSK identities consisting of up to 128 printable Unicode characters. 103 | if (Buffer.isBuffer(options.pskIdentity)) { 104 | validatePSKIdentity(options.pskIdentity); 105 | session.clientPSKIdentity = options.pskIdentity; 106 | } else if (typeof options.pskIdentity === 'string') { 107 | validatePSKIdentity(options.pskIdentity); 108 | session.clientPSKIdentity = Buffer.from(options.pskIdentity); 109 | } 110 | 111 | // Entering PSKs up to 64 octets in length as ASCII strings. 112 | if (Buffer.isBuffer(options.pskSecret)) { 113 | session.pskSecret = options.pskSecret; 114 | validatePSKSecret(session.pskSecret); 115 | } else if (typeof options.pskSecret === 'string') { 116 | session.pskSecret = Buffer.from(options.pskSecret, 'ascii'); 117 | validatePSKSecret(session.pskSecret); 118 | } 119 | 120 | if (options.ignorePSKIdentityHint === false) { 121 | session.ignorePSKIdentityHint = false; 122 | } 123 | 124 | // Set up custom cipher suites. 125 | if ( 126 | Array.isArray(options.cipherSuites) && 127 | options.cipherSuites.length > 0 128 | ) { 129 | const supportedCiphers = options.cipherSuites 130 | .map(toCipherSuite) 131 | .filter(cipher => cipher > -1); 132 | 133 | if (supportedCiphers.length === 0) { 134 | throw new Error('Invalid cipher suites list'); 135 | } 136 | 137 | session.cipherSuites = supportedCiphers; 138 | } 139 | 140 | session.retransmitter.once('close', () => { 141 | this.emit('timeout'); 142 | }); 143 | 144 | const onerror = err => { 145 | if (err) { 146 | this.emit('error', err); 147 | } 148 | }; 149 | const isdtls = streamfilter(chunkFilter); 150 | 151 | pipeline(writer, socket, onerror); 152 | pipeline(socket, isdtls, decoder, reorder, defrag, protocol, onerror); 153 | 154 | this[_session] = session; 155 | this[_queue] = []; 156 | this[_protocol] = protocol; 157 | this[_socket] = socket; 158 | this[_timeout] = null; 159 | 160 | session.on('data', packet => { 161 | this[_resetTimer](); 162 | this.push(packet); 163 | }); 164 | 165 | session.once('handshake:finish', () => { 166 | process.nextTick(() => this.emit('connect')); 167 | 168 | this[_queue].forEach(data => session.sendMessage(data)); 169 | this[_queue].length = 0; 170 | 171 | session.retransmitter.removeAllListeners('close'); 172 | 173 | if (session.connectionTimeout > 0) { 174 | this[_resetTimer](); 175 | } 176 | }); 177 | 178 | session.once('certificate', cert => 179 | process.nextTick(() => this.emit('certificate', cert)) 180 | ); 181 | 182 | session.on('error', code => 183 | this.emit('error', new Error(`alert code ${code}`)) 184 | ); 185 | 186 | this.once('timeout', () => { 187 | debug('got timeout, close connection'); 188 | this.close(); 189 | }); 190 | } 191 | 192 | /** 193 | * Opens DTLS connection. 194 | * @param {Function} [callback] 195 | */ 196 | connect(callback) { 197 | if (typeof callback === 'function') { 198 | this.once('connect', callback); 199 | } 200 | 201 | process.nextTick(() => this[_protocol].start()); 202 | } 203 | 204 | /** 205 | * Set MTU (Minimal Transfer Unit) for the socket. 206 | * @param {number} mtu 207 | */ 208 | setMTU(mtu) { 209 | if (typeof mtu !== 'number') { 210 | throw new TypeError('Invalid type of argument `mtu`'); 211 | } 212 | 213 | const isValid = 214 | Number.isInteger(mtu) && mtu <= DTLS_MAX_MTU && mtu >= DTLS_MIN_MTU; 215 | 216 | if (isValid) { 217 | this[_session].mtu = mtu; 218 | } else { 219 | throw new Error('Invalid MTU'); 220 | } 221 | } 222 | 223 | /** 224 | * Get MTU (Minimal Transfer Unit) for the socket. 225 | * @returns {number} 226 | */ 227 | getMTU() { 228 | return this[_session].mtu; 229 | } 230 | 231 | /** 232 | * Sets the socket to timeout after timeout milliseconds of inactivity on the socket. 233 | * By default `dtls.Socket` do not have a timeout. 234 | * @param {number} timeout 235 | * @param {Function} [callback] 236 | */ 237 | setTimeout(timeout, callback) { 238 | if (Number.isSafeInteger(timeout) && timeout > 0) { 239 | this[_session].connectionTimeout = timeout; 240 | } 241 | 242 | if (typeof callback === 'function') { 243 | this.once('timeout', callback); 244 | } 245 | } 246 | 247 | /** 248 | * @private 249 | */ 250 | [_onTimeout]() { 251 | clearTimeout(this[_timeout]); 252 | this[_timeout] = null; 253 | 254 | this.emit('timeout'); 255 | } 256 | 257 | /** 258 | * @private 259 | */ 260 | [_resetTimer]() { 261 | const { connectionTimeout } = this[_session]; 262 | 263 | if (connectionTimeout === 0) { 264 | return; 265 | } 266 | 267 | if (this[_timeout] !== null) { 268 | clearTimeout(this[_timeout]); 269 | } 270 | 271 | this[_timeout] = setTimeout(() => this[_onTimeout](), connectionTimeout); 272 | this[_timeout].unref(); 273 | } 274 | 275 | /** 276 | * Get a string that contains the selected ALPN protocol. 277 | * When ALPN has no selected protocol, Socket.alpnProtocol 278 | * equals to an empty string. 279 | * @returns {string} 280 | */ 281 | get alpnProtocol() { 282 | return this[_session].selectedALPNProtocol; 283 | } 284 | 285 | /** 286 | * Close the underlying socket and stop listening for data on it. 287 | */ 288 | close() { 289 | this[_socket].close(); 290 | 291 | if (this[_timeout] !== null) { 292 | clearTimeout(this[_timeout]); 293 | } 294 | } 295 | 296 | /** 297 | * @private 298 | */ 299 | _read() {} // eslint-disable-line class-methods-use-this 300 | 301 | /** 302 | * @private 303 | * @param {Buffer} chunk 304 | * @param {string} encoding 305 | * @param {Function} callback 306 | */ 307 | _write(chunk, encoding, callback) { 308 | if (this[_session].isHandshakeInProcess) { 309 | this[_queue].push(chunk); 310 | this.once('connect', () => callback()); 311 | } else { 312 | this[_session].sendMessage(chunk); 313 | this[_resetTimer](); 314 | callback(); 315 | } 316 | } 317 | 318 | /** 319 | * @private 320 | */ 321 | _destroy() { 322 | this[_queue].length = 0; 323 | this[_session] = null; 324 | } 325 | } 326 | 327 | /** 328 | * Connect the socket to dtls server. 329 | * @param {Object} options 330 | * @param {Function} [callback] 331 | * @returns {Socket} 332 | */ 333 | function connect(options = {}, callback) { 334 | if (!isDuplexStream(options.socket)) { 335 | options.socket = unicast.createSocket(options); 336 | } 337 | 338 | const socket = new Socket(options); 339 | socket.connect(callback); 340 | 341 | return socket; 342 | } 343 | 344 | /** 345 | * Check if incoming message is dtls. 346 | * @param {Buffer} data 347 | * @param {string} enc 348 | * @param {Function} callback 349 | */ 350 | function chunkFilter(data, enc, callback) { 351 | const isCorrect = isDtls(data); 352 | debug('got message, is dtls = %s', isCorrect); 353 | callback(!isCorrect); 354 | } 355 | 356 | /** 357 | * Validates PSK secret. 358 | * @param {Buffer} pskSecret 359 | */ 360 | function validatePSKSecret(pskSecret) { 361 | if (pskSecret.length === 0) { 362 | throw new Error('Invalid PSK secret'); 363 | } 364 | } 365 | 366 | /** 367 | * Validates PSK identity. 368 | * @param {string|Buffer} pskIdentity 369 | */ 370 | function validatePSKIdentity(pskIdentity) { 371 | if (pskIdentity.length === 0) { 372 | throw new Error('Invalid PSK identity'); 373 | } 374 | } 375 | 376 | module.exports = { 377 | connect, 378 | }; 379 | -------------------------------------------------------------------------------- /src/node_modules/protocol/client/handlers.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * This file contains a collection of DTLS protocol handlers for client side. 5 | * It hides many session-based stuffs. 6 | */ 7 | 8 | const assert = require('assert'); 9 | const crypto = require('crypto'); 10 | const debug = require('utils/debug')('dtls:client-protocol'); 11 | const { constants: states } = require('protocol/packet'); 12 | const { createCipher } = require('cipher/create'); 13 | const { decode, createDecode } = require('binary-data'); 14 | const x509 = require('@fidm/x509'); 15 | const { ASN1 } = require('@fidm/asn1'); 16 | const { 17 | HelloVerifyRequest, 18 | ServerHello, 19 | Certificate, 20 | Alert, 21 | ALPNProtocolNameList, 22 | ExtensionList, 23 | ServerECDHParams, 24 | DigitallySigned, 25 | CertificateRequest, 26 | ServerPSKIdentityHint, 27 | } = require('lib/protocol'); 28 | const { 29 | alertDescription, 30 | maxSessionIdSize, 31 | extensionTypes, 32 | keyTypes, 33 | signTypes, 34 | namedCurves, 35 | signatureScheme, 36 | kxTypes, 37 | } = require('lib/constants'); 38 | const { 39 | getHashNameBySignAlgo, 40 | getCertificateType, 41 | getCertificateSignatureAlgorithm, 42 | } = require('session/utils'); 43 | 44 | const { 45 | CLIENT_HELLO, 46 | CERTIFICATE, 47 | CERTIFICATE_VERIFY, 48 | CLIENT_KEY_EXCHANGE, 49 | FINISHED, 50 | CHANGE_CIPHER_SPEC, 51 | } = states; 52 | 53 | const supportedCurves = Object.keys(namedCurves); 54 | 55 | module.exports = { 56 | clientHello, 57 | helloVerifyRequest, 58 | serverHello, 59 | serverHelloExtensions, 60 | serverCertificate, 61 | serverKeyExchange, 62 | certificateRequest, 63 | serverHelloDone, 64 | clientCertificate, 65 | clientKeyExchange, 66 | certificateVerify, 67 | clientChangeCipherSpec, 68 | clientFinished, 69 | serverChangeCipherSpec, 70 | serverFinished, 71 | alert, 72 | applicationData, 73 | }; 74 | 75 | /** 76 | * Handles `client hello` out message. 77 | * @param {ClientSession} session 78 | */ 79 | function clientHello(session) { 80 | debug('prepare client hello'); 81 | session.send(CLIENT_HELLO); 82 | session.retransmitter.send(); 83 | } 84 | 85 | /** 86 | * Handles `hello verify request` incoming message. 87 | * @param {ClientSession} session 88 | * @param {Object} message 89 | */ 90 | function helloVerifyRequest(session, message) { 91 | debug('got hello verify request'); 92 | session.retransmitter.prepare(); 93 | 94 | const handshake = message.fragment; 95 | 96 | // Initial `ClientHello` and `HelloVerifyRequest` must not 97 | // use for calculate finished checksum. 98 | session.resetHandshakeQueue(); 99 | 100 | const packet = decode(handshake.body, HelloVerifyRequest); 101 | assert(decode.bytes === handshake.body.length); 102 | 103 | session.cookie = packet.cookie; 104 | debug('got cookie %h', packet.cookie); 105 | } 106 | 107 | /** 108 | * Handles `server hello` incoming message. 109 | * @param {ClientSession} session 110 | * @param {Object} message 111 | */ 112 | function serverHello(session, message) { 113 | debug('got server hello'); 114 | const handshake = message.fragment; 115 | 116 | if (handshake.body.length < 38) { 117 | session.error(alertDescription.DECODE_ERROR); 118 | return; 119 | } 120 | 121 | const istream = createDecode(handshake.body); 122 | const serverHelloPacket = decode(istream, ServerHello); 123 | 124 | if (serverHelloPacket.serverVersion !== session.version) { 125 | debug('mismatch protocol version'); 126 | session.error(alertDescription.PROTOCOL_VERSION); 127 | return; 128 | } 129 | 130 | if (serverHelloPacket.sessionId.length > maxSessionIdSize) { 131 | session.error(alertDescription.ILLEGAL_PARAMETER); 132 | return; 133 | } 134 | 135 | session.serverRandom = serverHelloPacket.random; 136 | session.id = serverHelloPacket.sessionId; 137 | 138 | const clientCipher = session.cipherSuites.find( 139 | cipherSuite => cipherSuite === serverHelloPacket.cipherSuite 140 | ); 141 | 142 | if (!clientCipher) { 143 | debug('server selected unknown cipher %s', serverHelloPacket.cipherSuite); 144 | session.error(alertDescription.HANDSHAKE_FAILURE); 145 | return; 146 | } 147 | 148 | const cipher = createCipher(clientCipher); 149 | 150 | debug(`server selected ${cipher.name} cipher`); 151 | session.nextCipher = cipher; 152 | 153 | if (istream.length > 0) { 154 | const extensions = decode(istream, ExtensionList); 155 | 156 | serverHelloExtensions(session, extensions); 157 | } 158 | 159 | const { extendedMasterSecret, peerExtendedMasterSecret } = session; 160 | 161 | // If a client receives a ServerHello without the extension, it SHOULD 162 | // abort the handshake if it does not wish to interoperate with legacy 163 | // servers. 164 | if (extendedMasterSecret && !peerExtendedMasterSecret) { 165 | session.error(alertDescription.HANDSHAKE_FAILURE); 166 | return; 167 | } 168 | 169 | // Ignore server's choise of Supported Point Formats Extension. 170 | // Force use of uncompressed points. 171 | 172 | session.appendHandshake(handshake); 173 | } 174 | 175 | /** 176 | * Handle `server hello` extensions. 177 | * @param {ClientSession} session 178 | * @param {Object[]} extensions 179 | */ 180 | function serverHelloExtensions(session, extensions) { 181 | for (const extension of extensions) { 182 | if (extension.type === extensionTypes.EXTENDED_MASTER_SECRET) { 183 | session.peerExtendedMasterSecret = true; 184 | } 185 | 186 | if ( 187 | extension.type === extensionTypes.APPLICATION_LAYER_PROTOCOL_NEGOTIATION 188 | ) { 189 | const protocols = decode(extension.data, ALPNProtocolNameList); 190 | 191 | if (protocols.length === 1) { 192 | const [alpn] = protocols; 193 | 194 | session.selectedALPNProtocol = alpn; 195 | } 196 | } 197 | } 198 | } 199 | 200 | /** 201 | * Handles `certificate` incoming message. 202 | * @param {ClientSession} session 203 | * @param {Object} message 204 | */ 205 | function serverCertificate(session, message) { 206 | debug('got server certificate'); 207 | const handshake = message.fragment; 208 | 209 | // PSK key exchange don't need this message. 210 | if (session.nextCipher.kx.id === kxTypes.PSK) { 211 | throw new Error('Invalid message.'); 212 | } 213 | 214 | const packet = decode(handshake.body, Certificate); 215 | 216 | if (packet.certificateList.length === 0) { 217 | session.error(alertDescription.CERTIFICATE_UNKNOWN); 218 | return; 219 | } 220 | 221 | // The sender's certificate MUST come first in the list. 222 | const serverCertificatePacket = packet.certificateList[0]; 223 | const certificate = new x509.Certificate( 224 | ASN1.fromDER(serverCertificatePacket) 225 | ); 226 | 227 | const isValid = session.verifyCertificate(certificate); 228 | 229 | if (!isValid) { 230 | session.error(alertDescription.BAD_CERTIFICATE); 231 | return; 232 | } 233 | 234 | session.certificate(certificate); 235 | session.serverPublicKey = certificate.publicKey.toPEM(); 236 | 237 | session.appendHandshake(handshake); 238 | } 239 | 240 | /** 241 | * Handle `ServerKeyExchange` message. 242 | * @param {ClientSession} session 243 | * @param {Object} message 244 | */ 245 | function serverKeyExchange(session, message) { 246 | debug('got server key exchange'); 247 | const { nextCipher } = session; 248 | 249 | const isECDHE = nextCipher.kx.signType === signTypes.ECDHE; 250 | const isPSK = nextCipher.kx.id === kxTypes.PSK; 251 | const isECDHE_PSK = nextCipher.kx.id === kxTypes.ECDHE_PSK; // eslint-disable-line camelcase 252 | 253 | // Only ECDHE_* and PSK may have this message. 254 | // eslint-disable-next-line camelcase 255 | if (isECDHE_PSK) { 256 | serverECDHEPSKKeyExchange(session, message); 257 | } else if (isECDHE) { 258 | serverECDHEKeyExchange(session, message); 259 | } else if (isPSK) { 260 | serverPSKKeyExchange(session, message); 261 | } else { 262 | throw new Error('Invalid message type.'); 263 | } 264 | } 265 | 266 | /** 267 | * Process `ServerKeyExchange` message for ECDHE_* key exchange (except ECDHE_PSK). 268 | * @param {ClientSession} session 269 | * @param {Object} message 270 | */ 271 | function serverECDHEKeyExchange(session, message) { 272 | debug('process server ECDHE key exchange'); 273 | 274 | const handshake = message.fragment; 275 | const rstream = createDecode(handshake.body); 276 | 277 | const ecdheParams = decode(rstream, ServerECDHParams); 278 | const ecdheParamsSize = decode.bytes; 279 | const digitalSign = decode(rstream, DigitallySigned); 280 | 281 | // check curve 282 | const selectedCurve = supportedCurves.find( 283 | curve => namedCurves[curve] === ecdheParams.curve 284 | ); 285 | 286 | if (selectedCurve === undefined) { 287 | throw new Error('Invalid curve name'); 288 | } 289 | 290 | // Default sign algo is sha1 for rsa 291 | if (session.nextCipher.kx.keyType === keyTypes.RSA) { 292 | assert(digitalSign.algorithm === signatureScheme.rsa_pkcs1_sha1); 293 | } 294 | 295 | if (session.nextCipher.kx.keyType === keyTypes.ECDSA) { 296 | const isECDSA = 297 | digitalSign.algorithm === signatureScheme.ecdsa_sha1 || 298 | digitalSign.algorithm === signatureScheme.ecdsa_secp256r1_sha256 || 299 | digitalSign.algorithm === signatureScheme.ecdsa_secp384r1_sha384 || 300 | digitalSign.algorithm === signatureScheme.ecdsa_secp521r1_sha512; 301 | assert(isECDSA); 302 | } 303 | 304 | const verifier = crypto.createVerify( 305 | getHashNameBySignAlgo(digitalSign.algorithm) 306 | ); 307 | 308 | verifier.update(session.clientRandom); 309 | verifier.update(session.serverRandom); 310 | verifier.update(handshake.body.slice(0, ecdheParamsSize)); 311 | 312 | const isSignValid = verifier.verify( 313 | { key: session.serverPublicKey }, 314 | digitalSign.signature 315 | ); 316 | 317 | if (!isSignValid) { 318 | throw new Error('Invalid sign'); 319 | } 320 | 321 | debug('sign valid'); 322 | 323 | session.appendHandshake(handshake); 324 | session.ellipticCurve = selectedCurve; 325 | session.peerEllipticPublicKey = ecdheParams.pubkey; 326 | session.createElliptic(); 327 | } 328 | 329 | /** 330 | * Process `ServerKeyExchange` message for PSK key exchange. 331 | * @param {ClientSession} session 332 | * @param {Object} message 333 | */ 334 | function serverPSKKeyExchange(session, message) { 335 | debug('process server PSK key exchange'); 336 | 337 | const handshake = message.fragment; 338 | const rstream = createDecode(handshake.body); 339 | 340 | /** 341 | * In the absence of an application profile specification specifying 342 | * otherwise, servers SHOULD NOT provide an identity hint and clients 343 | * MUST ignore the identity hint field. 344 | */ 345 | 346 | if (!session.ignorePSKIdentityHint) { 347 | const pskIdentityHint = decode(rstream, ServerPSKIdentityHint); 348 | 349 | session.serverPSKIdentityHint = pskIdentityHint; 350 | } 351 | 352 | session.appendHandshake(handshake); 353 | } 354 | 355 | /** 356 | * Process `ServerKeyExchange` message for ECDHE_PSK key exchange. 357 | * @param {ClientSession} session 358 | * @param {Object} message 359 | */ 360 | function serverECDHEPSKKeyExchange(session, message) { 361 | debug('process server ECDHE PSK key exchange'); 362 | 363 | const handshake = message.fragment; 364 | const rstream = createDecode(handshake.body); 365 | 366 | const pskIdentityHint = decode(rstream, ServerPSKIdentityHint); 367 | const ecdheParams = decode(rstream, ServerECDHParams); 368 | 369 | if (!session.ignorePSKIdentityHint) { 370 | session.serverPSKIdentityHint = pskIdentityHint; 371 | } 372 | 373 | // check curve 374 | const selectedCurve = supportedCurves.find( 375 | curve => namedCurves[curve] === ecdheParams.curve 376 | ); 377 | 378 | if (selectedCurve === undefined) { 379 | throw new Error('Invalid curve name'); 380 | } 381 | 382 | session.appendHandshake(handshake); 383 | session.ellipticCurve = selectedCurve; 384 | session.peerEllipticPublicKey = ecdheParams.pubkey; 385 | session.createElliptic(); 386 | } 387 | 388 | /** 389 | * Handles `certificate request` incoming message. 390 | * @param {ClientSession} session 391 | * @param {Object} message 392 | */ 393 | function certificateRequest(session, message) { 394 | debug('got certificate request'); 395 | const handshake = message.fragment; 396 | const { nextCipher } = session; 397 | 398 | // PSK key exchange don't need this message. 399 | if (nextCipher.kx.keyType === keyTypes.PSK) { 400 | throw new Error('Invalid message.'); 401 | } 402 | 403 | const { certificateTypes, signatures } = decode( 404 | handshake.body, 405 | CertificateRequest 406 | ); 407 | 408 | session.isCertificateRequested = true; 409 | session.requestedCertificateTypes = certificateTypes; 410 | session.requestedSignatureAlgorithms = signatures; 411 | 412 | session.appendHandshake(handshake); 413 | } 414 | 415 | /** 416 | * Handles `server hello done` incoming message. 417 | * @param {ClientSession} session 418 | * @param {Object} message 419 | * @param {Function} cb 420 | */ 421 | function serverHelloDone(session, message, cb) { 422 | debug('got server hello done'); 423 | const handshake = message.fragment; 424 | 425 | session.appendHandshake(handshake); 426 | session.createPreMasterSecret(() => { 427 | debug('PREMASTER SECRET %h', session.clientPremaster); 428 | cb(); 429 | }); 430 | } 431 | 432 | /** 433 | * Sends client side certificate. 434 | * @param {ClientSession} session 435 | */ 436 | function clientCertificate(session) { 437 | debug('prepare client certificate'); 438 | 439 | session.retransmitter.prepare(); 440 | 441 | if (session.clientCertificate !== null) { 442 | // The end-entity certificate provided by the client MUST contain a 443 | // key that is compatible with certificate_types. 444 | const certType = getCertificateType(session.clientCertificate); 445 | const isCertificateAllowed = session.requestedCertificateTypes.includes( 446 | certType 447 | ); 448 | 449 | if (!isCertificateAllowed) { 450 | throw new Error('Disallowed certificate type.'); 451 | } 452 | 453 | // Any certificates provided by the client MUST be signed using a 454 | // hash/signature algorithm pair found in 455 | // supported_signature_algorithms. 456 | const signalgo = getCertificateSignatureAlgorithm( 457 | session.clientCertificate 458 | ); 459 | const isCertificatSignatureAllowed = session.requestedSignatureAlgorithms.includes( 460 | signalgo 461 | ); 462 | 463 | if (!isCertificatSignatureAllowed) { 464 | throw new Error('Disallowed certificate signature algorithm.'); 465 | } 466 | 467 | session.clientCertificateSignatureAlgorithm = signalgo; 468 | } 469 | 470 | session.send(CERTIFICATE); 471 | } 472 | 473 | /** 474 | * Sends `client key exchange` message. 475 | * @param {ClientSession} session 476 | */ 477 | function clientKeyExchange(session) { 478 | debug('prepare client key exchange'); 479 | const { isCertificateRequested } = session; 480 | 481 | if (!isCertificateRequested) { 482 | session.retransmitter.prepare(); 483 | } 484 | 485 | session.send(CLIENT_KEY_EXCHANGE); 486 | 487 | session.createMasterSecret(); 488 | debug('MASTER SECRET %h', session.masterSecret); 489 | 490 | session.needCertificateVerify = 491 | isCertificateRequested && session.clientCertificate !== null; 492 | } 493 | 494 | /** 495 | * Send CERTIFICATE_VERIFY message. 496 | * @param {ClientSession} session 497 | */ 498 | function certificateVerify(session) { 499 | debug('prepare certificate verify'); 500 | 501 | session.createSignature(); 502 | session.send(CERTIFICATE_VERIFY); 503 | } 504 | 505 | /** 506 | * Send CHANGE_CIPHER_SPEC message. 507 | * @param {ClientSession} session 508 | */ 509 | function clientChangeCipherSpec(session) { 510 | debug('prepare change cipher spec'); 511 | 512 | session.send(CHANGE_CIPHER_SPEC); 513 | session.nextEpochClient(); 514 | } 515 | 516 | /** 517 | * Send FINISHED message. 518 | * @param {ClientSession} session 519 | */ 520 | function clientFinished(session) { 521 | debug('prepare client finished'); 522 | session.createClientFinished(); 523 | debug('client finished %h', session.clientFinished); 524 | 525 | session.send(FINISHED); 526 | session.retransmitter.send(); 527 | } 528 | 529 | /** 530 | * Send CHANGE_CIPHER_SPEC message. 531 | * @param {ClientSession} session 532 | */ 533 | function serverChangeCipherSpec(session) { 534 | debug('got change cipher spec'); 535 | session.nextEpochServer(); 536 | } 537 | 538 | /** 539 | * Handle FINISHED message. 540 | * @param {ClientSession} session 541 | * @param {Object} message 542 | */ 543 | function serverFinished(session, message) { 544 | debug('got finished'); 545 | 546 | const handshake = message.fragment; 547 | debug('received server finished %h', handshake.body); 548 | 549 | session.createServerFinished(); 550 | debug('computed server finished %h', session.serverFinished); 551 | 552 | if (Buffer.compare(handshake.body, session.serverFinished) !== 0) { 553 | throw new Error('Mismatch server finished messages'); 554 | } 555 | 556 | session.retransmitter.finish(); 557 | session.finishHandshake(); 558 | } 559 | 560 | /** 561 | * Handle incoming `alert` messages. 562 | * @param {ClientSession} session 563 | * @param {Object} message 564 | */ 565 | function alert(session, message) { 566 | debug('got alert'); 567 | const packet = message.fragment; 568 | 569 | const { level, description } = decode(packet, Alert); 570 | debug('level %s, description %s', level, description); 571 | 572 | session.error(description); 573 | } 574 | 575 | /** 576 | * Handle incoming `application data` message. 577 | * @param {ClientSession} session 578 | * @param {Object} message 579 | */ 580 | function applicationData(session, message) { 581 | debug('got application data'); 582 | 583 | const appdata = message.fragment; 584 | debug('packet: %h', appdata); 585 | 586 | session.packet(appdata); 587 | } 588 | -------------------------------------------------------------------------------- /src/node_modules/protocol/client/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const { Writable } = require('readable-stream'); 4 | const ClientSession = require('session/client'); // eslint-disable-line no-unused-vars 5 | const { contentType } = require('lib/constants'); 6 | const handlers = require('protocol/client/handlers'); 7 | const flow = require('protocol/flow'); 8 | 9 | const _session = Symbol('session'); 10 | const _message = Symbol('message'); 11 | const _state = Symbol('state'); 12 | 13 | const { 14 | clientHello, 15 | helloVerifyRequest, 16 | serverHello, 17 | serverCertificate, 18 | serverKeyExchange, 19 | certificateRequest, 20 | serverHelloDone, 21 | clientCertificate, 22 | clientKeyExchange, 23 | certificateVerify, 24 | clientChangeCipherSpec, 25 | clientFinished, 26 | serverChangeCipherSpec, 27 | serverFinished, 28 | alert, 29 | applicationData, 30 | } = handlers; 31 | 32 | const { DTLS_SERVER } = flow.constants; 33 | 34 | const { 35 | CLIENT_HELLO, 36 | SERVER_HELLO, 37 | HELLO_VERIFY_REQUEST, 38 | CERTIFICATE, 39 | SERVER_KEY_EXCHANGE, 40 | CERTIFICATE_REQUEST, 41 | SERVER_HELLO_DONE, 42 | CERTIFICATE_VERIFY, 43 | CLIENT_KEY_EXCHANGE, 44 | FINISHED, 45 | CHANGE_CIPHER_SPEC, 46 | CLIENT_CERTIFICATE, 47 | CLIENT_FINISHED, 48 | CLIENT_CHANGE_CIPHER_SPEC, 49 | } = flow.states; 50 | 51 | /** 52 | * This class implements DTLS v1.2 using finite state machine. 53 | */ 54 | class ClientProtocol extends Writable { 55 | /** 56 | * @class ClientProtocol 57 | * @param {ClientSession} session 58 | */ 59 | constructor(session) { 60 | super({ objectMode: true, decodeStrings: false }); 61 | this[_message] = null; 62 | this[_session] = session; 63 | 64 | const machine = flow.createDtls(); 65 | this[_state] = machine; 66 | 67 | setup(machine, this); 68 | } 69 | 70 | /** 71 | * @returns {ClientSession} 72 | */ 73 | get session() { 74 | return this[_session]; 75 | } 76 | 77 | /** 78 | * Last received message. 79 | * @returns {Object|null} 80 | */ 81 | get message() { 82 | return this[_message]; 83 | } 84 | 85 | /** 86 | * Get internal state machine. 87 | * @returns {StateMachine} 88 | */ 89 | get state() { 90 | return this[_state]; 91 | } 92 | 93 | /** 94 | * Starts a new client-side handshake flow. 95 | */ 96 | start() { 97 | this.session.startHandshake(() => this.state.next(CLIENT_HELLO)); 98 | } 99 | 100 | /** 101 | * @private 102 | * @param {Object} record 103 | * @param {string} encoding 104 | * @param {Function} callback 105 | */ 106 | _write(record, encoding, callback) { 107 | this[_message] = record; 108 | 109 | const protocol = record.type; 110 | 111 | const isAlert = protocol === contentType.ALERT; 112 | const isApplicationData = protocol === contentType.APPLICATION_DATA; 113 | const isHandshake = protocol === contentType.HANDSHAKE; 114 | 115 | if (isAlert) { 116 | alert(this.session, record); 117 | } else if (!this.session.isHandshakeInProcess && isApplicationData) { 118 | applicationData(this.session, record); 119 | } else { 120 | const type = isHandshake ? record.fragment.type : 0; 121 | const state = flow.createState(DTLS_SERVER, protocol, type); 122 | 123 | // Process current message. 124 | this.state.next(state); 125 | 126 | if (isHandshake) { 127 | this.session.lastRvHandshake = record.fragment.sequence; 128 | } 129 | } 130 | 131 | if (protocol !== contentType.CHANGE_CIPHER_SPEC) { 132 | this.session.window.accept(record.sequenceNumber); 133 | } 134 | 135 | this[_message] = null; 136 | callback(); 137 | } 138 | 139 | /** 140 | * @private 141 | */ 142 | _destroy() { 143 | this[_session] = null; 144 | this[_message] = null; 145 | this[_state] = null; 146 | } 147 | } 148 | 149 | /** 150 | * Set up session handlers. 151 | * @param {StateMachine} state 152 | * @param {ClientProtocol} protocol 153 | */ 154 | function setup(state, protocol) { 155 | state.on(CLIENT_HELLO, () => clientHello(protocol.session, protocol.message)); 156 | state.on(HELLO_VERIFY_REQUEST, () => { 157 | helloVerifyRequest(protocol.session, protocol.message); 158 | protocol.state.next(CLIENT_HELLO); 159 | }); 160 | state.on(SERVER_HELLO, () => serverHello(protocol.session, protocol.message)); 161 | state.on(CERTIFICATE, () => 162 | serverCertificate(protocol.session, protocol.message) 163 | ); 164 | state.on(SERVER_KEY_EXCHANGE, () => 165 | serverKeyExchange(protocol.session, protocol.message) 166 | ); 167 | state.on(CERTIFICATE_REQUEST, () => 168 | certificateRequest(protocol.session, protocol.message) 169 | ); 170 | state.on(SERVER_HELLO_DONE, () => 171 | serverHelloDone(protocol.session, protocol.message, () => { 172 | const nextState = protocol.session.isCertificateRequested 173 | ? CLIENT_CERTIFICATE 174 | : CLIENT_KEY_EXCHANGE; 175 | protocol.state.next(nextState); 176 | }) 177 | ); 178 | state.on(CLIENT_CERTIFICATE, () => { 179 | clientCertificate(protocol.session); 180 | protocol.state.next(CLIENT_KEY_EXCHANGE); 181 | }); 182 | state.on(CLIENT_KEY_EXCHANGE, () => { 183 | clientKeyExchange(protocol.session); 184 | 185 | const nextState = protocol.session.needCertificateVerify 186 | ? CERTIFICATE_VERIFY 187 | : CLIENT_CHANGE_CIPHER_SPEC; 188 | protocol.state.next(nextState); 189 | }); 190 | state.on(CERTIFICATE_VERIFY, () => { 191 | certificateVerify(protocol.session); 192 | protocol.state.next(CLIENT_CHANGE_CIPHER_SPEC); 193 | }); 194 | state.on(CLIENT_CHANGE_CIPHER_SPEC, () => { 195 | clientChangeCipherSpec(protocol.session); 196 | protocol.state.next(CLIENT_FINISHED); 197 | }); 198 | state.on(CLIENT_FINISHED, () => clientFinished(protocol.session)); 199 | state.on(CHANGE_CIPHER_SPEC, () => serverChangeCipherSpec(protocol.session)); 200 | state.on(FINISHED, () => serverFinished(protocol.session, protocol.message)); 201 | } 202 | 203 | module.exports = ClientProtocol; 204 | -------------------------------------------------------------------------------- /src/node_modules/protocol/flow.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // eslint-disable-next-line no-unused-vars 4 | const { createMachine, createState, StateMachine } = require('next-state'); 5 | const { constants, createSign } = require('protocol/packet'); 6 | 7 | const HANDSHAKE_INIT = 'HANDSHAKE_INIT'; 8 | const HANDSHAKE_FINISH = 'HANDSHAKE_FINISH'; 9 | const CLIENT_HELLO = 'CLIENT_HELLO'; 10 | const SERVER_HELLO = 'SERVER_HELLO'; 11 | const HELLO_VERIFY_REQUEST = 'HELLO_VERIFY_REQUEST'; 12 | const CERTIFICATE = 'CERTIFICATE'; 13 | const SERVER_KEY_EXCHANGE = 'SERVER_KEY_EXCHANGE'; 14 | const CERTIFICATE_REQUEST = 'CERTIFICATE_REQUEST'; 15 | const SERVER_HELLO_DONE = 'SERVER_HELLO_DONE'; 16 | const CERTIFICATE_VERIFY = 'CERTIFICATE_VERIFY'; 17 | const CLIENT_KEY_EXCHANGE = 'CLIENT_KEY_EXCHANGE'; 18 | const FINISHED = 'FINISHED'; 19 | const CHANGE_CIPHER_SPEC = 'CHANGE_CIPHER_SPEC'; 20 | const CLIENT_CERTIFICATE = 'CLIENT_CERTIFICATE'; 21 | const CLIENT_FINISHED = 'CLIENT_FINISHED'; 22 | const CLIENT_CHANGE_CIPHER_SPEC = 'CLIENT_CHANGE_CIPHER_SPEC'; 23 | 24 | const DTLS_CLIENT = 0xffff + 1; 25 | const DTLS_SERVER = 0; 26 | 27 | // Transitions of client state machine. 28 | const transitions = { 29 | [HANDSHAKE_INIT]: createState(CLIENT_HELLO), 30 | [CLIENT_HELLO]: createState(HELLO_VERIFY_REQUEST, SERVER_HELLO), 31 | [HELLO_VERIFY_REQUEST]: createState(CLIENT_HELLO), 32 | [SERVER_HELLO]: createState( 33 | CERTIFICATE, 34 | SERVER_KEY_EXCHANGE, 35 | SERVER_HELLO_DONE 36 | ), 37 | [CERTIFICATE]: createState( 38 | SERVER_KEY_EXCHANGE, 39 | CERTIFICATE_REQUEST, 40 | SERVER_HELLO_DONE 41 | ), 42 | [CLIENT_CERTIFICATE]: createState( 43 | CLIENT_KEY_EXCHANGE, 44 | CLIENT_CHANGE_CIPHER_SPEC 45 | ), 46 | [SERVER_KEY_EXCHANGE]: createState(CERTIFICATE_REQUEST, SERVER_HELLO_DONE), 47 | [CERTIFICATE_REQUEST]: createState(SERVER_HELLO_DONE), 48 | [SERVER_HELLO_DONE]: createState(CLIENT_KEY_EXCHANGE, CLIENT_CERTIFICATE), 49 | [CLIENT_KEY_EXCHANGE]: createState( 50 | CERTIFICATE_VERIFY, 51 | CLIENT_CHANGE_CIPHER_SPEC 52 | ), 53 | [CERTIFICATE_VERIFY]: createState(CLIENT_CHANGE_CIPHER_SPEC), 54 | [CHANGE_CIPHER_SPEC]: createState(FINISHED), 55 | [CLIENT_CHANGE_CIPHER_SPEC]: createState(CLIENT_FINISHED), 56 | [FINISHED]: HANDSHAKE_FINISH, 57 | [CLIENT_FINISHED]: createState(CHANGE_CIPHER_SPEC), 58 | }; 59 | 60 | const table = new Map([ 61 | [constants.CLIENT_HELLO, CLIENT_HELLO], 62 | [constants.SERVER_HELLO, SERVER_HELLO], 63 | [constants.HELLO_VERIFY_REQUEST, HELLO_VERIFY_REQUEST], 64 | [constants.CERTIFICATE, CERTIFICATE], 65 | [constants.SERVER_KEY_EXCHANGE, SERVER_KEY_EXCHANGE], 66 | [constants.CERTIFICATE_REQUEST, CERTIFICATE_REQUEST], 67 | [constants.SERVER_HELLO_DONE, SERVER_HELLO_DONE], 68 | [constants.CERTIFICATE_VERIFY, CERTIFICATE_VERIFY], 69 | [constants.CLIENT_KEY_EXCHANGE, CLIENT_KEY_EXCHANGE], 70 | [constants.FINISHED, FINISHED], 71 | [constants.CHANGE_CIPHER_SPEC, CHANGE_CIPHER_SPEC], 72 | [constants.CERTIFICATE | DTLS_CLIENT, CLIENT_CERTIFICATE], // eslint-disable-line no-bitwise 73 | [constants.FINISHED | DTLS_CLIENT, CLIENT_FINISHED], // eslint-disable-line no-bitwise 74 | [constants.CHANGE_CIPHER_SPEC | DTLS_CLIENT, CLIENT_CHANGE_CIPHER_SPEC], // eslint-disable-line no-bitwise 75 | ]); 76 | 77 | const states = { 78 | HANDSHAKE_INIT, 79 | HANDSHAKE_FINISH, 80 | CLIENT_HELLO, 81 | SERVER_HELLO, 82 | HELLO_VERIFY_REQUEST, 83 | CERTIFICATE, 84 | SERVER_KEY_EXCHANGE, 85 | CERTIFICATE_REQUEST, 86 | SERVER_HELLO_DONE, 87 | CERTIFICATE_VERIFY, 88 | CLIENT_KEY_EXCHANGE, 89 | FINISHED, 90 | CHANGE_CIPHER_SPEC, 91 | CLIENT_CERTIFICATE, 92 | CLIENT_FINISHED, 93 | CLIENT_CHANGE_CIPHER_SPEC, 94 | }; 95 | 96 | /** 97 | * Create DTLS state machine. 98 | * @returns {StateMachine} 99 | */ 100 | function createDtls() { 101 | return createMachine(transitions, HANDSHAKE_INIT); 102 | } 103 | 104 | /** 105 | * Creeate state machine's state from received packet. 106 | * @param {number} side DTLS client or server. 107 | * @param {number} protocol Record type. 108 | * @param {number} type Type of handshake or 0. 109 | * @returns {string} 110 | */ 111 | function createStateFromPacket(side, protocol, type) { 112 | const sign = createSign(protocol, type); 113 | return table.get(sign | side); // eslint-disable-line no-bitwise 114 | } 115 | 116 | module.exports = { 117 | createDtls, 118 | createState: createStateFromPacket, 119 | constants: { 120 | DTLS_CLIENT, 121 | DTLS_SERVER, 122 | }, 123 | states, 124 | }; 125 | -------------------------------------------------------------------------------- /src/node_modules/protocol/packet.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const { handshakeType, contentType } = require('lib/constants'); 4 | 5 | const { 6 | CLIENT_HELLO, 7 | SERVER_HELLO, 8 | HELLO_VERIFY_REQUEST, 9 | CERTIFICATE, 10 | SERVER_KEY_EXCHANGE, 11 | CERTIFICATE_REQUEST, 12 | SERVER_HELLO_DONE, 13 | CERTIFICATE_VERIFY, 14 | CLIENT_KEY_EXCHANGE, 15 | FINISHED, 16 | } = handshakeType; 17 | 18 | const { CHANGE_CIPHER_SPEC, HANDSHAKE, ALERT, APPLICATION_DATA } = contentType; 19 | 20 | /** 21 | * Create packet sign based on handshake and protocol types. 22 | * @param {number} protocol 23 | * @param {number} type 24 | * @returns {number} 25 | */ 26 | function createSign(protocol, type) { 27 | return (protocol << 8) | type; // eslint-disable-line no-bitwise 28 | } 29 | 30 | /** 31 | * Get message protocol from state. 32 | * @param {number} state 33 | * @returns {number} 34 | */ 35 | function getProtocol(state) { 36 | return (state >>> 8) & 0xff; // eslint-disable-line no-bitwise 37 | } 38 | 39 | /** 40 | * Get message type from state. 41 | * @param {number} state 42 | * @returns {number} 43 | */ 44 | function getType(state) { 45 | return state & 0xff; // eslint-disable-line no-bitwise 46 | } 47 | 48 | const constants = { 49 | CLIENT_HELLO: createSign(HANDSHAKE, CLIENT_HELLO), 50 | SERVER_HELLO: createSign(HANDSHAKE, SERVER_HELLO), 51 | HELLO_VERIFY_REQUEST: createSign(HANDSHAKE, HELLO_VERIFY_REQUEST), 52 | CERTIFICATE: createSign(HANDSHAKE, CERTIFICATE), 53 | SERVER_KEY_EXCHANGE: createSign(HANDSHAKE, SERVER_KEY_EXCHANGE), 54 | CERTIFICATE_REQUEST: createSign(HANDSHAKE, CERTIFICATE_REQUEST), 55 | SERVER_HELLO_DONE: createSign(HANDSHAKE, SERVER_HELLO_DONE), 56 | CERTIFICATE_VERIFY: createSign(HANDSHAKE, CERTIFICATE_VERIFY), 57 | CLIENT_KEY_EXCHANGE: createSign(HANDSHAKE, CLIENT_KEY_EXCHANGE), 58 | FINISHED: createSign(HANDSHAKE, FINISHED), 59 | CHANGE_CIPHER_SPEC: createSign(CHANGE_CIPHER_SPEC, 0), 60 | ALERT: createSign(ALERT, 0), 61 | APPLICATION_DATA: createSign(APPLICATION_DATA, 0), 62 | HANDSHAKE: createSign(HANDSHAKE, 0), 63 | }; 64 | 65 | module.exports = { 66 | createSign, 67 | getProtocol, 68 | getType, 69 | constants, 70 | }; 71 | -------------------------------------------------------------------------------- /src/node_modules/session/abstract.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /* eslint class-methods-use-this: ["error", { "exceptMethods": ["type"] }] */ 4 | /* eslint-disable getter-return */ 5 | 6 | const assert = require('assert'); 7 | const crypto = require('crypto'); 8 | const Emitter = require('events'); 9 | const { encode, BinaryStream } = require('binary-data'); 10 | const { 11 | protocolVersion, 12 | signTypes, 13 | sessionType, 14 | kxTypes, 15 | defaultCipherSuites, 16 | } = require('lib/constants'); 17 | const { 18 | createMasterSecret, 19 | createPreMasterSecret, 20 | createExtendedMasterSecret, 21 | createPSKPreMasterSecret, 22 | } = require('session/utils'); 23 | const NullCipher = require('cipher/null'); 24 | const debug = require('utils/debug')('dtls:session'); 25 | const { Handshake } = require('lib/protocol'); 26 | const SlidingWindow = require('lib/sliding-window'); 27 | 28 | /** 29 | * This class implements abstract DTLS session. 30 | */ 31 | module.exports = class AbstractSession extends Emitter { 32 | /** 33 | * @class AbstractSession 34 | */ 35 | constructor() { 36 | super(); 37 | 38 | this.version = protocolVersion.DTLS_1_2; 39 | this.clientRandom = null; 40 | this.serverRandom = null; 41 | this.clientEpoch = 0; 42 | this.serverEpoch = 0; 43 | this.recordSequenceNumber = 0; 44 | this.handshakeSequenceNumber = 0; 45 | this.clientFinished = null; 46 | this.serverFinished = null; 47 | this.id = null; 48 | this.mtu = 1200; // default value for Google Chrome 49 | 50 | this.isHandshakeInProcess = false; // getter from FSM? 51 | this.lastRvHandshake = -1; 52 | 53 | this.cipher = new NullCipher(); 54 | this.nextCipher = null; 55 | this.prevCipher = null; 56 | 57 | // List of supported cipher cuites. 58 | this.cipherSuites = defaultCipherSuites; 59 | 60 | this.serverPublicKey = null; 61 | this.masterSecret = null; 62 | this.clientPremaster = null; 63 | 64 | this.peerEllipticPublicKey = null; // peer's ecdhe public key 65 | this.ellipticPublicKey = null; // my public ecdhe key 66 | this.ellipticCurve = null; // curve name 67 | this.ecdhe = null; 68 | 69 | this.extendedMasterSecret = true; 70 | this.peerExtendedMasterSecret = false; 71 | 72 | this.handshakeProtocolReaderState = null; 73 | this.handshakeQueue = new BinaryStream(); 74 | 75 | this.retransmitter = null; 76 | this.window = new SlidingWindow(); 77 | 78 | this.serverCertificate = null; 79 | this.clientCertificate = null; 80 | 81 | this.connectionTimeout = 0; 82 | 83 | // secret part of PSK key exchange 84 | this.pskSecret = null; 85 | } 86 | 87 | /** 88 | * Abstract method to get session type. 89 | */ 90 | get type() { 91 | notImplemented(); 92 | } 93 | 94 | /** 95 | * Emit the event to send a message. 96 | * @param {number} type Message type to send. 97 | */ 98 | send(type) { 99 | this.emit('send', type); 100 | } 101 | 102 | /** 103 | * Send the `application data` message. 104 | * @param {Buffer} data 105 | */ 106 | sendMessage(data) { 107 | this.emit('send:appdata', data); 108 | } 109 | 110 | /** 111 | * Send the `alert` message. 112 | * @param {number} description 113 | * @param {number} level 114 | */ 115 | sendAlert(description, level) { 116 | this.emit('send:alert', description, level); 117 | } 118 | 119 | /** 120 | * Handles starting handshake. 121 | */ 122 | startHandshake() { 123 | debug('start handshake'); 124 | this.emit('handshake:start'); 125 | 126 | this.isHandshakeInProcess = true; 127 | this.lastRvHandshake = -1; 128 | this.handshakeSequenceNumber = -1; 129 | } 130 | 131 | /** 132 | * Handshake successfully ends. 133 | */ 134 | finishHandshake() { 135 | debug('stop handshake'); 136 | this.isHandshakeInProcess = false; 137 | this.handshakeProtocolReaderState = null; 138 | this.peerEllipticPublicKey = null; 139 | this.ellipticPublicKey = null; 140 | this.ecdhe = null; 141 | this.resetHandshakeQueue(); 142 | 143 | this.emit('handshake:finish'); 144 | } 145 | 146 | /** 147 | * Emit the error event. 148 | * @param {number} type Alert description type. 149 | */ 150 | error(type) { 151 | this.emit('error', type); 152 | } 153 | 154 | /** 155 | * Notify the application about arrived server certificate. 156 | * @param {Object} cert The x509 server certificate. 157 | */ 158 | certificate(cert) { 159 | this.serverCertificate = cert; 160 | this.emit('certificate', cert); 161 | } 162 | 163 | /** 164 | * @protected 165 | * @param {Function} done 166 | */ 167 | createPreMasterSecret(done) { 168 | if (this.nextCipher.kx.id === kxTypes.ECDHE_PSK) { 169 | const secret = this.ecdhe.computeSecret(this.peerEllipticPublicKey); 170 | this.clientPremaster = createPSKPreMasterSecret(this.pskSecret, secret); 171 | process.nextTick(done); 172 | } else if (this.nextCipher.kx.signType === signTypes.ECDHE) { 173 | this.clientPremaster = this.ecdhe.computeSecret( 174 | this.peerEllipticPublicKey 175 | ); 176 | process.nextTick(done); 177 | } else if (this.nextCipher.kx.id === kxTypes.PSK) { 178 | this.clientPremaster = createPSKPreMasterSecret(this.pskSecret); 179 | process.nextTick(done); 180 | } else { 181 | createPreMasterSecret(this.version, (err, premaster) => { 182 | if (err) { 183 | throw err; 184 | } 185 | 186 | this.clientPremaster = premaster; 187 | done(); 188 | }); 189 | } 190 | } 191 | 192 | /** 193 | * @protected 194 | */ 195 | createMasterSecret() { 196 | if (this.extendedMasterSecret) { 197 | const handshakes = this.handshakeQueue.slice(); 198 | 199 | this.masterSecret = createExtendedMasterSecret( 200 | this.clientPremaster, 201 | handshakes, 202 | this.nextCipher 203 | ); 204 | } else { 205 | this.masterSecret = createMasterSecret( 206 | this.clientRandom, 207 | this.serverRandom, 208 | this.clientPremaster, 209 | this.nextCipher 210 | ); 211 | } 212 | 213 | this.clientPremaster = null; 214 | } 215 | 216 | /** 217 | * @protected 218 | */ 219 | createElliptic() { 220 | assert.strictEqual(signTypes.ECDHE, this.nextCipher.kx.signType); 221 | 222 | this.ecdhe = crypto.createECDH(this.ellipticCurve); 223 | this.ellipticPublicKey = this.ecdhe.generateKeys(); 224 | } 225 | 226 | /** 227 | * Starts next epoch for clients. 228 | */ 229 | nextEpochClient() { 230 | this.clientEpoch += 1; 231 | 232 | this.prevCipher = this.cipher; 233 | this.cipher = this.nextCipher; 234 | this.nextCipher = null; 235 | 236 | this.cipher.init(this); 237 | } 238 | 239 | /** 240 | * Starts next epoch for server. 241 | */ 242 | nextEpochServer() { 243 | this.serverEpoch += 1; 244 | assert(this.clientEpoch === this.serverEpoch, 'mismatch epoches'); 245 | } 246 | 247 | /** 248 | * Increment outgoing sequence number of record layer 249 | * and return this one. 250 | * @returns {number} 251 | */ 252 | nextRecordNumber() { 253 | this.recordSequenceNumber += 1; 254 | return this.recordSequenceNumber; 255 | } 256 | 257 | /** 258 | * Increment outgoing sequence number of handshake layer 259 | * and return this one. 260 | * @returns {number} 261 | */ 262 | nextHandshakeNumber() { 263 | this.handshakeSequenceNumber += 1; 264 | return this.handshakeSequenceNumber; 265 | } 266 | 267 | /** 268 | * Get epoch of connected peer. 269 | */ 270 | get peerEpoch() { 271 | return this.type === sessionType.CLIENT 272 | ? this.serverEpoch 273 | : this.clientEpoch; 274 | } 275 | 276 | /** 277 | * Store the handshake message for the finished checksum. 278 | * @param {Buffer} packet Encoded handshake message. 279 | */ 280 | appendHandshake(packet) { 281 | assert(this.isHandshakeInProcess); 282 | 283 | if (!Buffer.isBuffer(packet)) { 284 | packet = encode(packet, Handshake).buffer; // eslint-disable-line no-param-reassign 285 | } 286 | 287 | debug('packet added to handshake queue, type = %s', packet.readUInt8(0)); 288 | this.handshakeQueue.append(packet); 289 | } 290 | 291 | /** 292 | * Sometimes we need to clear queue. Just re-create queue for this. 293 | */ 294 | resetHandshakeQueue() { 295 | debug('reset handshake queue'); 296 | this.handshakeQueue = new BinaryStream(); 297 | } 298 | 299 | /** 300 | * Encrypt outgoing record message. 301 | * @param {Cipher} cipher 302 | * @param {Object} record Record layer message. 303 | */ 304 | encrypt(cipher, record) { 305 | const data = record.fragment; 306 | 307 | const encrypted = cipher.encrypt(this, data, record); 308 | record.fragment = encrypted; 309 | record.length = encrypted.length; 310 | } 311 | 312 | /** 313 | * Decrypt incoming record message. 314 | * @param {Cipher} cipher 315 | * @param {Object} record Record layer message. 316 | */ 317 | decrypt(cipher, record) { 318 | const encrypted = record.fragment; 319 | const data = cipher.decrypt(this, encrypted, record); 320 | 321 | record.fragment = data; 322 | } 323 | 324 | /** 325 | * Notify about `application data` message. 326 | * @param {Buffer} data 327 | */ 328 | packet(data) { 329 | this.emit('data', data); 330 | } 331 | }; 332 | 333 | /** 334 | * Fallback for abstract methods. 335 | */ 336 | function notImplemented() { 337 | throw new Error('not implemented'); 338 | } 339 | -------------------------------------------------------------------------------- /src/node_modules/session/client.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /* eslint class-methods-use-this: ["error", { "exceptMethods": ["type"] }] */ 4 | 5 | const crypto = require('crypto'); 6 | const AbstractSession = require('session/abstract'); 7 | const { sessionType, randomSize } = require('lib/constants'); 8 | const { 9 | createRandom, 10 | createFinished, 11 | getHashNameBySignAlgo, 12 | } = require('session/utils'); 13 | const { createRetransmitClient } = require('lib/retransmitter'); 14 | 15 | /** 16 | * This class implements DTLS client-side session. 17 | */ 18 | module.exports = class ClientSession extends AbstractSession { 19 | /** 20 | * @class ClientSession 21 | */ 22 | constructor() { 23 | super(); 24 | 25 | this.cookie = null; 26 | this.retransmitter = createRetransmitClient(); 27 | 28 | // check if `CertificateRequest` message arrived 29 | this.isCertificateRequested = false; 30 | 31 | // check the need to send CERTIFICATE_VERIFY message 32 | this.needCertificateVerify = false; 33 | 34 | // possible certificate types from `CertificateRequest` 35 | this.requestedCertificateTypes = []; 36 | 37 | // possible signature algorithms from `CertificateRequest` 38 | this.requestedSignatureAlgorithms = []; 39 | 40 | // selected by client from offered options in `CertificateRequest` 41 | this.clientCertificateSignatureAlgorithm = null; 42 | 43 | // signed part of `ClientVerify` message 44 | this.clientCertificateSignature = null; 45 | 46 | // private key for client certificate 47 | this.clientCertificatePrivateKey = null; 48 | 49 | this.serverCertificateVerifyCallback = () => true; 50 | 51 | // the list of the supported alpn protocols 52 | this.alpnProtocols = []; 53 | 54 | // The name of the protocol selected by server 55 | this.selectedALPNProtocol = ''; 56 | 57 | // auth credentials for PSK key exchange 58 | this.clientPSKIdentity = null; 59 | 60 | this.ignorePSKIdentityHint = true; 61 | this.serverPSKIdentityHint = null; 62 | } 63 | 64 | /** 65 | * Get type of the Session. 66 | * @returns {number} 67 | */ 68 | get type() { 69 | return sessionType.CLIENT; 70 | } 71 | 72 | /** 73 | * Handles starting of handshake. 74 | * @param {Function} done 75 | */ 76 | startHandshake(done) { 77 | super.startHandshake(); 78 | 79 | this.clientRandom = Buffer.allocUnsafe(randomSize); 80 | createRandom(this.clientRandom, done); 81 | } 82 | 83 | /** 84 | * Create finished message checksum for client. 85 | */ 86 | createClientFinished() { 87 | const queue = this.handshakeQueue.slice(); 88 | 89 | this.clientFinished = createFinished( 90 | this.cipher, 91 | this.masterSecret, 92 | queue, 93 | 'client finished' 94 | ); 95 | } 96 | 97 | /** 98 | * Create finished message checksum for server. 99 | */ 100 | createServerFinished() { 101 | const queue = this.handshakeQueue.slice(); 102 | 103 | this.serverFinished = createFinished( 104 | this.cipher, 105 | this.masterSecret, 106 | queue, 107 | 'server finished' 108 | ); 109 | } 110 | 111 | /** 112 | * Starts next epoch for server. 113 | */ 114 | nextEpochServer() { 115 | super.nextEpochServer(); 116 | 117 | this.window.reset(); 118 | } 119 | 120 | /** 121 | * The x509 Certificate. 122 | * An instance of @fidm/x509/Certificate. 123 | * @param {Certificate} certificate 124 | * @returns {bool} 125 | */ 126 | verifyCertificate(certificate) { 127 | return Boolean(this.serverCertificateVerifyCallback(certificate)); 128 | } 129 | 130 | /** 131 | * Create signature for `Certificate Verify` message. 132 | */ 133 | createSignature() { 134 | const handshakeMessages = this.handshakeQueue.slice(); 135 | const hash = getHashNameBySignAlgo( 136 | this.clientCertificateSignatureAlgorithm 137 | ); 138 | 139 | const signature = crypto.createSign(hash).update(handshakeMessages); 140 | 141 | this.clientCertificateSignature = signature.sign( 142 | this.clientCertificatePrivateKey 143 | ); 144 | } 145 | }; 146 | -------------------------------------------------------------------------------- /src/node_modules/session/utils.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const assert = require('assert'); 4 | const crypto = require('crypto'); 5 | const { 6 | sessionType, 7 | signatureScheme, 8 | certificateType, 9 | } = require('lib/constants'); 10 | const { 11 | encode, 12 | createEncode, 13 | types: { uint16be, buffer }, 14 | } = require('binary-data'); 15 | 16 | const { RSA_PKCS1_PADDING } = crypto.constants; 17 | 18 | /** 19 | * Check if argument is client type session. 20 | * @param {AbstractSession} session 21 | */ 22 | function assertClient(session) { 23 | assert((session.sessionType = sessionType.CLIENT)); 24 | } 25 | 26 | /** 27 | * Check if argument is server type session. 28 | * @param {AbstractSession} session 29 | */ 30 | function assertServer(session) { 31 | assert((session.sessionType = sessionType.SERVER)); 32 | } 33 | 34 | /** 35 | * Create unix time from now. 36 | * @returns {number} 37 | */ 38 | function unixtime() { 39 | return parseInt(Date.now() / 1e3, 10); 40 | } 41 | 42 | /** 43 | * @param {Buffer} random 44 | * @param {Function} [done] 45 | */ 46 | function createRandom(random, done) { 47 | random.writeUInt32BE(unixtime(), 0); 48 | crypto.randomFill(random, 4, done); 49 | } 50 | 51 | /** 52 | * @param {number} version 53 | * @param {Function} [done] 54 | */ 55 | function createPreMasterSecret(version, done) { 56 | const premaster = Buffer.allocUnsafe(48); 57 | 58 | premaster.writeUInt16BE(version, 0); 59 | crypto.randomFill(premaster, 2, done); 60 | } 61 | 62 | /** 63 | * Create premaster secret for PSK key exchange. 64 | * @param {Buffer} psk Secret part of psk key exchange. 65 | * @param {Buffer} [otherSecret] 66 | * @returns {Buffer} 67 | */ 68 | function createPSKPreMasterSecret(psk, otherSecret) { 69 | const premaster = createEncode(); 70 | 71 | if (Buffer.isBuffer(otherSecret)) { 72 | encode(otherSecret, buffer(uint16be), premaster); 73 | } else { 74 | const zeros = Buffer.alloc(psk.length, 0); 75 | encode(zeros, buffer(uint16be), premaster); 76 | } 77 | 78 | encode(psk, buffer(uint16be), premaster); 79 | 80 | return premaster.slice(); 81 | } 82 | 83 | /** 84 | * @param {Buffer} clientRandom 85 | * @param {Buffer} serverRandom 86 | * @param {Buffer} premaster 87 | * @param {Object} cipher 88 | * @returns {Buffer} 89 | */ 90 | function createMasterSecret(clientRandom, serverRandom, premaster, cipher) { 91 | const seed = Buffer.concat([clientRandom, serverRandom]); 92 | 93 | const label = 'master secret'; 94 | const masterSecret = cipher.prf(48, premaster, label, seed); 95 | 96 | return masterSecret; 97 | } 98 | 99 | /** 100 | * @param {Buffer} premaster 101 | * @param {Buffer} handshakes List of all handshake messages. 102 | * @param {Object} cipher 103 | * @returns {Buffer} 104 | */ 105 | function createExtendedMasterSecret(premaster, handshakes, cipher) { 106 | const sessionHash = hash(cipher.hash, handshakes); 107 | const label = 'extended master secret'; 108 | const masterSecret = cipher.prf(48, premaster, label, sessionHash); 109 | 110 | return masterSecret; 111 | } 112 | 113 | /** 114 | * @param {Buffer} publicKey 115 | * @param {Buffer} premaster 116 | * @returns {Buffer} 117 | */ 118 | function encryptPreMasterSecret(publicKey, premaster) { 119 | const encrypted = crypto.publicEncrypt( 120 | { key: publicKey, padding: RSA_PKCS1_PADDING }, 121 | premaster 122 | ); 123 | 124 | return encrypted; 125 | } 126 | 127 | /** 128 | * Create `Finished` message. 129 | * @param {cipher} cipher 130 | * @param {Buffer} master Master secret. 131 | * @param {Buffer} handshakes List of all handshake messages. 132 | * @param {string} label 133 | * @returns {Buffer} 134 | */ 135 | function createFinished(cipher, master, handshakes, label) { 136 | const bytes = hash(cipher.hash, handshakes); 137 | const final = cipher.prf(cipher.verifyDataLength, master, label, bytes); 138 | 139 | return final; 140 | } 141 | 142 | /** 143 | * @param {string} algorithm Hash algorithm. 144 | * @param {Buffer} data Data to encrypt. 145 | * @returns {Buffer} 146 | */ 147 | function hash(algorithm, data) { 148 | return crypto 149 | .createHash(algorithm) 150 | .update(data) 151 | .digest(); 152 | } 153 | 154 | /** 155 | * Get hash name by signature algorithm. 156 | * @param {number} algorithm 157 | * @returns {string|null} 158 | */ 159 | function getHashNameBySignAlgo(algorithm) { 160 | switch (algorithm) { 161 | case signatureScheme.ecdsa_secp256r1_sha256: 162 | case signatureScheme.rsa_pkcs1_sha256: 163 | case signatureScheme.rsa_pss_pss_sha256: 164 | case signatureScheme.rsa_pss_rsae_sha256: 165 | return 'sha256'; 166 | case signatureScheme.ecdsa_secp384r1_sha384: 167 | case signatureScheme.rsa_pkcs1_sha384: 168 | case signatureScheme.rsa_pss_pss_sha384: 169 | case signatureScheme.rsa_pss_rsae_sha384: 170 | return 'sha384'; 171 | case signatureScheme.ecdsa_secp521r1_sha512: 172 | case signatureScheme.rsa_pkcs1_sha512: 173 | case signatureScheme.rsa_pss_pss_sha512: 174 | case signatureScheme.rsa_pss_rsae_sha512: 175 | return 'sha512'; 176 | case signatureScheme.ecdsa_sha1: 177 | case signatureScheme.rsa_pkcs1_sha1: 178 | return 'sha1'; 179 | default: 180 | break; 181 | } 182 | return null; 183 | } 184 | 185 | /** 186 | * Get certificate type. 187 | * @param {Object} certificate The x509 certificate object. 188 | * @returns {number} 189 | */ 190 | function getCertificateType(certificate) { 191 | switch (certificate.publicKey.algo) { 192 | case 'ecEncryption': 193 | return certificateType.ecdsa_sign; 194 | case 'rsaEncryption': 195 | return certificateType.rsa_sign; 196 | default: 197 | break; 198 | } 199 | 200 | throw new Error('Unknown certificate public key'); 201 | } 202 | 203 | /** 204 | * Get certificate signature algorithm. 205 | * @param {Object} certificate The x509 certificate object. 206 | * @returns {number} 207 | */ 208 | function getCertificateSignatureAlgorithm(certificate) { 209 | switch (certificate.signatureAlgorithm) { 210 | case 'ecdsaWithSha1': 211 | return signatureScheme.ecdsa_sha1; 212 | case 'ecdsaWithSha256': 213 | return signatureScheme.ecdsa_secp256r1_sha256; 214 | case 'ecdsaWithSha384': 215 | return signatureScheme.ecdsa_secp384r1_sha384; 216 | case 'ecdsaWithSha512': 217 | return signatureScheme.ecdsa_secp521r1_sha512; 218 | case 'sha512WithRsaEncryption': 219 | return signatureScheme.rsa_pkcs1_sha512; 220 | case 'sha384WithRsaEncryption': 221 | return signatureScheme.rsa_pkcs1_sha384; 222 | case 'sha256WithRsaEncryption': 223 | return signatureScheme.rsa_pkcs1_sha256; 224 | case 'sha1WithRsaEncryption': 225 | return signatureScheme.rsa_pkcs1_sha1; 226 | default: 227 | break; 228 | } 229 | 230 | throw new Error('Unknown certificate signature algorithm'); 231 | } 232 | 233 | module.exports = { 234 | hash, 235 | createRandom, 236 | unixtime, 237 | assertClient, 238 | assertServer, 239 | createPreMasterSecret, 240 | createPSKPreMasterSecret, 241 | createMasterSecret, 242 | createExtendedMasterSecret, 243 | encryptPreMasterSecret, 244 | createFinished, 245 | getHashNameBySignAlgo, 246 | getCertificateSignatureAlgorithm, 247 | getCertificateType, 248 | }; 249 | -------------------------------------------------------------------------------- /src/node_modules/utils/cipher-suite.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const { cipherSuites } = require('lib/constants'); 4 | const isChachaSupported = require('is-chacha20-poly1305-supported'); 5 | 6 | const ciphers = new Set(Object.values(cipherSuites)); 7 | 8 | const chacha20Ciphers = [ 9 | 'TLS_PSK_WITH_CHACHA20_POLY1305_SHA256', 10 | 'TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256', 11 | 'TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256', 12 | ]; 13 | 14 | module.exports = { 15 | isCipherSuite, 16 | toCipherSuite, 17 | }; 18 | 19 | /** 20 | * Check if argument is supported cipher suite. 21 | * @param {number} cipher 22 | * @returns {boolean} 23 | */ 24 | function isCipherSuite(cipher) { 25 | return ciphers.has(cipher); 26 | } 27 | 28 | /** 29 | * Convert cipher name to it's value. 30 | * @param {string|number} cipher 31 | * @returns {number} 32 | */ 33 | function toCipherSuite(cipher) { 34 | const toCipher = maybe => (isCipherSuite(maybe) ? maybe : -1); 35 | 36 | if (typeof cipher === 'string') { 37 | if (!isChachaSupported && chacha20Ciphers.includes(cipher)) { 38 | return -1; 39 | } 40 | 41 | return toCipher(cipherSuites[cipher]); 42 | } 43 | 44 | if (typeof cipher === 'number') { 45 | return toCipher(cipher); 46 | } 47 | 48 | return -1; 49 | } 50 | -------------------------------------------------------------------------------- /src/node_modules/utils/debug.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const debug = require('debug'); 4 | 5 | debug.formatters.h = v => v.toString('hex'); 6 | 7 | module.exports = debug; 8 | -------------------------------------------------------------------------------- /test/integrated/test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const dtls = require('../..'); 4 | 5 | const { CIPHER } = process.env; 6 | 7 | const socket = dtls.connect({ 8 | type: 'udp4', 9 | remotePort: 4444, 10 | remoteAddress: '127.0.0.1', 11 | maxHandshakeRetransmissions: 4, 12 | pskIdentity: 'travis', 13 | pskSecret: Buffer.from('deadbeef', 'hex'), 14 | cipherSuites: [CIPHER], 15 | }); 16 | 17 | socket.once('error', err => { 18 | console.error(err); 19 | socket.close(); 20 | process.exit(-1); 21 | }); 22 | 23 | socket.once('connect', () => { 24 | socket.close(); 25 | }); 26 | 27 | socket.once('timeout', () => { 28 | console.log('got timeout'); 29 | process.exit(-1); 30 | }); 31 | 32 | socket.setTimeout(5e3); 33 | -------------------------------------------------------------------------------- /test/unit/filter/reordering.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /* eslint-env jest */ 4 | 5 | const Emitter = require('events'); 6 | const Reordering = require('filter/reordering'); 7 | const { contentType } = require('lib/constants'); 8 | 9 | class MockSlidingWindow { 10 | constructor(value) { 11 | this._value = Boolean(value); 12 | } 13 | 14 | /** 15 | * @returns {bool} 16 | */ 17 | check() { 18 | return this._value; 19 | } 20 | } 21 | 22 | test('epoches should equals', () => { 23 | const session = { 24 | isHandshakeInProcess: false, 25 | peerEpoch: 1, 26 | lastRvHandshake: 1, 27 | retransmitter: new Emitter(), 28 | window: new MockSlidingWindow(true), 29 | }; 30 | 31 | const validRecord = { 32 | epoch: session.peerEpoch, 33 | type: contentType.APPLICATION_DATA, 34 | fragment: { 35 | sequence: session.lastRvHandshake + 1, 36 | }, 37 | }; 38 | 39 | const invalidRecord = { 40 | epoch: session.peerEpoch + 1, 41 | type: contentType.APPLICATION_DATA, 42 | fragment: { 43 | sequence: session.lastRvHandshake + 1, 44 | }, 45 | }; 46 | 47 | const reorder = new Reordering(session); 48 | 49 | const callbackValid = jest.fn(); 50 | const callbackInvalid = jest.fn(); 51 | reorder.push = jest.fn(); 52 | 53 | reorder._transform(validRecord, null, callbackValid); 54 | reorder._transform(invalidRecord, null, callbackInvalid); 55 | 56 | expect(reorder.push).toHaveBeenCalledTimes(1); 57 | expect(reorder.push).toBeCalledWith(validRecord); 58 | 59 | expect(callbackValid).toBeCalledWith(); 60 | expect(callbackValid).toHaveBeenCalledTimes(1); 61 | 62 | expect(callbackInvalid).toBeCalledWith(); 63 | expect(callbackInvalid).toHaveBeenCalledTimes(1); 64 | }); 65 | 66 | test('drop record replays', () => { 67 | const session = { 68 | isHandshakeInProcess: true, 69 | peerEpoch: 1, 70 | lastRvHandshake: 2, 71 | retransmitter: new Emitter(), 72 | window: new MockSlidingWindow(true), 73 | }; 74 | 75 | const validRecord = { 76 | epoch: session.peerEpoch, 77 | type: contentType.HANDSHAKE, 78 | fragment: { 79 | sequence: session.lastRvHandshake + 1, 80 | }, 81 | }; 82 | 83 | const invalidRecord = { 84 | epoch: session.peerEpoch, 85 | type: contentType.HANDSHAKE, 86 | fragment: { 87 | sequence: session.lastRvHandshake + 1, 88 | }, 89 | }; 90 | 91 | const reorder = new Reordering(session); 92 | 93 | const callbackValid = jest.fn(); 94 | const callbackInvalid = jest.fn(); 95 | reorder.push = jest.fn(); 96 | 97 | reorder._transform(validRecord, null, callbackValid); 98 | 99 | session.window = new MockSlidingWindow(false); 100 | reorder._transform(invalidRecord, null, callbackInvalid); 101 | 102 | expect(reorder.push).toHaveBeenCalledTimes(1); 103 | expect(reorder.push).toBeCalledWith(validRecord); 104 | 105 | expect(callbackValid).toBeCalledWith(); 106 | expect(callbackValid).toHaveBeenCalledTimes(1); 107 | 108 | expect(callbackInvalid).toBeCalledWith(); 109 | expect(callbackInvalid).toHaveBeenCalledTimes(1); 110 | }); 111 | 112 | test('drop handshake replays', () => { 113 | const session = { 114 | isHandshakeInProcess: true, 115 | peerEpoch: 1, 116 | lastRvHandshake: 2, 117 | retransmitter: new Emitter(), 118 | window: new MockSlidingWindow(true), 119 | }; 120 | 121 | const validRecord = { 122 | epoch: session.peerEpoch, 123 | type: contentType.HANDSHAKE, 124 | fragment: { 125 | sequence: session.lastRvHandshake + 1, 126 | }, 127 | }; 128 | 129 | const invalidRecord = { 130 | epoch: session.peerEpoch, 131 | type: contentType.HANDSHAKE, 132 | fragment: { 133 | sequence: session.lastRvHandshake, 134 | }, 135 | }; 136 | 137 | const reorder = new Reordering(session); 138 | 139 | const callbackValid = jest.fn(); 140 | const callbackInvalid = jest.fn(); 141 | reorder.push = jest.fn(); 142 | 143 | reorder._transform(validRecord, null, callbackValid); 144 | reorder._transform(invalidRecord, null, callbackInvalid); 145 | 146 | expect(reorder.push).toHaveBeenCalledTimes(1); 147 | expect(reorder.push).toBeCalledWith(validRecord); 148 | 149 | expect(callbackValid).toBeCalledWith(); 150 | expect(callbackValid).toHaveBeenCalledTimes(1); 151 | 152 | expect(callbackInvalid).toBeCalledWith(); 153 | expect(callbackInvalid).toHaveBeenCalledTimes(1); 154 | }); 155 | 156 | test('should sort unordered records', () => { 157 | const session = { 158 | isHandshakeInProcess: true, 159 | peerEpoch: 1, 160 | lastRvHandshake: 1, 161 | retransmitter: new Emitter(), 162 | window: new MockSlidingWindow(true), 163 | }; 164 | 165 | const firstRecord = { 166 | epoch: session.peerEpoch, 167 | type: contentType.HANDSHAKE, 168 | sequenceNumber: 1, 169 | fragment: { 170 | sequence: session.lastRvHandshake + 1, 171 | }, 172 | }; 173 | 174 | const secondRecord = { 175 | epoch: session.peerEpoch, 176 | type: contentType.HANDSHAKE, 177 | sequenceNumber: 2, 178 | fragment: { 179 | sequence: session.lastRvHandshake + 2, 180 | }, 181 | }; 182 | 183 | const reorder = new Reordering(session); 184 | reorder.push = jest.fn(() => { 185 | session.lastRvHandshake += 1; 186 | }); 187 | 188 | const callbackFirst = jest.fn(); 189 | const callbackSecond = jest.fn(); 190 | 191 | reorder._transform(secondRecord, null, callbackFirst); 192 | expect(reorder.push).not.toBeCalled(); 193 | expect(callbackFirst).toBeCalledWith(); 194 | expect(reorder.queueSize).toEqual(1); 195 | 196 | reorder._transform(firstRecord, null, callbackSecond); 197 | expect(callbackSecond).toBeCalledWith(); 198 | expect(reorder.push).toHaveBeenCalledTimes(2); 199 | expect(reorder.push).toHaveBeenNthCalledWith(1, firstRecord); 200 | expect(reorder.push).toHaveBeenNthCalledWith(2, secondRecord); 201 | expect(reorder.queueSize).toEqual(0); 202 | }); 203 | --------------------------------------------------------------------------------