├── .gitignore
├── LICENSE
├── README.md
├── h2q.js
├── index.js
├── lib
├── QuicClientHello.js
├── QuicConnection.js
├── QuicConnectionId.js
├── QuicConnectionState.js
├── QuicDeltaTime.js
├── QuicHandshakeMessage.js
├── QuicKeyExchange.js
├── QuicNonce.js
├── QuicOffset.js
├── QuicPacketManager.js
├── QuicSequenceNumber.js
├── QuicServerConfig.js
├── QuicSession.js
├── QuicStreamId.js
├── QuicTag.js
├── crypto
│ ├── C255.js
│ ├── CryptoUtil.js
│ ├── QuicDecrypter.js
│ ├── QuicEncrypter.js
│ └── hkdf.js
├── frame
│ ├── QuicAckFrame.js
│ ├── QuicBlockedFrame.js
│ ├── QuicCongestionFeedbackFrame.js
│ ├── QuicConnectionCloseFrame.js
│ ├── QuicFrame.js
│ ├── QuicPaddingFrame.js
│ ├── QuicPingFrame.js
│ ├── QuicRstStreamFrame.js
│ ├── QuicStopWaitingFrame.js
│ ├── QuicStreamFrame.js
│ └── QuicWindowUpdateFrame.js
├── packet
│ ├── QuicDecryptedPacket.js
│ ├── QuicPacket.js
│ ├── QuicPacketPublicHeader.js
│ └── QuicVersionNegotiationPacket.js
└── stream
│ ├── QuicCryptoStream.js
│ ├── QuicDataStream.js
│ ├── QuicHeadersStream.js
│ └── QuicStream.js
├── package.json
└── test
└── test-hkdf.js
/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 |
5 | # Runtime data
6 | pids
7 | *.pid
8 | *.seed
9 |
10 | # Directory for instrumented libs generated by jscoverage/JSCover
11 | lib-cov
12 |
13 | # Coverage directory used by tools like istanbul
14 | coverage
15 |
16 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
17 | .grunt
18 |
19 | # node-waf configuration
20 | .lock-wscript
21 |
22 | # Compiled binary addons (http://nodejs.org/api/addons.html)
23 | build/Release
24 |
25 | # Dependency directory
26 | # https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git
27 | node_modules
28 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2015 Masakazu Kitajo
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
23 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # quic
2 | A QUIC (Quick UDP Internet Connections) client implementation.
3 |
4 | ```
5 | $ node index.js
6 | << Stream [SID: 1, DLEN: 1076]
7 | ## QUIC Version negotiated: Q025
8 | >> Ack [H: 2, LO: 0x01]
9 | >> StopWaiting [SE: 0, LUD: 0x00]
10 | >> Stream [SID: 1, DLEN: 231]
11 | ## rejected
12 | >> Padding
13 | << Stream [SID: 1, DLEN: 1272]
14 | << Ack [H: 2, LO: 0x01]
15 | @@ trying another decrypter
16 | @@ switched (seq 0x02)
17 | >> Ack [H: 6, LO: 0x02]
18 | >> StopWaiting [SE: 0, LUD: 0x00]
19 | >> Stream [SID: 1, DLEN: 230]
20 | ## established
21 | >> Padding
22 | << Stream [SID: 3, DLEN: 30]
23 | [ [ ':method', 'GET' ],
24 | [ ':scheme', 'http' ],
25 | [ ':authority', 'www.google.com' ],
26 | [ ':path', '/' ] ]
27 | << Ack [H: 6, LO: 0x02]
28 | >> Ack [H: 30, LO: 0x04]
29 | >> StopWaiting [SE: 2, LUD: 0x02]
30 | >> Stream [SID: 3, DLEN: 156]
31 | [ [ ':status', '302' ],
32 | [ 'alternate-protocol', '80:quic,p=0' ],
33 | [ 'cache-control', 'private' ],
34 | [ 'content-length', '261' ],
35 | [ 'content-type', 'text/html; charset=UTF-8' ],
36 | [ 'date', 'Sat, 23 May 2015 06:02:33 GMT' ],
37 | [ 'location',
38 | 'http://www.google.co.jp/?gfe_rd=cr&ei=-RdgVfyDGoqg8wft9IGwCA' ],
39 | [ 'server', 'GFE/2.0' ] ]
40 | >> Stream [SID: 5, DLEN: 261]
41 |
42 | 302 Moved
43 | 302 Moved
44 | The document has moved
45 | here.
46 |
47 |
48 | << Ack [H: 14, LO: 0x03]
49 | << Ack [H: 30, LO: 0x04]
50 | @@ trying another decrypter
51 | @@ switched (seq 0x05)
52 | >> Ack [H: 254, LO: 0x07]
53 | >> StopWaiting [SE: 30, LUD: 0x1e]
54 | >> ConnectionClose [EC: 25, RL: 0, R: ]
55 | ```
56 |
57 | # status
58 |
59 | - still under development
60 |
--------------------------------------------------------------------------------
/h2q.js:
--------------------------------------------------------------------------------
1 | var urlUtil = require('url');
2 | var QuicSession = require('./lib/QuicSession');
3 |
4 | var h2q = module.exports = function h2q (qSession) {
5 | this._qSession = qSession;
6 | };
7 |
8 | h2q.prototype.connect = function connect (host, port, callback) {
9 | var self = this;
10 | if (this._qSession) {
11 | if (this._qSession.established) {
12 | this._qSession.headersStream.on('headers', function (h) { console.log(h); });
13 | callback();
14 | } else {
15 | this._qSession.on('established', callback);
16 | this._qSession.headersStream.on('headers', function (h) { console.log(h); });
17 | }
18 | } else {
19 | this._qSession = new QuicSession({
20 | 'host': host,
21 | 'port': port,
22 | });
23 | this._qSession.on('established', callback);
24 | this._qSession.headersStream.on('headers', function (h) { console.log(h); });
25 | }
26 | };
27 | h2q.prototype.get = function get (url) {
28 | var self = this;
29 | url = urlUtil.parse(url);
30 | this.connect(url.hostname, url.port, function () {
31 | var headers = [
32 | [':method', 'GET'],
33 | [':scheme', url.protocol.substring(0, url.protocol.length - 1)],
34 | [':authority', url.hostname + (url.port ? ':' + url.port : '')],
35 | [':path', url.pathname],
36 | ];
37 | var stream = self._qSession.headersStream.sendHeaders(headers, function () {
38 | console.log(headers);
39 | });
40 | stream.on('data', function (data) {
41 | console.log(data.toString());
42 | });
43 | });
44 | };
45 |
--------------------------------------------------------------------------------
/index.js:
--------------------------------------------------------------------------------
1 | var urlUtil = require('url');
2 | var https = require('https');
3 | var fs = require('fs');
4 | var QuicSession = require('./lib/QuicSession');
5 | var Http2Quic= require('./h2q.js');
6 |
7 |
8 | function getAltProtocols (url, callback) {
9 | var protocols = {} ;
10 |
11 | https.get(url ,function (res) {
12 | var altProtocols = res.headers['alternate-protocol'];
13 | altProtocols.split(', ').forEach(function (range, i) {
14 | var protocol = '', parameters = '';
15 | var paramPos = range.indexOf(',');
16 | if (paramPos > 0) {
17 | protocol = range.substring(0, paramPos);
18 | parameters = range.substring(paramPos + 1);
19 | } else {
20 | protocol = range;
21 | }
22 | var colonPos = protocol.indexOf(':');
23 | protocols[protocol.substring(0, colonPos)] = {
24 | 'protocol': protocol.substring(colonPos + 1),
25 | 'parameters': parameters
26 | };
27 | });
28 | callback(false, protocols);
29 | }).on('error', function (e) {
30 | callback(e);
31 | });
32 | }
33 |
34 | function getQuicPort(protocols) {
35 | for (var port in protocols) {
36 | if (protocols[port].protocol == 'quic') {
37 | return port;
38 | }
39 | }
40 | }
41 |
42 | function addEventListeners(qSession) {
43 | qSession.on('error', function (e) {
44 | console.log(e);
45 | });
46 | qSession.on('frame', function (frame) {
47 | console.log('>> ' + frame.toString());
48 | });
49 | qSession.on('sent', function (frame) {
50 | console.log('<< ' + frame.toString());
51 | });
52 | qSession.on('rejected', function (hsMessage) {
53 | console.log('## rejected');
54 | // console.log(hsMessage);
55 | });
56 | qSession.on('established', function (hsMessage) {
57 | console.log('## established');
58 | // console.log(hsMessage);
59 | });
60 | qSession.connection.on('packet', function (packet) {
61 | // console.log(packet.toString());
62 | });
63 | qSession.connection.on('negotiating', function (versions) {
64 | console.log('## QUIC Version candidates: ' + versions);
65 | });
66 | qSession.connection.on('negotiated', function (version) {
67 | console.log('## QUIC Version negotiated: ' + version);
68 | });
69 | }
70 |
71 | var request = 'http://www.google.com/';
72 | var target = 'https://www.google.com/';
73 | // var target = 'quic://localhost:6121/';
74 |
75 | var url = urlUtil.parse(target);
76 | var qSession = null;
77 | var h2q = null;
78 |
79 | function doRequest (host, port) {
80 | var options;
81 | try {
82 | options = JSON.parse(fs.readFileSync('quic.session'));
83 | if (options.host !== host || options.port !== port) {
84 | options = { 'host': host, 'port': port };
85 | }
86 | } catch (e) {
87 | options = { 'host': host, 'port': port };
88 | }
89 | qSession = new QuicSession(options);
90 | qSession.on('established', function () {
91 | fs.writeFile('quic.session', JSON.stringify(qSession.options));
92 | });
93 | addEventListeners(qSession);
94 | h2q = new Http2Quic(qSession);
95 | h2q.get(request);
96 | }
97 |
98 | if (url.protocol === 'quic:') {
99 | doRequest(url.hostname, url.port);
100 | } else {
101 | getAltProtocols(target, function (err, protocols) {
102 | if (err) {
103 | throw new Error("The target doesn't provide alternate protocols");
104 | }
105 | var host = url.hostname;
106 | var port = getQuicPort(protocols);
107 | if (!port) {
108 | throw new Error("The target doesn't support QUIC");
109 | }
110 | doRequest(host, port);
111 | });
112 | }
113 |
--------------------------------------------------------------------------------
/lib/QuicClientHello.js:
--------------------------------------------------------------------------------
1 | var util = require('util');
2 | var QuicHandshakeMessage = require('./QuicHandshakeMessage');
3 | var QuicTag = require('./QuicTag');
4 |
5 | var QuicClientHello = module.exports = function QuicClientHello (buf) {
6 | QuicClientHello.super_.call(this, buf);
7 |
8 | if (!buf) {
9 | this._messageTag = QuicTag.CHLO;
10 | this._tags[QuicTag.ICSL] = new Buffer([300, 0, 0, 0]);
11 | this._tags[QuicTag.MSPC] = new Buffer([100, 0, 0, 0]);
12 | }
13 | this._tags['COPT'] = new Buffer('FIXD'); // ???
14 | };
15 | util.inherits(QuicClientHello, QuicHandshakeMessage);
16 | var map = {
17 | 'certificateSigndCertTimestamp': QuicTag.CSCT,
18 | 'clientNonce': QuicTag.NONC,
19 | 'encryptionAlgorithm': QuicTag.AEAD,
20 | 'keyExchangeMethod': QuicTag.KEXS,
21 | 'proofDemand': QuicTag.PDMD,
22 | 'publicKey': QuicTag.PUBS,
23 | 'serverConfigId': QuicTag.SCID,
24 | 'serverName': QuicTag.SNI,
25 | 'serverNonce': QuicTag.SNO,
26 | 'serverToken': QuicTag.STK,
27 | 'version': QuicTag.VER,
28 | 'expectedLeafCertificate': QuicTag.XLCT,
29 | };
30 | function createAccessor (prop, tag) {
31 | Object.defineProperty(QuicClientHello.prototype, prop, {
32 | 'get': function () {
33 | return this._tags[tag];
34 | },
35 | 'set': function (value) {
36 | this._tags[tag] = new Buffer(value);
37 | }
38 | });
39 | }
40 | for (var prop in map) {
41 | createAccessor(prop, map[prop]);
42 | }
43 |
--------------------------------------------------------------------------------
/lib/QuicConnection.js:
--------------------------------------------------------------------------------
1 | var dgram = require('dgram');
2 | var events = require('events');
3 | var util = require('util');
4 | var QuicPacket = require('./packet/QuicPacket');
5 | var QuicPacketPublicHeader = require('./packet/QuicPacketPublicHeader');
6 | var QuicVersionNegotiationPacket = require('./packet/QuicVersionNegotiationPacket');
7 | var QuicConnectionId = require('./QuicConnectionId');
8 | var QuicConnectionState = require('./QuicConnectionState');
9 |
10 | var acceptableVersions = [
11 | new Buffer('Q025'), // this should be selected
12 | new Buffer('Q026'),
13 | ];
14 |
15 | function selectVersion (versions) {
16 | var selected = null;
17 | versions.every(function (va, i) {
18 | if (acceptableVersions.every(function (vb, i) {
19 | if (va.equals(vb)) {
20 | selected = i;
21 | return false;
22 | } else {
23 | return true;
24 | }
25 | })) {
26 | return true;
27 | } else {
28 | return false;
29 | }
30 | });
31 | return selected;
32 | }
33 |
34 | var QuicConnection = module.exports = function QuicConnection (options) {
35 | events.EventEmitter.call(this);
36 |
37 | this._currentVersionIndex = 0;
38 | this._id = new QuicConnectionId();
39 |
40 | this._state = QuicConnectionState.NEGOTIATING_VERSION;
41 |
42 | var self = this;
43 | this._host = options.host;
44 | this._port = options.port;
45 |
46 | this._socket = dgram.createSocket('udp4');
47 | this._socket.on('message', function (msg, rinfo) {
48 | var publicHeader = new QuicPacketPublicHeader(msg);
49 | if (publicHeader.isPublicResetPacket) {
50 | self.emit('unsupported', "Public Reset Packet");
51 | } else if (publicHeader.hasVersion) {
52 | var versions = (new QuicVersionNegotiationPacket(msg)).versions;
53 | self.emit('negotiating', versions);
54 | var v = selectVersion(versions);
55 | if (v === null) {
56 | self.emit('error', new Error('Version negotiation broke down. The server expects: ' + versions));
57 | } else {
58 | self._currentVersionIndex = v;
59 | self.sendPacket(new QuicPacket());
60 | }
61 | } else {
62 | if (self._state === QuicConnectionState.NEGOTIATING_VERSION) {
63 | self._state = QuicConnectionState.ESTABLISHED;
64 | self.emit('negotiated', acceptableVersions[self._currentVersionIndex]);
65 | }
66 | var packet = new QuicPacket(msg);
67 | self.emit('packet', packet);
68 | }
69 | });
70 | this._socket.on('error', function (e) {
71 | self.emit('error', e);
72 | });
73 | };
74 | util.inherits(QuicConnection, events.EventEmitter);
75 | Object.defineProperty(QuicConnection.prototype, 'version', {
76 | 'get': function () {
77 | return acceptableVersions[this._currentVersionIndex];
78 | }
79 | });
80 | Object.defineProperty(QuicConnection.prototype, 'id', {
81 | 'get': function () {
82 | return this._id;
83 | }
84 | });
85 | Object.defineProperty(QuicConnection.prototype, 'host', {
86 | 'get': function () {
87 | return this._host;
88 | }
89 | });
90 | Object.defineProperty(QuicConnection.prototype, 'port', {
91 | 'get': function () {
92 | return this._port;
93 | }
94 | });
95 | QuicConnection.prototype.send = function send (data, callback) {
96 | var packet = new QuicPacket();
97 | packet.payload = data;
98 | this.sendPacket(packet, callback);
99 | };
100 | QuicConnection.prototype.sendPacket = function sendPacket (packet, callback) {
101 | var data = packet.getBuffer();
102 | this._socket.send(data, 0, data.length, this._port, this._host, callback);
103 | };
104 |
--------------------------------------------------------------------------------
/lib/QuicConnectionId.js:
--------------------------------------------------------------------------------
1 | var crypto = require('crypto');
2 | var QuicConnectionId = module.exports = function QuicConnectionId (value) {
3 | if (value instanceof Buffer) {
4 | this._value = new Buffer(8).fill(0);
5 | value.copy(this._value, 0);
6 | } else if (value instanceof QuicConnectionId) {
7 | this._value = value.getBuffer();
8 | } else {
9 | this._value = crypto.randomBytes(8);
10 | }
11 | };
12 | Object.defineProperty(QuicConnectionId.prototype, 'minLen', {
13 | 'get': function () {
14 | if (this._value[7] === 0 && this._value[6] === 0 && this._value[5] === 0 && this._value[4] === 0) {
15 | if (this._value[3] === 0 && this._value[2] === 0 && this._value[1] === 0) {
16 | return 1;
17 | } else {
18 | return 4;
19 | }
20 | } else {
21 | return 8;
22 | }
23 | }
24 | });
25 | QuicConnectionId.prototype.toString = function toString () {
26 | var str = '0x';
27 | var i, n = this.minLen - 1;
28 | for (i = n; i >= 0; --i) {
29 | if (this._value[i] < 16) {
30 | str += '0';
31 | }
32 | str += this._value[i].toString(16);
33 | }
34 | return str;
35 | };
36 | QuicConnectionId.prototype.getBuffer = function getBuffer(len) {
37 | switch (len) {
38 | case 1:
39 | case 4:
40 | return new Buffer(this._value.slice(0, len));
41 | default:
42 | return new Buffer(this._value);
43 | }
44 | };
45 |
46 |
--------------------------------------------------------------------------------
/lib/QuicConnectionState.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | 'NEGOTIATING_VERSION': 0,
3 | 'ESTABLISHED': 1,
4 | };
5 |
--------------------------------------------------------------------------------
/lib/QuicDeltaTime.js:
--------------------------------------------------------------------------------
1 | var QuicDeltaTime = module.exports = function QuicDeltaTime () {
2 | switch (arguments.length) {
3 | case 0:
4 | this._value = new Buffer([0x00, 0x00]);
5 | break;
6 | case 1: // (buf)
7 | this._value = new Buffer(arguments[0]);
8 | break;
9 | case 2: // (from, to)
10 | var dt = arguments[1] - arguments[0];
11 | // TODO calc 16 bit unsigned float
12 | this._value = new Buffer([0xFF, 0xFF]);
13 | break;
14 | }
15 | };
16 |
--------------------------------------------------------------------------------
/lib/QuicHandshakeMessage.js:
--------------------------------------------------------------------------------
1 | var QuicTag = require('./QuicTag');
2 |
3 | var QuicHandshakeMessage = module.exports = function QuicHandshakeMessage (buf) {
4 | this._messageTag = "";
5 | this._tags = {};
6 |
7 | if (buf) {
8 | this._messageTag = buf.slice(0, 4).toString('binary');
9 | var i, n = buf.readUInt16LE(4), k, v, size = 0, offset = 8 + 8 * n, cursor = offset;
10 | for (i = 0; i < n; ++i) {
11 | k = buf.slice(8 + (i * 8), 8 + (i * 8) + 4).toString('binary');
12 | size = buf.readUInt32LE(8 + (i * 8) + 4);
13 | v = buf.slice(cursor, offset + size);
14 | cursor = offset + size;
15 | this._tags[k] = v;
16 | }
17 | }
18 |
19 | };
20 | Object.defineProperty(QuicHandshakeMessage.prototype, 'messageTag', {
21 | 'get': function () {
22 | return this._messageTag;
23 | },
24 | 'set': function (tag) {
25 | this._messageTag = tag;
26 | }
27 | });
28 | Object.defineProperty(QuicHandshakeMessage.prototype, 'tags', {
29 | 'get': function () {
30 | return this._tags;
31 | },
32 | });
33 | QuicHandshakeMessage.prototype.getBuffer = function getBuffer () {
34 | var minimumSize = 1024;
35 |
36 | var items = [
37 | new Buffer(this._messageTag),
38 | ];
39 | var nPairs = new Buffer(2);
40 | if (!this._tags[QuicTag.PAD]) {
41 | this._tags[QuicTag.PAD] = new Buffer(0);
42 | }
43 | nPairs.writeUInt16LE(Object.keys(this._tags).length);
44 | items.push(nPairs);
45 | items.push(new Buffer([0, 0]));
46 | index = new Buffer(8 * Object.keys(this._tags).length);
47 | items.push(index);
48 | var cursor = 0, size = 0;
49 | var self = this;
50 | Object.keys(this._tags).sort(function (a, b) {
51 | // FIXME: too rude
52 | var x = new Buffer(a), y = new Buffer(b);
53 | return (x[3] - y[3]) || (x[2] - y[2]) || (x[1] - y[1]) || (x[0] - y[0]);
54 | }).forEach(function (k) {
55 | index.write(k, cursor);
56 | if (k === QuicTag.PAD) {
57 | if (size > minimumSize) {
58 | index.writeUInt32LE(size, cursor + 4);
59 | } else {
60 | index.writeUInt32LE(minimumSize - size, cursor + 4);
61 | items.push(new Buffer(minimumSize - size).fill('-'));
62 | size += minimumSize - size;
63 | }
64 | } else {
65 | index.writeUInt32LE(size + self._tags[k].length, cursor + 4);
66 | items.push(self._tags[k]);
67 | size += self._tags[k].length;
68 | }
69 | cursor += 8;
70 | });
71 | return Buffer.concat(items);
72 | };
73 |
--------------------------------------------------------------------------------
/lib/QuicKeyExchange.js:
--------------------------------------------------------------------------------
1 | var C255 = require('./crypto/C255');
2 | var HKDF = require('./crypto/hkdf');
3 |
4 | var QuicKeyExchange = module.exports = function QuicKeyExchange () {
5 | this._hkdf = new HKDF('sha256');
6 | this._ready = false;
7 | this._method = null;
8 | this._secretKey = null;
9 | this._publicKey = null;
10 | };
11 | Object.defineProperty(QuicKeyExchange.prototype, 'ready', {
12 | 'get': function () {
13 | return this._ready;
14 | }
15 | });
16 | Object.defineProperty(QuicKeyExchange.prototype, 'method', {
17 | 'get': function () {
18 | return this._method;
19 | },
20 | 'set': function (method) {
21 | if (this._method !== method) {
22 | this._secretKey = C255.makeSecretKey();
23 | this._publicKey = C255.derivePublicKey(this._secretKey);
24 | this._method = method;
25 | }
26 | }
27 | });
28 | Object.defineProperty(QuicKeyExchange.prototype, 'publicKey', {
29 | 'get': function () {
30 | if (!this._method) {
31 | throw new Error('key exchange method is not specified');
32 | }
33 | return this._publicKey;
34 | }
35 | });
36 | QuicKeyExchange.prototype.setup = function setup (connectionId, clientNonce, serverNonce, clientHello, serverConfig) {
37 | // IKM suffix
38 | this._ikmSuffix = Buffer.concat([
39 | connectionId,
40 | clientHello,
41 | serverConfig
42 | ]);
43 |
44 | // salt
45 | var items = [];
46 | items.push(clientNonce);
47 | if (serverNonce) {
48 | items.push(serverNonce);
49 | }
50 | this._salt = Buffer.concat(items);
51 |
52 | this._ready = true;
53 | };
54 | QuicKeyExchange.prototype.update = function update (serverPublicKey, forwardSecure) {
55 | if (!this._method) {
56 | throw new Error('key exchange method is not specified');
57 | }
58 | if (!this._ready) {
59 | throw new Error('key exchange is not ready');
60 | }
61 |
62 | // premaster
63 | var premaster = C255.deriveSharedKey(this._secretKey, serverPublicKey);
64 | // IKM
65 | var items = [];
66 | if (forwardSecure) {
67 | items.push(new Buffer("QUIC forward secure key expansion\0"));
68 | } else {
69 | items.push(new Buffer("QUIC key expansion\0"));
70 | }
71 | items.push(this._ikmSuffix);
72 | var ikm = Buffer.concat(items);
73 |
74 | var materialLen = (16 + 4) * 2; // key len(16) + IV len(4) for encrypter and decrypter
75 | var material = this._hkdf.derive(this._salt, premaster, ikm, materialLen);
76 |
77 | return {
78 | 'clientWriteKey': material.slice(0, 16),
79 | 'serverWriteKey': material.slice(16, 32),
80 | 'clientWriteIv': material.slice(32, 36),
81 | 'serverWriteIv': material.slice(36, 40)
82 | };
83 | };
84 |
--------------------------------------------------------------------------------
/lib/QuicNonce.js:
--------------------------------------------------------------------------------
1 | var crypto = require('crypto');
2 |
3 | module.exports.generate = function generate (orbit) {
4 | var buf = new Buffer(32);
5 | buf.writeUInt32BE(Math.floor(Date.now() / 1000), 0);
6 | orbit.copy(buf, 4);
7 | crypto.randomBytes(20).copy(buf, 12);
8 | return buf;
9 | };
10 |
--------------------------------------------------------------------------------
/lib/QuicOffset.js:
--------------------------------------------------------------------------------
1 | var QuicOffset = module.exports = function QuicOffset (buf) {
2 | this._value = new Buffer(8).fill(0);
3 |
4 | if (buf) {
5 | buf.copy(this._value, 0);
6 | }
7 | };
8 | Object.defineProperty(QuicOffset.prototype, 'minLen', {
9 | 'get': function () {
10 | var i;
11 | for (i = 7; i >= 0; --i) {
12 | if (this._value[i] !== 0) {
13 | return i + 1;
14 | }
15 | }
16 | return 0;
17 | }
18 | });
19 | QuicOffset.prototype.toString = function toString () {
20 | var str = '0x';
21 | var i, n = this.minLen - 1;
22 | for (i = n; i >= 0; --i) {
23 | if (this._value[i] < 16) {
24 | str += '0';
25 | }
26 | str += this._value[i].toString(16);
27 | }
28 | return str;
29 | };
30 | QuicOffset.prototype.increment = function increment (v) {
31 | var i, n = this._value.length, carry = 0;
32 | if (typeof v === 'undefined') {
33 | v = 1;
34 | }
35 | x = new Buffer(4).fill(0);
36 | x.writeUInt32LE(v);
37 | for (i = 0; i < 4; ++i) {
38 | if (this._value[i] + x[i] + carry > 255) {
39 | this._value[i] += x[i] + carry;
40 | carry = 1;
41 | } else {
42 | this._value[i] += x[i] + carry;
43 | carry = 0;
44 | }
45 | }
46 | for (; i < n; ++i) {
47 | if (this._value[i] + carry > 255) {
48 | this._value[i] += carry;
49 | carry = 1;
50 | } else {
51 | this._value[i] += carry;
52 | carry = 0;
53 | }
54 | }
55 | };
56 | QuicOffset.prototype.set = function set (offset) {
57 | offset.getBuffer().copy(this._value, 0);
58 | };
59 | QuicOffset.prototype.getBuffer = function getBuffer(len) {
60 | switch (len) {
61 | case 1:
62 | case 2:
63 | case 3:
64 | case 4:
65 | case 5:
66 | case 6:
67 | case 7:
68 | case 8:
69 | return new Buffer(this._value.slice(0, len));
70 | default:
71 | return new Buffer(this._value);
72 | }
73 | };
74 |
--------------------------------------------------------------------------------
/lib/QuicPacketManager.js:
--------------------------------------------------------------------------------
1 |
2 | var QuicPacketManager = module.exports = function QuicPacketManager (session) {
3 | var self = this;
4 |
5 | this._session = session;
6 | this._baseTime = Date.now();
7 | this._lgtObservedNumber = null;
8 | this._lgtObservedTime = null;
9 | this._receivedEntropy = 0;
10 |
11 | session.on('packetSent', function onPacketProcessed (packet) {
12 | });
13 | session.on('packetProcessed', function onPacketProcessed (packet) {
14 | self._receivedEntropy ^= calcContribution(packet);
15 | self._lgtObservedTime = Date.now();
16 | self._lgtObservedNumber = packet.sequenceNumber;
17 | });
18 | };
19 | Object.defineProperty(QuicPacketManager.prototype, 'receivedEntropy', {
20 | 'get': function () {
21 | return this._receivedEntropy;
22 | }
23 | });
24 | Object.defineProperty(QuicPacketManager.prototype, 'largestObservedSeqNum', {
25 | 'get': function () {
26 | return this._lgtObservedNumber;
27 | }
28 | });
29 |
30 | function calcContribution (packet) {
31 | if (packet.hasEntropy) {
32 | return 1 << (packet.sequenceNumber.getBuffer(1)[0] & 0x07);
33 | } else {
34 | return 0;
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/lib/QuicSequenceNumber.js:
--------------------------------------------------------------------------------
1 | var QuicSequenceNumber = module.exports = function QuicSequenceNumber (buf) {
2 | this._value = new Buffer([0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]);
3 | if (buf) {
4 | buf.copy(this._value, 0);
5 | }
6 | };
7 | Object.defineProperty(QuicSequenceNumber.prototype, 'minLen', {
8 | 'get': function () {
9 | if (this._value[5] === 0 && this._value[4] === 0) {
10 | if (this._value[3] === 0 && this._value[2] === 0) {
11 | if (this._value[1] === 0) {
12 | return 1;
13 | } else {
14 | return 2;
15 | }
16 | } else {
17 | return 4;
18 | }
19 | } else {
20 | return 6;
21 | }
22 | }
23 | });
24 | QuicSequenceNumber.prototype.toString = function toString () {
25 | var str = '0x';
26 | var i, n = this.minLen - 1;
27 | for (i = n; i >= 0; --i) {
28 | if (this._value[i] < 16) {
29 | str += '0';
30 | }
31 | str += this._value[i].toString(16);
32 | }
33 | return str;
34 | };
35 | QuicSequenceNumber.prototype.increment = function increment () {
36 | var i, n = this._value.length;
37 | for (i = 0; i < n; i++) {
38 | if (this._value[i] === 255) {
39 | this._value[i] = 0;
40 | } else {
41 | this._value[i]++;
42 | break;
43 | }
44 | }
45 | };
46 | QuicSequenceNumber.prototype.set = function set (seqNum) {
47 | seqNum.getBuffer().copy(this._value, 0);
48 | };
49 | QuicSequenceNumber.prototype.getBuffer = function getBuffer(len) {
50 | switch (len) {
51 | case 1:
52 | case 2:
53 | case 4:
54 | case 6:
55 | return new Buffer(this._value.slice(0, len));
56 | default:
57 | return new Buffer(this._value);
58 | }
59 | };
60 |
--------------------------------------------------------------------------------
/lib/QuicServerConfig.js:
--------------------------------------------------------------------------------
1 | var QuicTag = require('./QuicTag');
2 |
3 | var QuicServerConfig = module.exports = function QuicServerConfig (buf) {
4 | this._buf = new Buffer(buf);
5 | this._tags = {};
6 | var i, n = buf.readUInt16LE(4), k, v, size, offset = 8 + 8 * n, cursor = offset;
7 | for (i = 0; i < n; ++i) {
8 | k = buf.slice(8 + (i * 8), 8 + (i * 8) + 4).toString('binary');
9 | size = buf.readUInt32LE(8 + (i * 8) + 4);
10 | v = buf.slice(cursor, offset + size);
11 | cursor = offset + size;
12 | this._tags[k] = v;
13 | }
14 | };
15 | Object.defineProperty(QuicServerConfig.prototype, 'tags', {
16 | 'get': function () {
17 | return this._tags;
18 | }
19 | });
20 | Object.defineProperty(QuicServerConfig.prototype, 'id', {
21 | 'get': function () {
22 | return this._tags[QuicTag.SCID];
23 | }
24 | });
25 | Object.defineProperty(QuicServerConfig.prototype, 'publicKeys', {
26 | 'get': function () {
27 | var keys = [];
28 | var buf = this._tags[QuicTag.PUBS];
29 | var i, bufLen = buf.length, len;
30 | for (i = 0; i < bufLen; ++i) {
31 | len = buf.readUIntLE(i, 3);
32 | i += 3;
33 | keys.push(buf.slice(i, i + len));
34 | i += len;
35 | }
36 | return keys;
37 | }
38 | });
39 | Object.defineProperty(QuicServerConfig.prototype, 'orbit', {
40 | 'get': function () {
41 | return this._tags[QuicTag.OBIT];
42 | }
43 | });
44 | QuicServerConfig.prototype.getBuffer = function getBuffer () {
45 | return this._buf;
46 | };
47 |
--------------------------------------------------------------------------------
/lib/QuicSession.js:
--------------------------------------------------------------------------------
1 | var util = require('util');
2 | var events = require('events');
3 |
4 | var QuicConnection = require('./QuicConnection');
5 | var QuicSequenceNumber = require('./QuicSequenceNumber');
6 | var QuicStreamId = require('./QuicStreamId');
7 | var QuicPacketManager = require('./QuicPacketManager');
8 | var QuicClientHello = require('./QuicClientHello');
9 | var QuicOffset = require('./QuicOffset');
10 | var QuicNonce = require('./QuicNonce');
11 | var QuicTag = require('./QuicTag');
12 | var QuicServerConfig = require('./QuicServerConfig');
13 | var QuicKeyExchange = require('./QuicKeyExchange');
14 | var QuicDeltaTime = require('./QuicDeltaTime');
15 | var QuicStream = require('./stream/QuicStream');
16 | var QuicCryptoStream = require('./stream/QuicCryptoStream');
17 | var QuicHeadersStream = require('./stream/QuicHeadersStream');
18 | var QuicDataStream = require('./stream/QuicDataStream');
19 | var QuicPacket = require('./packet/QuicPacket');
20 | var QuicDecryptedPacket = require('./packet/QuicDecryptedPacket');
21 | var QuicFrame = require('./frame/QuicFrame');
22 | var QuicAckFrame = require('./frame/QuicAckFrame');
23 | var QuicStreamFrame = require('./frame/QuicStreamFrame');
24 | var QuicWindowUpdateFrame = require('./frame/QuicWindowUpdateFrame');
25 | var QuicPaddingFrame = require('./frame/QuicPaddingFrame');
26 | var QuicConnectionCloseFrame = require('./frame/QuicConnectionCloseFrame');
27 | var QuicEncrypter = require('./crypto/QuicEncrypter');
28 | var QuicDecrypter = require('./crypto/QuicDecrypter');
29 |
30 | var QuicSession = module.exports = function QuicSession(options) {
31 | QuicSession.super_.call(this);
32 |
33 | var self = this;
34 | this._seqNum = new QuicSequenceNumber();
35 | this._packetManager = new QuicPacketManager(this);
36 | this._keyExchange = new QuicKeyExchange();
37 | this._encrypter = [new QuicEncrypter('null')];
38 | this._decrypter = [new QuicDecrypter('null')];
39 | this._qConn = new QuicConnection(options);
40 | if (options.token) {
41 | this._serverToken = new Buffer(options.token, 'hex');
42 | }
43 | if (options.config) {
44 | this._serverConfig = new QuicServerConfig(new Buffer(options.config, 'hex'));
45 | }
46 | if (options.nonce) {
47 | this._serverNonce = new Buffer(options.nonce, 'hex');
48 | }
49 | if (options.keyExchange) {
50 | this._keyExchange.method = options.keyExchange;
51 | }
52 | this._qConn.on('negotiated', function () {
53 | self._versionNegotiated = true;
54 | });
55 | this._qConn.on('packet', function (packet) {
56 | self._processPacket(packet);
57 | });
58 | this._qConn.on('unsupported', function (e) {
59 | self.emit('error', e);
60 | });
61 |
62 | this._receiveWindow = new QuicOffset();
63 | this._receiveWindow.increment(16384);
64 |
65 | this._streams = {};
66 | this._streams[new QuicStreamId(1)] = new QuicCryptoStream(this, 1);
67 | this._streams[new QuicStreamId(3)] = new QuicHeadersStream(this, 3);
68 | this.cryptoStream.on('message', function (hsMessage) {
69 | if (hsMessage.messageTag === QuicTag.REJ) {
70 | if (self._keyExchange.ready) {
71 | return;
72 | }
73 | if (hsMessage.tags[QuicTag.STK]) {
74 | self._serverToken = hsMessage.tags[QuicTag.STK];
75 | }
76 | if (hsMessage.tags[QuicTag.SNO]) {
77 | self._serverNonce = hsMessage.tags[QuicTag.SNO];
78 | }
79 | if (hsMessage.tags[QuicTag.SCFG]) {
80 | self._serverConfig = new QuicServerConfig(hsMessage.tags[QuicTag.SCFG]);
81 | }
82 | if (self._serverConfig.tags[QuicTag.KEXS]) {
83 | self._keyExchange.method = 'C255';
84 | }
85 | self.emit('rejected', hsMessage);
86 | self._sendClientHello();
87 | self._setupCrypto(self._serverConfig.publicKeys[0], false);
88 | } else if (hsMessage.messageTag === QuicTag.SHLO) {
89 | self._established = true;
90 | if (hsMessage.tags[QuicTag.STK]) {
91 | self._serverToken = hsMessage.tags[QuicTag.STK];
92 | }
93 | if (hsMessage.tags[QuicTag.PUBS]) {
94 | self._setupCrypto(hsMessage.tags[QuicTag.PUBS], true);
95 | } else {
96 | self.emit('error', 'SHLO must have PUBS tag');
97 | }
98 | self.emit('established', hsMessage);
99 | } else {
100 | self.emit('error', hsMessage, 'unknown handshake message');
101 | }
102 | });
103 |
104 | this._sendClientHello();
105 | };
106 | util.inherits(QuicSession, events.EventEmitter);
107 |
108 | Object.defineProperty(QuicSession.prototype, 'options', {
109 | 'get': function () {
110 | var options = {};
111 | options.host = this._qConn.host;
112 | options.port = this._qConn.port;
113 | if (this._serverToken) {
114 | options.token = this._serverToken.toString('hex');
115 | }
116 | if (this._serverConfig) {
117 | options.config = this._serverConfig.getBuffer().toString('hex');
118 | }
119 | if (this._serverNonce) {
120 | options.nonce = this._serverNonce.toString('hex');
121 | }
122 | if (this._keyExchange.method) {
123 | options.keyExchange = this._keyExchange.method;
124 | }
125 | return options;
126 | }
127 | });
128 |
129 | Object.defineProperty(QuicSession.prototype, 'connection', {
130 | 'get': function () {
131 | return this._qConn;
132 | }
133 | });
134 | Object.defineProperty(QuicSession.prototype, 'isEstablished', {
135 | 'get': function () {
136 | return this._established;
137 | }
138 | });
139 | Object.defineProperty(QuicSession.prototype, 'cryptoStream', {
140 | 'get': function () {
141 | return this._streams[new QuicStreamId(1)];
142 | }
143 | });
144 | Object.defineProperty(QuicSession.prototype, 'headersStream', {
145 | 'get': function () {
146 | return this._streams[new QuicStreamId(3)];
147 | }
148 | });
149 | QuicSession.prototype.sendFrame = function sendFrame (frame, callback) {
150 | var self = this;
151 | var packet = new QuicDecryptedPacket();
152 | if (!this._versionNegotiated) {
153 | packet.version = this._qConn.version;
154 | }
155 | packet.connectionId = this._qConn.id;
156 | packet.sequenceNumber = this._seqNum;
157 | packet.data = Buffer.concat([frame.getBuffer(), new QuicPaddingFrame().getBuffer()]);
158 | this.sendPacket(packet, function () {
159 | self.emit('sent', frame);
160 | if (callback) {
161 | callback();
162 | }
163 | });
164 | this._seqNum.increment();
165 | };
166 | QuicSession.prototype.sendPacket = function sendPacket (packet, callback) {
167 | var self = this;
168 | this._qConn.sendPacket(this._encrypter[0].encrypt(packet), function () {
169 | if (callback) {
170 | callback();
171 | }
172 | });
173 | };
174 |
175 | QuicSession.prototype.createDataStream = function createDataStream () {
176 | var largestStreamId = Math.max.apply({}, Object.keys(this._streams));
177 | var streamId = largestStreamId + ((largestStreamId & 0x01) ? 2 : 1);
178 | var stream = new QuicDataStream(this, streamId);
179 | this._streams[stream.id] = stream;
180 | return stream;
181 | };
182 |
183 | QuicSession.prototype.incrementWindow = function incrementWindow (size) {
184 | this._receiveWindow.increment(size);
185 | var wuf = new QuicWindowUpdateFrame();
186 | wuf.byteOffset = this._receiveWindow;
187 | this.sendFrame(wuf);
188 | };
189 |
190 | QuicSession.prototype._processPacket = function processPacket (packet) {
191 | var data = null,
192 | frame = null,
193 | cursor = 0,
194 | decrypted = null;
195 | decrypted = this._decryptPacket(packet);
196 | if (!decrypted) {
197 | this.emit('error', 'failed to decrypt (seq ' + packet.sequenceNumber + ')');
198 | return;
199 | }
200 | data = decrypted.data;
201 | var sendAck = false;
202 | while (cursor < data.length) {
203 | frame = QuicFrame.create(data.slice(cursor), packet);
204 | if (frame) {
205 | cursor += frame.size;
206 | this.emit('frame', frame);
207 | if (!this._processFrame(frame)) {
208 | break;
209 | }
210 | } else {
211 | this.emit('error', new Error('Unknown frame'));
212 | break;
213 | }
214 | }
215 | this.emit('packetProcessed', decrypted);
216 | if (this._shouldSendAck) {
217 | var f = new QuicAckFrame();
218 | f.entropy = this._packetManager.receivedEntropy;
219 | f.largestObserved = this._packetManager.largestObservedSeqNum;
220 | f.largestObservedDeltaTime = new QuicDeltaTime();
221 | f.deltaLargestObserved = 0;
222 | f.timeSinceLargestObserved = 0;
223 | this.sendFrame(f);
224 | this._shouldSendAck = false;
225 | }
226 | };
227 |
228 | QuicSession.prototype._decryptPacket = function decryptPacket (packet) {
229 | var decrypted = this._decrypter[0].decrypt(packet);
230 | if (!decrypted) {
231 | if (this._decrypter[1]) {
232 | console.log('@@ trying another decrypter');
233 | decrypted = this._decrypter[1].decrypt(packet);
234 | if (decrypted) {
235 | console.log('@@ switched (seq ' + packet.sequenceNumber + ')');
236 | this._decrypter.shift();
237 | }
238 | }
239 | }
240 | return decrypted;
241 | };
242 |
243 | QuicSession.prototype._processFrame = function processFrame (frame) {
244 | if (frame instanceof QuicStreamFrame) {
245 | this._shouldSendAck = true;
246 | } else if (frame instanceof QuicPaddingFrame) {
247 | this._shouldSendAck = true;
248 | return false;
249 | } else if (frame instanceof QuicAckFrame) {
250 | } else if (frame instanceof QuicConnectionCloseFrame) {
251 | this._shouldSendAck = false;
252 | } else {
253 | this._shouldSendAck = true;
254 | }
255 | return true;
256 | };
257 | QuicSession.prototype._createClientHello = function createClientHello () {
258 | var chlo = new QuicClientHello();
259 | chlo.version = this._qConn.version;
260 | chlo.proofDemand = 'X509';
261 | chlo.serverName = this.options.host;
262 | // chlo.expectedLeafCertificate = 'abcdefgh';
263 | if (this._serverToken) {
264 | chlo.serverToken = this._serverToken;
265 | }
266 | if (this._serverConfig) {
267 | this._clientNonce = QuicNonce.generate(this._serverConfig.orbit);
268 | chlo.clientNonce = this._clientNonce;
269 | if (this._serverNonce) {
270 | chlo.serverNonce = this._serverNonce;
271 | }
272 | chlo.serverConfigId = this._serverConfig.id;
273 | chlo.encryptionAlgorithm = 'AESG';
274 | chlo.keyExchangeMethod = this._keyExchange.method;
275 | chlo.publicKey = this._keyExchange.publicKey;
276 | }
277 | return chlo;
278 | };
279 | QuicSession.prototype._sendClientHello = function sendClientHello() {
280 | var chlo = this._createClientHello();
281 | console.log(chlo);
282 | var frame = new QuicStreamFrame();
283 | frame.data = chlo.getBuffer();
284 | this.cryptoStream.sendFrame(frame);
285 | this._clientHello = chlo;
286 | };
287 |
288 | QuicSession.prototype._setupCrypto = function setupCrypto (serverPublicKey, forwardSecure) {
289 | if (!this._keyExchange.ready) {
290 | this._keyExchange.setup(
291 | this._qConn.id.getBuffer(),
292 | this._clientNonce,
293 | this._serverNonce,
294 | this._clientHello.getBuffer(),
295 | this._serverConfig.getBuffer()
296 | );
297 | }
298 | var keyAndIv = this._keyExchange.update(serverPublicKey, forwardSecure);
299 | this._encrypter[0] = new QuicEncrypter(
300 | 'aesg',
301 | keyAndIv.clientWriteKey,
302 | keyAndIv.clientWriteIv);
303 | this._decrypter[1] = new QuicDecrypter(
304 | 'aesg',
305 | keyAndIv.serverWriteKey,
306 | keyAndIv.serverWriteIv);
307 | };
308 |
309 |
--------------------------------------------------------------------------------
/lib/QuicStreamId.js:
--------------------------------------------------------------------------------
1 | var QuicStreamId = module.exports = function QuicStreamId (id) {
2 | if (id) {
3 | this._id = 0 + id;
4 | } else {
5 | this._id = 0;
6 | }
7 | };
8 | QuicStreamId.prototype.toString = function toString () {
9 | return this._id.toString();
10 | };
11 | QuicStreamId.prototype.valueOf = function valueOf () {
12 | return this._id;
13 | };
14 | QuicStreamId.prototype.getBuffer = function getBuffer (len) {
15 | if (!len) {
16 | len = 4;
17 | }
18 | var buf = new Buffer(len);
19 | buf.writeUIntLE(this._id, 0, len);
20 | return buf;
21 | };
22 |
--------------------------------------------------------------------------------
/lib/QuicTag.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | 'AEAD': "AEAD",
3 | 'CHLO': "CHLO", // Client HelLO
4 | 'CSCT': "CSCT", // Certificate Signed Cert Timestamp
5 | 'ICSL': "ICSL", // Idle Connection State Lifetime
6 | 'KEXS': "KEXS",
7 | 'MSPC': "MSPC", // Max Streams Per Connection
8 | 'NONC': "NONC", // NONCe
9 | 'OBIT': "OBIT", // Orbit
10 | 'PUBS': "PUBS",
11 | 'SCFG': "SCFG", // Server ConFiG
12 | 'SCID': "SCID", // Server Config ID
13 | 'SHLO': "SHLO", // Server HelLO
14 | 'SNO': "SNO\0", // Server NOnce
15 | 'STK': "STK\0",
16 | 'SNI': "SNI\0", // Server Name Indication
17 | 'PAD': "PAD\0", // PADding
18 | 'PDMD': "PDMD", // Proof DeManD
19 | 'REJ': "REJ\0", // REJect
20 | 'VER': "VER\0", // VERsion
21 | 'XLCT': "XLCT", // eXpected Leaf CerTificate
22 | };
23 |
--------------------------------------------------------------------------------
/lib/crypto/C255.js:
--------------------------------------------------------------------------------
1 | var crypto = require('crypto');
2 | var ec = new (require('elliptic').ec)('curve25519');
3 |
4 | function flip(buf) {
5 | var i,
6 | n = buf.length,
7 | newBuf = new Buffer(n);
8 |
9 | for (i = 0; i < n; ++i) {
10 | newBuf[i] = buf[n - i - 1];
11 | }
12 | return newBuf;
13 | }
14 |
15 | var C255 = module.exports = {
16 | makeSecretKey: function makeSecretKey () {
17 | return new Buffer(ec.genKeyPair().getPrivate('hex'), 'hex');
18 | },
19 | derivePublicKey: function derivePublicKey (secretKey) {
20 | return flip(new Buffer(ec.keyFromPrivate(secretKey).getPublic('hex'), 'hex'));
21 | },
22 | deriveSharedKey: function deriveSharedKey (secretKey, publicKey) {
23 | var pub = ec.keyFromPublic(flip(publicKey)).getPublic();
24 | var kp = ec.keyFromPrivate(secretKey);
25 | var sharedKeyHex = kp.derive(pub).toString(16);
26 | if (sharedKeyHex.length === 63) {
27 | sharedKeyHex = '0' + sharedKeyHex;
28 | }
29 | var sharedKey = new Buffer(sharedKeyHex, 'hex');
30 | return flip(sharedKey);
31 | // return flip(new Buffer(ec.keyFromPrivate(secretKey).derive(pub).toString(16), 'hex'));
32 | }
33 | };
34 |
--------------------------------------------------------------------------------
/lib/crypto/CryptoUtil.js:
--------------------------------------------------------------------------------
1 | var fnv = require('fnv-plus');
2 |
3 | module.exports = {
4 | fnvHash: function (data1, data2) {
5 | var buf = Buffer.concat([data1, data2]).toString('binary');
6 | return new Buffer(fnv.hash(buf, 128).value.toByteArray().slice(-12).reverse());
7 | }
8 | };
9 |
--------------------------------------------------------------------------------
/lib/crypto/QuicDecrypter.js:
--------------------------------------------------------------------------------
1 | var crypto = require('crypto');
2 | var CryptoUtil = require('./CryptoUtil');
3 | var QuicDecryptedPacket = require('../packet/QuicDecryptedPacket');
4 |
5 | var QuicDecrypter = module.exports = function QuicDecrypter (algo, key, ivPrefix) {
6 | this._impl = new impl[algo](key, ivPrefix);
7 | };
8 | QuicDecrypter.prototype.decrypt = function decrypt (packet) {
9 | return this._impl.decrypt(packet);
10 | };
11 |
12 | var NullDecrypter = function NullDecrypter (key, ivPrefix) {
13 | };
14 | NullDecrypter.prototype.decrypt = function decrypt (packet) {
15 | var items = [];
16 | items.push(packet.associatedData);
17 | items.push(packet.payload.slice(12));
18 | var hash = CryptoUtil.fnvHash(items[0], items[1]);
19 | if (hash.equals(packet.payload.slice(0, 12))) {
20 | return new QuicDecryptedPacket(Buffer.concat(items));
21 | } else {
22 | return null;
23 | }
24 | };
25 |
26 | var AesgDecrypter = function AesgDecrypter (key, ivPrefix) {
27 | this._key = key;
28 | this._ivPrefix = ivPrefix;
29 | };
30 | AesgDecrypter.prototype.decrypt = function decrypt (packet) {
31 | var iv = Buffer.concat([this._ivPrefix, packet.sequenceNumber.getBuffer()]);
32 | var decipher = crypto.createDecipheriv('aes-128-gcm', this._key, iv);
33 | decipher.setAAD(packet.associatedData);
34 | decipher.setAuthTag(packet.payload.slice(-12));
35 | var encrypted = packet.payload.slice(0, -12);
36 | var decrypted = decipher.update(encrypted);
37 | try {
38 | decipher.final();
39 | decrypted = new QuicDecryptedPacket(Buffer.concat([packet.associatedData, decrypted]));
40 | } catch (e) {
41 | // console.log(e);
42 | decrypted = null;
43 | }
44 | return decrypted;
45 | };
46 |
47 | var impl = {
48 | 'null': NullDecrypter,
49 | 'aesg': AesgDecrypter,
50 | };
51 |
--------------------------------------------------------------------------------
/lib/crypto/QuicEncrypter.js:
--------------------------------------------------------------------------------
1 | var crypto = require('crypto');
2 | var CryptoUtil = require('./CryptoUtil');
3 | var QuicPacket = require('../packet/QuicPacket');
4 |
5 | var QuicEncrypter = module.exports = function QuicEncrypter (algo, key, ivPrefix) {
6 | this._impl = new impl[algo](key, ivPrefix);
7 | };
8 | QuicEncrypter.prototype.encrypt = function encrypt (packet) {
9 | return this._impl.encrypt(packet);
10 | };
11 |
12 | var NullEncrypter = function NullEncrypter (key, ivPrefix) {
13 | };
14 | NullEncrypter.prototype.encrypt = function encrypt (packet) {
15 | var items = new Array(3);
16 | items[0] = packet.associatedData;
17 | items[2] = packet.payload;
18 | items[1] = CryptoUtil.fnvHash(items[0], items[2]);
19 | return new QuicPacket(Buffer.concat(items));
20 | };
21 |
22 | var AesgEncrypter = function AesgEncrypter (key, ivPrefix) {
23 | this._key = key;
24 | this._ivPrefix = ivPrefix;
25 | };
26 | AesgEncrypter.prototype.encrypt = function encrypt (packet) {
27 | var iv = Buffer.concat([this._ivPrefix, packet.sequenceNumber.getBuffer()]);
28 | var cipher = crypto.createCipheriv('aes-128-gcm', this._key, iv);
29 | cipher.setAAD(packet.associatedData);
30 | var items = [];
31 | items.push(packet.associatedData);
32 | items.push(cipher.update(packet.payload));
33 | items.push(cipher.final());
34 | items.push(cipher.getAuthTag().slice(0, 12));
35 | return new QuicPacket(Buffer.concat(items));
36 | };
37 |
38 | var impl = {
39 | 'null': NullEncrypter,
40 | 'aesg': AesgEncrypter,
41 | };
42 |
43 |
--------------------------------------------------------------------------------
/lib/crypto/hkdf.js:
--------------------------------------------------------------------------------
1 | /*
2 | * HMAC-based Extract-and-Expand Key Derivation Function (HKDF)
3 | * https://tools.ietf.org/rfc/rfc5869.txt
4 | */
5 | var crypto = require('crypto');
6 | var HKDF = module.exports = function HKDF (hashAlg) {
7 | this._hashAlg = hashAlg;
8 | };
9 |
10 | HKDF.prototype.extract = function extract (salt, ikm) {
11 | if (!salt) {
12 | salt = new Buffer(0);
13 | }
14 | var hmac = crypto.createHmac(this._hashAlg, salt);
15 | hmac.update(ikm);
16 | return hmac.digest();
17 | };
18 |
19 | HKDF.prototype.expand = function expand (prk, info, len) {
20 | var i,
21 | hashLen = crypto.createHash(this._hashAlg).digest().length,
22 | n = Math.ceil(len / hashLen),
23 | t = new Array(n + 1);
24 | t[0] = new Buffer(0);
25 | for (i = 1; i <= n; ++i) {
26 | t[i] = crypto.createHmac(this._hashAlg, prk)
27 | .update(Buffer.concat([t[i - 1], info, new Buffer([i])]))
28 | .digest();
29 | }
30 | return Buffer.concat(t, hashLen * n).slice(0, len);
31 | };
32 |
33 | HKDF.prototype.derive = function derive (salt, ikm, info, len) {
34 | return this.expand(this.extract(salt, ikm), info, len);
35 | };
36 |
--------------------------------------------------------------------------------
/lib/frame/QuicAckFrame.js:
--------------------------------------------------------------------------------
1 | var util = require('util');
2 | var QuicDeltaTime = require('../QuicDeltaTime');
3 | var QuicSequenceNumber = require('../QuicSequenceNumber');
4 | var QuicFrame = require('./QuicFrame');
5 |
6 | var QuicAckFrame = module.exports = function QuicAckFrame (buf, packet) {
7 | QuicAckFrame.super_.call(this, buf, packet);
8 |
9 | this._flags = 0x40;
10 | this._entropy = 0;
11 | this._lgtObservedSeqNum = new QuicSequenceNumber();
12 | this._lgtObservedDeltaTime = new QuicDeltaTime();
13 | this._numTimestamp = 1;
14 | this._deltaLgtObservedSeqNum = 0;
15 | this._timeSinceLgtObserved = 0;
16 | this._timestamps = [];
17 | this._numberRanges = 0;
18 | this._numRevived = 0;
19 |
20 | if (buf) {
21 | var cursor = 0;
22 | this._flags = buf[cursor];
23 | cursor += 1;
24 | this._entropy = buf[cursor];
25 | cursor += 1;
26 | this._lgtObservedSeqNum = new QuicSequenceNumber(buf.slice(
27 | cursor,
28 | cursor + this.largestObservedSeqNumLen));
29 | cursor += this.largestObservedSeqNumLen;
30 | this._lgtObservedDeltaTime = new QuicDeltaTime(buf.slice(
31 | cursor,
32 | cursor + 2));
33 | cursor += 2;
34 | this._numTimestamp = buf[cursor];
35 | cursor += 1;
36 | this._deltaLgtObservedSeqNum = buf[cursor];
37 | cursor += 1;
38 | this._timeSinceLgtObserved = buf.readUInt32LE(cursor);
39 | cursor += 4;
40 | this._timestamps = new Array(this._numTimestamp);
41 | for (i = 0; i < this._numTimestamp - 1; ++i) {
42 | cursor += 1 + 2;
43 | }
44 | if (this.hasNackRange) {
45 | this._numberRanges = buf[cursor];
46 | cursor += (this.missingPacketRangeSeqNumLen + 1) * this.numberRanges;
47 | this._numRevived = buf[cursor];
48 | cursor += this.largestObservedSeqNumLen * this.numRevived;
49 | } else {
50 | this._numberRanges = 0;
51 | this._numRevived = 0;
52 | }
53 | }
54 | };
55 | util.inherits(QuicAckFrame, QuicFrame);
56 | Object.defineProperty(QuicAckFrame.prototype, 'size', {
57 | 'get': function () {
58 | var size = 0;
59 | size += 1; // Type
60 | size += 1; // Received Entropy
61 | size += this.largestObservedSeqNumLen;
62 | size += 2; // Largest Observed Delta Time
63 | size += 1; // Num Timestamp
64 | if (this.numTimestamp) {
65 | size += 1; // Delta Largest Observed
66 | size += 4; // Time Since Largest Observed
67 | size += (1 + 2) * (this.numTimestamp - 1); // (Delta Largest Observed + Time Since Previous Timestamp) * (n - 1)
68 | }
69 | if (this.hasNackRange) {
70 | size += 1; // Number Ranges
71 | size += (this.missingPacketRangeSeqNumLen + 1) * this.numberRanges;
72 | size += 1; // Num Revived
73 | size += this.largestObservedSeqNumLen * this.numRevived;
74 | }
75 | return size;
76 | }
77 | });
78 | Object.defineProperty(QuicAckFrame.prototype, 'hasNackRange', {
79 | 'get': function () {
80 | return (this._flags & 0x20) !== 0;
81 | }
82 | });
83 | Object.defineProperty(QuicAckFrame.prototype, 'isTruncated', {
84 | 'get': function () {
85 | return (this._flags & 0x10) !== 0;
86 | }
87 | });
88 | Object.defineProperty(QuicAckFrame.prototype, 'entropy', {
89 | 'get': function () {
90 | return this._entropy;
91 | },
92 | 'set': function (entropy) {
93 | this._entropy = entropy;
94 | }
95 | });
96 | Object.defineProperty(QuicAckFrame.prototype, 'largestObserved', {
97 | 'get': function () {
98 | return this._lgtObservedSeqNum;
99 | },
100 | 'set': function (seqNum) {
101 | this._lgtObservedSeqNum = seqNum;
102 | }
103 | });
104 | Object.defineProperty(QuicAckFrame.prototype, 'largestObservedDeltaTime', {
105 | 'get': function () {
106 | return this._lgtObservedDeltaTime;
107 | },
108 | 'set': function (deltaTime) {
109 | this._lgtObservedDeltaTime = deltaTime;
110 | }
111 | });
112 | Object.defineProperty(QuicAckFrame.prototype, 'numTimestamp', {
113 | 'get': function () {
114 | return this._numTimestamp;
115 | }
116 | });
117 | Object.defineProperty(QuicAckFrame.prototype, 'deltaLargestObserved', {
118 | 'get': function () {
119 | return this._deltaLgtObservedSeqNum;
120 | },
121 | 'set': function (delta) {
122 | this._deltaLgtObservedSeqNum = delta;
123 | }
124 | });
125 | Object.defineProperty(QuicAckFrame.prototype, 'timeSinceLargestObserved', {
126 | 'get': function () {
127 | return this._timeSinceLgtObserved;
128 | },
129 | 'set': function (time) {
130 | this._timeSinceLgtObserved = time;
131 | }
132 | });
133 | Object.defineProperty(QuicAckFrame.prototype, 'numberRanges', {
134 | 'get': function () {
135 | return this._numberRanges;
136 | }
137 | });
138 | Object.defineProperty(QuicAckFrame.prototype, 'numRevived', {
139 | 'get': function () {
140 | return this._numRevived;
141 | }
142 | });
143 | Object.defineProperty(QuicAckFrame.prototype, 'missingPacketRangeSeqNumLen', {
144 | 'get': function () {
145 | switch (this._flags & 0x03) {
146 | case 0x00:
147 | return 1;
148 | case 0x01:
149 | return 2;
150 | case 0x02:
151 | return 4;
152 | case 0x03:
153 | return 6;
154 | }
155 | }
156 | });
157 | Object.defineProperty(QuicAckFrame.prototype, 'largestObservedSeqNumLen', {
158 | 'get': function () {
159 | switch (this._flags & 0x0C) {
160 | case 0x00:
161 | return 1;
162 | case 0x04:
163 | return 2;
164 | case 0x08:
165 | return 4;
166 | case 0x0C:
167 | return 6;
168 | }
169 | }
170 | });
171 | QuicAckFrame.prototype.toString = function toString () {
172 | return util.format('Ack [H: %d, LO: %s]', this._entropy, this._lgtObservedSeqNum);
173 | };
174 | QuicAckFrame.prototype.getBuffer = function getBuffer () {
175 | var items = [
176 | new Buffer([this._flags, this._entropy]),
177 | this._lgtObservedSeqNum.getBuffer(this._lgtObservedSeqNum.minLen),
178 | new Buffer(2), // Largest Observed Delta Time
179 | new Buffer([this._timestamps.length + 1]),
180 | new Buffer([this._deltaLgtObservedSeqNum]),
181 | new Buffer(4) // Time Since Largest Observed
182 | ];
183 | items[2].writeUInt16LE(this._lgtObservedDeltaTime);
184 | items[5].writeUInt32LE(this._timeSinceLgtObserved);
185 | if (this.hasNackRange) {
186 | }
187 | var buf = Buffer.concat(items);
188 | return buf;
189 | };
190 |
--------------------------------------------------------------------------------
/lib/frame/QuicBlockedFrame.js:
--------------------------------------------------------------------------------
1 | var util = require('util');
2 | var QuicBlockedFrame = module.exports = function QuicBlockedFrame (buf) {
3 | this._streamId = 0;
4 |
5 | if (buf) {
6 | this._streamId = buf.readUInt32LE(1);
7 | }
8 | };
9 | Object.defineProperty(QuicBlockedFrame.prototype, 'size', {
10 | 'get': function () {
11 | return 5;
12 | }
13 | });
14 | Object.defineProperty(QuicBlockedFrame.prototype, 'streamId', {
15 | 'get': function () {
16 | return this._streamId;
17 | }
18 | });
19 | QuicBlockedFrame.prototype.toString = function toString () {
20 | return util.format('Blocked [SI: %d]', this.streamId);
21 | };
22 | QuicBlockedFrame.prototype.getBuffer = function getBuffer() {
23 | var buf = new Buffer([0x05, 0x00, 0x00, 0x00, 0x00]);
24 | items[0].writeUInt32LE(this._streamId, 1);
25 | return buf;
26 | };
27 |
--------------------------------------------------------------------------------
/lib/frame/QuicCongestionFeedbackFrame.js:
--------------------------------------------------------------------------------
1 | var QuicCongestionFeedbackFrame = module.exports = function QuicCongestionFeedbackFrame (buf) {
2 | };
3 | Object.defineProperty(QuicCongestionFeedbackFrame.prototype, 'size', {
4 | 'get': function () {
5 | return 1;
6 | }
7 | });
8 | QuicCongestionFeedbackFrame.prototype.toString = function toString () {
9 | return 'CongestionFeedback';
10 | };
11 | QuicCongestionFeedbackFrame.prototype.getBuffer = function getBuffer() {
12 | return new Buffer([0x20]);
13 | };
14 |
15 |
--------------------------------------------------------------------------------
/lib/frame/QuicConnectionCloseFrame.js:
--------------------------------------------------------------------------------
1 | var util = require('util');
2 | var QuicConnectionCloseFrame = module.exports = function QuicConnectionCloseFrame (buf) {
3 | this._errorCode = 0;
4 | this._reasonLen = 0;
5 |
6 | if (buf) {
7 | this._errorCode = buf.readUInt32LE(1);
8 | this._reasonLen = buf.readUInt16LE(5);
9 | this._reason = buf.toString('ascii', 7, 7 + this._reasonLen);
10 | }
11 | };
12 | Object.defineProperty(QuicConnectionCloseFrame.prototype, 'size', {
13 | 'get': function () {
14 | return 7 + this.reasonLen;
15 | }
16 | });
17 | Object.defineProperty(QuicConnectionCloseFrame.prototype, 'errorCode', {
18 | 'get': function () {
19 | return this._errorCode;
20 | }
21 | });
22 | Object.defineProperty(QuicConnectionCloseFrame.prototype, 'reasonLen', {
23 | 'get': function () {
24 | return this._reasonLen;
25 | }
26 | });
27 | Object.defineProperty(QuicConnectionCloseFrame.prototype, 'reason', {
28 | 'get': function () {
29 | return this._reason;
30 | }
31 | });
32 | QuicConnectionCloseFrame.prototype.toString = function toString () {
33 | return util.format('ConnectionClose [EC: %d, RL: %d, R: %s]', this.errorCode, this.reasonLen, this.reason);
34 | };
35 | QuicConnectionCloseFrame.prototype.getBuffer = function getBuffer() {
36 | var items = [];
37 | items.push(new Buffer([0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]));
38 | items[0].writeUInt32LE(this._errorCode, 1);
39 | items[0].writeUInt16LE(this._reasonLen, 5);
40 | if (this._reasonLen) {
41 | items.push(new Buffer(this._reason));
42 | }
43 | return Buffer.concat(items);
44 | };
45 |
--------------------------------------------------------------------------------
/lib/frame/QuicFrame.js:
--------------------------------------------------------------------------------
1 | var QuicFrame = module.exports = function QuicFrame (buf, packet) {
2 | if (packet) {
3 | this._packet = packet;
4 | }
5 | };
6 |
7 | QuicFrame.create = function create (buf, packet) {
8 | var frameType, instance;
9 |
10 | if (buf[0] > 0x20) {
11 | if ((buf[0] & 0x80) !== 0) {
12 | frameType = 'Stream';
13 | } else if ((buf[0] & 0x40) !== 0) {
14 | frameType = 'Ack';
15 | } else if ((buf[0] & 0x20) !== 0) {
16 | frameType = 'CongestionFeedback';
17 | }
18 | } else {
19 | frameType = getTypeById(buf[0]);
20 | }
21 | if (frameType) {
22 | var klass = require('./Quic' + frameType + 'Frame');
23 | if (klass) {
24 | instance = new klass(buf, packet);
25 | }
26 | }
27 | return instance;
28 | };
29 |
30 | function getTypeById(id) {
31 | return frameTypes[id];
32 | }
33 |
34 | var frameTypes = [
35 | 'Padding',
36 | 'RstStream',
37 | 'ConnectionClose',
38 | 'Goaway',
39 | 'WindowUpdate',
40 | 'Blocked',
41 | 'StopWaiting',
42 | 'Ping',
43 | ];
44 |
--------------------------------------------------------------------------------
/lib/frame/QuicPaddingFrame.js:
--------------------------------------------------------------------------------
1 | var QuicPaddingFrame = module.exports = function QuicPaddingFrame (buf) {
2 | };
3 | Object.defineProperty(QuicPaddingFrame.prototype, 'size', {
4 | 'get': function () {
5 | return 1;
6 | }
7 | });
8 | QuicPaddingFrame.prototype.toString = function toString () {
9 | return 'Padding';
10 | };
11 | QuicPaddingFrame.prototype.getBuffer = function getBuffer() {
12 | return new Buffer([0x00, 0x00]);
13 | };
14 |
--------------------------------------------------------------------------------
/lib/frame/QuicPingFrame.js:
--------------------------------------------------------------------------------
1 | var QuicPingFrame = module.exports = function QuicPingFrame (buf) {
2 | };
3 | Object.defineProperty(QuicPingFrame.prototype, 'size', {
4 | 'get': function () {
5 | return 1;
6 | }
7 | });
8 | QuicPingFrame.prototype.toString = function toString () {
9 | return 'Ping';
10 | };
11 | QuicPingFrame.prototype.getBuffer = function getBuffer() {
12 | return new Buffer([0x07]);
13 | };
14 |
--------------------------------------------------------------------------------
/lib/frame/QuicRstStreamFrame.js:
--------------------------------------------------------------------------------
1 | var util = require('util');
2 | var QuicRstStreamFrame = module.exports = function QuicRstStreamFrame (buf) {
3 | this._streamId = 0;
4 | this._errorCode = 0;
5 |
6 | if (buf) {
7 | this._streamId = buf.readUInt32LE(1);
8 | this._errorCode = buf.readUInt32LE(5);
9 | }
10 | };
11 | Object.defineProperty(QuicRstStreamFrame.prototype, 'size', {
12 | 'get': function () {
13 | return 9;
14 | }
15 | });
16 | Object.defineProperty(QuicRstStreamFrame.prototype, 'streamId', {
17 | 'get': function () {
18 | return this._streamId;
19 | }
20 | });
21 | Object.defineProperty(QuicRstStreamFrame.prototype, 'errorCode', {
22 | 'get': function () {
23 | return this._errorCode;
24 | }
25 | });
26 | QuicRstStreamFrame.prototype.toString = function toString () {
27 | return util.format('RstStream [SI: %d, EC: %d]', this.streamId, this.errorCode);
28 | };
29 | QuicRstStreamFrame.prototype.getBuffer = function getBuffer() {
30 | var buf = new Buffer([0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]);
31 | items[0].writeUInt32LE(this._streamId, 1);
32 | items[0].writeUInt32LE(this._errorCode, 5);
33 | return buf;
34 | };
35 |
--------------------------------------------------------------------------------
/lib/frame/QuicStopWaitingFrame.js:
--------------------------------------------------------------------------------
1 | var util = require('util');
2 | var QuicFrame = require('./QuicFrame');
3 | var QuicSequenceNumber = require('../QuicSequenceNumber');
4 | var QuicStopWaitingFrame = module.exports = function QuicStopWaitingFrame (buf, packet) {
5 | QuicStopWaitingFrame.super_.call(this, buf, packet);
6 | this._sentEntropy = 0;
7 | this._leastUnackedDelta = new QuicSequenceNumber();
8 |
9 | if (buf) {
10 | this._sentEntropy = buf[1];
11 | this._leastUnackedDelta = new QuicSequenceNumber(buf.slice(1, 1 + this._packet.sequenceNumLen));
12 | }
13 | };
14 | util.inherits(QuicStopWaitingFrame, QuicFrame);
15 | Object.defineProperty(QuicStopWaitingFrame.prototype, 'size', {
16 | 'get': function () {
17 | return 2 + this._packet.sequenceNumLen;
18 | }
19 | });
20 | Object.defineProperty(QuicStopWaitingFrame.prototype, 'sentEntropy', {
21 | 'get': function () {
22 | return this._sentEntropy;
23 | }
24 | });
25 | Object.defineProperty(QuicStopWaitingFrame.prototype, 'leastUnackedDelta', {
26 | 'get': function () {
27 | return this._leastUnackedDelta;
28 | }
29 | });
30 | QuicStopWaitingFrame.prototype.toString = function toString () {
31 | var str = util.format('StopWaiting [SE: %d, LUD: %s]',
32 | this.sentEntropy, this.leastUnackedDelta);
33 | return str;
34 | };
35 | QuicStopWaitingFrame.prototype.getBuffer = function getBuffer() {
36 | return new Buffer([0x06]);
37 | };
38 |
--------------------------------------------------------------------------------
/lib/frame/QuicStreamFrame.js:
--------------------------------------------------------------------------------
1 | var util = require('util');
2 | var QuicStreamId = require('../QuicStreamId');
3 | var QuicOffset = require('../QuicOffset');
4 |
5 | var QuicStreamFrame = module.exports = function QuicStreamFrame (buf) {
6 | this._flags = 0xA0;
7 | this._streamId = new QuicStreamId();
8 | this._offset = new QuicOffset();
9 | this._data = new Buffer(0);
10 |
11 | if (buf) {
12 | this._flags = buf[0];
13 | this._streamId = new QuicStreamId(buf.readUIntLE(1), this.streamIdLen);
14 | switch (this.offsetLen) {
15 | case 0:
16 | case 1:
17 | case 2:
18 | case 3:
19 | case 4:
20 | case 5:
21 | case 6:
22 | case 7:
23 | case 8:
24 | }
25 | if (this.hasDataLen) {
26 | var len = buf.readUInt16LE(this.size - 2);
27 | this._data = new Buffer(buf.slice(this.size, this.size + len));
28 | } else {
29 | this._data = new Buffer(buf.slice(this.size));
30 | }
31 | }
32 | };
33 | Object.defineProperty(QuicStreamFrame.prototype, 'size', {
34 | 'get': function () {
35 | return 1 + this.streamIdLen + this.offsetLen + (this.hasDataLen ? 2 : 0) + this.data.length;
36 | }
37 | });
38 | Object.defineProperty(QuicStreamFrame.prototype, 'isFin', {
39 | 'get': function () {
40 | return (this._flags & 0x40) !== 0;
41 | },
42 | 'set': function (value) {
43 | this._flags &= ~0x40;
44 | if (value) {
45 | this._flags |= 0x40;
46 | }
47 | }
48 | });
49 | Object.defineProperty(QuicStreamFrame.prototype, 'hasDataLen', {
50 | 'get': function () {
51 | return (this._flags & 0x20) !== 0;
52 | }
53 | });
54 | Object.defineProperty(QuicStreamFrame.prototype, 'offsetLen', {
55 | 'get': function () {
56 | switch (this._flags & 0x1C) {
57 | case 0x00:
58 | return 0;
59 | case 0x04:
60 | return 2;
61 | case 0x08:
62 | return 3;
63 | case 0x0C:
64 | return 4;
65 | case 0x10:
66 | return 5;
67 | case 0x14:
68 | return 6;
69 | case 0x18:
70 | return 7;
71 | case 0x1C:
72 | return 8;
73 | }
74 | }
75 | });
76 | Object.defineProperty(QuicStreamFrame.prototype, 'streamIdLen', {
77 | 'get': function () {
78 | switch (this._flags & 0x03) {
79 | case 0x00:
80 | return 1;
81 | case 0x01:
82 | return 2;
83 | case 0x02:
84 | return 3;
85 | case 0x03:
86 | return 4;
87 | }
88 | }
89 | });
90 | Object.defineProperty(QuicStreamFrame.prototype, 'streamId', {
91 | 'get': function () {
92 | return this._streamId;
93 | },
94 | 'set': function (id) {
95 | this._streamId = new QuicStreamId(id);
96 | }
97 | });
98 | Object.defineProperty(QuicStreamFrame.prototype, 'offset', {
99 | 'get': function () {
100 | return this._offset;
101 | },
102 | 'set': function (offset) {
103 | this._offset.set(offset);
104 | if (this._offset.minLen) {
105 | this._flags = this._flags & ~0x1C | (Math.max(this._offset.minLen, 2) - 1) << 2;
106 | }
107 | }
108 | });
109 | Object.defineProperty(QuicStreamFrame.prototype, 'dataLen', {
110 | 'get': function () {
111 | return this._data.length;
112 | },
113 | });
114 | Object.defineProperty(QuicStreamFrame.prototype, 'data', {
115 | 'get': function () {
116 | return this._data;
117 | },
118 | 'set': function (data) {
119 | this._data = new Buffer(data);
120 | }
121 | });
122 | QuicStreamFrame.prototype.toString = function toString () {
123 | return util.format('Stream [SID: %d, DLEN: %d]', this.streamId, this.data.length);
124 | };
125 | QuicStreamFrame.prototype.getBuffer = function getBuffer() {
126 | var items = [
127 | new Buffer([this._flags]),
128 | this._streamId.getBuffer(this.streamIdLen),
129 | ];
130 | if (this.offsetLen) {
131 | items.push(this._offset.getBuffer(this._offset.minLen));
132 | }
133 | if (this.hasDataLen) {
134 | var buf = new Buffer(2);
135 | buf.writeUInt16LE(this.dataLen);
136 | items.push(buf);
137 | }
138 | items.push(this._data);
139 | return Buffer.concat(items);
140 | };
141 |
--------------------------------------------------------------------------------
/lib/frame/QuicWindowUpdateFrame.js:
--------------------------------------------------------------------------------
1 | var util = require('util');
2 | var QuicStreamId = require('../QuicStreamId');
3 | var QuicOffset = require('../QuicOffset');
4 |
5 | var QuicWindowUpdateFrame = module.exports = function QuicWindowUpdateFrame (buf) {
6 | this._streamId = new QuicStreamId();
7 | this._byteOffset = new QuicOffset();
8 |
9 | if (buf) {
10 | this._streamId = new QuicStreamId(buf.readUInt32LE(1));
11 | this._byteOffset.set(buf.slice(5, 12));
12 | }
13 | };
14 | Object.defineProperty(QuicWindowUpdateFrame.prototype, 'size', {
15 | 'get': function () {
16 | return 13;
17 | }
18 | });
19 | Object.defineProperty(QuicWindowUpdateFrame.prototype, 'streamId', {
20 | 'get': function () {
21 | return this._streamId;
22 | },
23 | 'set': function (streamId) {
24 | this._streamId = new QuicStreamId(streamId);
25 | }
26 | });
27 | Object.defineProperty(QuicWindowUpdateFrame.prototype, 'byteOffset', {
28 | 'get': function () {
29 | return this._byteOffset;
30 | },
31 | 'set': function (byteOffset) {
32 | this._byteOffset.set(byteOffset);
33 | }
34 | });
35 | QuicWindowUpdateFrame.prototype.toString = function toString () {
36 | return util.format('WindowUpdate [SI: %d BO: %s]', this.streamId, this.byteOffset);
37 | };
38 | QuicWindowUpdateFrame.prototype.getBuffer = function getBuffer() {
39 | var items = [
40 | new Buffer([0x04]),
41 | this._streamId.getBuffer(),
42 | this._byteOffset.getBuffer()
43 | ];
44 | var buf = Buffer.concat(items);
45 | return buf;
46 | };
47 |
--------------------------------------------------------------------------------
/lib/packet/QuicDecryptedPacket.js:
--------------------------------------------------------------------------------
1 | var util = require('util');
2 | var QuicPacket = require('./QuicPacket');
3 |
4 | var QuicDecryptedPacket = module.exports = function QuicDecryptedPacket (buf) {
5 | QuicDecryptedPacket.super_.call(this, buf);
6 |
7 | this._privateFlags = 0;
8 | this._privateFlags |= 0x01; // TODO: Entropy flag should be set at random.
9 | this._fec = 0;
10 | this._data = new Buffer(0);
11 |
12 | if (buf) {
13 | this._privateFlags = buf[this.size];
14 | if ((this._privateFlags & 0xF8) !== 0) {
15 | throw new Error('invalid packet data');
16 | }
17 | if (this.hasFEC) {
18 | this._fec = buf[this.size + 1];
19 | this._data = buf.slice(this.size + 2);
20 | } else {
21 | this._data = buf.slice(this.size + 1);
22 | }
23 | }
24 | };
25 | util.inherits(QuicDecryptedPacket, QuicPacket);
26 | Object.defineProperty(QuicDecryptedPacket.prototype, 'hasEntropy', {
27 | 'get': function () {
28 | return (this._privateFlags & 0x01) !== 0;
29 | }
30 | });
31 | Object.defineProperty(QuicDecryptedPacket.prototype, 'hasFecGroup', {
32 | 'get': function () {
33 | return (this._privateFlags & 0x02) !== 0;
34 | }
35 | });
36 | Object.defineProperty(QuicDecryptedPacket.prototype, 'isFec', {
37 | 'get': function () {
38 | return (this._privateFlags & 0x04) !== 0;
39 | }
40 | });
41 | Object.defineProperty(QuicDecryptedPacket.prototype, 'data', {
42 | 'get': function () {
43 | return this._data;
44 | },
45 | 'set': function (data) {
46 | this._data = data;
47 | var items = [];
48 | items.push(new Buffer([this._privateFlags]));
49 | if (this.hasFEC) {
50 | items.push(new Buffer([this._fec]));
51 | }
52 | items.push(this._data);
53 | this._payload = Buffer.concat(items);
54 | }
55 | });
56 | QuicDecryptedPacket.prototype.toString = function toString () {
57 | var str = QuicDecryptedPacket.super_.prototype.toString.call(this) + "\n";
58 | str += "Private flags:\n";
59 | str += " ENTROPY: " + this.hasEntropy + "\n";
60 | str += " FEC_GROUP: " + this.hasFecGroup + "\n";
61 | str += " FEC: " + this.isFec;
62 | return str;
63 | };
64 | QuicDecryptedPacket.prototype.getBuffer = function getBuffer () {
65 | var items = [];
66 | items.push(this.associatedData);
67 | items.push(this._privateFlags);
68 | if (this.hasFEC) {
69 | items.push(new Buffer([0]));
70 | }
71 | items.push(this._payload);
72 | var buf = Buffer.concat(items);
73 | return buf;
74 | };
75 |
--------------------------------------------------------------------------------
/lib/packet/QuicPacket.js:
--------------------------------------------------------------------------------
1 | var util = require('util');
2 | var QuicPacketPublicHeader = require('./QuicPacketPublicHeader');
3 |
4 | var QuicPacket = module.exports = function QuicPacket (buf) {
5 | QuicPacket.super_.call(this, buf);
6 | this._payload = new Buffer(0);
7 |
8 | if (buf) {
9 | this._payload = buf.slice(this.size);
10 | }
11 | };
12 | util.inherits(QuicPacket, QuicPacketPublicHeader);
13 | Object.defineProperty(QuicPacket.prototype, 'payload', {
14 | 'get': function () {
15 | return this._payload;
16 | },
17 | });
18 | Object.defineProperty(QuicPacket.prototype, 'associatedData', {
19 | 'get': function () {
20 | var items = [
21 | new Buffer([this._publicFlags]),
22 | ];
23 | if (this.connectionIdLen !== 0) {
24 | items.push(this._connectionId.getBuffer());
25 | }
26 | if (this.hasVersion) {
27 | items.push(this._version);
28 | }
29 | items.push(this._sequenceNumber.getBuffer(this._sequenceNumber.minLen));
30 | return Buffer.concat(items);
31 | }
32 | });
33 | QuicPacket.prototype.toString = function toString () {
34 | var str = QuicPacket.super_.prototype.toString.call(this) + "\n";
35 | return str;
36 | };
37 | QuicPacket.prototype.getBuffer = function getBuffer () {
38 | var items = [
39 | this.associatedData,
40 | this.payload,
41 | ];
42 | var buf = Buffer.concat(items);
43 | return buf;
44 | };
45 |
--------------------------------------------------------------------------------
/lib/packet/QuicPacketPublicHeader.js:
--------------------------------------------------------------------------------
1 | var QuicConnectionId = require('../QuicConnectionId');
2 | var QuicSequenceNumber = require('../QuicSequenceNumber');
3 |
4 | var QuicPacketPublicHeader = module.exports = function QuicPacketPublicHeader (buf) {
5 | this._publicFlags = 0x3C;
6 | this._connectionId = new QuicConnectionId();
7 | this._sequenceNumber = new QuicSequenceNumber();
8 | this._version = new Buffer(4).fill(0);
9 |
10 | if (buf) {
11 | if ((buf[0] & 0xC0) !== 0) {
12 | throw new Error('invalid packet data');
13 | }
14 | this._publicFlags = buf[0];
15 | if (this.connectionIdLen) {
16 | this._connectionId = new QuicConnectionId(buf.slice(1, 1 + this.connectionIdLen));
17 | }
18 | if (this.hasVersion) {
19 | var versionStart = 1 + this.connectionIdLen;
20 | buf.copy(this._version, 0, versionStart, versionStart + 4);
21 | }
22 | var seqNumStart = 1 + this.connectionIdLen + (this.hasVersion ? 4 : 0);
23 | this._sequenceNumber = new QuicSequenceNumber(buf.slice(seqNumStart, seqNumStart + this.sequenceNumLen));
24 | }
25 | };
26 | Object.defineProperty(QuicPacketPublicHeader.prototype, 'connectionId', {
27 | 'get': function () {
28 | if (this.connectionIdLen) {
29 | return this._connectionId;
30 | } else {
31 | return void(0);
32 | }
33 | },
34 | 'set': function (id) {
35 | this._connectionId = new QuicConnectionId(id);
36 | }
37 | });
38 | Object.defineProperty(QuicPacketPublicHeader.prototype, 'sequenceNumber', {
39 | 'get': function () {
40 | return this._sequenceNumber;
41 | },
42 | 'set': function (seqNum) {
43 | this._sequenceNumber.set(seqNum);
44 | switch (this._sequenceNumber.minLen) {
45 | case 1:
46 | this._publicFlags = this._publicFlags & ~0x30;
47 | break;
48 | case 2:
49 | this._publicFlags = this._publicFlags & ~0x30 | 0x10;
50 | break;
51 | case 4:
52 | this._publicFlags = this._publicFlags & ~0x30 | 0x20;
53 | break;
54 | case 6:
55 | this._publicFlags = this._publicFlags & ~0x30 | 0x30;
56 | break;
57 | }
58 | }
59 | });
60 | Object.defineProperty(QuicPacketPublicHeader.prototype, 'version', {
61 | 'get': function () {
62 | if (this.hasVersion) {
63 | return this._version;
64 | } else {
65 | return void(0);
66 | }
67 | },
68 | 'set': function (v) {
69 | this._publicFlags |= 0x01;
70 | v.copy(this._version, 0);
71 | }
72 | });
73 | Object.defineProperty(QuicPacketPublicHeader.prototype, 'size', {
74 | 'get': function () {
75 | var size = 0;
76 | size += 1; // PublicFlags
77 | size += this.connectionIdLen;
78 | size += this.hasVersion ? 4 : 0;
79 | size += this.sequenceNumLen;
80 | return size;
81 | }
82 | });
83 | Object.defineProperty(QuicPacketPublicHeader.prototype, 'connectionIdLen', {
84 | 'get': function () {
85 | switch (this._publicFlags & 0x0C) {
86 | case 0x0C:
87 | return 8;
88 | case 0x08:
89 | return 4;
90 | case 0x04:
91 | return 1;
92 | case 0x00:
93 | return 0;
94 | }
95 | }
96 | });
97 | Object.defineProperty(QuicPacketPublicHeader.prototype, 'sequenceNumLen', {
98 | 'get': function () {
99 | switch (this._publicFlags & 0x30) {
100 | case 0x30:
101 | return 6;
102 | case 0x20:
103 | return 4;
104 | case 0x10:
105 | return 2;
106 | case 0x00:
107 | return 1;
108 | }
109 | }
110 | });
111 | Object.defineProperty(QuicPacketPublicHeader.prototype, 'hasVersion', {
112 | 'get': function () {
113 | return (this._publicFlags & 0x01) !== 0;
114 | }
115 | });
116 | Object.defineProperty(QuicPacketPublicHeader.prototype, 'isPublicResetPacket', {
117 | 'get': function () {
118 | return (this._publicFlags & 0x02) !== 0;
119 | }
120 | });
121 | QuicPacketPublicHeader.prototype.toString = function toString () {
122 | var str = '';
123 | str += 'Public Flags:' + "\n";
124 | str += ' VERSION: ' + this.hasVersion + "\n";
125 | str += ' PUBLIC RESET: ' + this.isPublicResetPacket + "\n";
126 | str += ' SIZE OF CONNECTION ID: ' + this.connectionIdLen + "\n";
127 | str += ' NUMBER OF BYTES OF SEQ#: ' + this.sequenceNumLen + "\n";
128 | str += 'Connection ID: ' + this.connectionId.toString() + "\n";
129 | str += 'Sequence Number: ' + this.sequenceNumber;
130 | return str;
131 | };
132 |
--------------------------------------------------------------------------------
/lib/packet/QuicVersionNegotiationPacket.js:
--------------------------------------------------------------------------------
1 | var QuicVersionNegotiationPacket = module.exports = function QuicVersionNegotiationPacket (buf) {
2 | var cursor;
3 | this._versionList = [];
4 |
5 | if (buf) {
6 | this._versionList = [];
7 | for (cursor = 9; cursor + 4 <= buf.length; cursor += 4) {
8 | this._versionList.push(new Buffer(buf.slice(cursor, cursor + 4)));
9 | }
10 | }
11 | };
12 | Object.defineProperty(QuicVersionNegotiationPacket.prototype, 'versions', {
13 | 'get': function () {
14 | return this._versionList;
15 | }
16 | });
17 | QuicVersionNegotiationPacket.prototype.toString = function toString () {
18 | var str = 'Versions:\n';
19 | this._versionList.forEach(function (item) {
20 | str += ' ' + item + "\n";
21 | });
22 | return str;
23 | };
24 |
--------------------------------------------------------------------------------
/lib/stream/QuicCryptoStream.js:
--------------------------------------------------------------------------------
1 | var util = require('util');
2 | var events = require('events');
3 | var QuicStream = require('./QuicStream');
4 | var QuicHandshakeMessage = require('../QuicHandshakeMessage');
5 |
6 | var QuicCryptoStream = module.exports = function QuicCryptoStream (session, id) {
7 | var self = this;
8 | QuicCryptoStream.super_.call(this, session, id);
9 | var buf = new Buffer(0);
10 | var nTags = null;
11 | var messageLen = null;
12 | var hsMessage = null;
13 | this.on('data', function (data) {
14 | if (nTags === null) {
15 | nTags = data.readUInt16LE(4);
16 | }
17 | buf = Buffer.concat([buf, data]);
18 | if (messageLen === null && buf.length >= 8 + nTags * 8) {
19 | messageLen = 8 + nTags * 8 + buf.readUInt32LE(8 + nTags * 8 - 4);
20 | }
21 | if (messageLen && buf.length >= messageLen) {
22 | hsMessage = new QuicHandshakeMessage(buf.slice(0, messageLen));
23 | self.emit('message', hsMessage);
24 | buf = buf.slice(messageLen);
25 | nTags = null;
26 | messageLen = null;
27 | hsMessage = null;
28 | }
29 | });
30 | };
31 | util.inherits(QuicCryptoStream, QuicStream);
32 |
--------------------------------------------------------------------------------
/lib/stream/QuicDataStream.js:
--------------------------------------------------------------------------------
1 | var util = require('util');
2 | var events = require('events');
3 | var QuicStream = require('./QuicStream');
4 |
5 | var QuicDataStream = module.exports = function QuicDataStream (session, id) {
6 | var self = this;
7 | QuicDataStream.super_.call(this, session, id);
8 | };
9 | util.inherits(QuicDataStream, QuicStream);
10 |
--------------------------------------------------------------------------------
/lib/stream/QuicHeadersStream.js:
--------------------------------------------------------------------------------
1 | var util = require('util');
2 | var events = require('events');
3 | var HPACK = require('hpack');
4 | var QuicStream = require('./QuicStream');
5 | var QuicStreamFrame = require('../frame/QuicStreamFrame');
6 |
7 | var QuicHeadersStream = module.exports = function QuicHeadersStream (session, id) {
8 | var self = this;
9 | QuicHeadersStream.super_.call(this, session, id);
10 |
11 | this._hpack = new HPACK();
12 |
13 | this.on('data', function (data) {
14 | var headers = self._hpack.decode(data.slice(9));
15 | self.emit('headers', headers);
16 | });
17 | };
18 | util.inherits(QuicHeadersStream, QuicStream);
19 |
20 | QuicHeadersStream.prototype.sendHeaders = function sendHeaders (headers, callback) {
21 | var stream = this._session.createDataStream();
22 | var frame = new QuicStreamFrame();
23 | var h2FrameHeader = new Buffer(9);
24 | h2FrameHeader[3] = 0x01; // h2 frame type
25 | h2FrameHeader[4] = 0x25; // h2 frame flag
26 | h2FrameHeader.writeUIntBE(stream.id.valueOf(), 5, 4); // stream id
27 | var h2HeadersFrameHeader = new Buffer([
28 | 0x00, 0x00, 0x00, 0x00, // dependency
29 | 0x01, // weight
30 | ]);
31 | var h2HeadersBlock = this._hpack.encode(headers);
32 | h2FrameHeader.writeUIntBE(h2HeadersFrameHeader.length + h2HeadersBlock.length , 0, 3);
33 | frame.data = Buffer.concat([
34 | h2FrameHeader,
35 | h2HeadersFrameHeader,
36 | h2HeadersBlock
37 | ]);
38 | this.sendFrame(frame, callback);
39 | return stream;
40 | };
41 |
--------------------------------------------------------------------------------
/lib/stream/QuicStream.js:
--------------------------------------------------------------------------------
1 | var util = require('util');
2 | var events = require('events');
3 | var QuicStreamId = require('../QuicStreamId');
4 | var QuicOffset = require('../QuicOffset');
5 | var QuicStreamFrame = require('../frame/QuicStreamFrame');
6 | var QuicWindowUpdateFrame = require('../frame/QuicWindowUpdateFrame');
7 |
8 | var QuicStream = module.exports = function QuicStream (session, id) {
9 | var self = this;
10 | QuicStream.super_.call(this);
11 | this._session = session;
12 | this._id = new QuicStreamId(id);
13 | this._dataOffset = new QuicOffset();
14 | this._receiveWindow = new QuicOffset();
15 | this._receiveWindow.increment(16384);
16 | var onFrame = function (frame) {
17 | if (frame instanceof QuicStreamFrame) {
18 | if (frame.streamId.valueOf() === self._id.valueOf()) {
19 | self._receiveWindow.increment(frame.dataLen);
20 | self._session.incrementWindow(frame.dataLen);
21 | self.emit('data', frame.data);
22 | var wuf = new QuicWindowUpdateFrame();
23 | wuf.byteOffset = self._receiveWindow;
24 | self.sendFrame(wuf);
25 | }
26 | }
27 | if (frame.isFin) {
28 | session.removeListener('frame', onFrame);
29 | }
30 | };
31 | session.on('frame', onFrame);
32 | };
33 | util.inherits(QuicStream, events.EventEmitter);
34 | QuicStream.prototype.sendFrame = function sendFrame (frame, callback) {
35 | frame.streamId = this._id;
36 | if (frame instanceof QuicStreamFrame) {
37 | frame.offset = this._dataOffset;
38 | this._dataOffset.increment(frame.dataLen);
39 | }
40 | this._session.sendFrame(frame, callback);
41 | };
42 | Object.defineProperty(QuicStream.prototype, 'id', {
43 | 'get': function () {
44 | return this._id;
45 | },
46 | 'set': function (id) {
47 | if (id instanceof QuicStreamId) {
48 | this._id = id;
49 | } else {
50 | this._id = new QuicStreamId(id);
51 | }
52 | }
53 | });
54 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "dependencies": {
3 | "elliptic": "^3.0.3",
4 | "fnv-plus": "^1.2.12",
5 | "hpack": "^1.0.0"
6 | },
7 | "devDependencies": {
8 | "mocha": "^2.2.4"
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/test/test-hkdf.js:
--------------------------------------------------------------------------------
1 | /*
2 | * The test vectors are from RFC5890
3 | * https://tools.ietf.org/rfc/rfc5869.txt
4 | */
5 | var HKDF = require('../hkdf.js');
6 | var assert = require('assert');
7 | describe('HKDF', function () {
8 | describe('#derive', function () {
9 | var tests = [
10 | {
11 | hash: 'sha256',
12 | args: [
13 | new Buffer('000102030405060708090a0b0c', 'hex'),
14 | new Buffer('0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b', 'hex'),
15 | new Buffer('f0f1f2f3f4f5f6f7f8f9', 'hex'),
16 | 42,
17 | ],
18 | expected: '3cb25f25faacd57a90434f64d0362f2a2d2d0a90cf1a5a4c5db02d56ecc4c5bf34007208d5b887185865'
19 | },
20 | {
21 | hash: 'sha256',
22 | args: [
23 | new Buffer('606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeaf', 'hex'),
24 | new Buffer('000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f', 'hex'),
25 | new Buffer('b0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff', 'hex'),
26 | 82
27 | ],
28 | expected: 'b11e398dc80327a1c8e7f78c596a49344f012eda2d4efad8a050cc4c19afa97c59045a99cac7827271cb41c65e590e09da3275600c2f09b8367793a9aca3db71cc30c58179ec3e87c14c01d5c1f3434f1d87'
29 | },
30 | {
31 | hash: 'sha256',
32 | args: [
33 | new Buffer('', 'hex'),
34 | new Buffer('0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b', 'hex'),
35 | new Buffer('', 'hex'),
36 | 42
37 | ],
38 | expected: '8da4e775a563c18f715f802a063c5a31b8a11f5c5ee1879ec3454e5f3c738d2d9d201395faa4b61a96c8'
39 | },
40 | {
41 | hash: 'sha1',
42 | args: [
43 | new Buffer('000102030405060708090a0b0c', 'hex'),
44 | new Buffer('0b0b0b0b0b0b0b0b0b0b0b', 'hex'),
45 | new Buffer('f0f1f2f3f4f5f6f7f8f9', 'hex'),
46 | 42
47 | ],
48 | expected: '085a01ea1b10f36933068b56efa5ad81a4f14b822f5b091568a9cdd4f155fda2c22e422478d305f3f896'
49 | },
50 | {
51 | hash: 'sha1',
52 | args: [
53 | new Buffer('606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeaf', 'hex'),
54 | new Buffer('000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f', 'hex'),
55 | new Buffer('b0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff', 'hex'),
56 | 82
57 | ],
58 | expected: '0bd770a74d1160f7c9f12cd5912a06ebff6adcae899d92191fe4305673ba2ffe8fa3f1a4e5ad79f3f334b3b202b2173c486ea37ce3d397ed034c7f9dfeb15c5e927336d0441f4c4300e2cff0d0900b52d3b4'
59 | },
60 | {
61 | hash: 'sha1',
62 | args: [
63 | new Buffer('', 'hex'),
64 | new Buffer('0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b', 'hex'),
65 | new Buffer('', 'hex'),
66 | 42
67 | ],
68 | expected: '0ac1af7002b3d761d1e55298da9d0506b9ae52057220a306e07b6b87e8df21d0ea00033de03984d34918'
69 | },
70 | {
71 | hash: 'sha1',
72 | args: [
73 | null,
74 | new Buffer('0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c', 'hex'),
75 | new Buffer('', 'hex'),
76 | 42
77 | ],
78 | expected: '2c91117204d745f3500d636a62f64f0ab3bae548aa53d423b0d1f27ebba6f5e5673a081d70cce7acfc48'
79 | },
80 | ];
81 | tests.forEach(function(test) {
82 | it('derives OKM', function () {
83 | var hkdf = new HKDF(test.hash);
84 | var okm = HKDF.prototype.derive.apply(hkdf, test.args);
85 | assert.equal(okm.toString('hex'), test.expected);
86 | });
87 | });
88 | });
89 | });
90 |
--------------------------------------------------------------------------------