-
Notifications
You must be signed in to change notification settings - Fork 59
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
bab1af0
commit 1c91cdb
Showing
5 changed files
with
390 additions
and
305 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
// telegram.link | ||
// Copyright 2014 Enrico Stara '[email protected]' | ||
// Released under the MIT License | ||
// https://github.com/enricostara/telegram-mt-node | ||
|
||
exports.RequestPQ = require('./request-pq'); | ||
exports.RequestDHParams = require('./request-dh-params'); | ||
exports.SetClientDHParams = require('./set-client-dh-params'); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,166 @@ | ||
// telegram.link | ||
// Copyright 2014 Enrico Stara '[email protected]' | ||
// Released under the MIT License | ||
// https://github.com/enricostara/telegram-mt-node | ||
|
||
// Export the class | ||
module.exports = exports = RequestDHParams; | ||
|
||
// Import dependencies | ||
var flow = require('get-flow'); | ||
var logger = require('get-log')('auth.RequestDHParams'); | ||
var TypeObject = require('telegram-tl-node').TypeObject; | ||
var mtproto = require('telegram-mt-node'); | ||
var utility = mtproto.utility; | ||
var security = mtproto.security; | ||
|
||
// Requires a callback function and the connection | ||
function RequestDHParams(callback, context) { | ||
flow.runSeries([ | ||
createPQInnerData, | ||
encryptPQInnerDataWithRSA, | ||
requestDHParams, | ||
decryptDHParams, | ||
deserializeDHInnerData, | ||
], callback, context); | ||
}; | ||
|
||
|
||
// Create the pq_inner_data buffer | ||
function createPQInnerData(context) { | ||
var resPQ = context.resPQ; | ||
var newNonce = utility.createNonce(32); | ||
var pqInnerData = new mtproto.type.P_q_inner_data({props: { | ||
pq: resPQ.pq, | ||
p: context.pBuffer, | ||
q: context.qBuffer, | ||
nonce: resPQ.nonce, | ||
server_nonce: resPQ.server_nonce, | ||
new_nonce: newNonce | ||
}}).serialize(); | ||
context.pqInnerData = pqInnerData; | ||
context.newNonce = newNonce; | ||
return context; | ||
} | ||
|
||
// Encrypt the pq_inner_data with RSA | ||
function encryptPQInnerDataWithRSA(context) { | ||
// Create the data with hash to be encrypt | ||
var hash = utility.createSHA1Hash(context.pqInnerData); | ||
var dataWithHash = Buffer.concat([hash, context.pqInnerData]); | ||
if (logger.isDebugEnabled()) { | ||
logger.debug('Data to be encrypted contains: hash(%s), pqInnerData(%s), total length %s', | ||
hash.length, context.pqInnerData.length, dataWithHash.length); | ||
} | ||
// Encrypt data with RSA | ||
context.encryptedData = security.cipher.rsaEncrypt(dataWithHash, context.publicKey); | ||
return context; | ||
} | ||
|
||
// Request server DH parameters | ||
function requestDHParams(callback, context) { | ||
var resPQ = context.resPQ; | ||
mtproto.service.req_DH_params({ | ||
props: { | ||
nonce: resPQ.nonce, | ||
server_nonce: resPQ.server_nonce, | ||
p: context.pBuffer, | ||
q: context.qBuffer, | ||
public_key_fingerprint: context.fingerprint, | ||
encrypted_data: context.encryptedData | ||
}, | ||
conn: context.connection, | ||
callback: function (ex, serverDHParams, duration) { | ||
if (ex) { | ||
logger.error(ex); | ||
if (callback) { | ||
callback(ex); | ||
} | ||
} else { | ||
if (serverDHParams.typeName === 'mtproto.type.Server_DH_params_ok') { | ||
if (logger.isDebugEnabled()) { | ||
logger.debug('\'Server_DH_params_ok\' received from Telegram.'); | ||
} | ||
context.serverDHParams = serverDHParams; | ||
context.reqDHDuration = duration; | ||
callback(null, context); | ||
} else if (serverDHParams.typeName === 'mtproto.type.Server_DH_params_ko') { | ||
logger.warn('\'Server_DH_params_ko\' received from Telegram!'); | ||
callback(createError(JSON.stringify(serverDHParams), 'EDHPARAMKO')); | ||
} else { | ||
var msg = 'Unknown error received from Telegram!'; | ||
logger.error(msg); | ||
callback(createError(msg, 'EUNKNOWN')); | ||
} | ||
} | ||
} | ||
}); | ||
} | ||
|
||
// Decrypt DH parameters and synch the local time with the server time | ||
function decryptDHParams(context) { | ||
var newNonce = TypeObject.stringValue2Buffer(context.newNonce, 32); | ||
var serverNonce = TypeObject.stringValue2Buffer(context.resPQ.server_nonce, 16); | ||
if (logger.isDebugEnabled()) { | ||
logger.debug('newNonce = %s, serverNonce = %s', newNonce.toString('hex'), serverNonce.toString('hex')); | ||
} | ||
var hashNS = utility.createSHA1Hash([newNonce, serverNonce]); | ||
var hashSN = utility.createSHA1Hash([serverNonce, newNonce]); | ||
var hashNN = utility.createSHA1Hash([newNonce, newNonce]); | ||
if (logger.isDebugEnabled()) { | ||
logger.debug('hashNS = %s, hashSN = %s, hashNN = %s', | ||
hashNS.toString('hex'), hashSN.toString('hex'), hashNN.toString('hex')); | ||
} | ||
// Create the AES key | ||
var aesKey = Buffer.concat([hashNS, hashSN.slice(0, 12)]); | ||
var aesIv = Buffer.concat([Buffer.concat([hashSN.slice(12), hashNN]), newNonce.slice(0, 4)]); | ||
if (logger.isDebugEnabled()) { | ||
logger.debug('aesKey = %s, aesIv = %s', aesKey.toString('hex'), aesIv.toString('hex')); | ||
} | ||
// Decrypt the message | ||
var answerWithHash = security.cipher.aesDecrypt( | ||
context.serverDHParams.encrypted_answer, | ||
aesKey, | ||
aesIv | ||
); | ||
context.decryptedDHParams = answerWithHash; | ||
// Save AES key | ||
context.aes = {key: aesKey, iv: aesIv}; | ||
return context; | ||
} | ||
|
||
// De-serialize the server DH inner data | ||
function deserializeDHInnerData(context) { | ||
var decryptedDHParamsWithHash = context.decryptedDHParams; | ||
if (logger.isDebugEnabled()) { | ||
logger.debug('decryptedDHParamsWithHash(%s) = %s', decryptedDHParamsWithHash.length, decryptedDHParamsWithHash.toString('hex')); | ||
} | ||
var decryptedDHParams = decryptedDHParamsWithHash.slice(20, 564 + 20); | ||
if (logger.isDebugEnabled()) { | ||
logger.debug('decryptedDHParams(%s) = %s', decryptedDHParams.length, decryptedDHParams.toString('hex')); | ||
} | ||
var serverDHInnerData = new mtproto.type.Server_DH_inner_data({ | ||
buffer: decryptedDHParams | ||
}).deserialize(); | ||
if (logger.isDebugEnabled()) { | ||
logger.debug('serverDHInnerData = %s obtained in %sms', JSON.stringify(serverDHInnerData), context.reqDHDuration); | ||
} | ||
// Check if the nonces are consistent | ||
if (serverDHInnerData.nonce !== context.serverDHParams.nonce) { | ||
throw createError('Nonce mismatch %s != %s', context.serverDHParams.nonce, serverDHInnerData.nonce); | ||
} | ||
if (serverDHInnerData.server_nonce !== context.serverDHParams.server_nonce) { | ||
throw createError('ServerNonce mismatch %s != %s', context.serverDHParams.server_nonce, serverDHInnerData.server_nonce); | ||
} | ||
// Synch the local time with the server time | ||
mtproto.time.timeSynchronization(serverDHInnerData.server_time * 1000, context.reqDHDuration); | ||
context.serverDHInnerData = serverDHInnerData; | ||
return context; | ||
} | ||
|
||
|
||
function createError(msg, code) { | ||
var error = new Error(msg); | ||
error.code = code; | ||
return error; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,92 @@ | ||
// telegram.link | ||
// Copyright 2014 Enrico Stara '[email protected]' | ||
// Released under the MIT License | ||
// https://github.com/enricostara/telegram-mt-node | ||
|
||
// Export the module | ||
module.exports = exports = RequestPQ; | ||
|
||
// Import dependencies | ||
var flow = require('get-flow'); | ||
var logger = require('get-log')('auth.RequestPQ'); | ||
var mtproto = require('telegram-mt-node'); | ||
var utility = mtproto.utility; | ||
var security = mtproto.security; | ||
|
||
// Requires a callback function and the connection | ||
function RequestPQ(callback, connection) { | ||
flow.runSeries([ | ||
requestPQ, | ||
findPAndQ, | ||
findPublicKey, | ||
], callback, connection); | ||
}; | ||
|
||
// Request a PQ pair number | ||
function requestPQ(callback, connection) { | ||
// Create a nonce for the client | ||
var clientNonce = utility.createNonce(16); | ||
mtproto.service.req_pq({ | ||
props: { | ||
nonce: clientNonce | ||
}, | ||
conn: connection, | ||
callback: function (ex, resPQ) { | ||
if (clientNonce === resPQ.nonce) { | ||
var context = { | ||
resPQ: resPQ, | ||
connection: connection | ||
}; | ||
callback(null, context); | ||
} else { | ||
callback(createError('Nonce mismatch.', 'ENONCE')); | ||
} | ||
} | ||
}); | ||
} | ||
|
||
// Find the P and Q prime numbers | ||
function findPAndQ(context) { | ||
var pqFinder = new security.PQFinder(context.resPQ.pq); | ||
if (logger.isDebugEnabled()) { | ||
logger.debug('Start finding P and Q, with PQ = %s', pqFinder.getPQPairNumber()); | ||
} | ||
var pq = pqFinder.findPQ(); | ||
if (logger.isDebugEnabled()) { | ||
logger.debug('Found P = %s and Q = %s', pq[0], pq[1]); | ||
} | ||
context.pBuffer = pqFinder.getPQAsBuffer()[0]; | ||
context.qBuffer = pqFinder.getPQAsBuffer()[1]; | ||
return context; | ||
} | ||
|
||
// Find the correct Public Key using fingerprint from server response | ||
function findPublicKey(context) { | ||
var fingerprints = context.resPQ.server_public_key_fingerprints.getList(); | ||
if (logger.isDebugEnabled()) { | ||
logger.debug('Public keys fingerprints from server: %s', fingerprints); | ||
} | ||
for (var i = 0; i < fingerprints.length; i++) { | ||
var fingerprint = fingerprints[i]; | ||
if (logger.isDebugEnabled()) { | ||
logger.debug('Searching fingerprint %s in store', fingerprint); | ||
} | ||
var publicKey = security.PublicKey.retrieveKey(fingerprint); | ||
if (publicKey) { | ||
if (logger.isDebugEnabled()) { | ||
logger.debug('Fingerprint %s found in keyStore.', fingerprint); | ||
} | ||
context.fingerprint = fingerprint; | ||
context.publicKey = publicKey; | ||
return context; | ||
} | ||
} | ||
throw createError('Fingerprints from server not found in keyStore.', 'EFINGERNOTFOUND'); | ||
} | ||
|
||
function createError(msg, code) { | ||
var error = new Error(msg); | ||
error.code = code; | ||
return error; | ||
} | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,112 @@ | ||
// telegram.link | ||
// Copyright 2014 Enrico Stara '[email protected]' | ||
// Released under the MIT License | ||
// https://github.com/enricostara/telegram-mt-node | ||
|
||
// Export the class | ||
module.exports = exports = SetClientDHParams; | ||
|
||
// Import dependencies | ||
var flow = require('get-flow'); | ||
var logger = require('get-log')('auth.SetClientDHParams'); | ||
var mtproto = require('telegram-mt-node'); | ||
var utility = mtproto.utility; | ||
var security = mtproto.security; | ||
|
||
// Requires a callback function and the connection | ||
function SetClientDHParams(callback, context) { | ||
flow.runSeries([ | ||
createClientDHInnerData, | ||
encryptClientDHInnerDataWithAES, | ||
setClientDHParams | ||
], callback, context); | ||
}; | ||
|
||
// Calculate the g_b = pow(g, b) mod dh_prime | ||
// Create the client DH inner data | ||
function createClientDHInnerData(context) { | ||
var retryCount = 0; | ||
if (logger.isDebugEnabled()) { | ||
logger.debug('Start calculating g_b'); | ||
} | ||
// Calculate g_b | ||
var g = context.serverDHInnerData.g; | ||
var b = utility.createNonce(256); | ||
var dhPrime = context.serverDHInnerData.dh_prime; | ||
var gb = utility.modPow(g, b, dhPrime); | ||
if (logger.isDebugEnabled()) { | ||
logger.debug('g_b(%s) = %s', gb.length, gb.toString('hex')); | ||
} | ||
// Create client DH inner data | ||
context.clientDHInnerData = new mtproto.type.Client_DH_inner_data({props: { | ||
nonce: context.resPQ.nonce, | ||
server_nonce: context.resPQ.server_nonce, | ||
retry_id: retryCount, | ||
g_b: gb | ||
}}).serialize(); | ||
return context; | ||
} | ||
|
||
// Encrypt Client DH inner data | ||
function encryptClientDHInnerDataWithAES(context) { | ||
var hash = utility.createSHA1Hash(context.clientDHInnerData); | ||
var dataWithHash = Buffer.concat([hash, context.clientDHInnerData]); | ||
if (logger.isDebugEnabled()) { | ||
logger.debug('Data to be encrypted contains: hash(%s), clientDHInnerData(%s), total length %s', | ||
hash.length, context.clientDHInnerData.length, dataWithHash.length); | ||
} | ||
context.encryptClientDHInnerData = security.cipher.aesEncrypt( | ||
dataWithHash, | ||
context.aes.key, | ||
context.aes.iv | ||
); | ||
if (logger.isDebugEnabled()) { | ||
logger.debug('encryptClientDHInnerData(%s) = %s', | ||
context.encryptClientDHInnerData.length, context.encryptClientDHInnerData.toString('hex')); | ||
} | ||
return context; | ||
} | ||
|
||
// Set client DH parameters | ||
function setClientDHParams(callback, context) { | ||
mtproto.service.set_client_DH_params({ | ||
props: { | ||
nonce: context.resPQ.nonce, | ||
server_nonce: context.resPQ.server_nonce, | ||
encrypted_data: context.encryptClientDHInnerData | ||
}, | ||
conn: context.connection, | ||
callback: function (ex, setClientDHParamsAnswer, duration) { | ||
if (ex) { | ||
logger.error(ex); | ||
if (callback) { | ||
callback(ex); | ||
} | ||
} else { | ||
if (setClientDHParamsAnswer.typeName === 'mtproto.type.Dh_gen_ok') { | ||
if (logger.isDebugEnabled()) { | ||
logger.debug('\'Dh_gen_ok\' received from Telegram.'); | ||
} | ||
context.setClientDHParamsAnswer = setClientDHParamsAnswer; | ||
callback(null, context, duration); | ||
} else if (setClientDHParamsAnswer.typeName === 'mtproto.type.Dh_gen_retry') { | ||
logger.warn('\'Dh_gen_retry\' received from Telegram!'); | ||
callback(createError(JSON.stringify(setClientDHParamsAnswer), 'EDHPARAMRETRY')); | ||
} else if (setClientDHParamsAnswer.typeName === 'mtproto.type.Dh_gen_fail') { | ||
logger.warn('\'Dh_gen_fail\' received from Telegram!'); | ||
callback(createError(JSON.stringify(setClientDHParamsAnswer), 'EDHPARAMFAIL')); | ||
} else { | ||
var msg = 'Unknown error received from Telegram!'; | ||
logger.error(msg); | ||
callback(createError(msg, 'EUNKNOWN')); | ||
} | ||
} | ||
} | ||
}); | ||
} | ||
|
||
function createError(msg, code) { | ||
var error = new Error(msg); | ||
error.code = code; | ||
return error; | ||
} |
Oops, something went wrong.