Skip to content

Commit

Permalink
complete handshake and receive app data.
Browse files Browse the repository at this point in the history
  • Loading branch information
reklatsmasters committed May 26, 2018
1 parent d765283 commit 3504527
Show file tree
Hide file tree
Showing 8 changed files with 198 additions and 31 deletions.
52 changes: 52 additions & 0 deletions lib/anti-replay.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
module.exports = {
createWindow
}

const defaultAccessor = sequenceNumber => sequenceNumber

class SlidingWindow {
constructor(size) {
this._size = size
this.reset()
}

match(sn, accessor) {
if (typeof accessor !== 'function') {
accessor = defaultAccessor
}

const sequenceNumber = accessor(sn)

if (typeof sequenceNumber !== 'number') {
throw new TypeError('The value of sequenceNumber should be a number.')
}

return sequenceNumber >= this._left && sequenceNumber <= this._right
}

reset(value = 0) {
if (typeof value !== 'number') {
throw new TypeError('Argument `value` should be a number.')
}

this._left = value
this._right = value + this._size
}
}

/**
* Creates a sliding window.
* @param {number} size
* @returns {SlidingWindow}
*/
function createWindow(size) {
if (typeof size !== 'number') {
throw new TypeError('Argument `size` should be a number.')
}

if (size < 32) {
throw new Error('Sliding window should equals at least 32.')
}

return new SlidingWindow(size)
}
4 changes: 4 additions & 0 deletions lib/cipher-suites.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
const crypto = require('crypto')
const debug = require('debug')('dtls:cipher')
const { encode, createDecodeStream, createEncodeStream } = require('binary-data')
const { cipherSuites, sessionType, AEAD_AES_128_GCM, AEAD_AES_256_GCM } = require('./constants')
const symbols = require('./symbols')
Expand Down Expand Up @@ -121,6 +122,9 @@ class AEADCipher {
const client_nonce_implicit = stream.readBuffer(this.iv_length)
const server_nonce_implicit = stream.readBuffer(this.iv_length)

debug('client iv', client_nonce_implicit)
debug('server iv', server_nonce_implicit)

this.client_nonce = Buffer.alloc(this.nonce_length, 0)
this.server_nonce = Buffer.alloc(this.nonce_length, 0)

Expand Down
4 changes: 0 additions & 4 deletions lib/client-session.js
Original file line number Diff line number Diff line change
Expand Up @@ -80,10 +80,6 @@ module.exports = class ClientSession extends Session {

this.sendHandshake(handshakeType.CLIENT_KEY_EXCHANGE, encrypted, EncryptedPreMasterSecret)
}

sendFinished() {
super.sendFinished('client finished')
}
}

function unixtime() {
Expand Down
68 changes: 58 additions & 10 deletions lib/parser.js
Original file line number Diff line number Diff line change
Expand Up @@ -76,15 +76,26 @@ class Parser extends Duplex {
process(record) {
debug('%O', record)

// Drop replays.
const currentSeq = record.sequenceNumber
const lastSeq = this.session[symbols.lastRecvRecord]
const currentEpoch = this.session[symbols.incomingEpoch]

if (currentSeq <= lastSeq) {
debug('warn: got msg seq=%s but last seq was %s, drop', currentSeq, lastSeq)
// Drop handshake replays.
if (this.session[symbols.handshakeInProgress]) {
const currentSeq = record.sequenceNumber
const lastSeq = this.session[symbols.lastRecvRecord]

// Drop replays in current epoch.
if (currentEpoch === record.epoch && currentSeq <= lastSeq) {
debug('warn: got msg seq=%s but last seq was %s, drop', currentSeq, lastSeq)
return
}

// Drop messages from unknown epoch
// If not in handshake state.
} else if (record.epoch !== currentEpoch) {
return
}

// TODO: change after success decrypt / handshake handle.
this.session[symbols.lastRecvRecord] = record.sequenceNumber

switch (record.type) {
Expand All @@ -94,6 +105,18 @@ class Parser extends Duplex {
case contentType.HANDSHAKE:
this.handleHandshake(record)
break
case contentType.CHANGE_CIPHER_SPEC:
if (this.session[symbols.handshakeState] !== handshakeState.STATE9) {
break
}
debug('got change cipher spec')
++this.session[symbols.incomingEpoch]
this.session[symbols.lastRecvRecord] = -1
this.session[symbols.handshakeState] = handshakeState.STATE10
break
case contentType.APPLICATION_DATA:
this.handleApplicationData(record)
break
default:
break
}
Expand All @@ -115,7 +138,8 @@ class Parser extends Duplex {
}

handleHandshake(record) {
const stream = createDecodeStream(record.fragment)
const fragment = this.session.decryptRecord(record, record.fragment)
const stream = createDecodeStream(fragment)
const handshake = decode(stream, Handshake)

debug('got Handshake')
Expand Down Expand Up @@ -187,22 +211,32 @@ class Parser extends Duplex {
this.session[symbols.handshakeState] = handshakeState.STATE6
case handshakeState.STATE6: // eslint-disable-line no-fallthrough
this.session.createMasterSecret()
this.session.initCipherSuite()
this.session[symbols.handshakeState] = handshakeState.STATE7
case handshakeState.STATE7: // eslint-disable-line no-fallthrough
this.session.sendChangeCipherSpec()
this.session.initCipherSuite()
this.session.setNextEpoch()
this.session[symbols.handshakeState] = handshakeState.STATE8
case handshakeState.STATE8: // eslint-disable-line no-fallthrough
this.session.sendFinished()
this.session[symbols.handshakeState] = -1
this.session[symbols.handshakeState] = handshakeState.STATE9
break
case handshakeState.STATE10:
this.handleFinished(stream)
break
default:
debug('invalid handshake state')
break
}
}

handleApplicationData(record) {
debug('got APP DATA')
const fragment = this.session.decryptRecord(record, record.fragment)

this.push(fragment.slice(0, -1))
}

handleClientHelloAnswer(stream, handshake) {
switch (handshake.type) {
case handshakeType.HELLO_VERIFY_REQUEST:
Expand Down Expand Up @@ -270,8 +304,7 @@ class Parser extends Duplex {
throw new Error('Invalid cipher suite.')
}

this.session[symbols.cipherSuite] = cipher
// cipher.init(this.session)
this.session[symbols.nextCipherSuite] = cipher
}

handleServerCertificate(stream) {
Expand Down Expand Up @@ -304,6 +337,21 @@ class Parser extends Duplex {
this.session[symbols.handshakeState] = handshakeState.STATE5
}

handleFinished(stream) {
debug('>>> got Finished')

const verifydata = stream.readBuffer(stream.length)
debug('finished data %s', verifydata.toString('hex'))

const serverFinal = this.session.createFinishedServer()

debug('client finished %s', this.session[symbols.clientFinished].toString('hex'))
debug('server finished %s', serverFinal.toString('hex'))

// TODO: check finished message!
this.session[symbols.handshakeInProgress] = false
}

_destroy() {
this.stream.destroy()
}
Expand Down
66 changes: 54 additions & 12 deletions lib/session.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@ module.exports = class Session extends Readable {
super({ objectMode: true })

this[symbols.sessionType] = 0
this[symbols.epoch] = 0
this[symbols.outgoingEpoch] = 0
this[symbols.incomingEpoch] = 0
this[symbols.sequenceNumber] = 0
this[symbols.clientRandom] = EMPTY_BUFFER
this[symbols.serverRandom] = EMPTY_BUFFER
Expand All @@ -22,22 +23,25 @@ module.exports = class Session extends Readable {
this[symbols.cookie] = EMPTY_BUFFER
this[symbols.handshakeSequenceNumber] = 0
this[symbols.sessionId] = EMPTY_BUFFER
this[symbols.cipherSuite] = 0
this[symbols.cipherSuite] = null
this[symbols.nextCipherSuite] = null
this[symbols.lastRecvRecord] = -1
this[symbols.lastRecvHandshake] = -1
this[symbols.serverPublicKey] = null // Stored in PEM.
this[symbols.serverWantCertificate] = false
this[symbols.clientPremaster] = null
this[symbols.masterSecret] = null
this[symbols.handshakeEncoder] = createEncodeStream()
this[symbols.clientFinished] = null
this[symbols.serverFinished] = null
}

get epoch() {
return this[symbols.epoch]
return this[symbols.outgoingEpoch]
}

setNextEpoch() {
++this[symbols.epoch]
++this[symbols.outgoingEpoch]
this[symbols.sequenceNumber] = 0
}

Expand Down Expand Up @@ -118,6 +122,10 @@ module.exports = class Session extends Readable {
protocol.fragment = buffer(encrypted.length)
}

if (isFinished) {
this[symbols.handshakeEncoder].append(record.fragment)
}

debug('%O', record)
this.write(record, protocol)
}
Expand All @@ -128,9 +136,17 @@ module.exports = class Session extends Readable {
* @returns {Buffer}
*/
decryptRecord(recordHeader, encrypted) {
if (this[symbols.cipherSuite] === null) {
return encrypted
}

const cipher = this[symbols.cipherSuite]

return cipher.decrypt(this, encrypted, recordHeader)
debug('decrypt message using %s', cipher.block_algorithm)
const message = cipher.decrypt(this, encrypted, recordHeader)

debug('decrypt success, got %s bytes', message.length)
return message
}

sendHandshake(type, payload, schema) {
Expand Down Expand Up @@ -193,18 +209,33 @@ module.exports = class Session extends Readable {
this.sendHandshake(handshakeType.CERTIFICATE, certificate, Certificate)
}

sendFinished(label) {
sendFinished() {
debug('send Finished')

const cipher = this[symbols.cipherSuite]
const final = this.createFinishedClient()

this[symbols.clientFinished] = final

this.sendHandshake(handshakeType.FINISHED, final, buffer(cipher.verify_data_length))
}

createFinished(label) {
debug('calc finished, size=%s bytes', this[symbols.handshakeEncoder].length)
const master = this[symbols.masterSecret]
const cipher = this[symbols.cipherSuite]
const hash = crypto.createHash(cipher.hash)
const bytes = hash(cipher.hash, this[symbols.handshakeEncoder].slice())
const final = cipher.prf(cipher.verify_data_length, master, label, bytes)

hash.update(this[symbols.handshakeEncoder].slice())
return final
}

const final = cipher.prf(cipher.verify_data_length, master, label, hash.digest())
createFinishedClient() {
return this.createFinished('client finished')
}

this.sendHandshake(handshakeType.FINISHED, final, buffer(cipher.verify_data_length))
createFinishedServer() {
return this.createFinished('server finished')
}

createPreMasterSecret() {
Expand All @@ -218,19 +249,30 @@ module.exports = class Session extends Readable {
])

const label = 'master secret'
const cipher = this[symbols.nextCipherSuite]

debug('client random', this.clientRandom)
debug('server random', this.serverRandom)

this[symbols.masterSecret] = this[symbols.cipherSuite].prf(48, this.premaster, label, seed)
this[symbols.masterSecret] = cipher.prf(48, this.premaster, label, seed)
this[symbols.clientPremaster] = null

debug('master secret', this[symbols.masterSecret])
}

initCipherSuite() {
const cipher = this[symbols.cipherSuite]
const cipher = this[symbols.nextCipherSuite]

cipher.init(this)

this[symbols.nextCipherSuite] = null
this[symbols.cipherSuite] = cipher
}
}

function hash(algorithm, data) {
const hash = crypto.createHash(algorithm)

hash.update(data)
return hash.digest()
}
4 changes: 4 additions & 0 deletions lib/socket.js
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,10 @@ class Socket extends Duplex {
this.emit('error', err)
}
})

parser.on('data', data => {
this.push(data)
})
}

_read() {
Expand Down
Loading

0 comments on commit 3504527

Please sign in to comment.