├── .editorconfig ├── .gitignore ├── .jshintrc ├── .travis.yml ├── BufferBuilder.js ├── BufferReader.js ├── Certificate.js ├── CipherInfo.js ├── ClientHandshakeHandler.js ├── DtlsRecordLayer.js ├── DtlsServer.js ├── DtlsSocket.js ├── HandshakeBuilder.js ├── KeyContext.js ├── LICENSE ├── README.md ├── SecurityParameterContainer.js ├── SecurityParameters.js ├── SequenceNumber.js ├── ServerHandshakeHandler.js ├── certificateUtilities.js ├── dtls.js ├── example ├── client.js ├── send_throughput.js ├── server.js └── throughput.js ├── index.js ├── package.json ├── packets ├── DtlsCertificate.js ├── DtlsChangeCipherSpec.js ├── DtlsClientHello.js ├── DtlsClientKeyExchange_rsa.js ├── DtlsExtension.js ├── DtlsFinished.js ├── DtlsHandshake.js ├── DtlsHelloVerifyRequest.js ├── DtlsPlaintext.js ├── DtlsPreMasterSecret.js ├── DtlsProtocolVersion.js ├── DtlsRandom.js ├── DtlsServerHello.js ├── DtlsServerHelloDone.js ├── DtlsServerKeyExchange.js ├── Packet.js ├── PacketSpec.js └── index.js ├── prf.js └── test ├── .jshintrc ├── Buffer.js ├── ClientHandshakeHandler.js ├── HandshakeBuilder.js ├── PacketSpec.js ├── Packets.js ├── SecurityParameterContainer.js ├── SecurityParameters.js ├── SequenceNumber.js ├── assets └── certificate.pem ├── openssl.js └── prf.js /.editorconfig: -------------------------------------------------------------------------------- 1 | 2 | root = true 3 | 4 | [*] 5 | end_of_line = lf 6 | insert_final_newline = true 7 | 8 | [*.js] 9 | charset = utf-8 10 | indent_style = space 11 | indent_size = 4 12 | trim_trailing_whitespace = true 13 | 14 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /coverage 2 | /node_modules 3 | *.swp 4 | server.pem 5 | -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | // 2 | // Global JSHint configuration file for common code. 3 | // 4 | // Note: 5 | // src/node_modules/client has it's own JSHint config that 6 | // is set up for browser environment. 7 | // 8 | { 9 | // Settings 10 | "passfail" : false, // Stop on first error. 11 | "maxerr" : 100, // Maximum error before stopping. 12 | 13 | 14 | // Predefined globals whom JSHint will ignore. 15 | "browser" : false, // Standard browser globals e.g. `window`, `document`. 16 | 17 | "node" : true, 18 | "rhino" : false, 19 | "couch" : false, 20 | "wsh" : false, // Windows Scripting Host. 21 | 22 | "jquery" : false, 23 | "prototypejs" : false, 24 | "mootools" : false, 25 | "dojo" : false, 26 | 27 | "predef" : [ // Custom globals. 28 | "load_on_client", 29 | "load_on_server", 30 | "load_environment", 31 | "environment" 32 | //"exampleVar", 33 | //"anotherCoolGlobal", 34 | //"iLoveDouglas" 35 | ], 36 | 37 | 38 | // Development. 39 | "debug" : false, // Allow debugger statements e.g. browser breakpoints. 40 | "devel" : false, // Allow developments statements e.g. `console.log();`. 41 | 42 | 43 | // ECMAScript 5. 44 | "strict" : true, // Require `use strict` pragma in every file. 45 | "globalstrict" : true, // Allow global "use strict" (also enables 'strict'). 46 | 47 | 48 | // The Good Parts. 49 | "asi" : false, // Tolerate Automatic Semicolon Insertion (no semicolons). 50 | "laxbreak" : true, // Tolerate unsafe line breaks e.g. `return [\n] x` without semicolons. 51 | "bitwise" : false, // Prohibit bitwise operators (&, |, ^, etc.). 52 | "boss" : false, // Tolerate assignments inside if, for & while. Usually conditions & loops are for comparison, not assignments. 53 | "curly" : false, // Require {} for every new block or scope. 54 | "eqeqeq" : true, // Require triple equals i.e. `===`. 55 | "eqnull" : false, // Tolerate use of `== null`. 56 | "evil" : false, // Tolerate use of `eval`. 57 | "expr" : false, // Tolerate `ExpressionStatement` as Programs. 58 | "forin" : false, // Tolerate `for in` loops without `hasOwnPrototype`. 59 | "immed" : false, // Require immediate invocations to be wrapped in parens e.g. `( function(){}() );` 60 | "latedef" : false, // Prohibit variable use before definition. 61 | "loopfunc" : true, // Allow functions to be defined within loops. 62 | "noarg" : true, // Prohibit use of `arguments.caller` and `arguments.callee`. 63 | "regexp" : true, // Prohibit `.` and `[^...]` in regular expressions. 64 | "regexdash" : false, // Tolerate unescaped last dash i.e. `[-...]`. 65 | "scripturl" : false, // Tolerate script-targeted URLs. 66 | "shadow" : false, // Allows re-define variables later in code e.g. `var x=1; x=2;`. 67 | "supernew" : false, // Tolerate `new function () { ... };` and `new Object;`. 68 | "undef" : true, // Require all non-global variables be declared before they are used. 69 | 70 | 71 | // Personal styling preferences. 72 | "newcap" : true, // Require capitalization of all constructor functions e.g. `new F()`. 73 | "noempty" : false, // Prohibit use of empty blocks. 74 | "nonew" : true, // Prohibit use of constructors for side-effects. 75 | "nomen" : false, // Prohibit use of initial or trailing underbars in names. 76 | "onevar" : false, // Allow only one `var` statement per function. 77 | "plusplus" : false, // Prohibit use of `++` & `--`. 78 | "sub" : false, // Tolerate all forms of subscript notation besides dot notation e.g. `dict['key']` instead of `dict.key`. 79 | "trailing" : true // Prohibit trailing whitespaces. 80 | } 81 | 82 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | install: 3 | - sudo apt-get update 4 | - sudo apt-get install libssl1.0.0 5 | - npm install -g istanbul 6 | - npm install -g codeclimate-test-reporter 7 | - npm install -g coveralls 8 | - npm install 9 | - npm install codecov.io 10 | node_js: 11 | - "node" 12 | - "iojs" 13 | script: 14 | - istanbul cover node_modules/mocha/bin/_mocha 15 | after_script: 16 | - codeclimate < coverage/lcov.info 17 | - coveralls < coverage/lcov.info 18 | - node_modules/codecov.io/bin/codecov.io.js < coverage/coverage.json 19 | -------------------------------------------------------------------------------- /BufferBuilder.js: -------------------------------------------------------------------------------- 1 | 2 | "use strict"; 3 | 4 | var BufferBuilder = function() { 5 | this.buffers = []; 6 | }; 7 | 8 | BufferBuilder.prototype.writeUInt24BE = function( value, offset ) { 9 | this.writeUInt8( ( value & 0xff0000 ) >> 16, offset ); 10 | this.writeUInt16BE( value & 0x00ffff ); 11 | }; 12 | BufferBuilder.prototype.writeUInt24LE = function( value, offset ) { 13 | this.writeUInt8( value & 0x0000ff, offset ); 14 | this.writeUInt16LE( ( value & 0xffff00 ) >> 8 ); 15 | }; 16 | 17 | BufferBuilder.prototype.writeBytes = function( buffer, size ) { 18 | this.buffers.push( buffer.slice( 0, size ) ); 19 | }; 20 | 21 | BufferBuilder.prototype.getBuffer = function() { 22 | return Buffer.concat( this.buffers ); 23 | }; 24 | 25 | var makeDelegate = function( type, size ) { 26 | if( type instanceof Object ) { 27 | return Object.keys( type ).forEach( function( k ) { 28 | makeDelegate( k, type[k] ); 29 | }); 30 | } 31 | 32 | BufferBuilder.prototype[ 'write' + type ] = function( value ) { 33 | 34 | var buffer = new Buffer( size ); 35 | buffer[ 'write' + type ]( value, 0 ); 36 | this.buffers.push( buffer ); 37 | }; 38 | }; 39 | 40 | var expandTypes = function( types /*, ... */ ) { 41 | 42 | for( var i = 1; i < arguments.length; i++ ) { 43 | var patterns = arguments[i]; 44 | 45 | var newTypes = []; 46 | for( var t in types ) { 47 | 48 | for( var p in patterns ) { 49 | newTypes[ patterns[p].replace( '*', t ) ] = types[t]; 50 | } 51 | } 52 | types = newTypes; 53 | } 54 | 55 | return types; 56 | }; 57 | 58 | // Types with Little and Big endian alternatives 59 | makeDelegate( 60 | expandTypes( { 61 | Int16: 2, 62 | Int32: 4 63 | }, [ '*LE', '*BE' ], [ 'U*', '*' ] ) ); 64 | 65 | makeDelegate( 66 | expandTypes( { 67 | Int8: 1 68 | }, [ 'U*', '*' ] ) ); 69 | 70 | makeDelegate( 71 | expandTypes( { 72 | Float: 4, 73 | Double: 8 74 | }, [ '*LE', '*BE' ] ) ); 75 | 76 | module.exports = BufferBuilder; 77 | module.exports = BufferBuilder; 78 | -------------------------------------------------------------------------------- /BufferReader.js: -------------------------------------------------------------------------------- 1 | 2 | "use strict"; 3 | 4 | var BufferReader = function( buffer ) { 5 | this.buffer = buffer; 6 | this.offset = 0; 7 | }; 8 | 9 | BufferReader.prototype.readUInt24BE = function( offset ) { 10 | return ( this.readUInt8( offset ) << 16 ) + this.readUInt16BE(); 11 | }; 12 | BufferReader.prototype.readUInt24LE = function( offset ) { 13 | return this.readUInt8( offset ) + ( this.readUInt16LE() << 8 ); 14 | }; 15 | 16 | BufferReader.prototype.readBytes = function( size ) { 17 | var value = this.buffer.slice( this.offset, this.offset + size ); 18 | this.offset += size; 19 | return value; 20 | }; 21 | 22 | BufferReader.prototype.seek = function( pos ) { 23 | this.offset = pos; 24 | }; 25 | 26 | BufferReader.prototype.remaining = function() { 27 | return this.buffer.slice( this.offset ); 28 | }; 29 | 30 | BufferReader.prototype.available = function() { 31 | return this.offset < this.buffer.length; 32 | }; 33 | 34 | var makeDelegate = function( type, size ) { 35 | if( type instanceof Object ) { 36 | return Object.keys( type ).forEach( function( k ) { 37 | makeDelegate( k, type[k] ); 38 | }); 39 | } 40 | 41 | BufferReader.prototype[ 'read' + type ] = function( offset ) { 42 | if( offset !== undefined ) 43 | this.offset = offset; 44 | 45 | var value = this.buffer[ 'read' + type ]( this.offset ); 46 | this.offset += size; 47 | return value; 48 | }; 49 | }; 50 | 51 | var expandTypes = function( types /*, ... */ ) { 52 | 53 | for( var i = 1; i < arguments.length; i++ ) { 54 | var patterns = arguments[i]; 55 | 56 | var newTypes = []; 57 | for( var t in types ) { 58 | 59 | for( var p in patterns ) { 60 | newTypes[ patterns[p].replace( '*', t ) ] = types[t]; 61 | } 62 | } 63 | types = newTypes; 64 | } 65 | 66 | return types; 67 | }; 68 | 69 | // Types with Little and Big endian alternatives 70 | makeDelegate( 71 | expandTypes( { 72 | Int16: 2, 73 | Int32: 4 74 | }, [ '*LE', '*BE' ], [ 'U*', '*' ] ) ); 75 | 76 | makeDelegate( 77 | expandTypes( { 78 | Int8: 1 79 | }, [ 'U*', '*' ] ) ); 80 | 81 | makeDelegate( 82 | expandTypes( { 83 | Float: 4, 84 | Double: 8 85 | }, [ '*LE', '*BE' ] ) ); 86 | 87 | module.exports = BufferReader; 88 | -------------------------------------------------------------------------------- /Certificate.js: -------------------------------------------------------------------------------- 1 | 2 | "use strict"; 3 | 4 | var asn = require( 'asn1.js' ); 5 | var fs = require( 'fs' ); 6 | 7 | var X509Certificate = asn.define( 'X509Certificate', function() { 8 | this.seq().obj( 9 | this.key( 'tbsCertificate' ).use( TBSCertificate ), 10 | this.key( 'signatureAlgorithm' ).use( AlgorithmIdentifier ), 11 | this.key( 'signatureValue' ).bitstr() 12 | ); 13 | }); 14 | 15 | var TBSCertificate = asn.define( 'TBSCertificate', function() { 16 | this.seq().obj( 17 | this.key( 'version' ).explicit(0).int(), 18 | this.key( 'serialNumber' ).int(), 19 | this.key( 'signature' ).use( AlgorithmIdentifier ), 20 | this.key( 'issuer' ).use( Name ), 21 | this.key( 'validity' ).use( Validity ), 22 | this.key( 'subject' ).use( Name ), 23 | this.key( 'subjectPublicKeyInfo' ).use( SubjectPublicKeyInfo ), 24 | this.key( 'issuerUniqueID' ).implicit(1).bitstr().optional(), 25 | this.key( 'subjectUniqueID' ).implicit(2).bitstr().optional(), 26 | this.key( 'extensions' ).explicit(3).seqof( Extension ).optional() 27 | ); 28 | }); 29 | 30 | var AlgorithmIdentifier = asn.define( 'AlgorithmIdentifier', function() { 31 | this.seq().obj( 32 | this.key( 'algorithm' ).objid(), 33 | this.key( 'parameters' ).optional() 34 | ); 35 | }); 36 | 37 | var Name = asn.define( 'Name', function() { 38 | this.choice({ 39 | rdnSequence: this.use( RDNSequence ) 40 | }); 41 | }); 42 | 43 | var RDNSequence = asn.define( 'RDNSequence', function() { 44 | this.seqof( RelativeDistinguishedName ); 45 | }); 46 | 47 | var RelativeDistinguishedName = asn.define( 'RelativeDistinguishedName', function() { 48 | this.setof( AttributeTypeValue ); 49 | }); 50 | 51 | var AttributeTypeValue = asn.define( 'AttributeTypeValue', function() { 52 | this.seq().obj( 53 | this.key( 'type' ).objid(), 54 | this.key( 'value' ).any() 55 | ); 56 | }); 57 | 58 | var Validity = asn.define( 'Validity', function() { 59 | this.seq().obj( 60 | this.key( 'notBefore' ).use( Time ), 61 | this.key( 'notAfter' ).use( Time ) 62 | ); 63 | }); 64 | 65 | var Time = asn.define( 'Time', function() { 66 | this.choice({ 67 | utcTime: this.utctime(), 68 | generalTime: this.gentime() 69 | }); 70 | }); 71 | 72 | var SubjectPublicKeyInfo = asn.define( 'SubjectPublicKeyInfo', function() { 73 | this.seq().obj( 74 | this.key( 'algorithm' ).use( AlgorithmIdentifier ), 75 | this.key( 'subjectPublicKey' ).bitstr() 76 | ); 77 | }); 78 | 79 | var Extension = asn.define( 'Extension', function() { 80 | this.seq().obj( 81 | this.key( 'extnID' ).objid(), 82 | this.key( 'critical' ).bool().def( false ), 83 | this.key( 'extnValue' ).octstr() 84 | ); 85 | }); 86 | 87 | var RSAPublicKey = asn.define( 'RSAPublicKey', function() { 88 | this.seq().obj( 89 | this.key( 'modulus' ).int(), 90 | this.key( 'publicExponent' ).int() 91 | ); 92 | }); 93 | 94 | exports.parse = function( data ) { 95 | return X509Certificate.decode( data, 'der' ); 96 | }; 97 | 98 | exports.getPublicKey = function( data ) { 99 | if( data.tbsCertificate ) 100 | data = data.tbsCertificate; 101 | 102 | if( !data.subjectPublicKeyInfo ) 103 | data = X509Certificate.decode( data, 'der' ).tbsCertificate; 104 | 105 | console.log( '------------------------------------------' ); 106 | console.log( data ); 107 | console.log( '------------------------------------------' ); 108 | console.log( data.subjectPublicKeyInfo.subjectPublicKey.data ); 109 | console.log( '------------------------------------------' ); 110 | var key = RSAPublicKey.decode( data.subjectPublicKeyInfo.subjectPublicKey.data, 'der' ); 111 | return key; 112 | }; 113 | 114 | exports.getPublicKeyPem = function( data ) { 115 | if( data.tbsCertificate ) 116 | data = data.tbsCertificate; 117 | 118 | if( !data.subjectPublicKeyInfo ) 119 | data = X509Certificate.decode( data, 'der' ).tbsCertificate; 120 | 121 | var key = SubjectPublicKeyInfo.encode( 122 | data.subjectPublicKeyInfo, 'der' ) 123 | .toString( 'base64' ); 124 | 125 | var pem = '-----BEGIN PUBLIC KEY-----\n'; 126 | while( key.length > 64 ) { 127 | pem += key.substr( 0, 64 ) + '\n'; 128 | key = key.substr( 64 ); 129 | } 130 | pem += key + '\n-----END PUBLIC KEY-----'; 131 | return pem; 132 | }; 133 | 134 | exports.print = function( cert ) { 135 | 136 | if( cert.tbsCertificate ) 137 | cert = cert.tbsCertificate; 138 | else 139 | cert = exports.parse( cert ).tbsCertificate; 140 | 141 | console.log( 'Certificate:' ); 142 | console.log( ' Data:' ); 143 | console.log( ' Version: %d (0x%d)', 144 | cert.version + 1, 145 | cert.version.toString( 16 ) ); 146 | console.log( ' Serial Number: %d (0x%d)', 147 | cert.serialNumber.toString(), 148 | cert.serialNumber.toString( 16 ) ); 149 | 150 | // There should prolly be one more level of indent below. 151 | // The current indent matches openssl -text flag. 152 | console.log( ' Signature Algorithm: %s', getAlgorithm( cert.signature.algorithm ) ); 153 | console.log( ' Issuer: ...' ); 154 | console.log( ' Validity' ); 155 | console.log( ' Not Before: %s', new Date( cert.validity.notBefore.value ) ) ; 156 | console.log( ' Not After: %s', new Date( cert.validity.notAfter.value ) ) ; 157 | console.log( ' Subject: ...' ); 158 | console.log( ' Subject Public Key Info:' ); 159 | console.log( ' PublicKeyAlgorithm: %s', getAlgorithm( cert.subjectPublicKeyInfo.algorithm.algorithm ) ); 160 | 161 | var publicKey = RSAPublicKey.decode( cert.subjectPublicKeyInfo.subjectPublicKey.data, 'der' ); 162 | 163 | console.log( publicKey ); 164 | }; 165 | 166 | var getIssuer = function( issuer ) { 167 | // TODO: Couldn't find the attribute types. Plus the names contain more 168 | // silly ASN.1 encoding which I can't be bothered looking at now. ":D" 169 | // 170 | // RFC 3280 seemst to have something around page 93. 171 | return '(encoded)'; 172 | }; 173 | 174 | var algorithms = { 175 | '1.2.840.113549.2.1': 'md2', 176 | '1.2.840.113549.1.1.2': 'md2rsa', 177 | '1.2.840.113549.2.5': 'md5', 178 | '1.2.840.113549.1.1.4': 'md5rsa', 179 | '1.3.14.3.2.26': 'sha1', 180 | '1.2.840.10040.4.3': 'sha1dsa', 181 | '1.2.840.10045.4.1': 'sha1ecdsa', 182 | '1.2.840.113549.1.1.5': 'sha1rsa', 183 | '2.16.840.1.101.3.4.2.4': 'sha224', 184 | '1.2.840.113549.1.1.14': 'sha224rsa', 185 | '2.16.840.1.101.3.4.2.1': 'sha256', 186 | '1.2.840.113549.1.1.11': 'sha256rsa', 187 | '2.16.840.1.101.3.4.2.2': 'sha384', 188 | '1.2.840.113549.1.1.12': 'sha384rsa', 189 | '2.16.840.1.101.3.4.2.3': 'sha512', 190 | '1.2.840.113549.1.1.13': 'sha512rsa' 191 | }; 192 | 193 | var getAlgorithm = function( a ) { 194 | return algorithms[ a.join('.') ] || a.join('.'); 195 | }; 196 | -------------------------------------------------------------------------------- /CipherInfo.js: -------------------------------------------------------------------------------- 1 | 2 | "use strict"; 3 | 4 | var dtls = require( './dtls' ); 5 | 6 | var CipherSuite = function( id, keyExchange, cipher, mac, prf ) { 7 | 8 | this.id = id; 9 | this.keyExchange = keyExchange; 10 | this.cipher = cipher; 11 | this.mac = mac; 12 | 13 | 14 | this.prf = prf || dtls.PRFAlgorithm.tlsPrfSha256; 15 | }; 16 | 17 | var Cipher = function( algorithm, type, keyMaterial, ivSize, blockSize ) { 18 | this.algorithm = algorithm; 19 | this.type = type; 20 | this.keyMaterial = keyMaterial; 21 | this.ivSize = ivSize; 22 | this.blockSize = blockSize; 23 | }; 24 | 25 | var Mac = function( algorithm, length, keyLength ) { 26 | this.algorithm = algorithm; 27 | this.length = length; 28 | this.keyLength = keyLength; 29 | }; 30 | 31 | var cipher = { 32 | none: new Cipher( dtls.BulkCipherAlgorithm.none, 33 | dtls.CipherType.stream, 0, 0, 0 ), 34 | rc4_128: new Cipher( dtls.BulkCipherAlgorithm.rc4, 35 | dtls.CipherType.stream, 16, 0, 0 ), 36 | des3_ede_cbc: new Cipher( dtls.BulkCipherAlgorithm.des3, 37 | dtls.CipherType.block, 24, 8, 8 ), 38 | aes_128_cbc: new Cipher( dtls.BulkCipherAlgorithm.aes, 39 | dtls.CipherType.block, 16, 16, 16 ), 40 | aes_256_cbc: new Cipher( dtls.BulkCipherAlgorithm.aes, 41 | dtls.CipherType.block, 32, 16, 16 ) 42 | }; 43 | 44 | var mac = { 45 | none: new Mac( dtls.MACAlgorithm.none, 0, 0 ), 46 | md5: new Mac( dtls.MACAlgorithm.hmac_md5, 16, 16 ), 47 | sha: new Mac( dtls.MACAlgorithm.hmac_sha1, 20, 20 ), 48 | sha256: new Mac( dtls.MACAlgorithm.hmac_sha256, 32, 32 ), 49 | }; 50 | 51 | var suites = { 52 | TLS_RSA_WITH_AES_128_CBC_SHA: new CipherSuite( 53 | 0x002f, dtls.KeyExchange.rsa, cipher.aes_128_cbc, mac.sha ), 54 | }; 55 | 56 | var suitesById = {}; 57 | for( var s in suites ) { 58 | suites[s].name = s; 59 | suitesById[ suites[s].id ] = suites[s]; 60 | } 61 | 62 | suites.get = function( id ) { 63 | return suitesById[ id ]; 64 | }; 65 | 66 | module.exports = suites; 67 | 68 | -------------------------------------------------------------------------------- /ClientHandshakeHandler.js: -------------------------------------------------------------------------------- 1 | 2 | "use strict"; 3 | 4 | var log = require( 'logg' ).getLogger( 'dtls.ClientHandshakeHandler' ); 5 | var crypto = require( 'crypto' ); 6 | var constants = require( 'constants' ); 7 | 8 | var dtls = require( './dtls' ); 9 | var HandshakeBuilder = require( './HandshakeBuilder' ); 10 | var CipherInfo = require( './CipherInfo' ); 11 | var prf = require( './prf' ); 12 | var Certificate = require( './Certificate' ); 13 | 14 | var DtlsHandshake = require( './packets/DtlsHandshake' ); 15 | var DtlsClientHello = require( './packets/DtlsClientHello' ); 16 | var DtlsHelloVerifyRequest = require( './packets/DtlsHelloVerifyRequest' ); 17 | var DtlsServerHello = require( './packets/DtlsServerHello' ); 18 | var DtlsCertificate = require( './packets/DtlsCertificate' ); 19 | var DtlsServerHelloDone = require( './packets/DtlsServerHelloDone' ); 20 | var DtlsClientKeyExchange_rsa = require( './packets/DtlsClientKeyExchange_rsa' ); 21 | var DtlsPreMasterSecret = require( './packets/DtlsPreMasterSecret' ); 22 | var DtlsChangeCipherSpec = require( './packets/DtlsChangeCipherSpec' ); 23 | var DtlsFinished = require( './packets/DtlsFinished' ); 24 | var DtlsProtocolVersion = require( './packets/DtlsProtocolVersion' ); 25 | var DtlsRandom = require( './packets/DtlsRandom' ); 26 | var DtlsExtension = require( './packets/DtlsExtension' ); 27 | 28 | /* Note the methods in this class aren't grouped with similar methods. Instead 29 | * the handle_ and send_ methods follow the logical order as defined in the 30 | * DTLS/TLS specs. 31 | */ 32 | 33 | /** 34 | * Implements the DTLS client handshake. 35 | */ 36 | var ClientHandshakeHandler = function( parameters, keyContext ) { 37 | 38 | this.parameters = parameters; 39 | this.handshakeBuilder = new HandshakeBuilder(); 40 | 41 | this.cookie = new Buffer(0); 42 | this.certificate = null; 43 | 44 | // Handshake builder makes sure that the normal handling methods never 45 | // receive duplicate packets. Duplicate packets may mean that the last 46 | // flight of packets we sent got lost though so we need to handle these. 47 | this.handshakeBuilder.onRetransmission = this.retransmitLast.bind( this ); 48 | 49 | this.version = new DtlsProtocolVersion( ~1, ~2 ); 50 | }; 51 | 52 | /** 53 | * Processes an incoming handshake message from the server. 54 | * 55 | * @param {DtlsPlaintext} message 56 | * The TLS envelope containing the handshake message. 57 | */ 58 | ClientHandshakeHandler.prototype.process = function( message ) { 59 | 60 | // Enqueue the current handshake. 61 | var newHandshake = new DtlsHandshake( message.fragment ); 62 | var newHandshakeName = dtls.HandshakeTypeName[ newHandshake.msgType ]; 63 | log.info( 'Received handshake fragment; sequence:', 64 | newHandshake.messageSeq + ':' + newHandshakeName ); 65 | this.handshakeBuilder.add( newHandshake ); 66 | 67 | // Process available defragmented handshakes. 68 | var handshake = this.handshakeBuilder.next(); 69 | while( handshake ) { 70 | var handshakeName = dtls.HandshakeTypeName[ handshake.msgType ]; 71 | 72 | var handler = this[ 'handle_' + handshakeName ]; 73 | if( !handler ) { 74 | log.error( 'Handshake handler not found for ', handshakeName ); 75 | continue; 76 | } 77 | 78 | log.info( 'Processing handshake:', 79 | handshake.messageSeq + ':' + handshakeName ); 80 | var action = this[ 'handle_' + handshakeName ]( handshake, message ); 81 | 82 | // Digest this message after handling it. 83 | // This way the ClientHello can create the new SecurityParamters before 84 | // we digest this so it'll get digested in the correct context AND the 85 | // Finished message can verify its digest without counting itself in 86 | // it. 87 | // 88 | // TODO: Make sure 'message' contains the defragmented buffer. 89 | // We read the buffer in HandshakeBuilder anyway so there's no real 90 | // reason to call getBuffer() here. 91 | if( this.newParameters ) { 92 | this.newParameters.digestHandshake( handshake.getBuffer() ); 93 | } 94 | 95 | // However to get the digests in correct order, the handle_ method 96 | // above couldn't have invoked the send_ methods as those take care of 97 | // digesting their own messages. So instead they returned the action 98 | // and we'll invoke them after the digest. 99 | if( action ) 100 | action.call( this ); 101 | 102 | handshake = this.handshakeBuilder.next(); 103 | } 104 | }; 105 | 106 | ClientHandshakeHandler.prototype.renegotiate = function() { 107 | this.send_clientHello(); 108 | }; 109 | 110 | /** 111 | * Sends the ServerHello message 112 | */ 113 | ClientHandshakeHandler.prototype.send_clientHello = function() { 114 | 115 | // TLS spec require all implementations MUST implement the 116 | // TLS_RSA_WITH_AES_128_CBC_SHA cipher. 117 | var cipher = CipherInfo.TLS_RSA_WITH_AES_128_CBC_SHA; 118 | 119 | var clientHello = new DtlsClientHello({ 120 | clientVersion: this.version, 121 | random: new DtlsRandom(), 122 | sessionId: new Buffer(0), 123 | cookie: this.cookie || new Buffer(0), 124 | cipherSuites: [ cipher.id ], 125 | compressionMethods: [ 0 ], 126 | 127 | // TODO: Remove the requirement for extensions. Currently packets with 128 | // 0 extensions will serialize wrong. I don't even remember which 129 | // extension this is. Maybe heartbeat? Whatever it is, we definitely do 130 | // not implement it. :) 131 | extensions: [ 132 | new DtlsExtension({ 133 | extensionType: 0x000f, 134 | extensionData: new Buffer([ 1 ]) 135 | }) 136 | ] 137 | }); 138 | 139 | // Store more parameters. 140 | // We'll assume DTLS 1.0 so the plaintext layer is understood by everyone. 141 | // The clientVersion contains the real supported DTLS version for the 142 | // server. In serverHello handler we'll read the DTLS version the server 143 | // decided on and use that for the future record layer packets. 144 | this.newParameters = this.parameters.initNew( 145 | new DtlsProtocolVersion( ~1, ~0 ) ); 146 | this.newParameters.isServer = false; 147 | this.newParameters.clientRandom = clientHello.random.getBuffer(); 148 | 149 | log.info( 'Sending ClientHello' ); 150 | var handshakes = this.handshakeBuilder.createHandshakes([ clientHello ]); 151 | 152 | handshakes = handshakes.map( function(h) { return h.getBuffer(); }); 153 | this.newParameters.digestHandshake( handshakes ); 154 | 155 | var packets = this.handshakeBuilder.fragmentHandshakes( handshakes ); 156 | 157 | this.setResponse( packets ); 158 | }; 159 | 160 | ClientHandshakeHandler.prototype.handle_helloVerifyRequest = function( handshake ) { 161 | var verifyRequest = new DtlsHelloVerifyRequest( handshake.body ); 162 | this.cookie = verifyRequest.cookie; 163 | 164 | return this.send_clientHello; 165 | }; 166 | 167 | /** 168 | * Handles the ClientHello message. 169 | * 170 | * The message is accepted only if it contains the correct cookie. If the 171 | * cookie is wrong, we'll send a HelloVerifyRequest packet instead of 172 | * proceeding with the handshake. 173 | */ 174 | ClientHandshakeHandler.prototype.handle_serverHello = function( handshake, message ) { 175 | 176 | var serverHello = new DtlsServerHello( handshake.body ); 177 | 178 | log.fine( 'ServerHello received. Server version:', 179 | ~serverHello.serverVersion.major + '.' + 180 | ~serverHello.serverVersion.minor ); 181 | 182 | // TODO: Validate server version 183 | this.newParameters.version = serverHello.serverVersion; 184 | this.newParameters.serverRandom = serverHello.random.getBuffer(); 185 | var cipher = CipherInfo.get( serverHello.cipherSuite ); 186 | this.newParameters.setFrom( cipher ); 187 | this.newParameters.compressionMethod = serverHello.compressionMethod; 188 | 189 | if( !this.parameters.first.version ) 190 | this.parameters.first.version = serverHello.serverVersion; 191 | 192 | this.setResponse( null ); 193 | }; 194 | 195 | ClientHandshakeHandler.prototype.handle_certificate = function( handshake, message ) { 196 | 197 | var certificate = new DtlsCertificate( handshake.body ); 198 | 199 | // Just grab the first ceritificate ":D" 200 | this.certificate = certificate.certificateList[ 0 ]; 201 | 202 | this.setResponse( null ); 203 | }; 204 | 205 | ClientHandshakeHandler.prototype.handle_serverHelloDone = function( handshake, message ) { 206 | 207 | log.info( 'Server hello done' ); 208 | 209 | // We need to use the real client version here, not the negotiated version. 210 | var preMasterKey = Buffer.concat([ 211 | this.version.getBuffer(), 212 | crypto.randomBytes( 46 ) ]); 213 | 214 | this.newParameters.calculateMasterKey( preMasterKey ); 215 | this.newParameters.preMasterKey = preMasterKey; 216 | 217 | this.newParameters.init(); 218 | 219 | return this.send_keyExchange; 220 | }; 221 | 222 | ClientHandshakeHandler.prototype.send_keyExchange = function() { 223 | 224 | log.info( 'Constructing key exchange' ); 225 | 226 | var publicKey = Certificate.getPublicKeyPem( this.certificate ); 227 | var exchangeKeys = crypto.publicEncrypt({ 228 | key: publicKey, 229 | padding: constants.RSA_PKCS1_PADDING 230 | }, this.newParameters.preMasterKey ); 231 | 232 | var keyExchange = new DtlsClientKeyExchange_rsa({ 233 | exchangeKeys: exchangeKeys 234 | }); 235 | var keyExchangeHandshake = this.handshakeBuilder.createHandshakes( 236 | keyExchange ).getBuffer(); 237 | 238 | this.newParameters.digestHandshake( keyExchangeHandshake ); 239 | this.newParameters.preMasterKey = null; 240 | 241 | var changeCipherSpec = new DtlsChangeCipherSpec({ value: 1 }); 242 | 243 | var prf_func = prf( this.newParameters.version ); 244 | var verifyData = prf_func( 245 | this.newParameters.masterKey, 246 | "client finished", 247 | this.newParameters.getHandshakeDigest(), 248 | 12 249 | ); 250 | var finished = new DtlsFinished({ verifyData: verifyData }); 251 | var finishedHandshake = this.handshakeBuilder.createHandshakes( 252 | finished ).getBuffer(); 253 | this.newParameters.digestHandshake( finishedHandshake ); 254 | 255 | var keyExchangeFragments = this.handshakeBuilder.fragmentHandshakes( keyExchangeHandshake ); 256 | var finishedFragments = this.handshakeBuilder.fragmentHandshakes( finishedHandshake ); 257 | 258 | var outgoingFragments = keyExchangeFragments; 259 | outgoingFragments.push( changeCipherSpec ); 260 | outgoingFragments = outgoingFragments.concat( finishedFragments ); 261 | 262 | this.setResponse( outgoingFragments ); 263 | }; 264 | 265 | /** 266 | * Handles the client Finished message. 267 | * 268 | * Technically there is a ChangeCipherSpec message between ClientKeyExchange 269 | * and Finished messages. ChangeCipherSpec isn't a handshake message though so 270 | * it never makes it here. That message is handled in the RecordLayer. 271 | */ 272 | ClientHandshakeHandler.prototype.handle_finished = function( handshake, message ) { 273 | 274 | var finished = new DtlsFinished( handshake.body ); 275 | 276 | var prf_func = prf( this.newParameters.version ); 277 | 278 | var expected = prf_func( 279 | this.newParameters.masterKey, 280 | "server finished", 281 | this.newParameters.getHandshakeDigest(), 282 | finished.verifyData.length 283 | ); 284 | 285 | if( !finished.verifyData.equals( expected ) ) { 286 | log.warn( 'Finished digest does not match. Expected:', 287 | expected, 288 | 'Actual:', 289 | finished.verifyData ); 290 | return; 291 | } 292 | 293 | this.setResponse( null ); 294 | 295 | // The handle_ methods should RETURN the response action. 296 | // See the handle() method for explanation. 297 | return this.onHandshake(); 298 | }; 299 | 300 | /** 301 | * Sets the response for the last client message. 302 | * 303 | * The last flight of packets is stored so we can somewhat automatically handle 304 | * retransmission when we see the client doing it. 305 | */ 306 | ClientHandshakeHandler.prototype.setResponse = function( packets, done ) { 307 | this.lastFlight = packets; 308 | 309 | // Clear the retansmit timer for the last packets. 310 | if( this.retransmitTimer ) 311 | clearTimeout( this.retransmitTimer ); 312 | 313 | // If there are no new packets to send, we don't need to send anything 314 | // or even set the retransmit timer again. 315 | if( !packets ) 316 | return; 317 | 318 | // Send the packets and set up the timer for retransmit. 319 | this.onSend( packets, done ); 320 | this.retransmitTimer = setTimeout( function() { 321 | this.retransmitLast(); 322 | }.bind( this ), 1000 ); 323 | }; 324 | 325 | /** 326 | * Retransmits the last response in case it got lost on the way last time. 327 | * 328 | * @param {DtlsPlaintext} message 329 | * The received packet that triggered this retransmit. 330 | */ 331 | ClientHandshakeHandler.prototype.retransmitLast = function( message ) { 332 | 333 | if( this.lastFlight ) 334 | this.onSend( this.lastFlight ); 335 | 336 | this.retransmitTimer = setTimeout( function() { 337 | this.retransmitLast(); 338 | }.bind( this ), 1000 ); 339 | }; 340 | 341 | module.exports = ClientHandshakeHandler; 342 | -------------------------------------------------------------------------------- /DtlsRecordLayer.js: -------------------------------------------------------------------------------- 1 | 2 | "use strict"; 3 | 4 | var log = require( 'logg' ).getLogger( 'dtls.DtlsRecordLayer' ); 5 | var crypto = require( 'crypto' ); 6 | 7 | var DtlsPlaintext = require( './packets/DtlsPlaintext' ); 8 | var DtlsProtocolVersion = require( './packets/DtlsProtocolVersion' ); 9 | var DtlsChangeCipherSpec = require( './packets/DtlsChangeCipherSpec' ); 10 | var dtls = require( './dtls' ); 11 | var BufferReader = require( './BufferReader' ); 12 | 13 | var DtlsRecordLayer = function( dgram, rinfo, parameters ) { 14 | 15 | this.dgram = dgram; 16 | this.rinfo = rinfo; 17 | 18 | this.parameters = parameters; 19 | 20 | this.receiveEpoch = 0; 21 | this.sendEpoch = 0; 22 | this.version = new DtlsProtocolVersion({ major: ~1, minor: ~0 }); 23 | }; 24 | 25 | DtlsRecordLayer.prototype.getPackets = function( buffer, callback ) { 26 | 27 | var packets = DtlsPlaintext.readPackets( buffer ); 28 | for( var p in packets ) { 29 | 30 | var packet = packets[p]; 31 | 32 | // Ignore early packets. 33 | // TODO: Buffer these 34 | if( packet.epoch > this.receiveEpoch ) 35 | continue; 36 | 37 | // Get the security parameters. Ignore the packet if we don't have 38 | // the parameters for the epoch. 39 | var parameters = this.parameters.get( packet ); 40 | if( !parameters ) { 41 | log.warn( 'Packet with unknown epoch:', packet.epoch ); 42 | continue; 43 | } 44 | 45 | if( parameters.bulkCipherAlgorithm ) { 46 | this.decrypt( packet ); 47 | } 48 | 49 | if( parameters.compressionAlgorithm ) { 50 | this.decompress( packet ); 51 | } 52 | 53 | if( packet.type === dtls.MessageType.changeCipherSpec ) { 54 | 55 | if( packet.epoch !== this.receiveEpoch ) 56 | continue; 57 | 58 | this.parameters.changeCipher( packet.epoch ); 59 | this.receiveEpoch = this.parameters.current; 60 | 61 | } 62 | 63 | callback( packet ); 64 | } 65 | }; 66 | 67 | DtlsRecordLayer.prototype.send = function( msg, callback ) { 68 | 69 | var buffers = []; 70 | if( !( msg instanceof Array ) ) 71 | msg = [msg]; 72 | 73 | for( var m in msg ) { 74 | if( msg[m].__epoch === undefined ) 75 | msg[m].__epoch = this.sendEpoch; 76 | 77 | var parameters = this.parameters.getCurrent( msg[m].__epoch ); 78 | 79 | if( msg[m].__sequenceNumber ) 80 | parameters.sendSequence.setNext( msg[m].__sequenceNumber ); 81 | 82 | var envelope = new DtlsPlaintext({ 83 | type: msg[m].type, 84 | version: parameters.version || this.version, 85 | epoch: msg[m].__epoch, 86 | sequenceNumber: parameters.sendSequence.next(), 87 | fragment: msg[m].getBuffer ? msg[m].getBuffer() : msg[m].buffer, 88 | }); 89 | 90 | if( !parameters ) { 91 | log.error( 'Local epoch parameters not found:', this.sendEpoch ); 92 | return; 93 | } 94 | 95 | if( parameters.bulkCipherAlgorithm ) { 96 | this.encrypt( envelope ); 97 | } 98 | 99 | buffers.push( envelope.getBuffer() ); 100 | if( msg[m].type === dtls.MessageType.changeCipherSpec && 101 | !msg[m].__sent ) { 102 | 103 | log.info( 'Change cipher spec' ); 104 | this.sendEpoch++; 105 | } 106 | 107 | msg[m].__sent = true; 108 | } 109 | 110 | this.sendInternal( buffers, callback ); 111 | }; 112 | 113 | DtlsRecordLayer.prototype.sendInternal = function( buffers, callback ) { 114 | 115 | // Define the single packet callback only if the caller was interested in a 116 | // callback. 117 | var singlePacketCallback = null; 118 | var pending = 0; 119 | if( callback ) { 120 | var errors = []; 121 | singlePacketCallback = function( err ) { 122 | if( err ) errors.push( err ); 123 | if( --pending === 0 ) { 124 | callback( errors.length ? errors : null ); 125 | } 126 | }; 127 | } 128 | 129 | var flight = [ buffers.shift() ]; 130 | var flight_length = flight[0].length; 131 | while( buffers.length > 0 ) { 132 | 133 | if( buffers[0].length + flight_length > 1000 ) { 134 | pending++; 135 | this.dgram.send( Buffer.concat( flight ), 136 | 0, flight_length, 137 | this.rinfo.port, this.rinfo.address, singlePacketCallback ); 138 | 139 | flight_length = 0; 140 | flight = []; 141 | } 142 | 143 | flight_length += buffers[0].length; 144 | flight.push( buffers.shift() ); 145 | } 146 | 147 | pending++; 148 | this.dgram.send( flight.length === 1 ? flight[0] : Buffer.concat( flight ), 149 | 0, flight_length, 150 | this.rinfo.port, this.rinfo.address, singlePacketCallback ); 151 | }; 152 | 153 | DtlsRecordLayer.prototype.decrypt = function( packet ) { 154 | var parameters = this.parameters.get( packet ); 155 | 156 | var iv = packet.fragment.slice( 0, parameters.recordIvLength ); 157 | var ciphered = packet.fragment.slice( parameters.recordIvLength ); 158 | 159 | // Decrypt the fragment 160 | var cipher = parameters.getDecipher( iv ); 161 | cipher.setAutoPadding( false ); 162 | var decrypted = Buffer.concat([ 163 | cipher.update( ciphered ), 164 | cipher.final() ]); 165 | 166 | // Remove the padding. 167 | var padding = decrypted[ decrypted.length - 1 ]; 168 | decrypted = decrypted.slice( 0, decrypted.length - padding - 1 ); 169 | 170 | // Remove the MAC 171 | packet.fragment = decrypted.slice( 0, decrypted.length - 20 ); 172 | var mac = decrypted.slice( packet.fragment.length ); 173 | 174 | // Verify MAC 175 | var header = this.getMacHeader( packet ); 176 | var expectedMac = parameters.calculateIncomingMac([ header, packet.fragment ]); 177 | mac = mac.slice( 0, expectedMac.length ); 178 | if( !mac.slice( 0, expectedMac.length ).equals( expectedMac ) ) { 179 | throw new Error( 180 | 'Mac mismatch: ' + expectedMac.toString( 'hex' ) + ' vs ' + mac.toString( 'hex' ) + '\n' + 181 | 'Full fragment: ' + iv.toString( 'hex' ) + ' - ' + ciphered.toString( 'hex' ) + '\n' + 182 | 'Keys:\n' + 183 | parameters.clientWriteMacKey.toString( 'hex' ) + '\n' + 184 | parameters.serverWriteMacKey.toString( 'hex' ) + '\n' + 185 | parameters.clientWriteKey.toString( 'hex' ) + '\n' + 186 | parameters.serverWriteKey.toString( 'hex' ) + '\n' + 187 | parameters.clientWriteIv.toString( 'hex' ) + '\n' + 188 | parameters.serverWriteIv.toString( 'hex' ) ); 189 | } 190 | }; 191 | 192 | DtlsRecordLayer.prototype.encrypt = function( packet ) { 193 | var parameters = this.parameters.get( packet ); 194 | 195 | // Figure out MAC 196 | var iv = crypto.pseudoRandomBytes( 16 ); 197 | var header = this.getMacHeader( packet ); 198 | var mac = parameters.calculateOutgoingMac([ header, packet.fragment ]); 199 | 200 | var cipher = parameters.getCipher( iv ); 201 | 202 | var blockSize = 16; 203 | var overflow = ( iv.length + packet.fragment.length + mac.length + 1 ) % blockSize; 204 | var padAmount = ( overflow === 0 ) ? 0 : ( blockSize - overflow ); 205 | var padding = new Buffer([ padAmount ]); 206 | 207 | cipher.write( iv ); // The first chunk is used as IV and it's content is garbage. 208 | cipher.write( packet.fragment ); 209 | cipher.write( mac ); 210 | cipher.write( padding ); 211 | cipher.end(); 212 | 213 | packet.fragment = cipher.read(); 214 | }; 215 | 216 | DtlsRecordLayer.prototype.getMacHeader = function( packet ) { 217 | var header = new Buffer(13); 218 | header.writeUInt16BE( packet.epoch, 0 ); 219 | packet.sequenceNumber.copy( header, 2 ); 220 | header.writeUInt8( packet.type, 8 ); 221 | header.writeInt8( packet.version.major, 9 ); 222 | header.writeInt8( packet.version.minor, 10 ); 223 | header.writeUInt16BE( packet.fragment.length, 11 ); 224 | 225 | return header; 226 | }; 227 | 228 | module.exports = DtlsRecordLayer; 229 | -------------------------------------------------------------------------------- /DtlsServer.js: -------------------------------------------------------------------------------- 1 | 2 | "use strict"; 3 | 4 | var log = require( 'logg' ).getLogger( 'dtls.DtlsServer' ); 5 | 6 | var util = require( 'util' ); 7 | var EventEmitter = require( 'events' ).EventEmitter; 8 | var DtlsSocket = require( './DtlsSocket' ); 9 | var KeyContext = require( './KeyContext' ); 10 | 11 | var DtlsServer = function( dgramSocket, options ) { 12 | 13 | this.dgram = dgramSocket; 14 | this.keyContext = new KeyContext( options ); 15 | 16 | this.sockets = {}; 17 | 18 | this.dgram.on( 'message', this._onMessage.bind( this ) ); 19 | }; 20 | util.inherits( DtlsServer, EventEmitter ); 21 | 22 | DtlsServer.createServer = function( options, callback ) { 23 | 24 | var dgram = require( 'dgram' ); 25 | 26 | var dgramSocket = dgram.createSocket( options ); 27 | var dtlsServer = new DtlsServer( dgramSocket, options ); 28 | 29 | if( callback ) 30 | dtlsServer.on( 'message', callback ); 31 | 32 | return dtlsServer; 33 | }; 34 | 35 | DtlsServer.prototype.close = function() { 36 | this.dgram.close(); 37 | }; 38 | 39 | 40 | DtlsServer.prototype.bind = function( port ) { 41 | if( !this.keyContext ) 42 | throw new Error( 43 | 'Cannot act as a server without a certificate. ' + 44 | 'Use options.cert to specify certificate.' ); 45 | 46 | this.dgram.bind( port ); 47 | }; 48 | 49 | DtlsServer.prototype._onMessage = function( message, rinfo ) { 50 | 51 | var socketKey = rinfo.address + ':' + rinfo.port; 52 | var socket = this.sockets[ socketKey ]; 53 | if( !socket ) { 54 | this.sockets[ socketKey ] = socket = 55 | new DtlsSocket( this.dgram, rinfo, this.keyContext, true ); 56 | 57 | socket.once( 'secureConnect', function( socket ) { 58 | log.info( 'Handshake done' ); 59 | this.emit( 'secureConnection', socket ); 60 | }.bind( this )); 61 | } 62 | 63 | socket.handle( message ); 64 | }; 65 | 66 | module.exports = DtlsServer; 67 | -------------------------------------------------------------------------------- /DtlsSocket.js: -------------------------------------------------------------------------------- 1 | 2 | "use strict"; 3 | 4 | var util = require( 'util' ); 5 | var EventEmitter = require( 'events' ).EventEmitter; 6 | var log = require( 'logg' ).getLogger( 'dtls.DtlsSocket' ); 7 | var crypto = require( 'crypto' ); 8 | var constants = require( 'constants' ); 9 | var dgram = require( 'dgram' ); 10 | 11 | var dtls = require( './dtls' ); 12 | var SecurityParameterContainer = require( './SecurityParameterContainer' ); 13 | var DtlsRecordLayer = require( './DtlsRecordLayer' ); 14 | var ServerHandshakeHandler = require( './ServerHandshakeHandler' ); 15 | var ClientHandshakeHandler = require( './ClientHandshakeHandler' ); 16 | var HandshakeBuilder = require( './HandshakeBuilder' ); 17 | var CipherInfo = require( './CipherInfo' ); 18 | 19 | var DtlsHandshake = require( './packets/DtlsHandshake' ); 20 | 21 | var DtlsClientHello = require( './packets/DtlsClientHello' ); 22 | var DtlsHelloVerifyRequest = require( './packets/DtlsHelloVerifyRequest' ); 23 | var DtlsServerHello = require( './packets/DtlsServerHello' ); 24 | var DtlsCertificate = require( './packets/DtlsCertificate' ); 25 | var DtlsServerHelloDone = require( './packets/DtlsServerHelloDone' ); 26 | var DtlsClientKeyExchange_rsa = require( './packets/DtlsClientKeyExchange_rsa' ); 27 | var DtlsPreMasterSecret = require( './packets/DtlsPreMasterSecret' ); 28 | var DtlsChangeCipherSpec = require( './packets/DtlsChangeCipherSpec' ); 29 | var DtlsFinished = require( './packets/DtlsFinished' ); 30 | 31 | var DtlsExtension = require( './packets/DtlsExtension' ); 32 | var DtlsProtocolVersion = require( './packets/DtlsProtocolVersion' ); 33 | var DtlsRandom = require( './packets/DtlsRandom' ); 34 | 35 | var SocketState = { 36 | uninitialized: 0, 37 | sendHello: 1, 38 | handshakeDone: 2, 39 | clientFinished: 3, 40 | }; 41 | 42 | var DtlsSocket = function( dgram, rinfo, keyContext, isServer ) { 43 | log.info( 'New session' ); 44 | 45 | this.dgram = dgram; 46 | this.rinfo = rinfo; 47 | this.keyContext = keyContext; 48 | this.isServer = isServer; 49 | 50 | this.parameters = new SecurityParameterContainer(); 51 | this.recordLayer = new DtlsRecordLayer( dgram, rinfo, this.parameters ); 52 | this.handshakeHandler = isServer 53 | ? new ServerHandshakeHandler( this.parameters, this.keyContext, rinfo ) 54 | : new ClientHandshakeHandler( this.parameters ); 55 | 56 | this.handshakeHandler.onSend = function( packets, callback ) { 57 | this.recordLayer.send( packets, callback ); 58 | }.bind( this ); 59 | 60 | this.handshakeHandler.onHandshake = function() { 61 | log.info( 'Handshake done' ); 62 | this.emit( 'secureConnect', this ); 63 | }.bind( this ); 64 | }; 65 | util.inherits( DtlsSocket, EventEmitter ); 66 | 67 | DtlsSocket.connect = function( port, address, type, callback ) { 68 | var dgramSocket = dgram.createSocket( type ); 69 | 70 | var socket = new DtlsSocket( dgramSocket, { address: address, port: port }); 71 | socket.renegotiate(); 72 | 73 | dgramSocket.on( 'message', socket.handle.bind( socket ) ); 74 | 75 | if( callback ) 76 | socket.once( 'secureConnect', callback ); 77 | 78 | return socket; 79 | }; 80 | 81 | DtlsSocket.prototype.renegotiate = function() { 82 | this.handshakeHandler.renegotiate(); 83 | }; 84 | 85 | DtlsSocket.prototype.send = function( buffer, offset, length, callback ) { 86 | 87 | // Slice the buffer if we have offset specified and wrap it into a packet 88 | // structure that holds the message type as well. 89 | if( offset ) 90 | buffer = buffer.slice( offset, offset + length ); 91 | 92 | var packet = { 93 | type: dtls.MessageType.applicationData, 94 | buffer: buffer 95 | }; 96 | 97 | this.recordLayer.send( packet, callback ); 98 | }; 99 | 100 | DtlsSocket.prototype.close = function() { 101 | if( this.isServer ) 102 | throw new Error( 103 | 'Attempting to close a server socket. Close the server instead' ); 104 | 105 | this.dgram.close(); 106 | }; 107 | 108 | DtlsSocket.prototype.handle = function( buffer ) { 109 | var self = this; 110 | 111 | this.recordLayer.getPackets( buffer, function( packet ) { 112 | 113 | var handler = DtlsSocket.handlers[ packet.type ]; 114 | 115 | if( !handler ) { 116 | var msgType = dtls.MessageTypeName[ packet.type ]; 117 | return log.error( 'Handler not found for', msgType, 'message' ); 118 | } 119 | 120 | handler.call( self, packet ); 121 | }); 122 | }; 123 | 124 | DtlsSocket.handlers = []; 125 | DtlsSocket.handlers[ dtls.MessageType.handshake ] = function( message ) { 126 | this.handshakeHandler.process( message ); 127 | }; 128 | 129 | DtlsSocket.handlers[ dtls.MessageType.changeCipherSpec ] = function( message ) { 130 | // Record layer does the work here. 131 | log.info( 'Changed Cipher Spec' ); 132 | }; 133 | 134 | DtlsSocket.handlers[ dtls.MessageType.applicationData ] = function( message ) { 135 | this.emit( 'message', message.fragment ); 136 | }; 137 | 138 | module.exports = DtlsSocket; 139 | -------------------------------------------------------------------------------- /HandshakeBuilder.js: -------------------------------------------------------------------------------- 1 | 2 | "use strict"; 3 | 4 | var log = require( 'logg' ).getLogger( 'dtls.HandshakeBuilder' ); 5 | 6 | var DtlsHandshake = require( './packets/DtlsHandshake' ); 7 | 8 | var HandshakeBuilder = function() { 9 | 10 | this.buffers = {}; 11 | this.merged = {}; 12 | 13 | this.messageSeqToDecode = 0; 14 | this.messageSeqToRead = 0; 15 | 16 | this.outgoingMessageSeq = 0; 17 | 18 | this.packetLength = 1000; 19 | }; 20 | 21 | HandshakeBuilder.prototype.createHandshakes = function( message ) { 22 | var handshakes = []; 23 | 24 | // If parameter was an array, recurse into this function with single values. 25 | if( message instanceof Array ) { 26 | for( var m in message ) 27 | handshakes = handshakes.concat( this.createHandshakes( message[m] ) ); 28 | return handshakes; 29 | } 30 | 31 | var buffer = message.getBuffer(); 32 | var handshake = new DtlsHandshake({ 33 | msgType: message.messageType, 34 | length: buffer.length, 35 | messageSeq: this.outgoingMessageSeq, 36 | fragmentOffset: 0, 37 | body: buffer 38 | }); 39 | this.outgoingMessageSeq++; 40 | 41 | return handshake; 42 | }; 43 | 44 | HandshakeBuilder.prototype.fragmentHandshakes = function( packet ) { 45 | 46 | var packets = []; 47 | 48 | // If parameter was an array, recurse into this function with single values. 49 | if( packet instanceof Array ) { 50 | for( var p in packet ) 51 | packets = packets.concat( this.fragmentHandshakes( packet[p] ) ); 52 | return packets; 53 | } 54 | 55 | if( packet instanceof DtlsHandshake ) 56 | packet = packet.getBuffer(); 57 | 58 | // Get the raw body. 59 | // The header before body includes: 60 | // msgType : uint8 (1 byte), 61 | // length : uint24 (3 bytes), 62 | // messageSeq : uint16 (2 bytes), 63 | // fragmentOffset : uint24 (3 bytes), 64 | // bodyLength : uint24 (3 bytes) 65 | var remainingBody = packet.slice( 1 + 3 + 2 + 3 + 3 ); 66 | 67 | // Create the fragments 68 | // Make sure there is at least one fragment even if body is 0 bytes. 69 | var offset = 0; 70 | var first = true; 71 | while( first || remainingBody.length ) { 72 | first = false; 73 | 74 | // Create each handshake message and insert the fragment into it. 75 | var fragmentSize = Math.min( this.packetLength, remainingBody.length ); 76 | packets.push( new DtlsHandshake({ 77 | msgType: packet.readUInt8( 0 ), 78 | length: packet.readUInt32BE( 0 ) & 0x00ffffff, 79 | messageSeq: packet.readUInt16BE( 4 ), 80 | fragmentOffset: offset, 81 | body: remainingBody.slice( 0, fragmentSize ) 82 | })); 83 | 84 | // Advance the packet 85 | remainingBody = remainingBody.slice( fragmentSize ); 86 | offset += fragmentSize; 87 | } 88 | 89 | return packets; 90 | }; 91 | 92 | HandshakeBuilder.prototype.add = function( handshake ) { 93 | 94 | // Ignore this if it's part of a handshake we've already read. 95 | if( handshake.messageSeq < this.messageSeqToDecode ) { 96 | log.warn( 'seq < decode' ); 97 | return false; 98 | } 99 | log.info( 'Received fragment of sequence:', handshake.messageSeq ); 100 | 101 | var buffer = this._getBuffer( handshake ); 102 | 103 | // Ignore this fragment if we've already got all bytes it would contain. 104 | if( handshake.body.length > 0 && 105 | handshake.fragmentOffset + handshake.body.length <= buffer.bytesRead ) { 106 | 107 | log.warn( 'no new data' ); 108 | return false; 109 | } 110 | 111 | // Buffer the data if we're not ready to read it yet. 112 | if( handshake.fragmentOffset > buffer.bytesRead ) { 113 | log.warn( 'not ready to handle' ); 114 | buffer.fragments.push( handshake ); 115 | return false; 116 | } 117 | 118 | log.info( 'Valid data' ); 119 | 120 | // Write the fragment into the buffer 121 | this._writeToBuffer( handshake, buffer ); 122 | 123 | // Sort the buffered fragments so we can read them in order. 124 | buffer.fragments.sort( function( a, b ) { 125 | return a.fragmentOffset - b.fragmentOffset; 126 | }); 127 | 128 | // Go through as many of the buffered fragments as we can while 129 | // still not skipping any bytes in the body buffer. 130 | while( buffer.fragments.length > 0 && 131 | buffer.fragments[0].fragmentOffset <= buffer.bytesRead ) { 132 | 133 | this._writeToBuffer( buffer.fragments.shift(), buffer ); 134 | } 135 | 136 | // Check if the buffer is ready. 137 | // Return false if it isn't. We'll keep buffering more. 138 | if( buffer.bytesRead < buffer.length ) 139 | return false; 140 | 141 | // Store the completed Handshake message in the merged array. 142 | log.info( 'Merged' ); 143 | this.merged[ buffer.messageSeq ] = new DtlsHandshake({ 144 | msgType: buffer.msgType, 145 | length: buffer.length, 146 | messageSeq: buffer.messageSeq, 147 | fragmentOffset: 0, 148 | body: buffer.body 149 | }); 150 | 151 | // Clear the buffer and raise the messageSeqToDecode so we won't read this 152 | // message again. 153 | delete this.buffers[ buffer.messageSeq ]; 154 | this.messageSeqToDecode++; 155 | log.info( 'Raised decode++', this.messageSeqToDecode ); 156 | 157 | return true; 158 | }; 159 | 160 | HandshakeBuilder.prototype.next = function() { 161 | 162 | // Return false if we are still buffering the next to read packet. 163 | if( this.messageSeqToRead === this.messageSeqToDecode ) 164 | return false; 165 | 166 | // Pop the next to read out of the merged collection and advance the 167 | // counter. 168 | var msg = this.merged[ this.messageSeqToRead ]; 169 | delete this.merged[ this.messageSeqToRead ]; 170 | this.messageSeqToRead++; 171 | 172 | return msg; 173 | }; 174 | 175 | HandshakeBuilder.prototype._getBuffer = function( handshake ) { 176 | if( !this.buffers[ handshake.messageSeq ] ) { 177 | 178 | this.buffers[ handshake.messageSeq ] = { 179 | msgType: handshake.msgType, 180 | messageSeq: handshake.messageSeq, 181 | length: handshake.length, 182 | bytesRead: 0, 183 | fragments: [], 184 | body: new Buffer( handshake.length ) 185 | }; 186 | } 187 | 188 | return this.buffers[ handshake.messageSeq ]; 189 | }; 190 | 191 | HandshakeBuilder.prototype._writeToBuffer = function( handshake, buffer ) { 192 | 193 | // Ignore this fragment if we've already got all bytes it would contain. 194 | // 195 | // We do this check again because we might have buffered overlapping 196 | // fragments. 197 | if( handshake.fragmentOffset + handshake.body.length <= buffer.bytesRead ) { 198 | return; 199 | } 200 | 201 | handshake.body.copy( buffer.body, handshake.fragmentOffset ); 202 | buffer.bytesRead = handshake.fragmentOffset + handshake.body.length; 203 | }; 204 | 205 | module.exports = HandshakeBuilder; 206 | 207 | -------------------------------------------------------------------------------- /KeyContext.js: -------------------------------------------------------------------------------- 1 | 2 | "use strict"; 3 | 4 | var crypto = require( 'crypto' ); 5 | 6 | var certificateUtilities = require( './certificateUtilities' ); 7 | 8 | var KeyContext = function( options ) { 9 | 10 | this.key = options.key; 11 | 12 | if( options.key ) 13 | this.privateKey = certificateUtilities.extractKey( options.key ); 14 | 15 | if( options.cert ) 16 | this.certificate = certificateUtilities.extractCertificate( options.cert ); 17 | 18 | this.cookieSecret = crypto.randomBytes( 32 ); 19 | }; 20 | 21 | module.exports = KeyContext; 22 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2016-2018 Mikko Rantanen 2 | 3 | Permission to use, copy, modify, and/or distribute this software for any 4 | purpose with or without fee is hereby granted, provided that the above 5 | copyright notice and this permission notice appear in all copies. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH 8 | REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND 9 | FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, 10 | INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM 11 | LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR 12 | OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR 13 | PERFORMANCE OF THIS SOFTWARE. 14 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # See [nodertc/dtls](https://github.com/nodertc/dtls) for a maintained implementation 2 | 3 | ---- 4 | 5 | ## node-dtls 6 | #### DTLS implementation in JavaScript (Work in progress) 7 | 8 | [![Travis build](https://travis-ci.org/Rantanen/node-dtls.svg?branch=master)](https://travis-ci.org/Rantanen/node-dtls) 9 | [![Test Coverage](https://codeclimate.com/github/Rantanen/node-dtls/badges/coverage.svg)](https://codeclimate.com/github/Rantanen/node-dtls/coverage) 10 | [![Code Climate](https://codeclimate.com/github/Rantanen/node-dtls/badges/gpa.svg)](https://codeclimate.com/github/Rantanen/node-dtls) 11 | 12 | [![No Maintenance Intended](http://unmaintained.tech/badge.svg)](http://unmaintained.tech/) 13 | 14 | _This repository contains an unfinished DTLS implementation written in 15 | JavaScript. The implementation was an attempt to give Node.js DTLS support 16 | while waiting for https://github.com/nodejs/node/issues/2398 to get resolved._ 17 | 18 | _DTLS handshake and packet communication has been implemented. The largest 19 | missing things are re-negotiation, other cipher suites, respecting configuration 20 | options and handling the DTLS alerts._ 21 | 22 | _The primary goal of the project was to "support" DTLS in the sense that Node.js 23 | could communicate with services that require DTLS. The code should not be 24 | relied on for any actual security._ 25 | 26 | Datagram Transport Layer Security (DTLS) Protocol implementation for Node.js 27 | written in JavaScript. 28 | 29 | While Node.js still lacks support for DTLS protocol, this library attempts to 30 | fix the issue. This is in no way a security implementation as it's main goal is 31 | to allow using protocols that _require_ DTLS. While the library implements the 32 | proper DTLS encryption and validation, there has been no effort to protect it 33 | against well known TLS attacks. 34 | 35 | #### Example 36 | 37 | ##### Server 38 | 39 | ```javascript 40 | var dtls = require( 'dtls' ); 41 | var fs = require( 'fs' ); 42 | 43 | var pem = fs.readFileSync( 'server.pem' ); 44 | 45 | var server = dtls.createServer({ type: 'udp4', key: pem, cert: pem }); 46 | server.bind( 4433 ); 47 | 48 | server.on( 'secureConnection', function( socket ) { 49 | 50 | console.log( 'New connection from ' + 51 | [ socket.rinfo.address, socket.rinfo.port ].join(':') ); 52 | 53 | socket.on( 'message', function( message ) { 54 | 55 | // Echo the message back 56 | socket.send( message ); 57 | }); 58 | }); 59 | ``` 60 | 61 | ##### Client 62 | 63 | ```javascript 64 | var dtls = require( '../' ); 65 | 66 | dtls.setLogLevel( dtls.logLevel.FINE ); 67 | 68 | var client = dtls.connect( 4433, 'example.org', 'udp4', function() { 69 | client.send( new Buffer( 'foo\n' ) ); 70 | }); 71 | 72 | client.on( 'message', function( msg ) { 73 | console.log( msg ); 74 | }); 75 | ``` 76 | 77 | #### Current state 78 | 79 | 80 | - [x] DTLS 1.2 handshake in server role 81 | - [x] DTLS 1.2 handshake in client role 82 | - Still some problems when it comes to receiving messages. Not very confident. 83 | - [x] Handle application data 84 | - [x] Proper API to handle sessions/messages outside the node-dtls internals. 85 | - [ ] DTLS 1.0 handshake in server role 86 | - There shouldn't be _too_ many changes. Main one is propably the PRF hash. 87 | - [ ] Handle renegotiation 88 | - [ ] Robustness 89 | - [x] Handshake reassembly/buffering/reordering 90 | - [x] Retransmission 91 | - [ ] Handle alert-messages 92 | - [ ] Validate handshake state and expected messages 93 | 94 | ## API 95 | 96 | #### dtls.setLogLevel( level ) 97 | 98 | - `level` - [logg](https://github.com/dpup/node-logg) log level. For convenience possible log levels are also available in `dtls.logLevel.*` 99 | 100 | Sets the global node-dtls logging level. 101 | 102 | node-dtls uses /logg/ for logging and tracing various dtls events. This function can be used to alter the amount of information that is logged during the DTLS handshake/session. In future logging will most likely be disabled by default, but for now the default log level (FINE) is quite verbose. 103 | 104 | ----- 105 | 106 | #### dtls.createServer(options[, callback]) 107 | 108 | - `options` - Server options, see below. 109 | - `callback` - Optional callback registered to the `secureConnect` event. 110 | 111 | Creates a `dtls.DtlsServer`. 112 | 113 | Mimics the Node.js [tls.createServer](https://nodejs.org/api/tls.html#tls_tls_createserver_options_secureconnectionlistener) function. Although given DTLS is a datagram protocol, the actual network object is created with [dgram.createSocket()](https://nodejs.org/api/dgram.html#dgram_dgram_createsocket_options_callback). 114 | 115 | `options` object is a rough combination of the option objects of `tls.createServer()` and `dgram.createSocket()`. DTLS-specific options are parsed by the `dtls.DtlsServer` and the dgram-options are passed directly to `dgram.createSocket()`. 116 | 117 | - `type` - _Required._ `dgram` socket type. Passed to `dgram.createSocket()`. 118 | - `key` - _Required._ The server private key in PEM format. 119 | - `cert` - _Required._ The server certificate in PEM format. 120 | 121 | ##### Example 122 | 123 | ```javascript 124 | var dtls = require( 'dtls' ); 125 | var fs = require( 'fs' ); 126 | 127 | var pem = fs.readFileSync( 'server.pem' ); 128 | 129 | var server = dtls.createServer({ type: 'udp4', cert: pem, key: pem }); 130 | server.bind( 4433 ); 131 | 132 | server.on( 'secureConnection', function( socket ) { 133 | console.log( 'New secure connection: ' + 134 | [ socket.rinfo.address, socket.rinfo.port ].join( ':' ) ); 135 | }); 136 | ``` 137 | 138 | The server.pem certificate can be created with 139 | 140 | openssl req -x509 -nodes -newkey rsa:2048 -keyout server.pem -out server.pem 141 | 142 | ----- 143 | 144 | #### dtls.connect( port, address, type, callback ) 145 | 146 | - `port` - Remote port to connect to. 147 | - `address` - Remote address to connect to. 148 | - `type` - Datagram socket type: `udp4` or `udp6`. Ssee [`dgram`](https://nodejs.org/api/dgram.html) for full explanation. 149 | - `callback` - Callback for when the handshake is ready. 150 | 151 | Initiates a connection to a remote server and returns the `dtls.DtlsSocket`. 152 | 153 | ##### Example 154 | 155 | ```javascript 156 | var dtls = require( 'dtls' ); 157 | 158 | var client = dtls.connect( 4433, 'example.org', 'udp4', function() { 159 | client.send( new Buffer( 'foo\n' ) ); 160 | }); 161 | 162 | client.on( 'message', function( msg ) { 163 | console.log( msg ); 164 | }); 165 | ``` 166 | 167 | ----- 168 | 169 | ### Class: dtls.DtlsServer 170 | 171 | Server accepting DTLS connections. Created with `dtls.createServer` 172 | 173 | ----- 174 | 175 | #### Event: 'secureConnection' 176 | 177 | - `socket` - dtls.DtlsSocket 178 | 179 | Emitted after the `DtlsSocket` has finished handshaking a connection and is ready for use. 180 | 181 | ----- 182 | 183 | #### server.bind(); 184 | 185 | - `port` - UDP port to listen to. This is passed over to `socket.bind()` of the underlying `dgram.Socket` 186 | 187 | Starts listening to the defined port. Delegated to `dgram.Socket#bind()` 188 | 189 | ----- 190 | ----- 191 | 192 | ### Class: dtls.DtlsSocket 193 | 194 | A single DTLS session between a local and remote endpoints. Acquired through the `server::secureConnection` event. 195 | 196 | ---- 197 | 198 | #### Event: 'secureConnect' 199 | 200 | Emitted after the `DtlsSocket` has finished handshaking a connection. 201 | 202 | This method is emitted after the server sends the `Finished` handshake message. As datagram protocols aren't reliable transports, the handshake might still be in progress if that last handshake message was lost. It is recommended that the client initiates the actual application communication as the client gets confirmation on when the handshake has been completed. 203 | 204 | _Note:_ that usually it is impossible to catch this event as it is raised before the user has a reference to the socket. Use `server::secureConnection` event instead. 205 | 206 | ----- 207 | 208 | #### Event: 'message' 209 | 210 | - `buffer` - Application data within a [`Buffer`](https://nodejs.org/api/buffer.html#buffer_class_buffer). 211 | 212 | Emitted when the socket has received and decrypted application data from the remote endpoint. 213 | 214 | ##### Example 215 | 216 | ```javascript 217 | var server = dtls.createServer({ 218 | type: 'udp4', 219 | key: pem, 220 | cert: pem 221 | }); 222 | server.bind( 4433 ); 223 | 224 | server.on( 'secureConnection', function( socket ) { 225 | socket.on( 'message', function( message ) { 226 | console.log( 'In: ' + message.toString( 'ascii' ) ); 227 | }); 228 | }); 229 | ``` 230 | 231 | ----- 232 | 233 | #### socket.send( buffer[, offset][, length][, callback] ) 234 | 235 | - `buffer` - `Buffer` object to send. 236 | - `offset` - Offset in the buffer where the message starts. Optional. 237 | - `length` - Number of bytes in the message. Optional. 238 | - `calback` - called when the message has been sent. Optional. 239 | 240 | Sends application data to the remote endpoint. 241 | 242 | ##### Example 243 | 244 | ```javascript 245 | var server = dtls.createServer({ 246 | type: 'udp4', 247 | key: pem, 248 | cert: pem 249 | }); 250 | server.bind( 4433 ); 251 | 252 | server.on( 'secureConnection', function( socket ) { 253 | socket.send( new Buffer( 'Hello!\n', 'ascii' ) ); 254 | }); 255 | ``` 256 | 257 | ----- 258 | 259 | ## References 260 | 261 | [Datagram Transport Layer Security Version 1.2, RFC 6347] 262 | (https://tools.ietf.org/html/rfc6347) 263 | 264 | [The Transport Layer Security (TLS) Protocol Version 1.2, RFC 5246] 265 | (https://tools.ietf.org/html/rfc5246) 266 | -------------------------------------------------------------------------------- /SecurityParameterContainer.js: -------------------------------------------------------------------------------- 1 | 2 | "use strict"; 3 | 4 | var log = require( 'logg' ).getLogger( 'dtls.SecurityParameterContainer' ); 5 | var SecurityParameters = require( './SecurityParameters' ); 6 | 7 | var SecurityParameterContainer = function() { 8 | this.parameters = {}; 9 | this.pending = null; 10 | this.first = new SecurityParameters( 0 ); 11 | 12 | this.parameters[0] = this.first; 13 | this.current = 0; 14 | }; 15 | 16 | SecurityParameterContainer.prototype.initNew = function( version ) { 17 | this.pending = new SecurityParameters( this.current + 1, version ); 18 | this.parameters[ this.pending.epoch ] = this.pending; 19 | return this.pending; 20 | }; 21 | 22 | SecurityParameterContainer.prototype.getCurrent = function( epoch ) { 23 | return this.parameters[ epoch ]; 24 | }; 25 | 26 | SecurityParameterContainer.prototype.get = function( packet ) { 27 | return this.parameters[ packet.epoch ]; 28 | }; 29 | 30 | SecurityParameterContainer.prototype.changeCipher = function( epoch ) { 31 | if( epoch + 1 !== this.pending.epoch ) 32 | return log.error( 'Trying to change cipher from', 33 | epoch, '->', epoch+1, 34 | '- pending epoch is', this.pending.epoch ); 35 | 36 | this.pending.init(); 37 | this.current++; 38 | }; 39 | 40 | module.exports = SecurityParameterContainer; 41 | -------------------------------------------------------------------------------- /SecurityParameters.js: -------------------------------------------------------------------------------- 1 | 2 | "use strict"; 3 | 4 | var crypto = require( 'crypto' ); 5 | var dtls = require( './dtls' ); 6 | var prf = require( './prf' ); 7 | var BufferReader = require( './BufferReader' ); 8 | var SequenceNumber = require( './SequenceNumber' ); 9 | var DtlsProtocolVersion = require( './packets/DtlsProtocolVersion' ); 10 | 11 | var log = require( 'logg' ).getLogger( 'dtls.SecurityParameters' ); 12 | var logDigest = require( 'logg' ).getLogger( 'dtls.SecurityParameters.digest' ); 13 | 14 | var SecurityParameters = function( epoch, version ) { 15 | 16 | this.epoch = epoch; 17 | this.version = version; 18 | this.isServer = true; 19 | 20 | this.entity = dtls.ConnectionEnd.server; 21 | 22 | // Cipher suite prf 23 | this.prfAlgorithm = dtls.PRFAlgorithm.tlsPrfSha256; 24 | 25 | // Cipher suite cipher 26 | this.bulkCipherAlgorithm = dtls.BulkCipherAlgorithm.none; 27 | this.cipherType = dtls.CipherType.block; 28 | this.encKeyLength = 0; 29 | this.blockLength = 0; 30 | this.fixedIvLength = 0; 31 | this.recordIvLength = 0; 32 | 33 | // Cipher suite mac 34 | this.macAlgorithm = dtls.MACAlgorithm.none; 35 | this.macLength = 0; 36 | this.macKeyLength = 0; 37 | 38 | // Handshake 39 | this.compressionAlgorithm = dtls.CompressionMethod.none; 40 | this.masterKey = null; 41 | this.clientRandom = null; 42 | this.serverRandom = null; 43 | 44 | this.handshakeDigest = []; 45 | 46 | this.sendSequence = new SequenceNumber(); 47 | }; 48 | 49 | SecurityParameters.prototype.setFrom = function( suite ) { 50 | 51 | this.prfAlgorithm = suite.prf; 52 | 53 | this.bulkCipherAlgorithm = suite.cipher.algorithm; 54 | this.cipherType = suite.cipher.type; 55 | this.encKeyLength = suite.cipher.keyMaterial; 56 | this.blockLength = suite.cipher.blockSize; 57 | this.fixedIvLength = 0; 58 | this.recordIvLength = suite.cipher.ivSize; 59 | 60 | this.macAlgorithm = suite.mac.algorithm; 61 | this.macLength = suite.mac.length; 62 | this.macKeyLength = suite.mac.keyLength; 63 | }; 64 | 65 | SecurityParameters.prototype.calculateMasterKey = function( preMasterKey ) { 66 | 67 | this.preMasterKey = preMasterKey; 68 | this.masterKey = prf( this.version )( 69 | preMasterKey, 70 | "master secret", 71 | Buffer.concat([ 72 | this.clientRandom, 73 | this.serverRandom ]), 48 ); 74 | }; 75 | 76 | SecurityParameters.prototype.init = function() { 77 | 78 | var keyBlock = prf( this.version )( 79 | this.masterKey, 80 | "key expansion", 81 | Buffer.concat([ this.serverRandom, this.clientRandom ]), 82 | this.macKeyLength * 2 + this.encKeyLength * 2 + this.recordIvLength * 2 ); 83 | 84 | var bufferReader = new BufferReader( keyBlock ); 85 | this.clientWriteMacKey = bufferReader.readBytes( this.macKeyLength ); 86 | this.serverWriteMacKey = bufferReader.readBytes( this.macKeyLength ); 87 | this.clientWriteKey = bufferReader.readBytes( this.encKeyLength ); 88 | this.serverWriteKey = bufferReader.readBytes( this.encKeyLength ); 89 | this.clientWriteIv = bufferReader.readBytes( this.recordIvLength ); 90 | this.serverWriteIv = bufferReader.readBytes( this.recordIvLength ); 91 | 92 | log.info( 'Key content' ); 93 | log.info( 'C-Mac:', this.clientWriteMacKey ); 94 | log.info( 'S-Mac:', this.serverWriteMacKey ); 95 | log.info( 'C-Key:', this.clientWriteKey ); 96 | log.info( 'S-Key:', this.serverWriteKey ); 97 | log.info( 'C-IV: ', this.clientWriteIv ); 98 | log.info( 'S-IV: ', this.serverWriteIv ); 99 | }; 100 | 101 | SecurityParameters.prototype.getDecipher = function( iv ) { 102 | var key = this.isServer ? this.clientWriteKey : this.serverWriteKey; 103 | return crypto.createDecipheriv( 'aes-128-cbc', key, iv ); 104 | }; 105 | 106 | SecurityParameters.prototype.calculateIncomingMac = function( buffer ) { 107 | var key = this.isServer ? this.clientWriteMacKey : this.serverWriteMacKey; 108 | return this.calculateMac( key, buffer ); 109 | }; 110 | 111 | SecurityParameters.prototype.calculateOutgoingMac = function( buffer ) { 112 | var key = this.isServer ? this.serverWriteMacKey : this.clientWriteMacKey; 113 | return this.calculateMac( key, buffer ); 114 | }; 115 | 116 | SecurityParameters.prototype.calculateMac = function( key, buffer ) { 117 | var mac = crypto.createHmac( 'sha1', key ); 118 | 119 | // Accept both single buffers and buffer arrays. 120 | if( buffer instanceof Array ) { 121 | buffer.forEach( function(b) { mac.update(b); } ); 122 | } else { 123 | mac.update( buffer ); 124 | } 125 | 126 | return mac.digest(); 127 | }; 128 | 129 | SecurityParameters.prototype.getCipher = function( iv ) { 130 | var key = this.isServer ? this.serverWriteKey : this.clientWriteKey; 131 | return crypto.createCipheriv( 'aes-128-cbc', key, iv ); 132 | }; 133 | 134 | SecurityParameters.prototype.resetHandshakeDigest = function() { 135 | this.handshakeDigest = []; 136 | }; 137 | 138 | SecurityParameters.prototype.digestHandshake = function( msg ) { 139 | if( !this.handshakeDigest ) 140 | return; 141 | 142 | if( msg instanceof Array ) 143 | for( var m in msg ) 144 | this._digestHandshake( msg[m] ); 145 | else 146 | this._digestHandshake( msg ); 147 | }; 148 | 149 | SecurityParameters.prototype._digestHandshake = function( msg ) { 150 | if( msg.fragment ) 151 | msg = msg.fragment; 152 | 153 | if( !( msg instanceof Buffer ) ) 154 | throw new Error( 'Message must be a buffer or containing buffer fragment.' ); 155 | 156 | logDigest.fine( 'Handshake digest:' ); 157 | for( var i = 0; i < msg.length; i += 16 ) { 158 | logDigest.fine( i.toString( 16 ), '\t', msg.slice( i, Math.min( msg.length, i + 16 ) ) ); 159 | } 160 | logDigest.fine( 'Length:', msg.length ); 161 | 162 | this.handshakeDigest.push( msg ); 163 | }; 164 | 165 | SecurityParameters.prototype.getHandshakeDigest = function() { 166 | log.info( 'Digesting', this.handshakeDigest.length, 'messages' ); 167 | var hash = prf( this.version ).createHash(); 168 | this.handshakeDigest.forEach( function(d) { 169 | hash.update( d ); 170 | }); 171 | 172 | var digest = hash.digest(); 173 | logDigest.fine( 'DIGEST:', digest.toString( 'hex' ) ); 174 | return digest; 175 | }; 176 | 177 | module.exports = SecurityParameters; 178 | -------------------------------------------------------------------------------- /SequenceNumber.js: -------------------------------------------------------------------------------- 1 | 2 | "use strict"; 3 | 4 | /** 5 | * 48-bit Sequence number generator 6 | */ 7 | var SequenceNumber = function() { 8 | this.current = new Buffer([ 0, 0, 0, 0, 0, 0 ]); 9 | }; 10 | 11 | /** 12 | * Resets the generator state to return the given value as the next number 13 | * 14 | * @param {Number[]} value - Next sequence number. 15 | */ 16 | SequenceNumber.prototype.setNext = function( value ) { 17 | 18 | // Clone the value. 19 | value.copy( this.current ); 20 | 21 | // The next invocation will increase current value by one so we need to 22 | // assign current as value-1. 23 | for( var i = 5; i >= 0; i-- ) { 24 | this.current[i] = ( this.current[i] - 1 ) & 0xff; 25 | 26 | // If the current 8-bit value isn't 255 (0xff) after subtraction there 27 | // was no overflow and we can break. 28 | if( this.current[i] !== 0xff ) 29 | break; 30 | } 31 | }; 32 | 33 | /** 34 | * Retrieves the next value in sequence 35 | * 36 | * @returns {Number[]} 48-bit value that increases by 1 with every call. 37 | */ 38 | SequenceNumber.prototype.next = function() { 39 | 40 | // Increase the current value by one minding the overflow. 41 | for( var i = 5; i >= 0; i-- ) { 42 | this.current[i] = ( this.current[i] + 1 ) & 0xff; 43 | 44 | // If the current value isn't 0 there was no overflow and we can break 45 | // the iteration. 46 | if( this.current[i] ) 47 | break; 48 | } 49 | 50 | return this.current; 51 | }; 52 | 53 | module.exports = SequenceNumber; 54 | -------------------------------------------------------------------------------- /ServerHandshakeHandler.js: -------------------------------------------------------------------------------- 1 | 2 | "use strict"; 3 | 4 | var log = require( 'logg' ).getLogger( 'dtls.ServerHandshakeHandler' ); 5 | var crypto = require( 'crypto' ); 6 | var constants = require( 'constants' ); 7 | 8 | var dtls = require( './dtls' ); 9 | var HandshakeBuilder = require( './HandshakeBuilder' ); 10 | var CipherInfo = require( './CipherInfo' ); 11 | var prf = require( './prf' ); 12 | 13 | var DtlsHandshake = require( './packets/DtlsHandshake' ); 14 | var DtlsClientHello = require( './packets/DtlsClientHello' ); 15 | var DtlsHelloVerifyRequest = require( './packets/DtlsHelloVerifyRequest' ); 16 | var DtlsServerHello = require( './packets/DtlsServerHello' ); 17 | var DtlsCertificate = require( './packets/DtlsCertificate' ); 18 | var DtlsServerHelloDone = require( './packets/DtlsServerHelloDone' ); 19 | var DtlsClientKeyExchange_rsa = require( './packets/DtlsClientKeyExchange_rsa' ); 20 | var DtlsPreMasterSecret = require( './packets/DtlsPreMasterSecret' ); 21 | var DtlsChangeCipherSpec = require( './packets/DtlsChangeCipherSpec' ); 22 | var DtlsFinished = require( './packets/DtlsFinished' ); 23 | var DtlsRandom = require( './packets/DtlsRandom' ); 24 | var DtlsExtension = require( './packets/DtlsExtension' ); 25 | 26 | /* Note the methods in this class aren't grouped with similar methods. Instead 27 | * the handle_ and send_ methods follow the logical order as defined in the 28 | * DTLS/TLS specs. 29 | */ 30 | 31 | /** 32 | * Implements the DTLS server handshake. 33 | */ 34 | var ServerHandshakeHandler = function( parameters, keyContext, rinfo ) { 35 | 36 | this.parameters = parameters; 37 | this.keyContext = keyContext; 38 | this.rinfo = rinfo; 39 | 40 | this.handshakeBuilder = new HandshakeBuilder(); 41 | 42 | // Handshake builder makes sure that the normal handling methods never 43 | // receive duplicate packets. Duplicate packets may mean that the last 44 | // flight of packets we sent got lost though so we need to handle these. 45 | this.handshakeBuilder.onRetransmission = this.retransmitLast.bind( this ); 46 | }; 47 | 48 | /** 49 | * Processes an incoming handshake message from the client. 50 | * 51 | * @param {DtlsPlaintext} message 52 | * The TLS envelope containing the handshake message. 53 | */ 54 | ServerHandshakeHandler.prototype.process = function( message ) { 55 | 56 | // Enqueue the current handshake. 57 | var newHandshake = new DtlsHandshake( message.fragment ); 58 | var newHandshakeName = dtls.HandshakeTypeName[ newHandshake.msgType ]; 59 | log.info( 'Received handshake fragment; sequence:', 60 | newHandshake.messageSeq + ':' + newHandshakeName ); 61 | this.handshakeBuilder.add( newHandshake ); 62 | 63 | // Process available defragmented handshakes. 64 | var handshake = this.handshakeBuilder.next(); 65 | while( handshake ) { 66 | var handshakeName = dtls.HandshakeTypeName[ handshake.msgType ]; 67 | 68 | var handler = this[ 'handle_' + handshakeName ]; 69 | if( !handler ) { 70 | log.error( 'Handshake handler not found for ', handshakeName ); 71 | continue; 72 | } 73 | 74 | log.info( 'Processing handshake:', 75 | handshake.messageSeq + ':' + handshakeName ); 76 | var action = this[ 'handle_' + handshakeName ]( handshake, message ); 77 | 78 | // Digest this message after handling it. 79 | // This way the ClientHello can create the new SecurityParamters before 80 | // we digest this so it'll get digested in the correct context AND the 81 | // Finished message can verify its digest without counting itself in 82 | // it. 83 | // 84 | // TODO: Make sure 'message' contains the defragmented buffer. 85 | // We read the buffer in HandshakeBuilder anyway so there's no real 86 | // reason to call getBuffer() here. 87 | if( this.newParameters ) { 88 | this.newParameters.digestHandshake( handshake.getBuffer() ); 89 | } 90 | 91 | // However to get the digests in correct order, the handle_ method 92 | // above couldn't have invoked the send_ methods as those take care of 93 | // digesting their own messages. So instead they returned the action 94 | // and we'll invoke them after the digest. 95 | if( action ) 96 | action.call( this ); 97 | 98 | handshake = this.handshakeBuilder.next(); 99 | } 100 | }; 101 | 102 | /** 103 | * Handles the ClientHello message. 104 | * 105 | * The message is accepted only if it contains the correct cookie. If the 106 | * cookie is wrong, we'll send a HelloVerifyRequest packet instead of 107 | * proceeding with the handshake. 108 | */ 109 | ServerHandshakeHandler.prototype.handle_clientHello = function( handshake, message ) { 110 | 111 | var clientHello = new DtlsClientHello( handshake.body ); 112 | 113 | // TODO: If this is the very first handshake, the version of the initial 114 | // SecurityParameters hasn't been set. Set it to equal the current version. 115 | if( !this.parameters.first.version ) 116 | this.parameters.first.version = clientHello.clientVersion; 117 | 118 | // Derive the cookie from the internal cookieSecret and client specific 119 | // information, including client address and information present in 120 | // the ClientHello message. 121 | // 122 | // The information used from ClientHello includes the cipher suites, 123 | // compression methods and extensions. Assuming extensions don't contain 124 | // random data, these fields should remain static between handshakes. 125 | // 126 | // (It might be worth it to exclude extensions from these though.. as 127 | // we can't guarantee that all extensions use static values in 128 | // ClientHello) 129 | // 130 | // The cookie is derived using the PRF of the clientHello.clientVersion 131 | // which means the clientVersion affects the cookie formation as well. 132 | if( !this.cookie ) { 133 | this.cookie = prf( clientHello.clientVersion )( 134 | this.keyContext.cookieSecret, 135 | this.rinfo.address, 136 | handshake.body.slice( 137 | /* clientVersion */ 2 + 138 | /* Random */ 32 + 139 | /* sessionId */ clientHello.sessionId.length + 140 | /* cookie */ clientHello.cookie.length 141 | ), 16 ); 142 | } 143 | 144 | if( clientHello.cookie.length === 0 || 145 | !clientHello.cookie.equals( this.cookie ) ) { 146 | 147 | log.fine( 'ClientHello without cookie. Requesting verify.' ); 148 | 149 | var cookieVerify = new DtlsHelloVerifyRequest({ 150 | serverVersion: clientHello.clientVersion, 151 | cookie: this.cookie 152 | }); 153 | 154 | // Generate the Handshake message containing the HelloVerifyRequest 155 | // This message should be small enough to not require fragmentation. 156 | var handshakes = this.handshakeBuilder.createHandshakes( cookieVerify ); 157 | 158 | // The server MUST use the record sequence number in the ClientHello 159 | // as the record sequence number in the HelloVerifyRequest. 160 | // - RFC 161 | handshakes.__sequenceNumber = message.sequenceNumber; 162 | 163 | this.setResponse( handshakes ); 164 | 165 | } else { 166 | 167 | log.fine( 'ClientHello received. Client version:', 168 | ~clientHello.clientVersion.major + '.' + 169 | ~clientHello.clientVersion.minor ); 170 | 171 | // ClientHello is the first message of a new handshake. This is a good 172 | // place to create the new SecurityParamters that will be negotiated 173 | // with this handshake sequence. 174 | // TODO: Validate client version 175 | this.version = clientHello.clientVersion; 176 | 177 | this.newParameters = this.parameters.initNew( this.version ); 178 | this.newParameters.clientRandom = clientHello.random.getBuffer(); 179 | 180 | log.fine( 'Client ciphers' ); 181 | log.fine( clientHello.cipherSuites ); 182 | 183 | // The handle_ methods should RETURN the response action. 184 | // See the handle() method for explanation. 185 | return this.send_serverHello; 186 | } 187 | }; 188 | 189 | /** 190 | * Sends the ServerHello message 191 | */ 192 | ServerHandshakeHandler.prototype.send_serverHello = function() { 193 | 194 | // TLS spec require all implementations MUST implement the 195 | // TLS_RSA_WITH_AES_128_CBC_SHA cipher. 196 | var cipher = CipherInfo.TLS_RSA_WITH_AES_128_CBC_SHA; 197 | 198 | var serverHello = new DtlsServerHello({ 199 | serverVersion: this.version, 200 | random: new DtlsRandom(), 201 | sessionId: new Buffer(0), 202 | cipherSuite: cipher.id, 203 | compressionMethod: 0, 204 | 205 | // TODO: Remove the requirement for extensions. Currently packets with 206 | // 0 extensions will serialize wrong. I don't even remember which 207 | // extension this is. Maybe heartbeat? Whatever it is, we definitely do 208 | // not implement it. :) 209 | extensions: [ 210 | new DtlsExtension({ 211 | extensionType: 0x000f, 212 | extensionData: new Buffer([ 1 ]) 213 | }) 214 | ] 215 | }); 216 | 217 | log.info( 'Server cipher used:', cipher.id ); 218 | 219 | // Store more parameters. 220 | this.newParameters.serverRandom = serverHello.random.getBuffer(); 221 | this.newParameters.setFrom( cipher ); 222 | 223 | var certificate = new DtlsCertificate({ 224 | certificateList: [ this.keyContext.certificate ] 225 | }); 226 | 227 | var helloDone = new DtlsServerHelloDone(); 228 | 229 | log.info( 'Sending ServerHello, Certificate, HelloDone' ); 230 | var handshakes = this.handshakeBuilder.createHandshakes([ 231 | serverHello, 232 | certificate, 233 | helloDone 234 | ]); 235 | 236 | handshakes = handshakes.map( function(h) { return h.getBuffer(); }); 237 | this.newParameters.digestHandshake( handshakes ); 238 | 239 | var packets = this.handshakeBuilder.fragmentHandshakes( handshakes ); 240 | 241 | this.setResponse( packets ); 242 | }; 243 | 244 | /** 245 | * Handles the ClientKeyExchange message. 246 | */ 247 | ServerHandshakeHandler.prototype.handle_clientKeyExchange = function( handshake ) { 248 | 249 | var clientKeyExchange = new DtlsClientKeyExchange_rsa( handshake.body ); 250 | 251 | // TODO: if this fails, create random preMasterKey to guard against chosen 252 | // ciphertext/PKCS#1 attack. 253 | var preMasterSecret = crypto.privateDecrypt({ 254 | key: this.keyContext.key, 255 | padding: constants.RSA_PKCS1_PADDING 256 | }, clientKeyExchange.exchangeKeys ); 257 | 258 | this.newParameters.calculateMasterKey( preMasterSecret ); 259 | 260 | // Do nothing here. We're still waiting for the Finished message. 261 | // 262 | // Set the response to null though as we know the client got the last 263 | // flight. 264 | this.setResponse( null ); 265 | }; 266 | 267 | /** 268 | * Handles the client Finished message. 269 | * 270 | * Technically there is a ChangeCipherSpec message between ClientKeyExchange 271 | * and Finished messages. ChangeCipherSpec isn't a handshake message though so 272 | * it never makes it here. That message is handled in the RecordLayer. 273 | */ 274 | ServerHandshakeHandler.prototype.handle_finished = function( handshake, message ) { 275 | 276 | var finished = new DtlsFinished( handshake.body ); 277 | 278 | var prf_func = prf( this.version ); 279 | 280 | var expected = prf_func( 281 | this.newParameters.masterKey, 282 | "client finished", 283 | this.newParameters.getHandshakeDigest(), 284 | finished.verifyData.length 285 | ); 286 | 287 | if( !finished.verifyData.equals( expected ) ) { 288 | log.warn( 'Finished digest does not match. Expected:', 289 | expected, 290 | 'Actual:', 291 | finished.verifyData ); 292 | return; 293 | } 294 | 295 | // The handle_ methods should RETURN the response action. 296 | // See the handle() method for explanation. 297 | return this.send_serverFinished; 298 | }; 299 | 300 | ServerHandshakeHandler.prototype.send_serverFinished = function() { 301 | 302 | var changeCipherSpec = new DtlsChangeCipherSpec({ value: 1 }); 303 | 304 | var prf_func = prf( this.version ); 305 | 306 | var finished = new DtlsFinished({ 307 | verifyData: prf_func( 308 | this.newParameters.masterKey, 309 | "server finished", 310 | this.newParameters.getHandshakeDigest(), 12 311 | )}); 312 | 313 | var handshakes = this.handshakeBuilder.createHandshakes([ finished ]); 314 | handshakes = this.handshakeBuilder.fragmentHandshakes( handshakes ); 315 | handshakes.unshift( changeCipherSpec ); 316 | 317 | log.info( 'Verify data:', finished.verifyData ); 318 | log.info( 'Sending ChangeCipherSpec, Finished' ); 319 | 320 | var messages = this.setResponse( handshakes, this.onHandshake ); 321 | }; 322 | 323 | /** 324 | * Sets the response for the last client message. 325 | * 326 | * The last flight of packets is stored so we can somewhat automatically handle 327 | * retransmission when we see the client doing it. 328 | */ 329 | ServerHandshakeHandler.prototype.setResponse = function( packets, done ) { 330 | this.lastFlight = packets; 331 | 332 | if( packets ) 333 | this.onSend( packets, done ); 334 | }; 335 | 336 | /** 337 | * Retransmits the last response in case it got lost on the way last time. 338 | * 339 | * @param {DtlsPlaintext} message 340 | * The received packet that triggered this retransmit. 341 | */ 342 | ServerHandshakeHandler.prototype.retransmitLast = function( message ) { 343 | 344 | if( this.lastFlight ) 345 | this.onSend( this.lastFlight ); 346 | }; 347 | 348 | module.exports = ServerHandshakeHandler; 349 | -------------------------------------------------------------------------------- /certificateUtilities.js: -------------------------------------------------------------------------------- 1 | 2 | "use strict"; 3 | 4 | var Buffer = require( 'buffer' ).Buffer; 5 | var parseKeys = require( 'parse-asn1' ); 6 | 7 | var extractCertificate = function( pem ) { 8 | 9 | if( pem instanceof Buffer ) 10 | pem = pem.toString( 'ascii' ); 11 | 12 | var beginRe = /^-----BEGIN CERTIFICATE-----$/; 13 | var endRe = /^-----END CERTIFICATE-----$/; 14 | var match; 15 | 16 | var certLines = null; 17 | var lines = pem.split( '\n' ); 18 | for( var l in lines ) { 19 | var line = lines[l].trim(); 20 | 21 | if( !certLines ) { 22 | 23 | // Seek start of a segment 24 | match = beginRe.exec( line ); 25 | if( !match ) 26 | continue; 27 | 28 | certLines = []; 29 | } else if( certLines ) { 30 | 31 | match = endRe.exec( line ); 32 | if( match ) 33 | return new Buffer( certLines.join( '' ), 'base64' ); 34 | 35 | certLines.push( line ); 36 | } 37 | } 38 | 39 | return null; 40 | }; 41 | 42 | module.exports = { 43 | extractKey: parseKeys, 44 | extractCertificate: extractCertificate 45 | }; 46 | -------------------------------------------------------------------------------- /dtls.js: -------------------------------------------------------------------------------- 1 | 2 | "use strict"; 3 | 4 | var dtls = { 5 | 6 | MessageType: { 7 | changeCipherSpec: 20, 8 | alert: 21, 9 | handshake: 22, 10 | applicationData: 23 11 | }, 12 | 13 | HandshakeType: { 14 | helloRequest: 0, 15 | clientHello: 1, 16 | serverHello: 2, 17 | helloVerifyRequest: 3, 18 | certificate: 11, 19 | serverKeyExchange: 12, 20 | certificateRequest: 13, 21 | serverHelloDone: 14, 22 | certificateVerify: 15, 23 | clientKeyExchange: 16, 24 | finished: 20 25 | }, 26 | 27 | ConnectionEnd: { 28 | server: 0, 29 | client: 1 30 | }, 31 | 32 | PRFAlgorithm: { 33 | tlsPrfSha256: 0 34 | }, 35 | 36 | BulkCipherAlgorithm: { 37 | none: 0, 38 | rc4: 1, 39 | des3: 2, 40 | aes: 3 41 | }, 42 | 43 | CipherType: { 44 | stream: 0, 45 | block: 1, 46 | aead: 2 47 | }, 48 | 49 | MACAlgorithm: { 50 | none: 0, 51 | hmac_md5: 1, 52 | hmac_sha1: 2, 53 | hmac_sha256: 3, 54 | hmac_sha384: 4, 55 | hmac_sha512: 5 56 | }, 57 | 58 | CompressionMethod: { 59 | none: 0 60 | }, 61 | 62 | KeyExchange: { 63 | rsa: 0 64 | } 65 | }; 66 | 67 | for( var e in dtls ) { 68 | 69 | var enumeration = dtls[ e ]; 70 | var reversed = []; 71 | 72 | for( var v in enumeration ) 73 | reversed[ enumeration[v] ] = v; 74 | 75 | dtls[ e + 'Name' ] = reversed; 76 | } 77 | 78 | module.exports = dtls; 79 | -------------------------------------------------------------------------------- /example/client.js: -------------------------------------------------------------------------------- 1 | 2 | "use strict"; 3 | 4 | var dtls = require( '../' ); 5 | var fs = require( 'fs' ); 6 | 7 | dtls.setLogLevel( dtls.logLevel.FINE ); 8 | var pem = fs.readFileSync( 'server.pem' ); 9 | 10 | var client = dtls.connect( 4433, 'localhost', 'udp4', function() { 11 | client.send( new Buffer( 'foo\n' ) ); 12 | }); 13 | 14 | client.on( 'message', function( msg ) { 15 | console.log( 'Received application data' ); 16 | console.log( msg ); 17 | }); 18 | -------------------------------------------------------------------------------- /example/send_throughput.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var dtls = require('../'); 4 | var fs = require('fs'); 5 | var dgram = require('dgram'); 6 | var crypto = require('crypto'); 7 | var events = require('events'); 8 | var argv = require('minimist')(process.argv.slice(2)); 9 | 10 | dtls.setLogLevel(dtls.logLevel.WARN); 11 | 12 | argv.integrity = argv.integrity || false; 13 | argv.size = argv.size || 8000; 14 | argv.batch = argv.batch || 20; 15 | argv.acktime = argv.acktime || 20; 16 | argv.udp = argv.udp || false; 17 | argv.port = argv.port || 23395; 18 | argv.time = argv.time || 5000; 19 | console.log('Using Arguments', argv, '\n'); 20 | 21 | var buffer = crypto.pseudoRandomBytes(argv.size); 22 | var sendCount = 0; 23 | var receiveCount = 0; 24 | var receiveCountOnBatchStart = 0; 25 | var ackCount = 0; 26 | var ackTimedoutCount = 0; 27 | var ackTimeout; 28 | var serverSocket; 29 | var clientSocket; 30 | 31 | if (!argv.udp) { 32 | var pem = fs.readFileSync('server.pem'); 33 | var server = dtls.createServer({ 34 | type: 'udp4', 35 | key: pem, 36 | cert: pem 37 | }); 38 | server.bind(argv.port); 39 | server.on('secureConnection', function(socket) { 40 | serverSocket = socket; 41 | onServerConnect(); 42 | }); 43 | clientSocket = dtls.connect(argv.port, 'localhost', 'udp4', onClientConnect); 44 | } else { 45 | var onUdpBind = function(socket) { 46 | socket.port = socket.udp.address().port; 47 | socket.udp.on('message', onUdpMessage.bind(null, socket)); 48 | socket.emit('bind'); 49 | }; 50 | var onUdpMessage = function(socket, msg) { 51 | socket.emit('message', msg); 52 | }; 53 | var udpSend = function(socket, target, msg) { 54 | socket.udp.send(msg, 0, msg.length, target.port, '127.0.0.1'); 55 | }; 56 | serverSocket = new events.EventEmitter(); 57 | clientSocket = new events.EventEmitter(); 58 | serverSocket.udp = dgram.createSocket('udp4'); 59 | clientSocket.udp = dgram.createSocket('udp4'); 60 | serverSocket.send = udpSend.bind(null, serverSocket, clientSocket); 61 | clientSocket.send = udpSend.bind(null, clientSocket, serverSocket); 62 | // bind the client first, and then the server, 63 | // so that first ack will be sent to client and received 64 | clientSocket.on('bind', function() { 65 | onClientConnect(); 66 | serverSocket.on('bind', onServerConnect); 67 | serverSocket.udp.bind(onUdpBind.bind(null, serverSocket)); 68 | }); 69 | clientSocket.udp.bind(onUdpBind.bind(null, clientSocket)); 70 | 71 | } 72 | 73 | 74 | function onClientConnect() { 75 | console.log('Client connected.'); 76 | // when we get the server ack we send the next batch 77 | clientSocket.on('message', sendBatch); 78 | } 79 | 80 | function sendBatch() { 81 | for (var i = 0; i < argv.batch; i += 1) { 82 | clientSocket.send(buffer); 83 | } 84 | sendCount += argv.batch; 85 | } 86 | 87 | function onServerConnect() { 88 | console.log('Server connected.'); 89 | 90 | // timer to finish the test 91 | setTimeout(finish, argv.time); 92 | 93 | // track received messages 94 | serverSocket.on('message', onServerReceive); 95 | 96 | // send first ack to start off the sender 97 | sendAck(); 98 | } 99 | 100 | function onServerReceive(msg) { 101 | receiveCount += 1; 102 | testMessage(msg); 103 | doAck(); 104 | } 105 | 106 | function doAck(timedout) { 107 | 108 | // count the number of acks we force from the ackTimeout 109 | if (timedout) { 110 | ackTimedoutCount += 1; 111 | sendAck(); 112 | return; 113 | } 114 | 115 | // got a complete batch, send ack 116 | if (receiveCount === receiveCountOnBatchStart + argv.batch) { 117 | sendAck(); 118 | } 119 | } 120 | 121 | function sendAck() { 122 | ackCount += 1; 123 | if (ackCount % 100 === 0) { 124 | process.stdout.write('.'); 125 | } 126 | 127 | // keep the receive count at this point of sending ack so that we know a full batch was send 128 | receiveCountOnBatchStart = receiveCount; 129 | serverSocket.send('ack'); 130 | 131 | // set a timer to send next ack, so that even if a full batch is not received we still continue 132 | clearTimeout(ackTimeout); 133 | ackTimeout = setTimeout(doAck, argv.acktime, 'timedout'); 134 | } 135 | 136 | function testMessage(msg) { 137 | if (argv.integrity && !msg.equals(buffer)) { 138 | console.error('Buffers differ!'); 139 | console.error(buffer); 140 | console.error(msg); 141 | process.exit(-1); 142 | } 143 | } 144 | 145 | function finish() { 146 | var throughput = receiveCount * buffer.length / (argv.time / 1000) / 1024; 147 | console.log('\n'); 148 | console.log('Sent Packets :', sendCount); 149 | console.log('Received Packets :', receiveCount); 150 | console.log('Acks :', ackCount, '(' + ackTimedoutCount + ' timedout)'); 151 | console.log('Size :', buffer.length + ' B'); 152 | console.log('Time :', argv.time + ' ms'); 153 | console.log('Throughput :', throughput.toFixed(3), 'KB/s'); 154 | 155 | // there is no socket.close() so we exit for now 156 | serverSocket.removeAllListeners('message'); 157 | clientSocket.removeAllListeners('message'); 158 | process.exit(0); 159 | } 160 | -------------------------------------------------------------------------------- /example/server.js: -------------------------------------------------------------------------------- 1 | 2 | "use strict"; 3 | 4 | var dtls = require( '../' ); 5 | var fs = require( 'fs' ); 6 | 7 | dtls.setLogLevel( dtls.logLevel.INFO ); 8 | var pem = fs.readFileSync( 'server.pem' ); 9 | 10 | var server = dtls.createServer({ 11 | type: 'udp4', 12 | key: pem, 13 | cert: pem 14 | }); 15 | server.bind( 4433 ); 16 | 17 | server.on( 'secureConnection', function( socket ) { 18 | 19 | console.log( 'New connection from ' + 20 | [ socket.rinfo.address, socket.rinfo.port ].join(':') ); 21 | 22 | socket.on( 'message', function( message ) { 23 | 24 | // Get the ascii encoded text content and trim whitespace at the end. 25 | var inText = message.toString( 'ascii' ).replace( /\s*$/, '' ); 26 | var outText = '[ECHO]' + inText + '[/ECHO]'; 27 | 28 | console.log( 'in: ' + inText ); 29 | console.log( 'out: ' + outText ); 30 | socket.send( new Buffer( outText + '\n', 'ascii' ) ); 31 | }); 32 | }); 33 | 34 | -------------------------------------------------------------------------------- /example/throughput.js: -------------------------------------------------------------------------------- 1 | 2 | "use strict"; 3 | 4 | var dtls = require( '../' ); 5 | var fs = require( 'fs' ); 6 | var crypto = require( 'crypto' ); 7 | 8 | dtls.setLogLevel( dtls.logLevel.WARN ); 9 | 10 | var testIntegrity = true; 11 | var buffer = crypto.pseudoRandomBytes( 1000 ); 12 | var stop = false; 13 | var count = 0; 14 | var time = 5000; 15 | 16 | var pem = fs.readFileSync( 'server.pem' ); 17 | 18 | var server = dtls.createServer({ 19 | type: 'udp4', 20 | key: pem, 21 | cert: pem 22 | }); 23 | server.bind( 23395 ); 24 | 25 | var serverSocket, clientSocket; 26 | server.on( 'secureConnection', function( socket ) { 27 | console.log( 'Server received client#Finished and is ready.' ); 28 | 29 | serverSocket = socket; 30 | 31 | serverSocket.on( 'message', function( msg ) { 32 | if( stop ) 33 | return; 34 | serverSocket.send( msg ); 35 | }); 36 | }); 37 | 38 | 39 | clientSocket = dtls.connect( 23395, 'localhost', 'udp4', function() { 40 | console.log( 'Client received server#Finished and is ready.' ); 41 | 42 | startTest(); 43 | }); 44 | 45 | clientSocket.on( 'message', function( msg ) { 46 | if( stop ) 47 | return; 48 | 49 | count++; 50 | 51 | if( testIntegrity && !msg.equals( buffer ) ) { 52 | console.error( 'Buffers differ!' ); 53 | console.error( buffer ); 54 | console.error( msg ); 55 | return; 56 | } 57 | 58 | clientSocket.send( msg ); 59 | }); 60 | 61 | var startTest = function() { 62 | 63 | count = 0; 64 | stop = false; 65 | 66 | clientSocket.send( buffer ); 67 | clientSocket.send( buffer ); 68 | clientSocket.send( buffer ); 69 | clientSocket.send( buffer ); 70 | clientSocket.send( buffer ); 71 | clientSocket.send( buffer ); 72 | clientSocket.send( buffer ); 73 | clientSocket.send( buffer ); 74 | clientSocket.send( buffer ); 75 | 76 | setTimeout( function() { 77 | stop = true; 78 | console.log( 'Packets: ' + count ); 79 | console.log( 'Size: ' + buffer.length + ' B' ); 80 | console.log( 'Time: ' + time + ' ms' ); 81 | console.log( 'Throughput: ' + ( count * buffer.length / ( time/1000 * 1024 ) ) + ' KB/s'); 82 | }, time ); 83 | }; 84 | 85 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | 2 | "use strict"; 3 | 4 | var logg = require( 'logg' ); 5 | var DtlsServer = require( './DtlsServer' ); 6 | var DtlsSocket = require( './DtlsSocket' ); 7 | 8 | var log = logg.getLogger( 'dtls' ); 9 | log.setLogLevel( logg.Level.SEVERE ); 10 | 11 | var logLevels = {}; 12 | for( var l in logg.Level ) 13 | logLevels[ l ] = logg.Level[ l ]; 14 | 15 | logg.getLogger( 'dtls.SecurityParameters.digest' ).setLogLevel( logg.Level.WARN ); 16 | 17 | module.exports = { 18 | DtlsServer: DtlsServer, 19 | createServer: DtlsServer.createServer, 20 | connect: DtlsSocket.connect, 21 | setLogLevel: log.setLogLevel.bind( log ), 22 | logLevel: logLevels 23 | }; 24 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "node-dtls", 3 | "version": "0.0.1", 4 | "description": "JavaScript implementation of DTLS protocol", 5 | "main": "index.js", 6 | "directories": { 7 | "example": "example", 8 | "test": "test" 9 | }, 10 | "dependencies": { 11 | "asn1.js": "^1.0.3", 12 | "logg": "^0.3.0", 13 | "parse-asn1": "^3.0.0" 14 | }, 15 | "devDependencies": { 16 | "chai": "^2.2.0", 17 | "minimist": "^1.1.1", 18 | "mocha": "^2.2.4" 19 | }, 20 | "scripts": { 21 | "test": "mocha" 22 | }, 23 | "repository": { 24 | "type": "git", 25 | "url": "https://github.com/Rantanen/node-dtls.git" 26 | }, 27 | "keywords": [ 28 | "dtls", 29 | "dgram", 30 | "insecurity" 31 | ], 32 | "author": "Mikko Rantanen", 33 | "license": "ISC", 34 | "bugs": { 35 | "url": "https://github.com/Rantanen/node-dtls/issues" 36 | }, 37 | "homepage": "https://github.com/Rantanen/node-dtls" 38 | } 39 | -------------------------------------------------------------------------------- /packets/DtlsCertificate.js: -------------------------------------------------------------------------------- 1 | 2 | "use strict"; 3 | 4 | var util = require( 'util' ); 5 | var Packet = require( './Packet' ); 6 | var PacketSpec = require( './PacketSpec' ); 7 | var dtls = require( '../dtls' ); 8 | 9 | var DtlsCertificate = function( data ) { 10 | Packet.call( this, data ); 11 | }; 12 | util.inherits( DtlsCertificate, Packet ); 13 | 14 | DtlsCertificate.prototype.messageType = dtls.HandshakeType.certificate; 15 | DtlsCertificate.prototype.spec = new PacketSpec([ 16 | { name: 'certificateList', type: 'var24', itemType: 'var24' } 17 | ]); 18 | 19 | module.exports = DtlsCertificate; 20 | -------------------------------------------------------------------------------- /packets/DtlsChangeCipherSpec.js: -------------------------------------------------------------------------------- 1 | 2 | "use strict"; 3 | 4 | var util = require( 'util' ); 5 | var Packet = require( './Packet' ); 6 | var PacketSpec = require( './PacketSpec' ); 7 | var dtls = require( '../dtls' ); 8 | 9 | var DtlsChangeCipherSpec = function( data ) { 10 | Packet.call( this, data ); 11 | }; 12 | util.inherits( DtlsChangeCipherSpec, Packet ); 13 | 14 | DtlsChangeCipherSpec.prototype.type = dtls.MessageType.changeCipherSpec; 15 | DtlsChangeCipherSpec.prototype.spec = new PacketSpec([ 16 | { value: 'uint8' } 17 | ]); 18 | 19 | module.exports = DtlsChangeCipherSpec; 20 | -------------------------------------------------------------------------------- /packets/DtlsClientHello.js: -------------------------------------------------------------------------------- 1 | 2 | "use strict"; 3 | 4 | var util = require( 'util' ); 5 | var Packet = require( './Packet' ); 6 | var PacketSpec = require( './PacketSpec' ); 7 | var DtlsProtocolVersion = require( './DtlsProtocolVersion' ); 8 | var DtlsRandom = require( './DtlsRandom' ); 9 | var DtlsExtension = require( './DtlsExtension' ); 10 | var dtls = require( '../dtls' ); 11 | 12 | var DtlsClientHello = function( data ) { 13 | Packet.call( this, data ); 14 | }; 15 | util.inherits( DtlsClientHello, Packet ); 16 | 17 | DtlsClientHello.prototype.messageType = dtls.HandshakeType.clientHello; 18 | DtlsClientHello.prototype.spec = new PacketSpec([ 19 | 20 | { clientVersion: DtlsProtocolVersion }, 21 | { random: DtlsRandom }, 22 | { sessionId: 'var8' }, 23 | { cookie: 'var8' }, 24 | { name: 'cipherSuites', type: 'var16', itemType: 'uint16' }, 25 | { name: 'compressionMethods', type: 'var8', itemType: 'uint8' }, 26 | { name: 'extensions', type: 'var16', itemType: DtlsExtension, optional: true } 27 | ]); 28 | 29 | module.exports = DtlsClientHello; 30 | -------------------------------------------------------------------------------- /packets/DtlsClientKeyExchange_rsa.js: -------------------------------------------------------------------------------- 1 | 2 | "use strict"; 3 | 4 | var util = require( 'util' ); 5 | var Packet = require( './Packet' ); 6 | var PacketSpec = require( './PacketSpec' ); 7 | var dtls = require( '../dtls' ); 8 | 9 | var DtlsClientKeyExchange_rsa = function( data ) { 10 | Packet.call( this, data ); 11 | }; 12 | util.inherits( DtlsClientKeyExchange_rsa, Packet ); 13 | 14 | DtlsClientKeyExchange_rsa.prototype.messageType = dtls.HandshakeType.clientKeyExchange; 15 | DtlsClientKeyExchange_rsa.prototype.spec = new PacketSpec([ 16 | { exchangeKeys: 'var16' } 17 | ]); 18 | 19 | module.exports = DtlsClientKeyExchange_rsa; 20 | -------------------------------------------------------------------------------- /packets/DtlsExtension.js: -------------------------------------------------------------------------------- 1 | 2 | "use strict"; 3 | 4 | var util = require( 'util' ); 5 | var Packet = require( './Packet' ); 6 | var PacketSpec = require( './PacketSpec' ); 7 | 8 | var DtlsExtension = function( data ) { 9 | Packet.call( this, data ); 10 | }; 11 | 12 | DtlsExtension.prototype.spec = new PacketSpec([ 13 | 14 | { extensionType: 'uint16' }, 15 | { extensionData: 'var16' } 16 | ]); 17 | 18 | module.exports = DtlsExtension; 19 | -------------------------------------------------------------------------------- /packets/DtlsFinished.js: -------------------------------------------------------------------------------- 1 | 2 | "use strict"; 3 | 4 | var util = require( 'util' ); 5 | var Packet = require( './Packet' ); 6 | var PacketSpec = require( './PacketSpec' ); 7 | var dtls = require( '../dtls' ); 8 | 9 | var DtlsFinished = function( data ) { 10 | Packet.call( this, data ); 11 | }; 12 | util.inherits( DtlsFinished, Packet ); 13 | 14 | DtlsFinished.prototype.messageType = dtls.HandshakeType.finished; 15 | DtlsFinished.prototype.spec = new PacketSpec([ 16 | { name: 'verifyData', type: 'bytes', size: 12 } 17 | ]); 18 | 19 | module.exports = DtlsFinished; 20 | 21 | -------------------------------------------------------------------------------- /packets/DtlsHandshake.js: -------------------------------------------------------------------------------- 1 | 2 | "use strict"; 3 | 4 | var util = require( 'util' ); 5 | var Packet = require( './Packet' ); 6 | var PacketSpec = require( './PacketSpec' ); 7 | var dtls = require( '../dtls' ); 8 | 9 | var DtlsHandshake = function( data ) { 10 | Packet.call( this, data ); 11 | }; 12 | util.inherits( DtlsHandshake, Packet ); 13 | 14 | DtlsHandshake.overhead = ( 8 + 24 + 16 + 24 ) / 8; 15 | DtlsHandshake.prototype.type = dtls.MessageType.handshake; 16 | DtlsHandshake.prototype.spec = new PacketSpec([ 17 | 18 | { msgType: 'uint8' }, 19 | { length: 'uint24' }, 20 | { messageSeq: 'uint16' }, 21 | { fragmentOffset: 'uint24' }, 22 | { body: 'var24' } 23 | ]); 24 | 25 | module.exports = DtlsHandshake; 26 | -------------------------------------------------------------------------------- /packets/DtlsHelloVerifyRequest.js: -------------------------------------------------------------------------------- 1 | 2 | "use strict"; 3 | 4 | var util = require( 'util' ); 5 | var Packet = require( './Packet' ); 6 | var PacketSpec = require( './PacketSpec' ); 7 | var DtlsProtocolVersion = require( './DtlsProtocolVersion' ); 8 | var DtlsRandom = require( './DtlsRandom' ); 9 | var DtlsExtension = require( './DtlsExtension' ); 10 | var dtls = require( '../dtls' ); 11 | 12 | var DtlsHelloVerifyRequest = function( data ) { 13 | Packet.call( this, data ); 14 | }; 15 | util.inherits( DtlsHelloVerifyRequest, Packet ); 16 | 17 | DtlsHelloVerifyRequest.prototype.messageType = 18 | dtls.HandshakeType.helloVerifyRequest; 19 | 20 | DtlsHelloVerifyRequest.prototype.spec = new PacketSpec([ 21 | { serverVersion: DtlsProtocolVersion }, 22 | { cookie: 'var8' }, 23 | ]); 24 | 25 | module.exports = DtlsHelloVerifyRequest; 26 | -------------------------------------------------------------------------------- /packets/DtlsPlaintext.js: -------------------------------------------------------------------------------- 1 | 2 | "use strict"; 3 | 4 | var log = require( 'logg' ).getLogger( 'dtls.DtlsPlaintext' ); 5 | var util = require( 'util' ); 6 | var Packet = require( './Packet' ); 7 | var PacketSpec = require( './PacketSpec' ); 8 | var DtlsProtocolVersion = require( './DtlsProtocolVersion' ); 9 | var dtls = require( '../dtls' ); 10 | 11 | var DtlsPlaintext = function( data ) { 12 | for( var d in data ) { 13 | this[d] = data[d]; 14 | } 15 | }; 16 | util.inherits( DtlsPlaintext, Packet ); 17 | 18 | DtlsPlaintext.prototype.spec = new PacketSpec([ 19 | 20 | { type: 'uint8' }, 21 | { version: DtlsProtocolVersion }, 22 | { epoch: 'uint16' }, 23 | { name: 'sequenceNumber', type: 'bytes', size: 48/8 }, 24 | { name: 'fragment', type: 'var16' } 25 | ]); 26 | 27 | var contentTypes = {}; 28 | DtlsPlaintext.prototype.getFragmentType = function() { 29 | var ct = contentTypes[ this.type ]; 30 | if( !ct ) return log.error( 'Unknown content type:', this.type ); 31 | 32 | return ct; 33 | }; 34 | 35 | DtlsPlaintext.readPackets = function( data ) { 36 | var start = 0; 37 | var plaintexts = []; 38 | while( data.length > start ) { 39 | 40 | // Start by checking the length: 41 | var fragmentLength = data.readUInt16BE( start + 11 ); 42 | if( data.length < start + ( 12 + fragmentLength ) ) 43 | break; 44 | 45 | var type = data.readUInt8( start, true ); 46 | var version = new DtlsProtocolVersion({ 47 | major: data.readInt8( start + 1, true ), 48 | minor: data.readInt8( start + 2, true ) 49 | }); 50 | var epoch = data.readUInt16BE( start + 3, true ); 51 | var sequenceNumber = data.slice( start + 5, start + 11 ); 52 | var fragment = data.slice( start + 13, start + 13 + fragmentLength ); 53 | 54 | var dtpt = new DtlsPlaintext({ 55 | type: type, 56 | version: version, 57 | epoch: epoch, 58 | sequenceNumber: sequenceNumber, 59 | fragment: fragment 60 | }); 61 | 62 | plaintexts.push( dtpt ); 63 | 64 | start += 13 + fragmentLength; 65 | } 66 | 67 | return plaintexts; 68 | }; 69 | 70 | DtlsPlaintext.prototype.getBuffer = function() { 71 | var buffer = new Buffer( 13 + this.fragment.length ); 72 | buffer.writeUInt8( this.type, 0, true ); 73 | buffer.writeUInt8( this.version.major, 1, true ); 74 | buffer.writeUInt8( this.version.minor, 2, true ); 75 | buffer.writeUInt16BE( this.epoch, 3, true ); 76 | this.sequenceNumber.copy( buffer, 5, 0, 6 ); 77 | buffer.writeUInt16BE( this.fragment.length, 11, true ); 78 | this.fragment.copy( buffer, 13 ); 79 | return buffer; 80 | }; 81 | 82 | contentTypes[ dtls.MessageType.handshake ] = require( './DtlsHandshake' ); 83 | contentTypes[ dtls.MessageType.changeCipherSpec ] = require( './DtlsChangeCipherSpec' ); 84 | 85 | module.exports = DtlsPlaintext; 86 | -------------------------------------------------------------------------------- /packets/DtlsPreMasterSecret.js: -------------------------------------------------------------------------------- 1 | 2 | "use strict"; 3 | 4 | var util = require( 'util' ); 5 | var Packet = require( './Packet' ); 6 | var PacketSpec = require( './PacketSpec' ); 7 | var dtls = require( '../dtls' ); 8 | 9 | var DtlsProtocolVersion = require( './DtlsProtocolVersion' ); 10 | 11 | var DtlsPreMasterSecret = function( data ) { 12 | Packet.call( this, data ); 13 | }; 14 | util.inherits( DtlsPreMasterSecret, Packet ); 15 | 16 | DtlsPreMasterSecret.prototype.messageType = dtls.HandshakeType.certificate; 17 | DtlsPreMasterSecret.prototype.spec = new PacketSpec([ 18 | { clientVersion: DtlsProtocolVersion }, 19 | { name: 'random', type: 'bytes', size: 46 } 20 | ]); 21 | 22 | module.exports = DtlsPreMasterSecret; 23 | -------------------------------------------------------------------------------- /packets/DtlsProtocolVersion.js: -------------------------------------------------------------------------------- 1 | 2 | "use strict"; 3 | 4 | var util = require( 'util' ); 5 | var Packet = require( './Packet' ); 6 | var PacketSpec = require( './PacketSpec' ); 7 | 8 | var DtlsProtocolVersion = function( data, minor ) { 9 | if( minor !== undefined ) { 10 | this.major = data; 11 | this.minor = minor; 12 | } else { 13 | Packet.call( this, data ); 14 | } 15 | }; 16 | util.inherits( DtlsProtocolVersion, Packet ); 17 | 18 | DtlsProtocolVersion.prototype.spec = new PacketSpec([ 19 | 20 | { major: 'int8' }, 21 | { minor: 'int8' } 22 | ]); 23 | 24 | module.exports = DtlsProtocolVersion; 25 | -------------------------------------------------------------------------------- /packets/DtlsRandom.js: -------------------------------------------------------------------------------- 1 | 2 | "use strict"; 3 | 4 | var util = require( 'util' ); 5 | var Packet = require( './Packet' ); 6 | var PacketSpec = require( './PacketSpec' ); 7 | var DtlsProtocolVersion = require( './DtlsProtocolVersion' ); 8 | var crypto = require( 'crypto' ); 9 | 10 | var DtlsRandom = function( data ) { 11 | Packet.call( this, data ); 12 | 13 | if( !data ) 14 | this.generate(); 15 | }; 16 | 17 | DtlsRandom.prototype.spec = new PacketSpec([ 18 | 19 | { gmtUnixTime: 'uint32' }, 20 | { name: 'randomBytes', type: 'bytes', size: 28 } 21 | ]); 22 | 23 | DtlsRandom.prototype.generate = function() { 24 | this.gmtUnixTime = Math.floor( Date.now() / 1000 ); 25 | this.randomBytes = crypto.randomBytes( 28 ); 26 | }; 27 | 28 | DtlsRandom.prototype.getBuffer = function() { 29 | 30 | if( this.bytes ) return this.bytes; 31 | 32 | this.bytes = new Buffer( 32 ); 33 | this.bytes.writeUInt32BE( this.gmtUnixTime, 0 ); 34 | this.randomBytes.copy( this.bytes, 4 ); 35 | return this.bytes; 36 | }; 37 | 38 | module.exports = DtlsRandom; 39 | -------------------------------------------------------------------------------- /packets/DtlsServerHello.js: -------------------------------------------------------------------------------- 1 | 2 | "use strict"; 3 | 4 | var util = require( 'util' ); 5 | var Packet = require( './Packet' ); 6 | var PacketSpec = require( './PacketSpec' ); 7 | var DtlsProtocolVersion = require( './DtlsProtocolVersion' ); 8 | var DtlsRandom = require( './DtlsRandom' ); 9 | var DtlsExtension = require( './DtlsExtension' ); 10 | var dtls = require( '../dtls' ); 11 | 12 | var DtlsServerHello = function( data ) { 13 | Packet.call( this, data ); 14 | }; 15 | util.inherits( DtlsServerHello, Packet ); 16 | 17 | DtlsServerHello.prototype.messageType = dtls.HandshakeType.serverHello; 18 | DtlsServerHello.prototype.spec = new PacketSpec([ 19 | { serverVersion: DtlsProtocolVersion }, 20 | { random: DtlsRandom }, 21 | { sessionId: 'var8' }, 22 | { cipherSuite: 'uint16' }, 23 | { compressionMethod: 'uint8' }, 24 | { name: 'extensions', type: 'var16', itemType: DtlsExtension, optional: true } 25 | ]); 26 | 27 | module.exports = DtlsServerHello; 28 | -------------------------------------------------------------------------------- /packets/DtlsServerHelloDone.js: -------------------------------------------------------------------------------- 1 | 2 | "use strict"; 3 | 4 | var util = require( 'util' ); 5 | var Packet = require( './Packet' ); 6 | var PacketSpec = require( './PacketSpec' ); 7 | var dtls = require( '../dtls' ); 8 | 9 | var DtlsServerHelloDone = function( data ) { 10 | Packet.call( this, data ); 11 | }; 12 | util.inherits( DtlsServerHelloDone, Packet ); 13 | 14 | DtlsServerHelloDone.prototype.messageType = dtls.HandshakeType.serverHelloDone; 15 | DtlsServerHelloDone.prototype.spec = new PacketSpec([ 16 | // This is an empty packet. 17 | ]); 18 | 19 | module.exports = DtlsServerHelloDone; 20 | 21 | -------------------------------------------------------------------------------- /packets/DtlsServerKeyExchange.js: -------------------------------------------------------------------------------- 1 | 2 | "use strict"; 3 | 4 | var util = require( 'util' ); 5 | var Packet = require( './Packet' ); 6 | var PacketSpec = require( './PacketSpec' ); 7 | var dtls = require( '../dtls' ); 8 | 9 | var DtlsServerKeyExchange = function( data ) { 10 | Packet.call( this, data ); 11 | }; 12 | util.inherits( DtlsServerKeyExchange, Packet ); 13 | -------------------------------------------------------------------------------- /packets/Packet.js: -------------------------------------------------------------------------------- 1 | 2 | "use strict"; 3 | 4 | var BufferReader = require( '../BufferReader' ); 5 | 6 | var Packet = function( data ) { 7 | 8 | if( data instanceof Buffer || data instanceof BufferReader ) 9 | return this.read( data ); 10 | 11 | for( var d in data ) { 12 | this[d] = data[d]; 13 | } 14 | }; 15 | 16 | Packet.prototype.read = function( data ) { 17 | return this.spec.read( data, this ); 18 | }; 19 | 20 | Packet.prototype.getBuffer = function() { 21 | return this.spec.write( this ); 22 | }; 23 | 24 | module.exports = Packet; 25 | -------------------------------------------------------------------------------- /packets/PacketSpec.js: -------------------------------------------------------------------------------- 1 | 2 | "use strict"; 3 | 4 | var BufferReader = require( '../BufferReader' ); 5 | var BufferBuilder = require( '../BufferBuilder' ); 6 | 7 | var PacketSpec = function( spec ) { 8 | 9 | // Normalise input parameter 10 | this.spec = []; 11 | for( var s in spec ) { 12 | var item = PacketSpec.normalize( spec[s] ); 13 | 14 | this.spec.push( item ); 15 | } 16 | }; 17 | 18 | PacketSpec.normalize = function( item ) { 19 | 20 | // Normalize { name: type } -> { name: name, type: type } 21 | if( typeof( item ) === 'object' ) { 22 | var keys = Object.keys( item ); 23 | if( keys.length === 1 ) { 24 | item = { 25 | name: keys[0], 26 | type: item[ keys[0] ] 27 | }; 28 | } 29 | } else if( typeof( item ) === 'string' || 30 | ( item.prototype && item.prototype.spec ) ) { 31 | 32 | item = { name: '_', type: item }; 33 | } 34 | 35 | if( item.type && typeof( item.type ) === 'string' ) 36 | item.type = item.type.toLowerCase(); 37 | 38 | var hasValidType = item.type && 39 | ( ( item.type.prototype && item.type.prototype.spec ) || 40 | types[ item.type ] ); 41 | var hasReadWrite = item.read && item.write; 42 | 43 | if( !( hasValidType || hasReadWrite ) ) { 44 | throw new Error( 'PacketSpec must have either a valid type or read/write functions, field: ' + item.name ); 45 | } 46 | 47 | if( item.itemType ) 48 | item.itemType = PacketSpec.normalize( item.itemType ); 49 | 50 | return item; 51 | }; 52 | 53 | PacketSpec.prototype.read = function( reader, obj ) { 54 | 55 | if( reader instanceof Buffer ) 56 | reader = new BufferReader( reader ); 57 | 58 | for( var s in this.spec ) { 59 | var item = this.spec[s]; 60 | 61 | // Finally assign the value. 62 | obj[ item.name ] = PacketSpec.readItem( reader, item ); 63 | } 64 | 65 | return reader.remaining(); 66 | }; 67 | 68 | PacketSpec.prototype.write = function( obj ) { 69 | 70 | var builder = new BufferBuilder(); 71 | 72 | for( var s in this.spec ) { 73 | var item = this.spec[s]; 74 | var field = obj[ item.name ]; 75 | 76 | PacketSpec.writeItem( builder, item, field, obj ); 77 | } 78 | 79 | return builder.getBuffer(); 80 | }; 81 | 82 | PacketSpec.readItem = function( reader, item, obj ) { 83 | 84 | if( reader.buffer.length - reader.offset === 0 && item.optional ) 85 | return; 86 | 87 | // Resolve the function used to read the value. 88 | var readerFunc = null; 89 | if( item.read ) { 90 | 91 | return item.read.call( obj, reader ); 92 | 93 | } else if( item.type.prototype && item.type.prototype.spec ) { 94 | 95 | var spec = item.type.prototype.spec; 96 | var newObj = new item.type(); 97 | spec.read( reader, newObj ); 98 | return newObj; 99 | 100 | } else { 101 | 102 | // Reader function not specified explicitly. Read the value type. 103 | var typeFunc = types[ item.type ]; 104 | 105 | // Check the type specification type 106 | if( typeof( typeFunc ) === 'string' ) { 107 | 108 | // strings refer to data types as defined in BufferReader/-Builder 109 | return reader[ 'read' + typeFunc ](); 110 | 111 | } else { 112 | 113 | // objects contain read/write methods. Use the read method. 114 | return typeFunc.read.call( obj, reader, item ); 115 | } 116 | } 117 | }; 118 | 119 | PacketSpec.writeItem = function( builder, item, field, obj ) { 120 | 121 | // Resolve the function used to write the value. 122 | var writerFunc = null; 123 | if( item.write ) { 124 | 125 | writerFunc = item.write.call( obj, builder, field ); 126 | 127 | } else if( item.type.prototype && item.type.prototype.spec ) { 128 | 129 | var spec = item.type.prototype.spec; 130 | builder.writeBytes( spec.write( field ) ); 131 | 132 | } else { 133 | 134 | // Reader function not specified explicitly. Read the value type. 135 | var typeFunc = types[ item.type ]; 136 | 137 | // Check the type specification type 138 | if( typeof( typeFunc ) === 'string' ) { 139 | 140 | // strings refer to data types as defined in BufferReader/-Builder 141 | builder[ 'write' + typeFunc ]( field ); 142 | 143 | } else { 144 | 145 | // objects contain read/write methods. Use the write method. 146 | typeFunc.write.call( obj, builder, field, item ); 147 | } 148 | } 149 | }; 150 | 151 | var types = { 152 | uint8: 'UInt8', 153 | uint16: 'UInt16BE', 154 | uint24: 'UInt24BE', 155 | uint32: 'UInt32BE', 156 | int8: 'Int8', 157 | int16: 'Int16BE', 158 | int32: 'Int32BE', 159 | float: 'FloatBE', 160 | double: 'DoubleBE', 161 | var8: { 162 | read: constructVariableLengthRead( 8 ), 163 | write: constructVariableLengthWrite( 8 ), 164 | }, 165 | var16: { 166 | read: constructVariableLengthRead( 16 ), 167 | write: constructVariableLengthWrite( 16 ), 168 | }, 169 | var24: { 170 | read: constructVariableLengthRead( 24 ), 171 | write: constructVariableLengthWrite( 24 ), 172 | }, 173 | var32: { 174 | read: constructVariableLengthRead( 32 ), 175 | write: constructVariableLengthWrite( 32 ), 176 | }, 177 | bytes: { 178 | read: function( reader, type ) { 179 | return reader.readBytes( type.size ); 180 | }, 181 | write: function( builder, value, type ) { 182 | builder.writeBytes( value.slice( 0, type.size ) ); 183 | } 184 | }, 185 | }; 186 | 187 | function constructVariableLengthRead( length ) { 188 | 189 | // If this is multi-byte read, specify Big endian format. 190 | if( length > 8 ) length = length + 'BE'; 191 | 192 | return function( reader, type ) { 193 | var dataLength = reader[ 'readUInt' + length ](); 194 | 195 | if( !type.itemType ) { 196 | return reader.readBytes( dataLength ); 197 | } else { 198 | var arr = []; 199 | var startOffset = reader.offset; 200 | while( reader.offset < startOffset + dataLength ) { 201 | arr.push( PacketSpec.readItem( reader, type.itemType ) ); 202 | } 203 | return arr; 204 | } 205 | }; 206 | } 207 | 208 | function constructVariableLengthWrite( length ) { 209 | 210 | // If this is multi-byte read, specify Big endian format. 211 | if( length > 8 ) length = length + 'BE'; 212 | 213 | return function( builder, value, type ) { 214 | 215 | if( !type.itemType ) { 216 | builder[ 'writeUInt' + length ]( value.length ); 217 | builder.writeBytes( value ); 218 | } else { 219 | 220 | var arrayBuilder = new BufferBuilder(); 221 | for( var i = 0; i < value.length; i++ ) { 222 | PacketSpec.writeItem( arrayBuilder, type.itemType, value[i], this ); 223 | } 224 | 225 | var arrayBuffer = arrayBuilder.getBuffer(); 226 | builder[ 'writeUInt' + length ]( arrayBuffer.length ); 227 | builder.writeBytes( arrayBuffer ); 228 | } 229 | }; 230 | } 231 | 232 | module.exports = PacketSpec; 233 | -------------------------------------------------------------------------------- /packets/index.js: -------------------------------------------------------------------------------- 1 | 2 | module.exports = { 3 | Plaintext: require( './DtlsPlaintext' ), 4 | ServerHello: require( './DtlsServerHello' ), 5 | ClientHello: require( './DtlsClientHello' ), 6 | ServerHelloDone: require( './DtlsServerHelloDone' ), 7 | ProtocolVersion: require( './DtlsProtocolVersion' ), 8 | HelloVerifyRequest: require( './DtlsHelloVerifyRequest' ), 9 | Random: require( './DtlsRandom' ), 10 | Certificate: require( './DtlsCertificate' ), 11 | ClientKeyExchange_rsa: require( './DtlsClientKeyExchange_rsa' ), 12 | Finished: require( './DtlsFinished' ), 13 | }; 14 | -------------------------------------------------------------------------------- /prf.js: -------------------------------------------------------------------------------- 1 | 2 | "use strict"; 3 | var crypto = require( 'crypto' ); 4 | 5 | var hmac_hash = function( algo, secret, text ) { 6 | 7 | var sum = crypto.createHmac( algo, secret ); 8 | sum.update( text ); 9 | return sum.digest(); 10 | }; 11 | 12 | var p = function( hashName, secret, seed, length ) { 13 | 14 | var hash_x = hmac_hash.bind( null, hashName ); 15 | 16 | length = length || 32; 17 | var a = function(n) { 18 | if( n === 0 ) 19 | return seed; 20 | 21 | return hash_x( secret, a( n-1 ) ); 22 | }; 23 | 24 | var hashes = []; 25 | var hashLength = 0; 26 | for( var i = 1; hashLength < length; i++ ) { 27 | 28 | var hashBytes = hash_x( secret, Buffer.concat([ a(i), seed ])); 29 | hashLength += hashBytes.length; 30 | hashes.push( hashBytes ); 31 | } 32 | 33 | return Buffer.concat( hashes, length ).slice( 0, length ); 34 | }; 35 | 36 | var p_md5 = p.bind( null, 'md5' ); 37 | var p_sha1 = p.bind( null, 'sha1' ); 38 | var p_sha256 = p.bind( null, 'sha256' ); 39 | 40 | var prf = {}; 41 | 42 | prf[ ~0 ] = prf[ ~1 ] = { 43 | prf: function( secret, label, seed, length ) { 44 | 45 | var splitLength = Math.ceil( secret.length / 2 ); 46 | var md5Secret = secret.slice( 0, splitLength ); 47 | var shaSecret = secret.slice( secret.length - splitLength, secret.length ); 48 | 49 | var labelSeed = Buffer.concat([ new Buffer( label ), seed ]); 50 | 51 | var md5Bytes = p_md5( md5Secret, labelSeed, length ); 52 | var shaBytes = p_sha1( shaSecret, labelSeed, length ); 53 | 54 | for( var i = 0; i < length; i++ ) 55 | md5Bytes[i] = md5Bytes[i] ^ shaBytes[i]; 56 | 57 | return md5Bytes; 58 | }, 59 | createHash: function() { 60 | 61 | var sha1 = crypto.createHash( 'sha1' ); 62 | var md5 = crypto.createHash( 'md5' ); 63 | 64 | return { 65 | update: function( data ) { 66 | sha1.update( data ); 67 | md5.update( data ); 68 | }, 69 | digest: function() { 70 | return Buffer.concat([ md5.digest(), sha1.digest() ]); 71 | } 72 | }; 73 | } 74 | }; 75 | 76 | prf[ ~2 ] = { 77 | 78 | prf: function( secret, label, seed, length ) { 79 | 80 | return p_sha256( 81 | secret, 82 | Buffer.concat([ new Buffer( label ), seed ]), 83 | length ); 84 | }, 85 | createHash: function() { 86 | return crypto.createHash( 'sha256' ); 87 | } 88 | }; 89 | 90 | module.exports = function( version ) { 91 | if( version.major !== ~1 ) 92 | throw new Error( 'Unsupported version: ' + 93 | [ ~version.major, ~version.minor ].join('.') ); 94 | 95 | var prfStruct = prf[ version.minor ]; 96 | 97 | var func = prfStruct.prf; 98 | func.createHash = prfStruct.createHash; 99 | return func; 100 | }; 101 | -------------------------------------------------------------------------------- /test/.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../.jshintrc", 3 | 4 | "nonew": false, 5 | "expr": true, 6 | 7 | 8 | "predef" : [ 9 | "describe", 10 | "it" 11 | ] 12 | } 13 | -------------------------------------------------------------------------------- /test/Buffer.js: -------------------------------------------------------------------------------- 1 | 2 | "use strict"; 3 | 4 | var should = require( 'chai' ).should(); 5 | var crypto = require( 'crypto' ); 6 | 7 | var BufferReader = require( '../BufferReader' ); 8 | var BufferBuilder = require( '../BufferBuilder' ); 9 | 10 | var datatypes = { 11 | Int8: 1, UInt8: 1, 12 | Int16LE: 2, UInt16LE: 2, 13 | Int16BE: 2, UInt16BE: 2, 14 | Int32LE: 4, UInt32LE: 4, 15 | Int32BE: 4, UInt32BE: 4, 16 | FloatLE: 4, DoubleLE: 8, 17 | FloatBE: 4, DoubleBE: 8 18 | }; 19 | 20 | describe( 'BufferReader', function() { 21 | 22 | Object.keys( datatypes ).forEach( function( dt ) { 23 | var method = 'read' + dt; 24 | var size = datatypes[dt]; 25 | 26 | describe( '#' + method + '()', function() { 27 | 28 | it( 'should advance offset', function() { 29 | 30 | var reader = new BufferReader( new Buffer( size ) ); 31 | reader[ method ](); 32 | 33 | reader.offset.should.equal( size ); 34 | }); 35 | 36 | it( 'should read bytes correctly', function() { 37 | 38 | var count = 16; 39 | 40 | var buffer = crypto.pseudoRandomBytes( size * count ); 41 | var reader = new BufferReader( buffer ); 42 | 43 | for( var i = 0; i < count; i++ ) { 44 | var actual = reader[ method ](); 45 | var expected = buffer[ method ]( size * i ); 46 | 47 | if( !isNaN( expected ) ) 48 | actual.should.equal( expected ); 49 | } 50 | }); 51 | 52 | it( 'should consider optional offset', function() { 53 | 54 | var count = 16; 55 | 56 | var buffer = crypto.pseudoRandomBytes( size * count ); 57 | var reader = new BufferReader( buffer ); 58 | 59 | for( var i = 0; i < count; i++ ) { 60 | var actual = reader[ method ]( buffer.length - ( size * (i+1) )); 61 | var expected = buffer[ method ]( buffer.length - ( size * (i+1) )); 62 | 63 | if( !isNaN( expected ) ) 64 | actual.should.equal( expected ); 65 | } 66 | }); 67 | }); 68 | }); 69 | 70 | describe( '#readUInt24BE()', function() { 71 | 72 | it( 'should write bytes correctly', function() { 73 | 74 | var builder = new BufferBuilder(); 75 | var value = Math.floor( Math.random() * 0xffffff ); 76 | builder.writeUInt24BE( value ); 77 | 78 | var reader = new BufferReader( builder.getBuffer() ); 79 | var actual = reader.readUInt24BE(); 80 | 81 | actual.should.equal( value ); 82 | }); 83 | }); 84 | 85 | describe( '#readUInt24LE()', function() { 86 | 87 | it( 'should write bytes correctly', function() { 88 | 89 | var builder = new BufferBuilder(); 90 | var value = Math.floor( Math.random() * 0xffffff ); 91 | builder.writeUInt24LE( value ); 92 | 93 | var reader = new BufferReader( builder.getBuffer() ); 94 | var actual = reader.readUInt24LE(); 95 | 96 | actual.should.equal( value ); 97 | }); 98 | }); 99 | 100 | describe( '#readBytes()', function() { 101 | 102 | it( 'should read bytes correctly', function() { 103 | var value = crypto.pseudoRandomBytes( 64 ); 104 | 105 | var reader = new BufferReader( value ); 106 | 107 | for( var i = 0; i < 64; i += 16 ) { 108 | 109 | var actual = reader.readBytes( 16 ); 110 | var expected = value.slice( i, i + 16 ); 111 | 112 | actual.should.deep.equal( expected ); 113 | } 114 | }); 115 | }); 116 | 117 | describe( '#seek()', function() { 118 | 119 | it( 'should change position in buffer', function() { 120 | 121 | var buffer = new Buffer( [ 0x10, 0x20, 0x30, 0x40 ] ); 122 | var reader = new BufferReader( buffer ); 123 | 124 | reader.readInt8().should.equal( 0x10 ); 125 | 126 | reader.seek( 2 ); 127 | reader.readInt8().should.equal( 0x30 ); 128 | 129 | reader.seek( 1 ); 130 | reader.readInt8().should.equal( 0x20 ); 131 | }); 132 | }); 133 | }); 134 | 135 | describe( 'BufferBuilder', function() { 136 | 137 | Object.keys( datatypes ).forEach( function( dt ) { 138 | var method = 'write' + dt; 139 | var size = datatypes[dt]; 140 | 141 | describe( '#' + method + '()', function() { 142 | 143 | it( 'should write bytes correctly', function() { 144 | 145 | var count = 16; 146 | 147 | var builder = new BufferBuilder(); 148 | var buffer = new Buffer( size * count ); 149 | 150 | for( var i = 0; i < count; i++ ) { 151 | var value = Math.random(); 152 | 153 | builder[method]( value ); 154 | buffer[method]( value, i * size ); 155 | } 156 | 157 | var actual = builder.getBuffer(); 158 | actual.should.deep.equal( buffer ); 159 | }); 160 | }); 161 | }); 162 | 163 | describe( '#writeUInt24BE()', function() { 164 | 165 | it( 'should write bytes correctly', function() { 166 | var count = 16; 167 | var size = 3; 168 | 169 | var builder = new BufferBuilder(); 170 | var buffer = new Buffer( 3 * count ); 171 | 172 | for( var i = 0; i < count; i++ ) { 173 | var value = Math.floor( Math.random() * 0xffffff ); 174 | 175 | builder.writeUInt24BE( value ); 176 | buffer.writeUInt8( ( value & 0xff0000 ) >> 16, i * size ); 177 | buffer.writeUInt16BE( value & 0xffff, i * size + 1 ); 178 | } 179 | 180 | var actual = builder.getBuffer(); 181 | buffer.should.deep.equal( actual ); 182 | }); 183 | }); 184 | 185 | describe( '#writeUInt24LE()', function() { 186 | 187 | it( 'should write bytes correctly', function() { 188 | var count = 16; 189 | var size = 3; 190 | 191 | var builder = new BufferBuilder(); 192 | var buffer = new Buffer( size * count ); 193 | 194 | for( var i = 0; i < count; i++ ) { 195 | var value = Math.floor( Math.random() * 0xffffff ); 196 | 197 | builder.writeUInt24LE( value ); 198 | buffer.writeUInt8( value & 0xff, i * size ); 199 | buffer.writeUInt16LE( ( value & 0xffff00 ) >> 8, i * size + 1 ); 200 | } 201 | 202 | var actual = builder.getBuffer(); 203 | buffer.should.deep.equal( actual ); 204 | }); 205 | }); 206 | 207 | describe( '#writeBytes()', function() { 208 | 209 | it( 'should write bytes correctly', function() { 210 | 211 | var count = 16; 212 | var size = 8; 213 | 214 | var builder = new BufferBuilder(); 215 | var buffer = new Buffer( size * count ); 216 | 217 | for( var i = 0; i < count; i++ ) { 218 | 219 | var value = crypto.pseudoRandomBytes( size ); 220 | builder.writeBytes( value ); 221 | value.copy( buffer, i * size ); 222 | } 223 | 224 | var actual = builder.getBuffer(); 225 | buffer.should.deep.equal( actual ); 226 | 227 | }); 228 | }); 229 | 230 | }); 231 | -------------------------------------------------------------------------------- /test/ClientHandshakeHandler.js: -------------------------------------------------------------------------------- 1 | 2 | "use strict"; 3 | 4 | var should = require( 'chai' ).should(); 5 | 6 | var crypto = require( 'crypto' ); 7 | var constants = require( 'constants' ); 8 | var fs = require( 'fs' ); 9 | 10 | var ClientHandshakeHandler = require( '../ClientHandshakeHandler' ); 11 | var SecurityParameterContainer = require( '../SecurityParameterContainer' ); 12 | var dtls = require( '../dtls' ); 13 | var packets = require( '../packets' ); 14 | var CipherInfo = require( '../CipherInfo' ); 15 | var KeyContext = require( '../KeyContext' ); 16 | var prf = require( '../prf' ); 17 | 18 | describe( 'ClientHandshakeHandler', function() { 19 | 20 | var versions = { 21 | '1.2': { 22 | major: ~1, 23 | minor: ~2 24 | }, 25 | '1.0': { 26 | major: ~1, 27 | minor: ~0 28 | } 29 | }; 30 | 31 | for( var v in versions ) describe( 'DTLS v' + v, function() { 32 | 33 | var ver = versions[v]; 34 | var version = new packets.ProtocolVersion( ver.major, ver.minor ); 35 | 36 | describe( 'send_clientHello', function() { 37 | 38 | it( 'should send ClientHello', function( done ) { 39 | 40 | var parameters = new SecurityParameterContainer(); 41 | var handshakeHandler = new ClientHandshakeHandler( parameters ); 42 | handshakeHandler.version = version; 43 | 44 | handshakeHandler.onSend = function( msgs ) { 45 | 46 | msgs.should.have.length( 1 ); 47 | 48 | var msg = msgs[0]; 49 | 50 | msg.msgType.should.equal( dtls.HandshakeType.clientHello ); 51 | msg.messageSeq.should.equal( 0 ); 52 | msg.fragmentOffset.should.equal( 0 ); 53 | msg.length.should.equal( msg.body.length ); 54 | 55 | var clientHello = new packets.ClientHello( msg.body ); 56 | 57 | clientHello.clientVersion.major.should.equal( ver.major ); 58 | clientHello.clientVersion.minor.should.equal( ver.minor ); 59 | 60 | clientHello.random.getBuffer().should.deep.equal( 61 | parameters.parameters[1].clientRandom ); 62 | 63 | clientHello.sessionId.should.have.length( 0 ); 64 | clientHello.cookie.should.have.length( 0 ); 65 | 66 | clientHello.cipherSuites.should.deep.equal([ 67 | CipherInfo.TLS_RSA_WITH_AES_128_CBC_SHA.id ]); 68 | clientHello.compressionMethods.should.deep.equal([0]); 69 | 70 | // Extensions not handled correctly at the moment. 71 | // clientHello.extensions.should.have.length( 0 ); 72 | 73 | done(); 74 | }; 75 | 76 | handshakeHandler.send_clientHello(); 77 | handshakeHandler.setResponse( null ); 78 | }); 79 | 80 | it( 'should create new SecurityParameter', function() { 81 | 82 | var parameters = new SecurityParameterContainer(); 83 | var handshakeHandler = new ClientHandshakeHandler( parameters ); 84 | handshakeHandler.onSend = function() {}; 85 | 86 | should.not.exist( parameters.pending ); 87 | 88 | handshakeHandler.send_clientHello(); 89 | handshakeHandler.setResponse( null ); 90 | 91 | should.exist( parameters.pending ); 92 | parameters.pending.epoch.should.equal( parameters.current + 1 ); 93 | }); 94 | }); 95 | 96 | describe( '#handle_helloVerifyRequest()', function() { 97 | 98 | it( 'should cause ClientHello', function() { 99 | 100 | var parameters = new SecurityParameterContainer(); 101 | var handshakeHandler = new ClientHandshakeHandler( parameters ); 102 | 103 | var cookie = new Buffer( 20 ); 104 | 105 | var action = handshakeHandler.handle_helloVerifyRequest({ 106 | body: new packets.HelloVerifyRequest({ 107 | serverVersion: new packets.ProtocolVersion({ 108 | major: ver.major, minor: ver.minor }), 109 | cookie: cookie 110 | }).getBuffer() 111 | }); 112 | 113 | action.should.equal( handshakeHandler.send_clientHello ); 114 | handshakeHandler.setResponse( null ); 115 | 116 | handshakeHandler.cookie.should.deep.equal( cookie ); 117 | }); 118 | }); 119 | 120 | describe( '#handle_serverHello()', function() { 121 | 122 | it( 'should set the parameters', function() { 123 | 124 | var parameters = new SecurityParameterContainer(); 125 | var handshakeHandler = new ClientHandshakeHandler( parameters ); 126 | 127 | var random = new packets.Random(); 128 | var sessionId = crypto.pseudoRandomBytes( 16 ); 129 | var cipherSuite = CipherInfo.TLS_RSA_WITH_AES_128_CBC_SHA; 130 | 131 | var param = handshakeHandler.newParameters = 132 | parameters.initNew( version ); 133 | 134 | var setFrom = false; 135 | param.setFrom = function( suite ) { 136 | setFrom = true; 137 | suite.should.equal( cipherSuite ); 138 | }; 139 | 140 | var action = handshakeHandler.handle_serverHello({ 141 | body: new packets.ServerHello({ 142 | serverVersion: version, 143 | random: random, 144 | sessionId: sessionId, 145 | cipherSuite: cipherSuite.id, 146 | compressionMethod: 0, 147 | extensions: [] 148 | }).getBuffer() 149 | }); 150 | 151 | // ServerHello alone doesn't result in action. Client should 152 | // wait for Certificate and HelloDone. 153 | should.not.exist( action ); 154 | 155 | param.version.major.should.equal( ver.major ); 156 | param.version.minor.should.equal( ver.minor ); 157 | param.serverRandom.should.deep.equal( random.getBuffer() ); 158 | param.compressionMethod.should.equal( 0 ); 159 | 160 | setFrom.should.be.true; 161 | }); 162 | }); 163 | 164 | describe( '#handle_certificate()', function() { 165 | 166 | it( 'should store certificate', function() { 167 | 168 | var parameters = new SecurityParameterContainer(); 169 | var handshakeHandler = new ClientHandshakeHandler( parameters ); 170 | 171 | var certificateList = [ 172 | crypto.pseudoRandomBytes( 1024 ) 173 | ]; 174 | 175 | var param = handshakeHandler.newParameters = 176 | parameters.initNew( version ); 177 | 178 | var action = handshakeHandler.handle_certificate({ 179 | body: new packets.Certificate({ 180 | certificateList: certificateList 181 | }).getBuffer() 182 | }); 183 | 184 | // Certificate alone doesn't result in action. Client should wait 185 | // for HelloDone. 186 | should.not.exist( action ); 187 | handshakeHandler.certificate.should.deep.equal( certificateList[0] ); 188 | }); 189 | }); 190 | 191 | describe( '#handle_serverHelloDone()', function() { 192 | 193 | it( 'should send pre-master key', function() { 194 | 195 | var clientRandom = crypto.pseudoRandomBytes( 16 ); 196 | var serverRandom = crypto.pseudoRandomBytes( 16 ); 197 | 198 | var parameters = new SecurityParameterContainer(); 199 | var handshakeHandler = new ClientHandshakeHandler( parameters ); 200 | var param = handshakeHandler.newParameters = 201 | parameters.initNew( version ); 202 | 203 | handshakeHandler.version = param.version = version; 204 | param.clientRandom = clientRandom; 205 | param.serverRandom = serverRandom; 206 | 207 | var action = handshakeHandler.handle_serverHelloDone({ 208 | body: new packets.ServerHelloDone().getBuffer() 209 | }); 210 | 211 | should.exist( param.masterKey ); 212 | should.exist( param.preMasterKey ); 213 | action.should.equal( handshakeHandler.send_keyExchange ); 214 | handshakeHandler.setResponse( null ); 215 | }); 216 | }); 217 | 218 | describe( '#send_keyExchange()', function() { 219 | 220 | it( 'should send key', function( done ) { 221 | 222 | var parameters = new SecurityParameterContainer(); 223 | var handshakeHandler = new ClientHandshakeHandler( parameters ); 224 | var param = handshakeHandler.newParameters = 225 | parameters.initNew( version ); 226 | 227 | var clientRandom = new packets.Random(); 228 | var serverRandom = new packets.Random(); 229 | param.clientRandom = clientRandom.getBuffer(); 230 | param.serverRandom = serverRandom.getBuffer(); 231 | 232 | var preMasterKey = crypto.pseudoRandomBytes( 20 ); 233 | param.calculateMasterKey( preMasterKey ); 234 | 235 | var pem = fs.readFileSync( 'test/assets/certificate.pem' ); 236 | var keyContext = new KeyContext({ 237 | key: pem, 238 | cert: pem 239 | }); 240 | 241 | param.preMasterKey = preMasterKey; 242 | handshakeHandler.certificate = keyContext.certificate; 243 | 244 | handshakeHandler.onSend = function( msgs ) { 245 | 246 | msgs.should.have.length( 3 ); 247 | 248 | msgs[0].type.should.equal( dtls.MessageType.handshake ); 249 | msgs[1].type.should.equal( dtls.MessageType.changeCipherSpec ); 250 | msgs[2].type.should.equal( dtls.MessageType.handshake ); 251 | 252 | msgs[0].msgType.should.equal( 253 | dtls.HandshakeType.clientKeyExchange ); 254 | msgs[2].msgType.should.equal( 255 | dtls.HandshakeType.finished ); 256 | 257 | var keyExchange = new packets.ClientKeyExchange_rsa( 258 | msgs[0].body ); 259 | var actualPreMaster = crypto.privateDecrypt({ 260 | key: keyContext.key, 261 | padding: constants.RSA_PKCS1_PADDING 262 | }, keyExchange.exchangeKeys ); 263 | 264 | actualPreMaster.should.deep.equal( preMasterKey ); 265 | 266 | msgs[1].value.should.equal( 1 ); 267 | 268 | // Pop the 'Finished' handshake off the params. 269 | param.handshakeDigest.pop(); 270 | var digest = param.getHandshakeDigest(); 271 | 272 | var expected = prf( version )( 273 | param.masterKey, 274 | "client finished", 275 | digest, 12 ); 276 | 277 | msgs[2].body.should.deep.equal( expected ); 278 | 279 | done(); 280 | }; 281 | 282 | var fragments = handshakeHandler.send_keyExchange(); 283 | handshakeHandler.setResponse( null ); 284 | }); 285 | }); 286 | 287 | describe( '#handle_finished', function() { 288 | 289 | it( 'should finish handshake', function( done ) { 290 | 291 | var parameters = new SecurityParameterContainer(); 292 | var handshakeHandler = new ClientHandshakeHandler( parameters ); 293 | var param = handshakeHandler.newParameters = 294 | parameters.initNew( version ); 295 | 296 | param.masterKey = crypto.pseudoRandomBytes( 16 ); 297 | 298 | var verifyData = prf( param.version )( 299 | param.masterKey, 300 | "server finished", 301 | param.getHandshakeDigest(), 32 ); 302 | 303 | handshakeHandler.onHandshake = function() { 304 | done(); 305 | }; 306 | 307 | var action = handshakeHandler.handle_finished({ 308 | body: new packets.Finished({ 309 | verifyData: verifyData }).getBuffer() 310 | }); 311 | }); 312 | }); 313 | }); 314 | }); 315 | -------------------------------------------------------------------------------- /test/HandshakeBuilder.js: -------------------------------------------------------------------------------- 1 | 2 | "use strict"; 3 | 4 | var should = require( 'chai' ).should(); 5 | var crypto = require( 'crypto' ); 6 | var log = require( 'logg' ).getLogger( 'dtls' ); 7 | log.setLogLevel( log.SEVERE ); 8 | 9 | var HandshakeBuilder = require( '../HandshakeBuilder' ); 10 | var dtls = require( '../dtls' ); 11 | 12 | var packets = require( '../packets' ); 13 | var DtlsHandshake = require( '../packets/DtlsHandshake' ); 14 | var DtlsHelloVerifyRequest = require( '../packets/DtlsHelloVerifyRequest' ); 15 | var DtlsProtocolVersion = require( '../packets/DtlsProtocolVersion' ); 16 | 17 | describe( 'HandshakeBuilder', function() { 18 | 19 | describe( '#add()', function() { 20 | 21 | it( 'should handle unfragmented packets', function() { 22 | 23 | var handshake = createVerifyRequest(); 24 | 25 | var builder = new HandshakeBuilder(); 26 | builder.add( handshake ); 27 | 28 | builder.merged.should.include.keys( '0' ); 29 | builder.merged[0].msgType.should.equal( handshake.msgType ); 30 | builder.merged[0].body.should.deep.equal( handshake.body ); 31 | }); 32 | 33 | it( 'should merge fragmented packets', function() { 34 | 35 | var original = createVerifyRequest(); 36 | 37 | var fragment1 = new DtlsHandshake({ 38 | msgType: original.msgType, 39 | length: original.body.length, 40 | messageSeq: 0, 41 | fragmentOffset: 0, 42 | body: original.body.slice( 0, 10 ) 43 | }); 44 | 45 | var fragment2 = new DtlsHandshake({ 46 | msgType: original.msgType, 47 | length: original.body.length, 48 | messageSeq: 0, 49 | fragmentOffset: 10, 50 | body: original.body.slice( 10 ) 51 | }); 52 | 53 | var builder = new HandshakeBuilder(); 54 | builder.add( fragment1 ); 55 | 56 | // First fragment written. It should be buffered and not merged. 57 | builder.buffers.should.include.keys( '0' ); 58 | builder.buffers[0].bytesRead.should.equal( 10 ); 59 | builder.buffers[0].body.slice( 0, 10 ).should.deep.equal( fragment1.body ); 60 | builder.buffers[0].msgType.should.equal( original.msgType ); 61 | builder.merged.should.not.include.keys( '0' ); 62 | 63 | builder.add( fragment2 ); 64 | 65 | // Second fragment written. Buffer should be empty. Packet should 66 | // be merged. 67 | builder.merged.should.include.keys( '0' ); 68 | builder.merged[0].body.should.deep.equal( original.body ); 69 | builder.merged[0].msgType.should.equal( original.msgType ); 70 | builder.buffers.should.not.include.keys( '0' ); 71 | }); 72 | 73 | it( 'should handle out of order fragments', function() { 74 | 75 | var original = createVerifyRequest(); 76 | 77 | var fragment1 = new DtlsHandshake({ 78 | msgType: original.messageType, 79 | length: original.body.length, 80 | messageSeq: 0, 81 | fragmentOffset: 0, 82 | body: original.body.slice( 0, 10 ) 83 | }); 84 | 85 | var fragment2 = new DtlsHandshake({ 86 | msgType: original.messageType, 87 | length: original.body.length, 88 | messageSeq: 0, 89 | fragmentOffset: 10, 90 | body: original.body.slice( 10, 20 ) 91 | }); 92 | 93 | var fragment3 = new DtlsHandshake({ 94 | msgType: original.messageType, 95 | length: original.body.length, 96 | messageSeq: 0, 97 | fragmentOffset: 20, 98 | body: original.body.slice( 20 ) 99 | }); 100 | 101 | var builder = new HandshakeBuilder(); 102 | builder.add( fragment3 ); 103 | 104 | // Third fragment added. It should be queued, not written yet. 105 | builder.buffers.should.include.keys( '0' ); 106 | builder.buffers[0].bytesRead.should.equal( 0 ); 107 | builder.buffers[0].fragments.length.should.equal( 1 ); 108 | builder.buffers[0].fragments[0].should.deep.equal( fragment3 ); 109 | builder.merged.should.not.include.keys( '0' ); 110 | 111 | builder.add( fragment2 ); 112 | 113 | // Second fragment added. It should be queued, not written yet. 114 | builder.buffers.should.include.keys( '0' ); 115 | builder.buffers[0].bytesRead.should.equal( 0 ); 116 | builder.buffers[0].fragments.length.should.equal( 2 ); 117 | builder.buffers[0].fragments[1].should.deep.equal( fragment2 ); 118 | builder.merged.should.not.include.keys( '0' ); 119 | 120 | builder.add( fragment1 ); 121 | 122 | // First fragment written. Buffer should be empty. Packet should 123 | // be merged. 124 | builder.buffers.should.not.include.keys( '0' ); 125 | builder.merged.should.include.keys( '0' ); 126 | builder.merged[0].body.should.deep.equal( original.body ); 127 | }); 128 | 129 | it( 'should handle late packets', function() { 130 | 131 | var first = createVerifyRequest(); 132 | var second = createVerifyRequest(); 133 | 134 | var builder = new HandshakeBuilder(); 135 | 136 | builder.add( first ).should.equal.true; 137 | builder.add( second ).should.equal.false; 138 | }); 139 | 140 | it( 'should handle late fragments', function() { 141 | 142 | var first = createVerifyRequest(); 143 | var second = createVerifyRequest(); 144 | 145 | first.body = first.body.slice( 0, 20 ); 146 | second.body = second.body.slice( 5, 15 ); 147 | 148 | var builder = new HandshakeBuilder(); 149 | 150 | builder.add( first ); 151 | builder.buffers.should.include.keys( '0' ); 152 | builder.buffers[0].bytesRead.should.equal( 20 ); 153 | builder.buffers[0].body.slice( 0, 20 ).should.deep.equal( first.body ); 154 | 155 | builder.add( second ); 156 | builder.buffers.should.include.keys( '0' ); 157 | builder.buffers[0].bytesRead.should.equal( 20 ); 158 | builder.buffers[0].body.slice( 0, 20 ).should.deep.equal( first.body ); 159 | }); 160 | 161 | it( 'should handle duplicate early paclets', function() { 162 | 163 | var original = createVerifyRequest(); 164 | 165 | var fragment1 = new DtlsHandshake({ 166 | msgType: original.messageType, 167 | length: original.body.length, 168 | messageSeq: 0, 169 | fragmentOffset: 0, 170 | body: original.body.slice( 0, 10 ) 171 | }); 172 | 173 | var fragment2 = new DtlsHandshake({ 174 | msgType: original.messageType, 175 | length: original.body.length, 176 | messageSeq: 0, 177 | fragmentOffset: 10, 178 | body: original.body.slice( 10, 20 ) 179 | }); 180 | 181 | var builder = new HandshakeBuilder(); 182 | builder.add( fragment2 ); 183 | builder.add( fragment2 ); 184 | 185 | builder.buffers.should.include.keys( '0' ); 186 | builder.buffers[0].bytesRead.should.equal( 0 ); 187 | builder.buffers[0].fragments.length.should.equal( 2 ); 188 | 189 | builder.add( fragment1 ); 190 | 191 | builder.buffers[0].bytesRead.should.equal( 20 ); 192 | builder.buffers[0].fragments.length.should.equal( 0 ); 193 | 194 | builder.buffers[0].body.slice( 0, 10 ).should.deep.equal( 195 | fragment1.body ); 196 | builder.buffers[0].body.slice( 10, 20 ).should.deep.equal( 197 | fragment2.body ); 198 | }); 199 | }); 200 | 201 | describe( '#next()', function() { 202 | 203 | it( 'should return false by default', function() { 204 | 205 | var builder = new HandshakeBuilder(); 206 | 207 | builder.messageSeqToRead.should.equal( 0 ); 208 | builder.next().should.be.false; 209 | builder.messageSeqToRead.should.equal( 0 ); 210 | }); 211 | 212 | it( 'should return packet when a merged packet exists', function() { 213 | 214 | var original = createVerifyRequest(); 215 | 216 | var builder = new HandshakeBuilder(); 217 | builder.add( original ); 218 | 219 | var out = builder.next(); 220 | 221 | out.should.deep.equal( original ); 222 | builder.messageSeqToRead.should.equal( 1 ); 223 | }); 224 | 225 | it( 'should return false when we have incomplete handshake', function() { 226 | 227 | var original = createVerifyRequest(); 228 | 229 | var fragment1 = new DtlsHandshake({ 230 | msgType: original.msgType, 231 | length: original.body.length, 232 | messageSeq: 0, 233 | fragmentOffset: 0, 234 | body: original.body.slice( 0, 10 ) 235 | }); 236 | 237 | var fragment2 = new DtlsHandshake({ 238 | msgType: original.msgType, 239 | length: original.body.length, 240 | messageSeq: 0, 241 | fragmentOffset: 10, 242 | body: original.body.slice( 10 ) 243 | }); 244 | 245 | var builder = new HandshakeBuilder(); 246 | 247 | builder.next().should.be.false; 248 | 249 | builder.add( fragment1 ); 250 | 251 | builder.next().should.be.false; 252 | 253 | builder.add( fragment2 ); 254 | 255 | builder.next().should.deep.equal( original ); 256 | 257 | }); 258 | }); 259 | 260 | describe( '#createHandshakes()', function() { 261 | 262 | it( 'should wrap single handshake', function() { 263 | 264 | var verifyRequest = new packets.HelloVerifyRequest({ 265 | serverVersion: new DtlsProtocolVersion({ major: 1, minor: 2 }), 266 | cookie: crypto.pseudoRandomBytes( 30 ) 267 | }); 268 | 269 | var builder = new HandshakeBuilder(); 270 | 271 | var hs = builder.createHandshakes( verifyRequest ); 272 | 273 | hs.msgType.should.equal( verifyRequest.messageType ); 274 | hs.length.should.equal( verifyRequest.getBuffer().length ); 275 | hs.messageSeq.should.equal( 0 ); 276 | hs.fragmentOffset.should.equal( 0 ); 277 | hs.body.should.deep.equal( verifyRequest.getBuffer() ); 278 | }); 279 | 280 | it( 'should wrap multiple handshakes', function() { 281 | 282 | var helloDone = new packets.ServerHelloDone({}); 283 | var verifyRequest = new packets.HelloVerifyRequest({ 284 | serverVersion: new DtlsProtocolVersion({ major: 1, minor: 2 }), 285 | cookie: crypto.pseudoRandomBytes( 30 ) 286 | }); 287 | 288 | var builder = new HandshakeBuilder(); 289 | 290 | var hss = builder.createHandshakes([ helloDone, verifyRequest ]); 291 | 292 | hss[0].msgType.should.equal( helloDone.messageType ); 293 | hss[0].body.should.deep.equal( helloDone.getBuffer() ); 294 | hss[0].messageSeq.should.equal( 0 ); 295 | 296 | hss[1].msgType.should.equal( verifyRequest.messageType ); 297 | hss[1].body.should.deep.equal( verifyRequest.getBuffer() ); 298 | hss[1].messageSeq.should.equal( 1 ); 299 | }); 300 | 301 | }); 302 | 303 | describe( '#fragmentHandshakes()', function() { 304 | 305 | it( 'should fragment single handshake', function() { 306 | 307 | var cert = crypto.pseudoRandomBytes( 1024 ); 308 | var certificate = new packets.Certificate({ 309 | certificateList: [ cert, cert, cert ] 310 | }); 311 | 312 | var builder = new HandshakeBuilder(); 313 | builder.outgoingMessageSeq = 10; 314 | var hs = builder.createHandshakes( certificate ); 315 | 316 | var fragments = builder.fragmentHandshakes( hs ); 317 | 318 | fragments.should.have.length( 4 ); 319 | 320 | for( var i = 0; i < 4; i++ ) { 321 | fragments[i].msgType.should.equal( certificate.messageType ); 322 | fragments[i].messageSeq.should.equal( hs.messageSeq ); 323 | fragments[i].length.should.equal( hs.length ); 324 | } 325 | 326 | // Handshake has 12 byte header 327 | fragments[0].body.should.deep.equal( 328 | certificate.getBuffer().slice( 0, 1000 ) ); 329 | fragments[1].body.should.deep.equal( 330 | certificate.getBuffer().slice( 1000, 2000 ) ); 331 | fragments[2].body.should.deep.equal( 332 | certificate.getBuffer().slice( 2000, 3000 ) ); 333 | fragments[3].body.should.deep.equal( 334 | certificate.getBuffer().slice( 3000 ) ); 335 | 336 | fragments[0].fragmentOffset.should.equal( 0 ); 337 | fragments[1].fragmentOffset.should.equal( 1000 ); 338 | fragments[2].fragmentOffset.should.equal( 2000 ); 339 | fragments[3].fragmentOffset.should.equal( 3000 ); 340 | }); 341 | 342 | it( 'should fragment multiple handshakes', function() { 343 | 344 | var cert1 = crypto.pseudoRandomBytes( 1500 ); 345 | var certificate1 = new packets.Certificate({ 346 | certificateList: [ cert1 ] 347 | }); 348 | var cert2 = crypto.pseudoRandomBytes( 1500 ); 349 | var certificate2 = new packets.Certificate({ 350 | certificateList: [ cert2 ] 351 | }); 352 | 353 | var builder = new HandshakeBuilder(); 354 | builder.outgoingMessageSeq = 10; 355 | var hs = builder.createHandshakes([ certificate1, certificate2 ]); 356 | 357 | hs.should.have.length( 2 ); 358 | var fragments = builder.fragmentHandshakes( hs ); 359 | 360 | fragments.should.have.length( 4 ); 361 | 362 | var i; 363 | for( i = 0; i < 2; i++ ) { 364 | fragments[i].msgType.should.equal( certificate1.messageType ); 365 | fragments[i].messageSeq.should.equal( hs[0].messageSeq ); 366 | fragments[i].length.should.equal( hs[0].length ); 367 | } 368 | for( i = 2; i < 4; i++ ) { 369 | fragments[i].msgType.should.equal( certificate2.messageType ); 370 | fragments[i].messageSeq.should.equal( hs[1].messageSeq ); 371 | fragments[i].length.should.equal( hs[1].length ); 372 | } 373 | 374 | // Handshake has 12 byte header 375 | fragments[0].body.should.deep.equal( 376 | certificate1.getBuffer().slice( 0, 1000 ) ); 377 | fragments[1].body.should.deep.equal( 378 | certificate1.getBuffer().slice( 1000 ) ); 379 | fragments[2].body.should.deep.equal( 380 | certificate2.getBuffer().slice( 0, 1000 ) ); 381 | fragments[3].body.should.deep.equal( 382 | certificate2.getBuffer().slice( 1000 ) ); 383 | 384 | fragments[0].fragmentOffset.should.equal( 0 ); 385 | fragments[1].fragmentOffset.should.equal( 1000 ); 386 | fragments[2].fragmentOffset.should.equal( 0 ); 387 | fragments[3].fragmentOffset.should.equal( 1000 ); 388 | }); 389 | 390 | it( 'should handle buffers', function() { 391 | 392 | var cert = crypto.pseudoRandomBytes( 1024 ); 393 | var certificate = new packets.Certificate({ 394 | certificateList: [ cert ] 395 | }); 396 | 397 | var builder = new HandshakeBuilder(); 398 | builder.outgoingMessageSeq = 10; 399 | var hs = builder.createHandshakes( certificate ); 400 | 401 | var fragments = builder.fragmentHandshakes( hs.getBuffer() ); 402 | 403 | fragments.should.have.length( 2 ); 404 | 405 | for( var i = 0; i < 2; i++ ) { 406 | fragments[i].msgType.should.equal( certificate.messageType ); 407 | fragments[i].messageSeq.should.equal( hs.messageSeq ); 408 | fragments[i].length.should.equal( hs.length ); 409 | } 410 | 411 | // Handshake has 12 byte header 412 | fragments[0].body.should.deep.equal( 413 | certificate.getBuffer().slice( 0, 1000 ) ); 414 | fragments[1].body.should.deep.equal( 415 | certificate.getBuffer().slice( 1000 ) ); 416 | 417 | fragments[0].fragmentOffset.should.equal( 0 ); 418 | fragments[1].fragmentOffset.should.equal( 1000 ); 419 | }); 420 | }); 421 | }); 422 | 423 | var createVerifyRequest = function() { 424 | 425 | var verifyRequest = new DtlsHelloVerifyRequest({ 426 | serverVersion: new DtlsProtocolVersion({ major: 1, minor: 2 }), 427 | cookie: crypto.pseudoRandomBytes( 30 ) 428 | }); 429 | 430 | var buffer = verifyRequest.getBuffer(); 431 | 432 | var handshake = new DtlsHandshake({ 433 | msgType: verifyRequest.messageType, 434 | length: buffer.length, 435 | messageSeq: 0, 436 | fragmentOffset: 0, 437 | body: buffer 438 | }); 439 | 440 | return handshake; 441 | }; 442 | -------------------------------------------------------------------------------- /test/PacketSpec.js: -------------------------------------------------------------------------------- 1 | 2 | "use strict"; 3 | 4 | var should = require( 'chai' ).should(); 5 | var crypto = require( 'crypto' ); 6 | 7 | var Packet = require( '../packets/Packet' ); 8 | var PacketSpec = require( '../packets/PacketSpec' ); 9 | 10 | 11 | describe( 'PacketSpec', function() { 12 | 13 | var unsignedSpec = new PacketSpec([ 14 | { name: 'uint8_1', type: 'uint8' }, 15 | { name: 'uint8_2', type: 'uint8' }, 16 | { name: 'uint16_1', type: 'uint16' }, 17 | { name: 'uint16_2', type: 'uint16' }, 18 | { name: 'uint24_1', type: 'uint24' }, 19 | { name: 'uint24_2', type: 'uint24' }, 20 | { name: 'uint32_1', type: 'uint32' }, 21 | { name: 'uint32_2', type: 'uint32' }, 22 | ]); 23 | 24 | var signedSpec = new PacketSpec([ 25 | { name: 'int8_1', type: 'int8' }, 26 | { name: 'int8_2', type: 'int8' }, 27 | { name: 'int16_1', type: 'int16' }, 28 | { name: 'int16_2', type: 'int16' }, 29 | { name: 'int32_1', type: 'int32' }, 30 | { name: 'int32_2', type: 'int32' }, 31 | ]); 32 | 33 | var variableSpec = new PacketSpec([ 34 | { name: 'var8', type: 'var8' }, 35 | { name: 'var16', type: 'var16' }, 36 | { name: 'var24', type: 'var24' }, 37 | { name: 'var32', type: 'var32' } 38 | ]); 39 | 40 | var bytesSpec = new PacketSpec([ 41 | { name: 'bytes1', type: 'bytes', size: 8 }, 42 | { name: 'bytes2', type: 'bytes', size: 8 } 43 | ]); 44 | 45 | var Version = function( data ) { Packet.call( this, data ); }; 46 | Version.prototype = Object.create( Packet ); 47 | Version.prototype.spec = new PacketSpec([ 48 | { major: 'uint8' }, 49 | { minor: 'uint8' } 50 | ]); 51 | 52 | var VersionRange = function( data ) { Packet.call( this, data ); }; 53 | VersionRange.prototype = Object.create( Packet ); 54 | VersionRange.prototype.spec = new PacketSpec([ 55 | { min: Version }, 56 | { max: Version } 57 | ]); 58 | 59 | var advancedVariableSpec = new PacketSpec([ 60 | { name: 'var8uint16', type: 'var8', itemType: 'uint16' }, 61 | { name: 'var8version', type: 'var8', itemType: Version }, 62 | { name: 'var8var8Version', type: 'var8', itemType: { type: 'var8', itemType: Version } } 63 | ]); 64 | 65 | var customSpec = new PacketSpec([ 66 | { 67 | name: 'custom', 68 | read: function( reader ) { 69 | var arr = []; 70 | var value = reader.readUInt8(); 71 | while( value !== 0 ) { 72 | arr.push( value ); 73 | value = reader.readUInt8(); 74 | } 75 | return arr; 76 | }, 77 | write: function( builder, value ) { 78 | for( var i = 0; i < value.length; i++ ) { 79 | builder.writeUInt8( value[i] ); 80 | } 81 | builder.writeUInt8( 0 ); 82 | } 83 | }]); 84 | 85 | describe( '#ctor()', function() { 86 | it( 'should handle normal specs', function() { 87 | 88 | var stdSpec = new PacketSpec([ 89 | { name: 'works', type: 'uint8' } 90 | ]); 91 | 92 | stdSpec.spec.length.should.equal( 1 ); 93 | stdSpec.spec[0].name.should.equal( 'works' ); 94 | stdSpec.spec[0].type.should.equal( 'uint8' ); 95 | }); 96 | 97 | it( 'should handle shorthand specs', function() { 98 | 99 | var stdSpec = new PacketSpec([ 100 | { works: 'uint8' } 101 | ]); 102 | 103 | stdSpec.spec.length.should.equal( 1 ); 104 | stdSpec.spec[0].name.should.equal( 'works' ); 105 | stdSpec.spec[0].type.should.equal( 'uint8' ); 106 | }); 107 | 108 | it( 'should handle nested specs', function() { 109 | 110 | var VersionRange = function() {}; 111 | var spec = new PacketSpec([ 112 | { max: Version }, 113 | { min: Version } 114 | ]); 115 | }); 116 | 117 | it( 'should validate specs', function() { 118 | 119 | (function() { 120 | new PacketSpec([{ fail: 'badType' }]); 121 | }).should.throw( Error ); 122 | 123 | (function() { 124 | new PacketSpec([{ name: 'fail', read: function() {} }]); 125 | }).should.throw( Error ); 126 | 127 | (function() { 128 | new PacketSpec([{ name: 'fail', write: function() {} }]); 129 | }).should.throw( Error ); 130 | }); 131 | }); 132 | 133 | describe( '#read()', function() { 134 | 135 | it( 'should read unsigned numbers correctly', function() { 136 | 137 | var buffer = new Buffer([ 138 | 0x0f, 139 | 0xf0, 140 | 0x0f, 0x0f, 141 | 0xf0, 0xf0, 142 | 0x11, 0x22, 0x33, 143 | 0xff, 0xee, 0xdd, 144 | 0x11, 0x22, 0x33, 0x44, 145 | 0xff, 0x77, 0x66, 0x55, 146 | ]); 147 | 148 | var obj = {}; 149 | unsignedSpec.read( buffer, obj ); 150 | 151 | obj.uint8_1.should.equal( 0x0f ); 152 | obj.uint8_2.should.equal( 0xf0 ); 153 | obj.uint16_1.should.equal( 0x0f0f ); 154 | obj.uint16_2.should.equal( 0xf0f0 ); 155 | obj.uint24_1.should.equal( 0x112233 ); 156 | obj.uint24_2.should.equal( 0xffeedd ); 157 | obj.uint32_1.should.equal( 0x11223344 ); 158 | obj.uint32_2.should.equal( 0xff776655 ); 159 | }); 160 | 161 | it( 'should read signed numbers correctly', function() { 162 | var buffer = new Buffer([ 163 | 0x01, 164 | 0xff, 165 | 0x10, 0x01, 166 | 0xff, 0xff, 167 | 0x10, 0x00, 0x00, 0x01, 168 | 0xff, 0xff, 0xff, 0xff 169 | ]); 170 | 171 | var obj = {}; 172 | signedSpec.read( buffer, obj ); 173 | 174 | obj.int8_1.should.equal( 0x01 ); 175 | obj.int8_2.should.equal( -1 ); 176 | obj.int16_1.should.equal( 0x1001 ); 177 | obj.int16_2.should.equal( -1 ); 178 | obj.int32_1.should.equal( 0x10000001 ); 179 | obj.int32_2.should.equal( -1 ); 180 | }); 181 | 182 | it( 'should read variable length fields', function() { 183 | 184 | var buffer = new Buffer([ 185 | 0x03, 0x00, 0x01, 0x02, 186 | 0x00, 0x05, 0x00, 0x01, 0x02, 0x03, 0x04, 187 | 0x00, 0x00, 0x02, 0x00, 0x01, 188 | 0x00, 0x00, 0x00, 0x04, 0x00, 0x01, 0x02, 0x03 189 | ]); 190 | 191 | var obj = {}; 192 | variableSpec.read( buffer, obj ); 193 | 194 | obj.var8.should.deep.equal( new Buffer([ 0x00, 0x01, 0x02 ]) ); 195 | obj.var16.should.deep.equal( new Buffer([ 0x00, 0x01, 0x02, 0x03, 0x04 ]) ); 196 | obj.var24.should.deep.equal( new Buffer([ 0x00, 0x01 ]) ); 197 | obj.var32.should.deep.equal( new Buffer([ 0x00, 0x01, 0x02, 0x03 ]) ); 198 | }); 199 | 200 | it( 'should read byte array', function() { 201 | 202 | var bytes1 = new Buffer([ 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08 ]); 203 | var bytes2 = new Buffer([ 0xff, 0x00, 0xff, 0x00, 0xff, 0x00, 0xff, 0x00 ]); 204 | 205 | var buffer = Buffer.concat([ bytes1, bytes2 ]); 206 | var obj = {}; 207 | bytesSpec.read( buffer, obj ); 208 | 209 | obj.bytes1.should.deep.equal( bytes1 ); 210 | obj.bytes2.should.deep.equal( bytes2 ); 211 | }); 212 | 213 | it( 'should read custom data', function() { 214 | 215 | var buffer = new Buffer([ 0x01, 0x03, 0x05, 0x00 ]); 216 | 217 | var obj = {}; 218 | customSpec.read( buffer, obj ); 219 | 220 | obj.custom.should.deep.equal([ 0x01, 0x03, 0x05 ]); 221 | }); 222 | 223 | it( 'should read nested data', function() { 224 | 225 | var buffer = new Buffer([ 0x01, 0x02, 0x03, 0x04 ]); 226 | 227 | var obj = new VersionRange(); 228 | obj.spec.read( buffer, obj ); 229 | 230 | obj.min.major.should.equal( 0x01 ); 231 | obj.min.minor.should.equal( 0x02 ); 232 | obj.max.major.should.equal( 0x03 ); 233 | obj.max.minor.should.equal( 0x04 ); 234 | }); 235 | 236 | it( 'should read advanced variable length item lists', function() { 237 | 238 | var buffer = new Buffer([ 239 | 0x06, 0x01, 0x02, 0xff, 0x00, 0x00, 0xff, 240 | 0x06, 0x10, 0x20, 0x30, 0x40, 0x50, 0x60, 241 | 0x08, 0x04, 0x10, 0x20, 0x30, 0x40, 0x02, 0x10, 0x20 242 | ]); 243 | 244 | var obj = {}; 245 | advancedVariableSpec.read( buffer, obj ); 246 | 247 | obj.var8uint16.should.deep.equal( [ 0x0102, 0xff00, 0xff ] ); 248 | obj.var8version.should.deep.equal( [ 249 | new Version({ major: 0x10, minor: 0x20 }), 250 | new Version({ major: 0x30, minor: 0x40 }), 251 | new Version({ major: 0x50, minor: 0x60 }) 252 | ]); 253 | obj.var8var8Version.should.deep.equal( [ 254 | [ 255 | new Version({ major: 0x10, minor: 0x20 }), 256 | new Version({ major: 0x30, minor: 0x40 }) 257 | ], [ 258 | new Version({ major: 0x10, minor: 0x20 }) 259 | ] 260 | ]); 261 | 262 | }); 263 | }); 264 | 265 | describe( '#write()', function() { 266 | 267 | it( 'should write unsigned numbers correctly', function() { 268 | 269 | var obj = { 270 | uint8_1: 0x10, 271 | uint8_2: 0xf0, 272 | uint16_1: 0x0110, 273 | uint16_2: 0xf00f, 274 | uint24_1: 0x080808, 275 | uint24_2: 0xabbacd, 276 | uint32_1: 0x0badbeef, 277 | uint32_2: 0xbadbeeff, 278 | }; 279 | 280 | var buffer = unsignedSpec.write( obj ); 281 | 282 | var newObj = {}; 283 | unsignedSpec.read( buffer, newObj ); 284 | 285 | newObj.uint8_1.should.equal( obj.uint8_1 ); 286 | newObj.uint8_2.should.equal( obj.uint8_2 ); 287 | newObj.uint16_1.should.equal( obj.uint16_1 ); 288 | newObj.uint16_2.should.equal( obj.uint16_2 ); 289 | newObj.uint24_1.should.equal( obj.uint24_1 ); 290 | newObj.uint24_2.should.equal( obj.uint24_2 ); 291 | newObj.uint32_1.should.equal( obj.uint32_1 ); 292 | newObj.uint32_2.should.equal( obj.uint32_2 ); 293 | 294 | }); 295 | 296 | it( 'should write signed numbers correctly', function() { 297 | 298 | var obj = { 299 | int8_1: 0x01, 300 | int8_2: -1, 301 | int16_1: 0x1001, 302 | int16_2: -1, 303 | int32_1: 0x10000001, 304 | int32_2: -1 305 | }; 306 | 307 | var buffer = signedSpec.write( obj ); 308 | 309 | var newObj = {}; 310 | signedSpec.read( buffer, newObj ); 311 | 312 | newObj.int8_1.should.equal( obj.int8_1 ); 313 | newObj.int8_2.should.equal( obj.int8_2 ); 314 | newObj.int16_1.should.equal( obj.int16_1 ); 315 | newObj.int16_2.should.equal( obj.int16_2 ); 316 | newObj.int32_1.should.equal( obj.int32_1 ); 317 | newObj.int32_2.should.equal( obj.int32_2 ); 318 | 319 | }); 320 | 321 | it( 'should write variable length fields', function() { 322 | 323 | var obj = { 324 | var8: crypto.pseudoRandomBytes( 0x10 ), 325 | var16: crypto.pseudoRandomBytes( 0x100 ), 326 | var24: crypto.pseudoRandomBytes( 0x100 ), 327 | var32: crypto.pseudoRandomBytes( 0x100 ) 328 | }; 329 | 330 | var buffer = variableSpec.write( obj ); 331 | 332 | var newObj = {}; 333 | variableSpec.read( buffer, newObj ); 334 | 335 | newObj.var8.should.deep.equal( obj.var8 ); 336 | newObj.var16.should.deep.equal( obj.var16 ); 337 | newObj.var24.should.deep.equal( obj.var24 ); 338 | newObj.var32.should.deep.equal( obj.var32 ); 339 | }); 340 | 341 | it( 'should write byte array', function() { 342 | 343 | var bytes1 = new Buffer([ 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08 ]); 344 | var bytes2 = new Buffer([ 0xff, 0x00, 0xff, 0x00, 0xff, 0x00, 0xff, 0x00 ]); 345 | 346 | var obj = { 347 | bytes1: bytes1, 348 | bytes2: bytes2 349 | }; 350 | var buffer = bytesSpec.write( obj ); 351 | 352 | buffer.should.deep.equal( Buffer.concat([ bytes1, bytes2 ]) ); 353 | }); 354 | 355 | it( 'should write custom data', function() { 356 | 357 | 358 | var obj = { custom: [ 0x01, 0x03, 0x05 ] }; 359 | var buffer = customSpec.write( obj ); 360 | 361 | buffer.should.deep.equal( new Buffer([ 0x01, 0x03, 0x05, 0x00 ]) ); 362 | }); 363 | 364 | it( 'should write nested data', function() { 365 | 366 | var range = new VersionRange({ 367 | min: new Version({ major: 0xff, minor: 0x88 }), 368 | max: new Version({ major: 0x00, minor: 0xff }) 369 | }); 370 | 371 | var buffer = range.spec.write( range ); 372 | 373 | buffer.should.deep.equal( new Buffer([ 0xff, 0x88, 0x00, 0xff ]) ); 374 | 375 | }); 376 | 377 | it( 'should write advanced variable data', function() { 378 | 379 | var obj = { 380 | var8uint16: [ 0x1001, 0xf00f ], 381 | var8version: [ 382 | new Version({ major: 0x01, minor: 0x10 }), 383 | new Version({ major: 0x10, minor: 0x01 }) 384 | ], 385 | var8var8Version: [ 386 | [ new Version({ major: 0x01, minor: 0x02 }) ], 387 | [ new Version({ major: 0x01, minor: 0x02 }), 388 | new Version({ major: 0x01, minor: 0x02 }) ], 389 | [ new Version({ major: 0x03, minor: 0x04 }) ] 390 | ] 391 | }; 392 | 393 | var buffer = advancedVariableSpec.write( obj ); 394 | 395 | buffer.should.deep.equal( new Buffer([ 396 | 0x04, 0x10, 0x01, 0xf0, 0x0f, 397 | 0x04, 0x01, 0x10, 0x10, 0x01, 398 | 0x0b, 399 | 0x02, 0x01, 0x02, 400 | 0x04, 0x01, 0x02, 0x01, 0x02, 401 | 0x02, 0x03, 0x04 ]) ); 402 | }); 403 | }); 404 | }); 405 | 406 | -------------------------------------------------------------------------------- /test/Packets.js: -------------------------------------------------------------------------------- 1 | 2 | "use strict"; 3 | 4 | var should = require( 'chai' ).should(); 5 | 6 | var DtlsHandshake = require( '../packets/DtlsHandshake' ); 7 | 8 | describe( 'DtlsHandshake', function() { 9 | 10 | it( 'should be readable', function() { 11 | 12 | var buffer = new Buffer([ 13 | 0x01, 14 | 0x02, 0x03, 0x04, 15 | 0x05, 0x06, 16 | 0x07, 0x08, 0x09, 17 | 0x00, 0x00, 0x05, 0x10, 0x11, 0x12, 0x13, 0x14 18 | ]); 19 | 20 | var dtlsHandshake = new DtlsHandshake( buffer ); 21 | 22 | dtlsHandshake.msgType.should.equal( 0x01 ); 23 | dtlsHandshake.length.should.equal( 0x020304 ); 24 | dtlsHandshake.messageSeq.should.equal( 0x0506 ); 25 | dtlsHandshake.fragmentOffset.should.equal( 0x070809 ); 26 | dtlsHandshake.body.should.deep.equal( new Buffer([ 0x10, 0x11, 0x12, 0x13, 0x14 ]) ); 27 | }); 28 | }); 29 | -------------------------------------------------------------------------------- /test/SecurityParameterContainer.js: -------------------------------------------------------------------------------- 1 | 2 | "use strict"; 3 | 4 | var should = require( 'chai' ).should(); 5 | var crypto = require( 'crypto' ); 6 | var packets = require( '../packets' ); 7 | 8 | var SecurityParameterContainer = require( '../SecurityParameterContainer' ); 9 | 10 | describe( 'SecurityParameterContainer', function() { 11 | 12 | describe( '#ctor()', function() { 13 | it( 'should init correctly', function() { 14 | 15 | var spc = new SecurityParameterContainer(); 16 | 17 | should.not.exist( spc.pending ); 18 | spc.current.should.equal( 0 ); 19 | should.exist( spc.parameters[ 0 ] ); 20 | spc.parameters[0].should.equal( spc.first ); 21 | }); 22 | }); 23 | 24 | describe( '#initNew()', function() { 25 | 26 | it( 'should create new pending parameter', function() { 27 | 28 | var spc = new SecurityParameterContainer(); 29 | 30 | should.not.exist( spc.pending ); 31 | 32 | var version = new packets.ProtocolVersion( ~1, ~2 ); 33 | var pending = spc.initNew( version ); 34 | 35 | should.exist( spc.pending ); 36 | pending.should.equal( spc.pending ); 37 | pending.version.should.equal( version ); 38 | 39 | spc.current.should.equal( 0 ); 40 | should.exist( spc.parameters[ spc.pending.epoch ] ); 41 | }); 42 | }); 43 | 44 | describe( '#getcurrent()', function() { 45 | 46 | it( 'should get the parameters for first epoch', function() { 47 | 48 | var spc = new SecurityParameterContainer(); 49 | 50 | var current = spc.getCurrent( 0 ); 51 | 52 | current.should.equal( spc.first ); 53 | }); 54 | 55 | it( 'should get the parameters for random epoch', function() { 56 | 57 | var spc = new SecurityParameterContainer(); 58 | 59 | var obj = { params: 1 }; 60 | spc.parameters[ 123 ] = obj; 61 | 62 | var current = spc.getCurrent( 123 ); 63 | 64 | current.should.equal( obj ); 65 | }); 66 | }); 67 | 68 | describe( '#get()', function() { 69 | 70 | it( 'should get the parameters for first packet', function() { 71 | 72 | var spc = new SecurityParameterContainer(); 73 | 74 | var current = spc.get({ epoch: 0 }); 75 | 76 | current.should.equal( spc.first ); 77 | }); 78 | 79 | it( 'should get the parameters for random packet', function() { 80 | 81 | var spc = new SecurityParameterContainer(); 82 | 83 | var obj = { params: 1 }; 84 | spc.parameters[ 123 ] = obj; 85 | 86 | var current = spc.get({ epoch: 123 }); 87 | 88 | current.should.equal( obj ); 89 | }); 90 | }); 91 | 92 | describe( '#changeCipher()', function() { 93 | 94 | it( 'should change the current parameters', function() { 95 | 96 | var spc = new SecurityParameterContainer(); 97 | var version = new packets.ProtocolVersion( ~1, ~2 ); 98 | var pending = spc.initNew( version ); 99 | 100 | pending.clientRandom = new Buffer( 10 ); 101 | pending.serverRandom = new Buffer( 10 ); 102 | pending.masterKey = new Buffer( 10 ); 103 | 104 | pending.should.not.equal( spc.first ); 105 | spc.parameters[ spc.current ].should.equal( spc.first ); 106 | 107 | spc.changeCipher( 0 ); 108 | 109 | spc.parameters[ spc.current ].should.equal( spc.pending ); 110 | spc.parameters[ spc.current ].should.not.equal( spc.first ); 111 | }); 112 | 113 | it( 'should refuse to skip epochs', function() { 114 | 115 | var spc = new SecurityParameterContainer(); 116 | spc.initNew( new packets.ProtocolVersion( ~1, ~2 ) ); 117 | 118 | (function() { 119 | spc.changeCipher( 10 ); 120 | }).should.not.throw( Error ); 121 | 122 | spc.current.should.equal( 0 ); 123 | }); 124 | }); 125 | }); 126 | -------------------------------------------------------------------------------- /test/SecurityParameters.js: -------------------------------------------------------------------------------- 1 | 2 | "use strict"; 3 | 4 | var should = require( 'chai' ).should(); 5 | var crypto = require( 'crypto' ); 6 | var log = require( 'logg' ).getLogger( 'dtls' ); 7 | log.setLogLevel( log.SEVERE ); 8 | 9 | var packets = require( '../packets' ); 10 | var dtls = require( '../dtls' ); 11 | var CipherInfo = require( '../CipherInfo' ); 12 | var prf = require( '../prf' ); 13 | 14 | var SecurityParameters = require( '../SecurityParameters' ); 15 | 16 | describe( 'SecurityParameters', function() { 17 | 18 | describe( '#ctor()', function() { 19 | 20 | it( 'should initialize the object', function() { 21 | 22 | var version = new packets.ProtocolVersion( ~1, ~2 ); 23 | var sp = new SecurityParameters( 1, version ); 24 | 25 | sp.epoch.should.equal( 1 ); 26 | sp.version.should.equal( version ); 27 | sp.entity.should.equal( dtls.ConnectionEnd.server ); 28 | 29 | sp.bulkCipherAlgorithm.should.equal( dtls.BulkCipherAlgorithm.none ); 30 | sp.cipherType.should.equal( dtls.CipherType.block ); 31 | sp.encKeyLength.should.equal( 0 ); 32 | sp.blockLength.should.equal( 0 ); 33 | sp.fixedIvLength.should.equal( 0 ); 34 | sp.recordIvLength.should.equal( 0 ); 35 | 36 | sp.macAlgorithm.should.equal( dtls.MACAlgorithm.none ); 37 | sp.macLength.should.equal( 0 ); 38 | sp.macKeyLength.should.equal( 0 ); 39 | 40 | sp.compressionAlgorithm.should.equal( dtls.CompressionMethod.none ); 41 | should.not.exist( sp.masterKey ); 42 | should.not.exist( sp.clientRandom ); 43 | should.not.exist( sp.serverRandom ); 44 | 45 | sp.handshakeDigest.should.have.length( 0 ); 46 | sp.sendSequence.current.should.deep.equal( new Buffer([ 0, 0, 0, 0, 0, 0 ]) ); 47 | }); 48 | }); 49 | 50 | describe( 'DTLS 1.2', function() { 51 | var version = new packets.ProtocolVersion( ~1, ~2 ); 52 | 53 | describe( '#setFrom()', function() { 54 | 55 | it( 'should set parameters from cipher suite', function() { 56 | 57 | var sp = new SecurityParameters( 0, version ); 58 | 59 | var suite = CipherInfo.TLS_RSA_WITH_AES_128_CBC_SHA; 60 | sp.setFrom( suite ); 61 | 62 | sp.prfAlgorithm.should.equal( suite.prf ); 63 | 64 | sp.bulkCipherAlgorithm.should.equal( suite.cipher.algorithm ); 65 | sp.cipherType.should.equal( suite.cipher.type ); 66 | sp.encKeyLength.should.equal( suite.cipher.keyMaterial ); 67 | sp.blockLength.should.equal( suite.cipher.blockSize ); 68 | sp.fixedIvLength.should.equal( 0 ); 69 | sp.recordIvLength.should.equal( suite.cipher.ivSize ); 70 | 71 | sp.macAlgorithm.should.equal( suite.mac.algorithm ); 72 | sp.macLength.should.equal( suite.mac.length ); 73 | sp.macKeyLength.should.equal( suite.mac.keyLength ); 74 | }); 75 | }); 76 | 77 | describe( '#calculateMasterKey()', function() { 78 | it( 'should calcualte master key correctly', function() { 79 | 80 | var pre = new Buffer([ 0x33, 0x42, 0xea, 0xb5, 0x5e ]); 81 | var sr = new Buffer([ 0xbf, 0x98, 0xdc, 0x2f, 0x32 ]); 82 | var cr = new Buffer([ 0x34, 0x14, 0x0b, 0x40, 0xaf ]); 83 | 84 | var sp = new SecurityParameters( 0, version ); 85 | sp.serverRandom = sr; 86 | sp.clientRandom = cr; 87 | 88 | sp.calculateMasterKey( pre ); 89 | 90 | var expected = new Buffer( 91 | '398e0dea84b8fae9aea65d09f538f22a' + 92 | '5e1b7eebce276f6e9a97ca6bb8934577' + 93 | 'f57c8b15b95daf8571ee19aeaa0550ab', 94 | 'hex' ); 95 | sp.masterKey.should.deep.equal( expected ); 96 | }); 97 | }); 98 | 99 | describe( '#init()', function() { 100 | 101 | it( 'should calculate key material correctly', function() { 102 | 103 | var b = new Buffer([ 0x5f, 0x1f, 0xd2, 0x29, 0x6b ]); 104 | var sr = new Buffer([ 0x02, 0x86, 0xea, 0x29, 0x91 ]); 105 | var cr = new Buffer([ 0x33, 0x55, 0x4d, 0x81, 0x54 ]); 106 | 107 | var expected = { 108 | cwmk: new Buffer( 'c83a7b69c782891a61ddc9306f35bc37a25f69db', 'hex' ), 109 | swmk: new Buffer( '4e7321133d2a6af97851feebb97f373d4098169c', 'hex' ), 110 | cwk: new Buffer( '373f963f4a2fbc13ffa22b256c46d36a', 'hex' ), 111 | swk: new Buffer( '41585768b95aa0fa9a18be07be5f1d3c', 'hex' ), 112 | cwi: new Buffer( 'c9babf9590a2ff90ad79c63f4d4ae2df', 'hex' ), 113 | swi: new Buffer( '6ac49161350293e99e67fa7833e32f2b', 'hex' ), 114 | }; 115 | 116 | var sp = new SecurityParameters( 0, version ); 117 | sp.setFrom( CipherInfo.TLS_RSA_WITH_AES_128_CBC_SHA ); 118 | sp.masterKey = b; 119 | sp.serverRandom = sr; 120 | sp.clientRandom = cr; 121 | 122 | sp.init(); 123 | 124 | sp.clientWriteMacKey.should.deep.equal( expected.cwmk ); 125 | sp.serverWriteMacKey.should.deep.equal( expected.swmk ); 126 | sp.clientWriteKey.should.deep.equal( expected.cwk ); 127 | sp.serverWriteKey.should.deep.equal( expected.swk ); 128 | sp.clientWriteIv.should.deep.equal( expected.cwi ); 129 | sp.serverWriteIv.should.deep.equal( expected.swi ); 130 | }); 131 | }); 132 | }); 133 | 134 | describe( 'AES_128_CBC cipher', function() { 135 | var version = new packets.ProtocolVersion( ~1, ~2 ); 136 | 137 | describe( '#getCipher()', function() { 138 | 139 | it( 'should return working server-write aes-128-cbc cipher', function() { 140 | 141 | var sp = new SecurityParameters( 1, version ); 142 | sp.isServer = true; 143 | sp.clientWriteKey = new Buffer( '373f963f4a2fbc13ffa22b256c46d36a', 'hex' ); 144 | sp.serverWriteKey = new Buffer( '41585768b95aa0fa9a18be07be5f1d3c', 'hex' ); 145 | 146 | var iv = new Buffer( '75b16855266f79a050903e2fba5cfd6f', 'hex' ); 147 | var data = new Buffer( 148 | '48edcabd93af9026843f4326be93c81f' + 149 | '0cb8556a2e56bc25cc9698f5ad19acad' + 150 | 'd1a47ddedc875100ec73b2094d486a38' + 151 | '2651894e05695abdc42214170de48f09', 'hex' ); 152 | 153 | var cipher = sp.getCipher( iv ); 154 | 155 | var encrypted = Buffer.concat([ 156 | cipher.update( data ), 157 | cipher.final() 158 | ]); 159 | 160 | var expected = new Buffer( 161 | '477c287763301575026ef7b399e8d23c' + 162 | '7440fbf02cb0b8da6078fc15d257047d' + 163 | '61520998f5b93a462fb2d8c7fb88ca1e' + 164 | '4986558eedf87389dd22dc0cfd938d30' + 165 | 'be4a7ad5486fa08ec5e9ff30ba4507d5', 'hex' ); 166 | 167 | encrypted.should.deep.equal( expected ); 168 | }); 169 | 170 | it( 'should return working client-write aes-128-cbc cipher', function() { 171 | 172 | var sp = new SecurityParameters( 1, version ); 173 | sp.isServer = false; 174 | sp.clientWriteKey = new Buffer( '373f963f4a2fbc13ffa22b256c46d36a', 'hex' ); 175 | sp.serverWriteKey = new Buffer( '41585768b95aa0fa9a18be07be5f1d3c', 'hex' ); 176 | 177 | var iv = new Buffer( '75b16855266f79a050903e2fba5cfd6f', 'hex' ); 178 | var data = new Buffer( 179 | '48edcabd93af9026843f4326be93c81f' + 180 | '0cb8556a2e56bc25cc9698f5ad19acad' + 181 | 'd1a47ddedc875100ec73b2094d486a38' + 182 | '2651894e05695abdc42214170de48f09', 'hex' ); 183 | 184 | var cipher = sp.getCipher( iv ); 185 | 186 | var encrypted = Buffer.concat([ 187 | cipher.update( data ), 188 | cipher.final() 189 | ]); 190 | 191 | var expected = new Buffer( 192 | '3ba3ad25235f5b5baa5467f556e71d96' + 193 | '7223d0484149fa70e7e4c6ff9a19f647' + 194 | 'b2a10a1179e73240e89b0a959869c200' + 195 | '370137001b14b8378d06f954e18ff6dd' + 196 | 'e18ec5cce8db90a4b8d39c70d041c4a2', 'hex' ); 197 | 198 | encrypted.should.deep.equal( expected ); 199 | }); 200 | }); 201 | 202 | describe( '#getDecipher()', function() { 203 | 204 | it( 'should return working client-read aes-128-cbc decipher', function() { 205 | 206 | var sp = new SecurityParameters( 1, version ); 207 | sp.isServer = false; 208 | sp.clientWriteKey = new Buffer( '373f963f4a2fbc13ffa22b256c46d36a', 'hex' ); 209 | sp.serverWriteKey = new Buffer( '41585768b95aa0fa9a18be07be5f1d3c', 'hex' ); 210 | 211 | var iv = new Buffer( '75b16855266f79a050903e2fba5cfd6f', 'hex' ); 212 | var data = new Buffer( 213 | '477c287763301575026ef7b399e8d23c' + 214 | '7440fbf02cb0b8da6078fc15d257047d' + 215 | '61520998f5b93a462fb2d8c7fb88ca1e' + 216 | '4986558eedf87389dd22dc0cfd938d30' + 217 | 'be4a7ad5486fa08ec5e9ff30ba4507d5', 'hex' ); 218 | 219 | var decipher = sp.getDecipher( iv ); 220 | 221 | var decrypted = Buffer.concat([ 222 | decipher.update( data ), 223 | decipher.final() 224 | ]); 225 | 226 | var expected = new Buffer( 227 | '48edcabd93af9026843f4326be93c81f' + 228 | '0cb8556a2e56bc25cc9698f5ad19acad' + 229 | 'd1a47ddedc875100ec73b2094d486a38' + 230 | '2651894e05695abdc42214170de48f09', 'hex' ); 231 | 232 | decrypted.should.deep.equal( expected ); 233 | }); 234 | 235 | it( 'should return working server-read aes-128-cbc decipher', function() { 236 | 237 | var sp = new SecurityParameters( 1, version ); 238 | sp.isServer = true; 239 | sp.clientWriteKey = new Buffer( '373f963f4a2fbc13ffa22b256c46d36a', 'hex' ); 240 | sp.serverWriteKey = new Buffer( '41585768b95aa0fa9a18be07be5f1d3c', 'hex' ); 241 | 242 | var iv = new Buffer( '75b16855266f79a050903e2fba5cfd6f', 'hex' ); 243 | var data = new Buffer( 244 | '3ba3ad25235f5b5baa5467f556e71d96' + 245 | '7223d0484149fa70e7e4c6ff9a19f647' + 246 | 'b2a10a1179e73240e89b0a959869c200' + 247 | '370137001b14b8378d06f954e18ff6dd' + 248 | 'e18ec5cce8db90a4b8d39c70d041c4a2', 'hex' ); 249 | 250 | var decipher = sp.getDecipher( iv ); 251 | 252 | var decrypted = Buffer.concat([ 253 | decipher.update( data ), 254 | decipher.final() 255 | ]); 256 | 257 | var expected = new Buffer( 258 | '48edcabd93af9026843f4326be93c81f' + 259 | '0cb8556a2e56bc25cc9698f5ad19acad' + 260 | 'd1a47ddedc875100ec73b2094d486a38' + 261 | '2651894e05695abdc42214170de48f09', 'hex' ); 262 | 263 | decrypted.should.deep.equal( expected ); 264 | }); 265 | }); 266 | 267 | describe( '#calculateIncomingMac()', function() { 268 | 269 | it( 'should calculate correct client-outgoing sha1 mac', function() { 270 | 271 | var sp = new SecurityParameters( 1, version ); 272 | sp.isServer = false; 273 | sp.clientWriteMacKey = new Buffer( 'c83a7b69c782891a61ddc9306f35bc37a25f69db', 'hex' ); 274 | sp.serverWriteMacKey = new Buffer( '4e7321133d2a6af97851feebb97f373d4098169c', 'hex' ); 275 | 276 | var data = new Buffer( 277 | '7804afb92904ad81ee5b046427c1a4dc' + 278 | '0f8c76031d25d29db259ebf86b7ad34e' + 279 | '227159354963c64e8c76c8500ab755e4' + 280 | '72e72aaf6fede1657bd638ecb01ca56e' + 281 | 'ce72cd1e03b57376a1732aba242fa0b6' + 282 | '2a94238f3107201b424b3d44cca9d3f5' + 283 | 'e43cdf7174f4d45ab724369b7c9f18c6' + 284 | '355295e4d1b7b4ccb700733cc3bc4958', 'hex' ); 285 | 286 | var actual = sp.calculateIncomingMac( data ); 287 | var expected = new Buffer( 288 | '827451fec1fffae0c0f4955f1da9fe53f1b42c30', 'hex' ); 289 | 290 | actual.should.deep.equal( expected ); 291 | }); 292 | 293 | it( 'should calculate correct server-outgoing sha1 mac', function() { 294 | 295 | var sp = new SecurityParameters( 1, version ); 296 | sp.isServer = true; 297 | sp.clientWriteMacKey = new Buffer( 'c83a7b69c782891a61ddc9306f35bc37a25f69db', 'hex' ); 298 | sp.serverWriteMacKey = new Buffer( '4e7321133d2a6af97851feebb97f373d4098169c', 'hex' ); 299 | 300 | var data = new Buffer( 301 | '7804afb92904ad81ee5b046427c1a4dc' + 302 | '0f8c76031d25d29db259ebf86b7ad34e' + 303 | '227159354963c64e8c76c8500ab755e4' + 304 | '72e72aaf6fede1657bd638ecb01ca56e' + 305 | 'ce72cd1e03b57376a1732aba242fa0b6' + 306 | '2a94238f3107201b424b3d44cca9d3f5' + 307 | 'e43cdf7174f4d45ab724369b7c9f18c6' + 308 | '355295e4d1b7b4ccb700733cc3bc4958', 'hex' ); 309 | 310 | var actual = sp.calculateIncomingMac( data ); 311 | var expected = new Buffer( 312 | 'ce9f001479227dbc11d60757bf94d113e9e7be9a', 'hex' ); 313 | 314 | actual.should.deep.equal( expected ); 315 | }); 316 | }); 317 | 318 | describe( '#calculateOutgoingMac()', function() { 319 | 320 | it( 'should calculate correct client-outgoing sha1 mac', function() { 321 | 322 | var sp = new SecurityParameters( 1, version ); 323 | sp.isServer = false; 324 | sp.clientWriteMacKey = new Buffer( 'c83a7b69c782891a61ddc9306f35bc37a25f69db', 'hex' ); 325 | sp.serverWriteMacKey = new Buffer( '4e7321133d2a6af97851feebb97f373d4098169c', 'hex' ); 326 | 327 | var data = [ 328 | new Buffer( '7804afb92904ad81ee5b046427c1a4dc', 'hex' ), 329 | new Buffer( '0f8c76031d25d29db259ebf86b7ad34e', 'hex' ), 330 | new Buffer( '227159354963c64e8c76c8500ab755e4', 'hex' ), 331 | new Buffer( '72e72aaf6fede1657bd638ecb01ca56e', 'hex' ), 332 | new Buffer( 'ce72cd1e03b57376a1732aba242fa0b6', 'hex' ), 333 | new Buffer( '2a94238f3107201b424b3d44cca9d3f5', 'hex' ), 334 | new Buffer( 'e43cdf7174f4d45ab724369b7c9f18c6', 'hex' ), 335 | new Buffer( '355295e4d1b7b4ccb700733cc3bc4958', 'hex' ), 336 | ]; 337 | 338 | var actual = sp.calculateOutgoingMac( data ); 339 | var expected = new Buffer( 340 | 'ce9f001479227dbc11d60757bf94d113e9e7be9a', 'hex' ); 341 | 342 | actual.should.deep.equal( expected ); 343 | }); 344 | 345 | it( 'should calculate correct server-outgoing sha1 mac', function() { 346 | 347 | var sp = new SecurityParameters( 1, version ); 348 | sp.isServer = true; 349 | sp.clientWriteMacKey = new Buffer( 'c83a7b69c782891a61ddc9306f35bc37a25f69db', 'hex' ); 350 | sp.serverWriteMacKey = new Buffer( '4e7321133d2a6af97851feebb97f373d4098169c', 'hex' ); 351 | 352 | var data = [ 353 | new Buffer( '7804afb92904ad81ee5b046427c1a4dc', 'hex' ), 354 | new Buffer( '0f8c76031d25d29db259ebf86b7ad34e', 'hex' ), 355 | new Buffer( '227159354963c64e8c76c8500ab755e4', 'hex' ), 356 | new Buffer( '72e72aaf6fede1657bd638ecb01ca56e', 'hex' ), 357 | new Buffer( 'ce72cd1e03b57376a1732aba242fa0b6', 'hex' ), 358 | new Buffer( '2a94238f3107201b424b3d44cca9d3f5', 'hex' ), 359 | new Buffer( 'e43cdf7174f4d45ab724369b7c9f18c6', 'hex' ), 360 | new Buffer( '355295e4d1b7b4ccb700733cc3bc4958', 'hex' ), 361 | ]; 362 | 363 | var actual = sp.calculateOutgoingMac( data ); 364 | var expected = new Buffer( 365 | '827451fec1fffae0c0f4955f1da9fe53f1b42c30', 'hex' ); 366 | 367 | 368 | actual.should.deep.equal( expected ); 369 | }); 370 | }); 371 | 372 | describe( '#digestHandshake()', function() { 373 | 374 | it( 'should perform TLS 1.2 digest on single data', function() { 375 | 376 | var sp = new SecurityParameters( 1, version ); 377 | 378 | var data = new Buffer( 379 | '7804afb92904ad81ee5b046427c1a4dc' + 380 | '0f8c76031d25d29db259ebf86b7ad34e' + 381 | '227159354963c64e8c76c8500ab755e4' + 382 | '72e72aaf6fede1657bd638ecb01ca56e' + 383 | 'ce72cd1e03b57376a1732aba242fa0b6' + 384 | '2a94238f3107201b424b3d44cca9d3f5' + 385 | 'e43cdf7174f4d45ab724369b7c9f18c6' + 386 | '355295e4d1b7b4ccb700733cc3bc4958', 'hex' ); 387 | 388 | sp.digestHandshake( data ); 389 | 390 | var actual = sp.getHandshakeDigest(); 391 | 392 | var expected = new Buffer( 393 | '44ea752d8cd1c819007758528c81da75' + 394 | '604feba1727222548221cea68db8c0d2', 'hex' ); 395 | 396 | actual.should.deep.equal( expected ); 397 | }); 398 | 399 | it( 'should perform TLS 1.2 digest on Handshake packets', function() { 400 | 401 | var sp = new SecurityParameters( 1, version ); 402 | 403 | var data = [ 404 | new Buffer( '7804afb92904ad81ee5b046427c1a4dc', 'hex' ), 405 | new Buffer( '0f8c76031d25d29db259ebf86b7ad34e', 'hex' ), 406 | new Buffer( '227159354963c64e8c76c8500ab755e4', 'hex' ), 407 | new Buffer( '72e72aaf6fede1657bd638ecb01ca56e', 'hex' ), 408 | new Buffer( 'ce72cd1e03b57376a1732aba242fa0b6', 'hex' ), 409 | new Buffer( '2a94238f3107201b424b3d44cca9d3f5', 'hex' ), 410 | new Buffer( 'e43cdf7174f4d45ab724369b7c9f18c6', 'hex' ), 411 | new Buffer( '355295e4d1b7b4ccb700733cc3bc4958', 'hex' ), 412 | ]; 413 | 414 | for( var d in data ) { 415 | var packet = new packets.Plaintext({ 416 | type: 0, 417 | version: version, 418 | epoch: 0, 419 | sequenceNumber: new Buffer([ 0x01 ]), 420 | fragment: data[ d ] 421 | }); 422 | sp.digestHandshake( packet ); 423 | } 424 | 425 | var actual = sp.getHandshakeDigest(); 426 | 427 | var expected = new Buffer( 428 | '44ea752d8cd1c819007758528c81da75' + 429 | '604feba1727222548221cea68db8c0d2', 'hex' ); 430 | 431 | actual.should.deep.equal( expected ); 432 | }); 433 | 434 | it( 'should perform TLS 1.2 digest on array data', function() { 435 | 436 | var sp = new SecurityParameters( 1, version ); 437 | 438 | var data = [ 439 | new Buffer( '7804afb92904ad81ee5b046427c1a4dc', 'hex' ), 440 | new Buffer( '0f8c76031d25d29db259ebf86b7ad34e', 'hex' ), 441 | new Buffer( '227159354963c64e8c76c8500ab755e4', 'hex' ), 442 | new Buffer( '72e72aaf6fede1657bd638ecb01ca56e', 'hex' ), 443 | new Buffer( 'ce72cd1e03b57376a1732aba242fa0b6', 'hex' ), 444 | new Buffer( '2a94238f3107201b424b3d44cca9d3f5', 'hex' ), 445 | new Buffer( 'e43cdf7174f4d45ab724369b7c9f18c6', 'hex' ), 446 | new Buffer( '355295e4d1b7b4ccb700733cc3bc4958', 'hex' ), 447 | ]; 448 | 449 | sp.digestHandshake( data ); 450 | var actual = sp.getHandshakeDigest(); 451 | 452 | var expected = new Buffer( 453 | '44ea752d8cd1c819007758528c81da75' + 454 | '604feba1727222548221cea68db8c0d2', 'hex' ); 455 | 456 | actual.should.deep.equal( expected ); 457 | }); 458 | }); 459 | }); 460 | }); 461 | 462 | -------------------------------------------------------------------------------- /test/SequenceNumber.js: -------------------------------------------------------------------------------- 1 | 2 | "use strict"; 3 | 4 | var should = require( 'chai' ).should(); 5 | var crypto = require( 'crypto' ); 6 | 7 | var SequenceNumber = require( '../SequenceNumber' ); 8 | 9 | describe( 'SequenceNumber', function() { 10 | 11 | describe( '#ctor()', function() { 12 | it( 'should init correctly', function() { 13 | 14 | var sn = new SequenceNumber(); 15 | 16 | sn.current.should.deep.equal( new Buffer([ 0, 0, 0, 0, 0, 0 ]) ); 17 | }); 18 | }); 19 | 20 | describe( '#next()', function() { 21 | it( 'should increase counter correctly', function() { 22 | 23 | var sn = new SequenceNumber(); 24 | sn.current.should.deep.equal( new Buffer([ 0, 0, 0, 0, 0, 0 ]) ); 25 | 26 | var next = sn.next(); 27 | 28 | next.should.deep.equal( new Buffer([ 0, 0, 0, 0, 0, 1 ]) ); 29 | sn.current.should.deep.equal( next ); 30 | }); 31 | 32 | it( 'should overflow correctly', function() { 33 | 34 | var sn = new SequenceNumber(); 35 | sn.current = new Buffer([ 0, 0, 0, 0, 0, 0xff ]); 36 | 37 | var next = sn.next(); 38 | 39 | next.should.deep.equal( new Buffer([ 0, 0, 0, 0, 1, 0 ]) ); 40 | sn.current.should.deep.equal( next ); 41 | }); 42 | 43 | it( 'should cascade the overflow', function() { 44 | 45 | var sn = new SequenceNumber(); 46 | sn.current = new Buffer([ 0, 0xff, 0xff, 0xff, 0xff, 0xff ]); 47 | 48 | var next = sn.next(); 49 | 50 | next.should.deep.equal( new Buffer([ 1, 0, 0, 0, 0, 0 ]) ); 51 | sn.current.should.deep.equal( next ); 52 | }); 53 | 54 | it( 'should overflow back to zero', function() { 55 | 56 | var sn = new SequenceNumber(); 57 | sn.current = new Buffer([ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff ]); 58 | 59 | var next = sn.next(); 60 | 61 | next.should.deep.equal( new Buffer([ 0, 0, 0, 0, 0, 0 ]) ); 62 | sn.current.should.deep.equal( next ); 63 | }); 64 | }); 65 | 66 | describe( '#setNext()', function() { 67 | 68 | it( 'should set the next value correctly', function() { 69 | 70 | var sn = new SequenceNumber(); 71 | sn.current.should.deep.equal( new Buffer([ 0, 0, 0, 0, 0, 0 ]) ); 72 | 73 | sn.setNext( new Buffer([ 1, 2, 3, 4, 5, 6 ]) ); 74 | sn.current.should.deep.equal( new Buffer([ 1, 2, 3, 4, 5, 5 ]) ); 75 | 76 | var next = sn.next(); 77 | next.should.deep.equal( new Buffer([ 1, 2, 3, 4, 5, 6 ]) ); 78 | }); 79 | 80 | it( 'should overflow backwards if needed', function() { 81 | 82 | var sn = new SequenceNumber(); 83 | sn.current.should.deep.equal( new Buffer([ 0, 0, 0, 0, 0, 0 ]) ); 84 | 85 | sn.setNext( new Buffer([ 1, 0, 0, 0, 0, 0 ]) ); 86 | sn.current.should.deep.equal( new Buffer([ 0, 0xff, 0xff, 0xff, 0xff, 0xff ]) ); 87 | 88 | var next = sn.next(); 89 | next.should.deep.equal( new Buffer([ 1, 0, 0, 0, 0, 0 ]) ); 90 | }); 91 | 92 | it( 'should overflow fully if needed', function() { 93 | 94 | var sn = new SequenceNumber(); 95 | sn.current.should.deep.equal( new Buffer([ 0, 0, 0, 0, 0, 0 ]) ); 96 | 97 | sn.setNext( new Buffer([ 0, 0, 0, 0, 0, 0 ]) ); 98 | sn.current.should.deep.equal( new Buffer([ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff ]) ); 99 | 100 | var next = sn.next(); 101 | next.should.deep.equal( new Buffer([ 0, 0, 0, 0, 0, 0 ]) ); 102 | }); 103 | }); 104 | }); 105 | -------------------------------------------------------------------------------- /test/assets/certificate.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN PRIVATE KEY----- 2 | MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQC3dz3nvtn/RGWU 3 | cRcucfUk7qSm2Zlb2v1mtrrLLu4A5GVuNeG8ZObBNnsEXnoZNfJV/rmIPEndktH9 4 | nhKokwhVhDFm9DLhO+iDpRyzhFUUppfP0EXSq6CcEXWUQUas6HEoC7o0B80Cmtb0 5 | I7pQy0LsKDg3mQ/BuYg4QBl64/3KTB4plshf7SrDGqDr3t0PgcE7XIME6x3F1IBJ 6 | om5Fv1bc30xXhqjYm5/rBaSxK0NeGO/lYcwCEG63VnJeQRVB5ivYfpAYsasM5uGN 7 | R3+D8kvOs0+oG2GB+Q0qnhYupW2rBFlj3c9VglkOBN05tnvOA5xqEI1Mkj8ADHBc 8 | nMOYuIO5AgMBAAECggEAXMJGI1iEQaLkNPQkw0/MoRqjVtSnzCBhhEAZG0ekAAF6 9 | IwnNEwJ1BPU1p1TZKMv0tXPvfCj3M7bawv7b8i08xnfqvmHzI5u1iHG/nCfpGGLO 10 | WLy1wLkToDTXnNiQEjYHmDats0bKaWm+CnvR5K2QLXR8T+fsZocWj1IhT9fb5h5P 11 | 7ktmL3uKvOglkVxeGs3Lt06H1P4n0XJZJOoe0ioT6QPx7Xgvq8HSIwwsUn64D2zK 12 | NLUbDFx8FKSS0ustLhylanq58Bc278hwI2IZQKKl+hUr3ZssX+uVSfnVZ/gxWDTg 13 | SUjti5r4DFs2fInUhRaHQYzB6N3tnYjflirWskQFcQKBgQDuhURTtzJAM5I3iRDc 14 | 6oKAZRVoRNQy+L2mfXXij/CxaOy7l5BUzyInaV2AZ7tQUIa3NH7Nbpew5G3yqmy4 15 | LhvdXryUg4cHIuPGBCZJuYc9jphIrXAXJdH7Xn0xT0zBmjUQQK6j/AHdmodcfclA 16 | Y/4DAa89uI4kzGad7MfRAXim1QKBgQDE6SCE4Hem+mg2wPYqDnwAH55UYc1ITct+ 17 | rOmmC7p/E8qNljPVaKeWffIpUaxBCa+pBdDdDKoxJ/lKnwn5K6ozeO4SVkqcMiWI 18 | +65L/0ewbaYmMZ2rc5X3aqYkEI5hcfZKUXoDGdX+9cdrHmZCSnf4yr1E/xtggSag 19 | 4dU8CnIjVQKBgF1UsEPBr1wH0fMBIyQObzomU5YVOKMpSaxX80TP5fLFh7xvtf45 20 | frfFNt0DufvXRp9xXxyrZZfGCm+l2BzJjgW1CD1kqfVU5aOaBBFdE1o27ceidfXY 21 | yq19b6dXzEUFPjY52Rw5g9FeohDC93jGp6ItipCwIo6rnIu3FwjldnxxAoGBAMFt 22 | qI4e2iri7KBsqOPjWpfcd3G4qSkPkoibXuHHv6m5TU4McFqA9a91hP5lxmoVE8Nb 23 | fTLHkB+9frt4wxlLdWQetO66aYxKDmkjorHw0QFUlNQMBTA42OY0k4P154d9pUyY 24 | AN0u8fIEiaKGODmCYZu5vHccik4gUEvVy9uw/zIJAoGAZVS6FTwSSljweyG25amH 25 | +AXFJ9BN2TwffwJiotwnKkw5HlibgvwWPcIhXFga6DBnmde5lMJfB64mg56ndKnv 26 | EKvW676x4lCHOGlWtNXpLym0HPm6qKkwW+3xSY/yqbLfUC1/iQaEDYXdcpcbQP1z 27 | YI48pPDuxoMiqJDcd6qDdQM= 28 | -----END PRIVATE KEY----- 29 | -----BEGIN CERTIFICATE----- 30 | MIIDXTCCAkWgAwIBAgIJAOzyLnW7y+GuMA0GCSqGSIb3DQEBCwUAMEUxCzAJBgNV 31 | BAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEwHwYDVQQKDBhJbnRlcm5ldCBX 32 | aWRnaXRzIFB0eSBMdGQwHhcNMTUwNDEzMTc1NDQyWhcNMTUwNTEzMTc1NDQyWjBF 33 | MQswCQYDVQQGEwJBVTETMBEGA1UECAwKU29tZS1TdGF0ZTEhMB8GA1UECgwYSW50 34 | ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB 35 | CgKCAQEAt3c9577Z/0RllHEXLnH1JO6kptmZW9r9Zra6yy7uAORlbjXhvGTmwTZ7 36 | BF56GTXyVf65iDxJ3ZLR/Z4SqJMIVYQxZvQy4Tvog6Ucs4RVFKaXz9BF0qugnBF1 37 | lEFGrOhxKAu6NAfNAprW9CO6UMtC7Cg4N5kPwbmIOEAZeuP9ykweKZbIX+0qwxqg 38 | 697dD4HBO1yDBOsdxdSASaJuRb9W3N9MV4ao2Juf6wWksStDXhjv5WHMAhBut1Zy 39 | XkEVQeYr2H6QGLGrDObhjUd/g/JLzrNPqBthgfkNKp4WLqVtqwRZY93PVYJZDgTd 40 | ObZ7zgOcahCNTJI/AAxwXJzDmLiDuQIDAQABo1AwTjAdBgNVHQ4EFgQUOtqR6WXi 41 | XaRpl5cHRU/oYt16OIwwHwYDVR0jBBgwFoAUOtqR6WXiXaRpl5cHRU/oYt16OIww 42 | DAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEAjkfDzaSpDEZHg1YKyKar 43 | vcx4UiK09DIhxdUIN0xyNL9Ds52EIHXgKIio3h7dtvFzGd+Pt+RzpCsQxqKwxWO4 44 | 1Au5PLD/rdgO4/KVKlFvpRSTozw8edO7mV26RJospqHRtgSA8/GE4irnWISVhY9E 45 | 2eFBjnaIRwTiG/XyknCMngP3/zpEfdfsc0aizD0YcHaDByPlXlOa0dxV7BU/TAkg 46 | Ml9YvI4gGq+xk4p+FAewr1smOAcR/RUqxpoadgsALzEpuDZ4ZW66wlrw0ubr1SjH 47 | 7Br2pLaTuHUwu4sD60BchbLROdltyFrimpcD6T9QlWgVtVW/hpE90m8LmgVNEtg8 48 | wg== 49 | -----END CERTIFICATE----- 50 | -------------------------------------------------------------------------------- /test/openssl.js: -------------------------------------------------------------------------------- 1 | 2 | "use strict"; 3 | 4 | var should = require( 'chai' ).should(); 5 | var fs = require( 'fs' ); 6 | var spawn = require( 'child_process' ).spawn; 7 | var dtls = require( '../' ); 8 | 9 | var cert = fs.readFileSync( __dirname + '/assets/certificate.pem' ); 10 | 11 | describe( 'openssl', function() { 12 | it( 'should validate itself', function( done ) { 13 | this.slow( 500 ); 14 | 15 | // Spawn the client a bit later. Give the UDP port time to init. 16 | var server = spawn( 'openssl', 17 | [ 's_server', 18 | '-accept', 24126, 19 | '-key', __dirname + '/assets/certificate.pem', 20 | '-cert', __dirname + '/assets/certificate.pem', 21 | '-state', '-msg', '-debug', 22 | '-dtls1' ]); 23 | 24 | server.stdout.setEncoding( 'ascii' ); 25 | server.stdout.on( 'data', function( data ) { 26 | if( data.indexOf( '### client->server\n' ) !== -1 ) { 27 | server.stdin.write( '### server->client\n' ); 28 | } 29 | }); 30 | 31 | // Spawn the client a bit later. Give the UDP port time to init. 32 | var client; 33 | setTimeout( function() { 34 | client = spawn( 'openssl', 35 | [ 's_client', '-port', 24126, '-dtls1', '-state', '-msg', '-debug' ]); 36 | 37 | client.stdout.setEncoding( 'ascii' ); 38 | client.stdout.on( 'data', function( data ) { 39 | if( data.indexOf( '### server->client\n' ) !== -1 ) { 40 | done(); 41 | client.kill(); 42 | server.kill(); 43 | } 44 | }); 45 | 46 | }, 50 ); 47 | 48 | setTimeout( function() { 49 | client.stdin.write( '### client->server\n' ); 50 | }, 100 ); 51 | 52 | }); 53 | 54 | describe( 's_client', function() { 55 | 56 | it( 'should connect to node-dtls server with DTLSv1', function( done ) { 57 | this.slow( 150 ); 58 | 59 | var server = dtls.createServer({ 60 | type: 'udp4', 61 | key: cert, 62 | cert: cert 63 | }); 64 | server.bind( 24124 ); 65 | 66 | // Spawn the client a bit later. Give the UDP port time to init. 67 | var client; 68 | setTimeout( function() { 69 | client = spawn( 'openssl', 70 | [ 's_client', '-port', 24124, '-dtls1' ]); 71 | 72 | client.stdout.setEncoding( 'ascii' ); 73 | client.stdout.on( 'data', function( data ) { 74 | if( data.indexOf( '### node->openssl\n' ) !== -1 ) { 75 | client.kill(); 76 | done(); 77 | } 78 | }); 79 | }, 10 ); 80 | 81 | server.on( 'secureConnection', function( socket ) { 82 | 83 | client.stdin.write( '### openssl->node\n' ); 84 | socket.on( 'message', function( msg ) { 85 | 86 | msg.should.deep.equal( new Buffer( '### openssl->node\n' ) ); 87 | socket.send( new Buffer( '### node->openssl\n' )); 88 | 89 | }); 90 | }); 91 | }); 92 | }); 93 | 94 | describe( 's_server', function() { 95 | 96 | it( 'should accept node-dtls client with DTLSv1', function( done ) { 97 | this.slow( 300 ); 98 | 99 | // Spawn the client a bit later. Give the UDP port time to init. 100 | var server = spawn( 'openssl', 101 | [ 's_server', 102 | '-accept', 24125, 103 | '-key', __dirname + '/assets/certificate.pem', 104 | '-cert', __dirname + '/assets/certificate.pem', 105 | '-dtls1' ]); 106 | 107 | 108 | server.stdout.setEncoding( 'ascii' ); 109 | server.stdout.on( 'data', function( data ) { 110 | if( data === '### node->openssl\n' ) { 111 | 112 | server.stdin.write( '### openssl->node\n' ); 113 | } 114 | }); 115 | 116 | setTimeout( function() { 117 | dtls.connect( 24125, 'localhost', 'udp4', function( client ) { 118 | client.send( new Buffer( '### node->openssl\n' ) ); 119 | 120 | client.on( 'message', function( msg ) { 121 | msg.should.deep.equal( new Buffer( '### openssl->node\n' ) ); 122 | server.kill(); 123 | done(); 124 | }); 125 | }); 126 | }, 100 ); 127 | }); 128 | }); 129 | }); 130 | -------------------------------------------------------------------------------- /test/prf.js: -------------------------------------------------------------------------------- 1 | 2 | "use strict"; 3 | 4 | var should = require( 'chai' ).should(); 5 | 6 | var prf = require( '../prf' ); 7 | 8 | describe( 'prf', function() { 9 | 10 | it( 'should return correct TLS 1.2 output', function() { 11 | 12 | // https://www.ietf.org/mail-archive/web/tls/current/msg03416.html 13 | 14 | var secret = new Buffer([ 15 | 0x9b, 0xbe, 0x43, 0x6b, 0xa9, 0x40, 0xf0, 0x17, 16 | 0xb1, 0x76, 0x52, 0x84, 0x9a, 0x71, 0xdb, 0x35 17 | ]); 18 | 19 | var seed = new Buffer([ 20 | 0xa0, 0xba, 0x9f, 0x93, 0x6c, 0xda, 0x31, 0x18, 21 | 0x27, 0xa6, 0xf7, 0x96, 0xff, 0xd5, 0x19, 0x8c 22 | ]); 23 | 24 | var label = new Buffer([ 25 | 0x74, 0x65, 0x73, 0x74, 0x20, 0x6c, 0x61, 0x62, 26 | 0x65, 0x6c 27 | ]); 28 | 29 | //label.toString( 'ascii' ).should.equal( 'test label' ); 30 | 31 | var output = prf({ major: ~1, minor: ~2 })( secret, label, seed, 100 ); 32 | 33 | var expected = new Buffer([ 34 | 0xe3, 0xf2, 0x29, 0xba, 0x72, 0x7b, 0xe1, 0x7b, 35 | 0x8d, 0x12, 0x26, 0x20, 0x55, 0x7c, 0xd4, 0x53, 36 | 0xc2, 0xaa, 0xb2, 0x1d, 0x07, 0xc3, 0xd4, 0x95, 37 | 0x32, 0x9b, 0x52, 0xd4, 0xe6, 0x1e, 0xdb, 0x5a, 38 | 0x6b, 0x30, 0x17, 0x91, 0xe9, 0x0d, 0x35, 0xc9, 39 | 0xc9, 0xa4, 0x6b, 0x4e, 0x14, 0xba, 0xf9, 0xaf, 40 | 0x0f, 0xa0, 0x22, 0xf7, 0x07, 0x7d, 0xef, 0x17, 41 | 0xab, 0xfd, 0x37, 0x97, 0xc0, 0x56, 0x4b, 0xab, 42 | 0x4f, 0xbc, 0x91, 0x66, 0x6e, 0x9d, 0xef, 0x9b, 43 | 0x97, 0xfc, 0xe3, 0x4f, 0x79, 0x67, 0x89, 0xba, 44 | 0xa4, 0x80, 0x82, 0xd1, 0x22, 0xee, 0x42, 0xc5, 45 | 0xa7, 0x2e, 0x5a, 0x51, 0x10, 0xff, 0xf7, 0x01, 46 | 0x87, 0x34, 0x7b, 0x66 ]); 47 | 48 | output.should.deep.equal( expected ); 49 | }); 50 | 51 | it( 'should return correct TLS 1.0 output', function() { 52 | 53 | // https://code.google.com/p/as3crypto/source/browse/trunk/as3crypto/src/com/hurlant/crypto/tests/TLSPRFTest.as?r=5 54 | 55 | var secret = new Buffer( 48 ); 56 | secret.fill( 0xab ); 57 | 58 | var label = new Buffer( 'PRF Testvector' ); 59 | 60 | var seed = new Buffer( 64 ); 61 | seed.fill( 0xcd ); 62 | 63 | var output = prf({ major: ~1, minor: ~0 })( secret, label, seed, 104 ); 64 | 65 | var expected = new Buffer([ 66 | 0xd3, 0xd4, 0xd1, 0xe3, 0x49, 0xb5, 0xd5, 0x15, 67 | 0x04, 0x46, 0x66, 0xd5, 0x1d, 0xe3, 0x2b, 0xab, 68 | 0x25, 0x8c, 0xb5, 0x21, 0xb6, 0xb0, 0x53, 0x46, 69 | 0x3e, 0x35, 0x48, 0x32, 0xfd, 0x97, 0x67, 0x54, 70 | 0x44, 0x3b, 0xcf, 0x9a, 0x29, 0x65, 0x19, 0xbc, 71 | 0x28, 0x9a, 0xbc, 0xbc, 0x11, 0x87, 0xe4, 0xeb, 72 | 0xd3, 0x1e, 0x60, 0x23, 0x53, 0x77, 0x6c, 0x40, 73 | 0x8a, 0xaf, 0xb7, 0x4c, 0xbc, 0x85, 0xef, 0xf6, 74 | 0x92, 0x55, 0xf9, 0x78, 0x8f, 0xaa, 0x18, 0x4c, 75 | 0xbb, 0x95, 0x7a, 0x98, 0x19, 0xd8, 0x4a, 0x5d, 76 | 0x7e, 0xb0, 0x06, 0xeb, 0x45, 0x9d, 0x3a, 0xe8, 77 | 0xde, 0x98, 0x10, 0x45, 0x4b, 0x8b, 0x2d, 0x8f, 78 | 0x1a, 0xfb, 0xc6, 0x55, 0xa8, 0xc9, 0xa0, 0x13 ]); 79 | 80 | output.should.deep.equal( expected ); 81 | }); 82 | }); 83 | --------------------------------------------------------------------------------