feat(wasm): added sha1/256 to wasm, removed most of async in crypto

This commit is contained in:
alina 🌸 2023-11-07 22:49:35 +03:00
parent 70f4e40ef5
commit 18178b438d
Signed by: teidesu
SSH key fingerprint: SHA256:uNeCpw6aTSU4aIObXLvHfLkDa82HWH9EiOj9AXOIRpI
41 changed files with 813 additions and 472 deletions

View file

@ -32,19 +32,19 @@ export class AuthKey {
return this.ready && buffersEqual(keyId, this.id) return this.ready && buffersEqual(keyId, this.id)
} }
async setup(authKey?: Uint8Array | null): Promise<void> { setup(authKey?: Uint8Array | null): void {
if (!authKey) return this.reset() if (!authKey) return this.reset()
this.ready = true this.ready = true
this.key = authKey this.key = authKey
this.clientSalt = authKey.subarray(88, 120) this.clientSalt = authKey.subarray(88, 120)
this.serverSalt = authKey.subarray(96, 128) this.serverSalt = authKey.subarray(96, 128)
this.id = (await this._crypto.sha1(authKey)).subarray(-8) this.id = this._crypto.sha1(authKey).subarray(-8)
this.log.verbose('auth key set up, id = %h', this.id) this.log.verbose('auth key set up, id = %h', this.id)
} }
async encryptMessage(message: Uint8Array, serverSalt: Long, sessionId: Long): Promise<Uint8Array> { encryptMessage(message: Uint8Array, serverSalt: Long, sessionId: Long): Uint8Array {
if (!this.ready) throw new MtcuteError('Keys are not set up!') if (!this.ready) throw new MtcuteError('Keys are not set up!')
let padding = (16 /* header size */ + message.length + 12) /* min padding */ % 16 let padding = (16 /* header size */ + message.length + 12) /* min padding */ % 16
@ -60,18 +60,18 @@ export class AuthKey {
buf.set(message, 16) buf.set(message, 16)
buf.set(randomBytes(padding), 16 + message.length) buf.set(randomBytes(padding), 16 + message.length)
const messageKey = (await this._crypto.sha256(concatBuffers([this.clientSalt, buf]))).subarray(8, 24) const messageKey = this._crypto.sha256(concatBuffers([this.clientSalt, buf])).subarray(8, 24)
const ige = await createAesIgeForMessage(this._crypto, this.key, messageKey, true) const ige = createAesIgeForMessage(this._crypto, this.key, messageKey, true)
const encryptedData = ige.encrypt(buf) const encryptedData = ige.encrypt(buf)
return concatBuffers([this.id, messageKey, encryptedData]) return concatBuffers([this.id, messageKey, encryptedData])
} }
async decryptMessage( decryptMessage(
data: Uint8Array, data: Uint8Array,
sessionId: Long, sessionId: Long,
callback: (msgId: tl.Long, seqNo: number, data: TlBinaryReader) => void, callback: (msgId: tl.Long, seqNo: number, data: TlBinaryReader) => void,
): Promise<void> { ): void {
const messageKey = data.subarray(8, 24) const messageKey = data.subarray(8, 24)
let encryptedData = data.subarray(24) let encryptedData = data.subarray(24)
@ -84,10 +84,10 @@ export class AuthKey {
encryptedData = encryptedData.subarray(0, encryptedData.byteLength - mod16) encryptedData = encryptedData.subarray(0, encryptedData.byteLength - mod16)
} }
const ige = await createAesIgeForMessage(this._crypto, this.key, messageKey, false) const ige = createAesIgeForMessage(this._crypto, this.key, messageKey, false)
const innerData = ige.decrypt(encryptedData) const innerData = ige.decrypt(encryptedData)
const msgKeySource = await this._crypto.sha256(concatBuffers([this.serverSalt, innerData])) const msgKeySource = this._crypto.sha256(concatBuffers([this.serverSalt, innerData]))
const expectedMessageKey = msgKeySource.subarray(8, 24) const expectedMessageKey = msgKeySource.subarray(8, 24)
if (!buffersEqual(messageKey, expectedMessageKey)) { if (!buffersEqual(messageKey, expectedMessageKey)) {

View file

@ -120,7 +120,7 @@ function checkDhPrime(log: Logger, dhPrime: bigint, g: number) {
log.debug('g = %d is safe to use with dh_prime', g) log.debug('g = %d is safe to use with dh_prime', g)
} }
async function rsaPad(data: Uint8Array, crypto: ICryptoProvider, key: TlPublicKey): Promise<Uint8Array> { function rsaPad(data: Uint8Array, crypto: ICryptoProvider, key: TlPublicKey): Uint8Array {
// since Summer 2021, they use "version of RSA with a variant of OAEP+ padding explained below" // since Summer 2021, they use "version of RSA with a variant of OAEP+ padding explained below"
const keyModulus = BigInt(`0x${key.modulus}`) const keyModulus = BigInt(`0x${key.modulus}`)
@ -137,13 +137,13 @@ async function rsaPad(data: Uint8Array, crypto: ICryptoProvider, key: TlPublicKe
const aesKey = randomBytes(32) const aesKey = randomBytes(32)
const dataWithHash = concatBuffers([data, await crypto.sha256(concatBuffers([aesKey, data]))]) const dataWithHash = concatBuffers([data, crypto.sha256(concatBuffers([aesKey, data]))])
// we only need to reverse the data // we only need to reverse the data
dataWithHash.subarray(0, 192).reverse() dataWithHash.subarray(0, 192).reverse()
const aes = crypto.createAesIge(aesKey, aesIv) const aes = crypto.createAesIge(aesKey, aesIv)
const encrypted = aes.encrypt(dataWithHash) const encrypted = aes.encrypt(dataWithHash)
const encryptedHash = await crypto.sha256(encrypted) const encryptedHash = crypto.sha256(encrypted)
xorBufferInPlace(aesKey, encryptedHash) xorBufferInPlace(aesKey, encryptedHash)
const decryptedData = concatBuffers([aesKey, encrypted]) const decryptedData = concatBuffers([aesKey, encrypted])
@ -160,9 +160,9 @@ async function rsaPad(data: Uint8Array, crypto: ICryptoProvider, key: TlPublicKe
} }
} }
async function rsaEncrypt(data: Uint8Array, crypto: ICryptoProvider, key: TlPublicKey): Promise<Uint8Array> { function rsaEncrypt(data: Uint8Array, crypto: ICryptoProvider, key: TlPublicKey): Uint8Array {
const toEncrypt = concatBuffers([ const toEncrypt = concatBuffers([
await crypto.sha1(data), crypto.sha1(data),
data, data,
// sha1 is always 20 bytes, so we're left with 255 - 20 - x padding // sha1 is always 20 bytes, so we're left with 255 - 20 - x padding
randomBytes(235 - data.length), randomBytes(235 - data.length),
@ -271,8 +271,8 @@ export async function doAuthorization(
const pqInnerData = TlBinaryWriter.serializeObject(writerMap, _pqInnerData) const pqInnerData = TlBinaryWriter.serializeObject(writerMap, _pqInnerData)
const encryptedData = publicKey.old ? const encryptedData = publicKey.old ?
await rsaEncrypt(pqInnerData, crypto, publicKey) : rsaEncrypt(pqInnerData, crypto, publicKey) :
await rsaPad(pqInnerData, crypto, publicKey) rsaPad(pqInnerData, crypto, publicKey)
log.debug('requesting DH params') log.debug('requesting DH params')
@ -303,7 +303,7 @@ export async function doAuthorization(
} }
// Step 3: complete DH exchange // Step 3: complete DH exchange
const [key, iv] = await generateKeyAndIvFromNonce(crypto, resPq.serverNonce, newNonce) const [key, iv] = generateKeyAndIvFromNonce(crypto, resPq.serverNonce, newNonce)
const ige = crypto.createAesIge(key, iv) const ige = crypto.createAesIge(key, iv)
const plainTextAnswer = ige.decrypt(serverDhParams.encryptedAnswer) const plainTextAnswer = ige.decrypt(serverDhParams.encryptedAnswer)
@ -311,7 +311,7 @@ export async function doAuthorization(
const serverDhInnerReader = new TlBinaryReader(readerMap, plainTextAnswer, 20) const serverDhInnerReader = new TlBinaryReader(readerMap, plainTextAnswer, 20)
const serverDhInner = serverDhInnerReader.object() as mtp.TlObject const serverDhInner = serverDhInnerReader.object() as mtp.TlObject
if (!buffersEqual(innerDataHash, await crypto.sha1(plainTextAnswer.subarray(20, serverDhInnerReader.pos)))) { if (!buffersEqual(innerDataHash, crypto.sha1(plainTextAnswer.subarray(20, serverDhInnerReader.pos)))) {
throw new MtSecurityError('Step 3: invalid inner data hash') throw new MtSecurityError('Step 3: invalid inner data hash')
} }
@ -340,7 +340,7 @@ export async function doAuthorization(
const gB = bigIntModPow(g, b, dhPrime) const gB = bigIntModPow(g, b, dhPrime)
const authKey = bigIntToBuffer(bigIntModPow(gA, b, dhPrime)) const authKey = bigIntToBuffer(bigIntModPow(gA, b, dhPrime))
const authKeyAuxHash = (await crypto.sha1(authKey)).subarray(0, 8) const authKeyAuxHash = crypto.sha1(authKey).subarray(0, 8)
// validate DH params // validate DH params
if (g <= 1 || g >= dhPrime - 1n) { if (g <= 1 || g >= dhPrime - 1n) {
@ -377,7 +377,7 @@ export async function doAuthorization(
const clientDhInnerWriter = TlBinaryWriter.alloc(writerMap, innerLength) const clientDhInnerWriter = TlBinaryWriter.alloc(writerMap, innerLength)
clientDhInnerWriter.pos = 20 clientDhInnerWriter.pos = 20
clientDhInnerWriter.object(clientDhInner) clientDhInnerWriter.object(clientDhInner)
const clientDhInnerHash = await crypto.sha1(clientDhInnerWriter.uint8View.subarray(20, clientDhInnerWriter.pos)) const clientDhInnerHash = crypto.sha1(clientDhInnerWriter.uint8View.subarray(20, clientDhInnerWriter.pos))
clientDhInnerWriter.pos = 0 clientDhInnerWriter.pos = 0
clientDhInnerWriter.raw(clientDhInnerHash) clientDhInnerWriter.raw(clientDhInnerHash)
@ -412,7 +412,7 @@ export async function doAuthorization(
} }
if (dhGen._ === 'mt_dh_gen_retry') { if (dhGen._ === 'mt_dh_gen_retry') {
const expectedHash = await crypto.sha1(concatBuffers([newNonce, new Uint8Array([2]), authKeyAuxHash])) const expectedHash = crypto.sha1(concatBuffers([newNonce, new Uint8Array([2]), authKeyAuxHash]))
if (!buffersEqual(expectedHash.subarray(4, 20), dhGen.newNonceHash2)) { if (!buffersEqual(expectedHash.subarray(4, 20), dhGen.newNonceHash2)) {
throw Error('Step 4: invalid retry nonce hash from server') throw Error('Step 4: invalid retry nonce hash from server')
@ -423,7 +423,7 @@ export async function doAuthorization(
if (dhGen._ !== 'mt_dh_gen_ok') throw new Error() // unreachable if (dhGen._ !== 'mt_dh_gen_ok') throw new Error() // unreachable
const expectedHash = await crypto.sha1(concatBuffers([newNonce, new Uint8Array([1]), authKeyAuxHash])) const expectedHash = crypto.sha1(concatBuffers([newNonce, new Uint8Array([1]), authKeyAuxHash]))
if (!buffersEqual(expectedHash.subarray(4, 20), dhGen.newNonceHash1)) { if (!buffersEqual(expectedHash.subarray(4, 20), dhGen.newNonceHash1)) {
throw Error('Step 4: invalid nonce hash from server') throw Error('Step 4: invalid nonce hash from server')

View file

@ -243,14 +243,14 @@ export class MtprotoSession {
} }
/** Encrypt a single MTProto message using session's keys */ /** Encrypt a single MTProto message using session's keys */
async encryptMessage(message: Uint8Array): Promise<Uint8Array> { encryptMessage(message: Uint8Array): Uint8Array {
const key = this._authKeyTemp.ready ? this._authKeyTemp : this._authKey const key = this._authKeyTemp.ready ? this._authKeyTemp : this._authKey
return key.encryptMessage(message, this.serverSalt, this._sessionId) return key.encryptMessage(message, this.serverSalt, this._sessionId)
} }
/** Decrypt a single MTProto message using session's keys */ /** Decrypt a single MTProto message using session's keys */
async decryptMessage(data: Uint8Array, callback: Parameters<AuthKey['decryptMessage']>[2]): Promise<void> { decryptMessage(data: Uint8Array, callback: Parameters<AuthKey['decryptMessage']>[2]): void {
if (!this._authKey.ready) throw new MtcuteError('Keys are not set up!') if (!this._authKey.ready) throw new MtcuteError('Keys are not set up!')
const authKeyId = data.subarray(0, 8) const authKeyId = data.subarray(0, 8)

View file

@ -237,10 +237,10 @@ export class MultiSessionConnection extends EventEmitter {
this.connect() this.connect()
} }
async setAuthKey(authKey: Uint8Array | null, temp = false, idx = 0): Promise<void> { setAuthKey(authKey: Uint8Array | null, temp = false, idx = 0): void {
const session = this._sessions[idx] const session = this._sessions[idx]
const key = temp ? session._authKeyTemp : session._authKey const key = temp ? session._authKeyTemp : session._authKey
await key.setup(authKey) key.setup(authKey)
} }
resetAuthKeys(): void { resetAuthKeys(): void {

View file

@ -330,12 +330,10 @@ export class DcConnectionManager {
async loadKeys(): Promise<boolean> { async loadKeys(): Promise<boolean> {
const permanent = await this.manager._storage.getAuthKeyFor(this.dcId) const permanent = await this.manager._storage.getAuthKeyFor(this.dcId)
await Promise.all([ this.main.setAuthKey(permanent)
this.main.setAuthKey(permanent), this.upload.setAuthKey(permanent)
this.upload.setAuthKey(permanent), this.download.setAuthKey(permanent)
this.download.setAuthKey(permanent), this.downloadSmall.setAuthKey(permanent)
this.downloadSmall.setAuthKey(permanent),
])
if (!permanent) { if (!permanent) {
return false return false
@ -345,14 +343,12 @@ export class DcConnectionManager {
await Promise.all( await Promise.all(
this.main._sessions.map(async (_, i) => { this.main._sessions.map(async (_, i) => {
const temp = await this.manager._storage.getAuthKeyFor(this.dcId, i) const temp = await this.manager._storage.getAuthKeyFor(this.dcId, i)
await this.main.setAuthKey(temp, true, i) this.main.setAuthKey(temp, true, i)
if (i === 0) { if (i === 0) {
await Promise.all([ this.upload.setAuthKey(temp, true)
this.upload.setAuthKey(temp, true), this.download.setAuthKey(temp, true)
this.download.setAuthKey(temp, true), this.downloadSmall.setAuthKey(temp, true)
this.downloadSmall.setAuthKey(temp, true),
])
} }
}), }),
) )

View file

@ -267,8 +267,8 @@ export class SessionConnection extends PersistentConnection {
this.emit('auth-begin') this.emit('auth-begin')
doAuthorization(this, this._crypto) doAuthorization(this, this._crypto)
.then(async ([authKey, serverSalt, timeOffset]) => { .then(([authKey, serverSalt, timeOffset]) => {
await this._session._authKey.setup(authKey) this._session._authKey.setup(authKey)
this._session.serverSalt = serverSalt this._session.serverSalt = serverSalt
this._session._timeOffset = timeOffset this._session._timeOffset = timeOffset
@ -322,7 +322,7 @@ export class SessionConnection extends PersistentConnection {
} }
const tempKey = this._session._authKeyTempSecondary const tempKey = this._session._authKeyTempSecondary
await tempKey.setup(tempAuthKey) tempKey.setup(tempAuthKey)
const msgId = this._session.getMessageId() const msgId = this._session.getMessageId()
@ -358,10 +358,10 @@ export class SessionConnection extends PersistentConnection {
writer.raw(randomBytes(8)) writer.raw(randomBytes(8))
const msgWithPadding = writer.result() const msgWithPadding = writer.result()
const hash = await this._crypto.sha1(msgWithoutPadding) const hash = this._crypto.sha1(msgWithoutPadding)
const msgKey = hash.subarray(4, 20) const msgKey = hash.subarray(4, 20)
const ige = await createAesIgeForMessageOld(this._crypto, this._session._authKey.key, msgKey, true) const ige = createAesIgeForMessageOld(this._crypto, this._session._authKey.key, msgKey, true)
const encryptedData = ige.encrypt(msgWithPadding) const encryptedData = ige.encrypt(msgWithPadding)
const encryptedMessage = concatBuffers([this._session._authKey.id, msgKey, encryptedData]) const encryptedMessage = concatBuffers([this._session._authKey.id, msgKey, encryptedData])
@ -399,7 +399,7 @@ export class SessionConnection extends PersistentConnection {
reqWriter.object(request) reqWriter.object(request)
// we can now send it as is // we can now send it as is
const requestEncrypted = await tempKey.encryptMessage( const requestEncrypted = tempKey.encryptMessage(
reqWriter.result(), reqWriter.result(),
tempServerSalt, tempServerSalt,
this._session._sessionId, this._session._sessionId,
@ -476,7 +476,7 @@ export class SessionConnection extends PersistentConnection {
return promise return promise
} }
protected async onMessage(data: Uint8Array): Promise<void> { protected onMessage(data: Uint8Array): void {
if (this._pendingWaitForUnencrypted.length) { if (this._pendingWaitForUnencrypted.length) {
const int32 = new Int32Array(data.buffer, data.byteOffset, 2) const int32 = new Int32Array(data.buffer, data.byteOffset, 2)
@ -501,7 +501,7 @@ export class SessionConnection extends PersistentConnection {
} }
try { try {
await this._session.decryptMessage(data, this._handleRawMessage) this._session.decryptMessage(data, this._handleRawMessage)
} catch (err) { } catch (err) {
this.log.error('failed to decrypt message: %s\ndata: %h', err, data) this.log.error('failed to decrypt message: %s\ndata: %h', err, data)
} }
@ -1793,17 +1793,15 @@ export class SessionConnection extends PersistentConnection {
rootMsgId, rootMsgId,
) )
this._session const enc = this._session.encryptMessage(result)
.encryptMessage(result) this.send(enc).catch((err: Error) => {
.then((enc) => this.send(enc)) this.log.error('error while sending pending messages (root msg_id = %l): %s', rootMsgId, err.stack)
.catch((err: Error) => {
this.log.error('error while sending pending messages (root msg_id = %l): %s', rootMsgId, err.stack)
// put acks in the front so they are the first to be sent // put acks in the front so they are the first to be sent
if (ackMsgIds) { if (ackMsgIds) {
this._session.queuedAcks.splice(0, 0, ...ackMsgIds) this._session.queuedAcks.splice(0, 0, ...ackMsgIds)
} }
this._onMessageFailed(rootMsgId!, 'unknown error') this._onMessageFailed(rootMsgId!, 'unknown error')
}) })
} }
} }

View file

@ -73,8 +73,8 @@ export class ObfuscatedPacketCodec extends WrappedCodec implements IPacketCodec
const decryptIv = randomRev.subarray(32, 48) const decryptIv = randomRev.subarray(32, 48)
if (this._proxy) { if (this._proxy) {
encryptKey = await this._crypto.sha256(concatBuffers([encryptKey, this._proxy.secret])) encryptKey = this._crypto.sha256(concatBuffers([encryptKey, this._proxy.secret]))
decryptKey = await this._crypto.sha256(concatBuffers([decryptKey, this._proxy.secret])) decryptKey = this._crypto.sha256(concatBuffers([decryptKey, this._proxy.secret]))
} }
this._encryptor = this._crypto.createAesCtr(encryptKey, encryptIv, true) this._encryptor = this._crypto.createAesCtr(encryptKey, encryptIv, true)

View file

@ -14,9 +14,9 @@ export interface IAesCtr {
export interface ICryptoProvider { export interface ICryptoProvider {
initialize?(): MaybeAsync<void> initialize?(): MaybeAsync<void>
sha1(data: Uint8Array): MaybeAsync<Uint8Array> sha1(data: Uint8Array): Uint8Array
sha256(data: Uint8Array): MaybeAsync<Uint8Array> sha256(data: Uint8Array): Uint8Array
pbkdf2( pbkdf2(
password: Uint8Array, password: Uint8Array,

View file

@ -14,7 +14,7 @@ import { ICryptoProvider } from './abstract.js'
* @param key PEM-encoded RSA public key * @param key PEM-encoded RSA public key
* @param old Whether this is an "old" key * @param old Whether this is an "old" key
*/ */
export async function parsePublicKey(crypto: ICryptoProvider, key: string, old = false): Promise<TlPublicKey> { export function parsePublicKey(crypto: ICryptoProvider, key: string, old = false): TlPublicKey {
const asn1 = parseAsn1(parsePemContents(key)) const asn1 = parseAsn1(parsePemContents(key))
const modulus = asn1.children?.[0].value const modulus = asn1.children?.[0].value
const exponent = asn1.children?.[1].value const exponent = asn1.children?.[1].value
@ -26,7 +26,7 @@ export async function parsePublicKey(crypto: ICryptoProvider, key: string, old =
writer.bytes(exponent) writer.bytes(exponent)
const data = writer.result() const data = writer.result()
const sha = await crypto.sha1(data) const sha = crypto.sha1(data)
const fp = hexEncode(sha.slice(-8).reverse()) const fp = hexEncode(sha.slice(-8).reverse())
return { return {
@ -44,8 +44,8 @@ export async function parsePublicKey(crypto: ICryptoProvider, key: string, old =
* @param key PEM-encoded RSA public key * @param key PEM-encoded RSA public key
* @param old Whether this is an "old" key * @param old Whether this is an "old" key
*/ */
export async function addPublicKey(crypto: ICryptoProvider, key: string, old = false): Promise<void> { export function addPublicKey(crypto: ICryptoProvider, key: string, old = false): void {
const parsed = await parsePublicKey(crypto, key, old) const parsed = parsePublicKey(crypto, key, old)
keysIndex[parsed.fingerprint] = parsed keysIndex[parsed.fingerprint] = parsed
} }

View file

@ -10,14 +10,14 @@ import { ICryptoProvider, IEncryptionScheme } from './abstract.js'
* @param newNonce New nonce * @param newNonce New nonce
* @returns Tuple: `[key, iv]` * @returns Tuple: `[key, iv]`
*/ */
export async function generateKeyAndIvFromNonce( export function generateKeyAndIvFromNonce(
crypto: ICryptoProvider, crypto: ICryptoProvider,
serverNonce: Uint8Array, serverNonce: Uint8Array,
newNonce: Uint8Array, newNonce: Uint8Array,
): Promise<[Uint8Array, Uint8Array]> { ): [Uint8Array, Uint8Array] {
const hash1 = await crypto.sha1(concatBuffers([newNonce, serverNonce])) const hash1 = crypto.sha1(concatBuffers([newNonce, serverNonce]))
const hash2 = await crypto.sha1(concatBuffers([serverNonce, newNonce])) const hash2 = crypto.sha1(concatBuffers([serverNonce, newNonce]))
const hash3 = await crypto.sha1(concatBuffers([newNonce, newNonce])) const hash3 = crypto.sha1(concatBuffers([newNonce, newNonce]))
const key = concatBuffers([hash1, hash2.subarray(0, 12)]) const key = concatBuffers([hash1, hash2.subarray(0, 12)])
const iv = concatBuffers([hash2.subarray(12, 20), hash3, newNonce.subarray(0, 4)]) const iv = concatBuffers([hash2.subarray(12, 20), hash3, newNonce.subarray(0, 4)])
@ -34,15 +34,15 @@ export async function generateKeyAndIvFromNonce(
* @param messageKey Message key * @param messageKey Message key
* @param client Whether this is a client to server message * @param client Whether this is a client to server message
*/ */
export async function createAesIgeForMessage( export function createAesIgeForMessage(
crypto: ICryptoProvider, crypto: ICryptoProvider,
authKey: Uint8Array, authKey: Uint8Array,
messageKey: Uint8Array, messageKey: Uint8Array,
client: boolean, client: boolean,
): Promise<IEncryptionScheme> { ): IEncryptionScheme {
const x = client ? 0 : 8 const x = client ? 0 : 8
const sha256a = await crypto.sha256(concatBuffers([messageKey, authKey.subarray(x, 36 + x)])) const sha256a = crypto.sha256(concatBuffers([messageKey, authKey.subarray(x, 36 + x)]))
const sha256b = await crypto.sha256(concatBuffers([authKey.subarray(40 + x, 76 + x), messageKey])) const sha256b = crypto.sha256(concatBuffers([authKey.subarray(40 + x, 76 + x), messageKey]))
const key = concatBuffers([sha256a.subarray(0, 8), sha256b.subarray(8, 24), sha256a.subarray(24, 32)]) const key = concatBuffers([sha256a.subarray(0, 8), sha256b.subarray(8, 24), sha256a.subarray(24, 32)])
const iv = concatBuffers([sha256b.subarray(0, 8), sha256a.subarray(8, 24), sha256b.subarray(24, 32)]) const iv = concatBuffers([sha256b.subarray(0, 8), sha256a.subarray(8, 24), sha256b.subarray(24, 32)])
@ -59,19 +59,19 @@ export async function createAesIgeForMessage(
* @param messageKey Message key * @param messageKey Message key
* @param client Whether this is a client to server message * @param client Whether this is a client to server message
*/ */
export async function createAesIgeForMessageOld( export function createAesIgeForMessageOld(
crypto: ICryptoProvider, crypto: ICryptoProvider,
authKey: Uint8Array, authKey: Uint8Array,
messageKey: Uint8Array, messageKey: Uint8Array,
client: boolean, client: boolean,
): Promise<IEncryptionScheme> { ): IEncryptionScheme {
const x = client ? 0 : 8 const x = client ? 0 : 8
const sha1a = await crypto.sha1(concatBuffers([messageKey, authKey.subarray(x, 32 + x)])) const sha1a = crypto.sha1(concatBuffers([messageKey, authKey.subarray(x, 32 + x)]))
const sha1b = await crypto.sha1( const sha1b = crypto.sha1(
concatBuffers([authKey.subarray(32 + x, 48 + x), messageKey, authKey.subarray(48 + x, 64 + x)]), concatBuffers([authKey.subarray(32 + x, 48 + x), messageKey, authKey.subarray(48 + x, 64 + x)]),
) )
const sha1c = await crypto.sha1(concatBuffers([authKey.subarray(64 + x, 96 + x), messageKey])) const sha1c = crypto.sha1(concatBuffers([authKey.subarray(64 + x, 96 + x), messageKey]))
const sha1d = await crypto.sha1(concatBuffers([messageKey, authKey.subarray(96 + x, 128 + x)])) const sha1d = crypto.sha1(concatBuffers([messageKey, authKey.subarray(96 + x, 128 + x)]))
const key = concatBuffers([sha1a.subarray(0, 8), sha1b.subarray(8, 20), sha1c.subarray(4, 16)]) const key = concatBuffers([sha1a.subarray(0, 8), sha1b.subarray(8, 20), sha1c.subarray(4, 16)])
const iv = concatBuffers([ const iv = concatBuffers([

View file

@ -63,7 +63,6 @@ export abstract class BaseNodeCryptoProvider extends BaseCryptoProvider {
} }
gunzip(data: Uint8Array): Uint8Array { gunzip(data: Uint8Array): Uint8Array {
// todo: test if wasm impl is better fit here
return gunzipSync(data) return gunzipSync(data)
} }
} }

View file

@ -24,9 +24,9 @@ export async function computePasswordHash(
salt2: Uint8Array, salt2: Uint8Array,
): Promise<Uint8Array> { ): Promise<Uint8Array> {
const SH = (data: Uint8Array, salt: Uint8Array) => crypto.sha256(concatBuffers([salt, data, salt])) const SH = (data: Uint8Array, salt: Uint8Array) => crypto.sha256(concatBuffers([salt, data, salt]))
const PH1 = async (pwd: Uint8Array, salt1: Uint8Array, salt2: Uint8Array) => SH(await SH(pwd, salt1), salt2) const PH1 = (pwd: Uint8Array, salt1: Uint8Array, salt2: Uint8Array) => SH(SH(pwd, salt1), salt2)
return SH(await crypto.pbkdf2(await PH1(password, salt1, salt2), salt1, 100000), salt2) return SH(await crypto.pbkdf2(PH1(password, salt1, salt2), salt1, 100000), salt2)
} }
/** /**
@ -95,12 +95,9 @@ export async function computeSrpParams(
const H = (data: Uint8Array) => crypto.sha256(data) const H = (data: Uint8Array) => crypto.sha256(data)
const [_k, _u, _x] = await Promise.all([ const _k = crypto.sha256(concatBuffers([algo.p, _g]))
// maybe, just maybe this will be a bit faster with some crypto providers const _u = crypto.sha256(concatBuffers([_gA, request.srpB]))
/* k = */ crypto.sha256(concatBuffers([algo.p, _g])), const _x = await computePasswordHash(crypto, utf8EncodeToBuffer(password), algo.salt1, algo.salt2)
/* u = */ crypto.sha256(concatBuffers([_gA, request.srpB])),
/* x = */ computePasswordHash(crypto, utf8EncodeToBuffer(password), algo.salt1, algo.salt2),
])
const k = bufferToBigInt(_k) const k = bufferToBigInt(_k)
const u = bufferToBigInt(_u) const u = bufferToBigInt(_u)
const x = bufferToBigInt(_x) const x = bufferToBigInt(_x)
@ -111,18 +108,9 @@ export async function computeSrpParams(
let t = gB - kV let t = gB - kV
if (t < 0n) t += p if (t < 0n) t += p
const sA = bigIntModPow(t, a + u * x, p) const sA = bigIntModPow(t, a + u * x, p)
const _kA = await H(bigIntToBuffer(sA, 256)) const _kA = H(bigIntToBuffer(sA, 256))
const _M1 = await H( const _M1 = H(concatBuffers([xorBuffer(H(algo.p), H(_g)), H(algo.salt1), H(algo.salt2), _gA, request.srpB, _kA]))
concatBuffers([
xorBuffer(await H(algo.p), await H(_g)),
await H(algo.salt1),
await H(algo.salt2),
_gA,
request.srpB,
_kA,
]),
)
return { return {
_: 'inputCheckPasswordSRP', _: 'inputCheckPasswordSRP',

View file

@ -0,0 +1,69 @@
import {
createCtr256,
ctr256,
deflateMaxSize,
freeCtr256,
gunzip,
ige256Decrypt,
ige256Encrypt,
initAsync,
InitInput,
sha1,
sha256,
} from '@mtcute/wasm'
import { BaseCryptoProvider, IAesCtr, ICryptoProvider, IEncryptionScheme } from './abstract.js'
export interface WasmCryptoProviderOptions {
/**
* WASM blob to use for crypto operations.
*
* Must conform to `@mtcute/wasm` interface.
*/
wasmInput?: InitInput
}
export class WasmCryptoProvider extends BaseCryptoProvider implements Partial<ICryptoProvider> {
readonly wasmInput?: InitInput
constructor(params?: WasmCryptoProviderOptions) {
super()
this.wasmInput = params?.wasmInput
}
initialize(): Promise<void> {
return initAsync(this.wasmInput)
}
sha1(data: Uint8Array): Uint8Array {
return sha1(data)
}
sha256(data: Uint8Array): Uint8Array {
return sha256(data)
}
createAesCtr(key: Uint8Array, iv: Uint8Array): IAesCtr {
const ctx = createCtr256(key, iv)
return {
process: (data) => ctr256(ctx, data),
close: () => freeCtr256(ctx),
}
}
createAesIge(key: Uint8Array, iv: Uint8Array): IEncryptionScheme {
return {
encrypt: (data) => ige256Encrypt(data, key, iv),
decrypt: (data) => ige256Decrypt(data, key, iv),
}
}
gzip(data: Uint8Array, maxSize: number): Uint8Array | null {
return deflateMaxSize(data, maxSize)
}
gunzip(data: Uint8Array): Uint8Array {
return gunzip(data)
}
}

View file

@ -1,17 +1,5 @@
import { import { ICryptoProvider } from './abstract.js'
createCtr256, import { WasmCryptoProvider, WasmCryptoProviderOptions } from './wasm.js'
ctr256,
deflateMaxSize,
freeCtr256,
gunzip,
ige256Decrypt,
ige256Encrypt,
initAsync,
InitInput,
} from '@mtcute/wasm'
import { MaybeAsync } from '../../index.js'
import { BaseCryptoProvider, IAesCtr, ICryptoProvider, IEncryptionScheme } from './abstract.js'
const ALGO_TO_SUBTLE: Record<string, string> = { const ALGO_TO_SUBTLE: Record<string, string> = {
sha256: 'SHA-256', sha256: 'SHA-256',
@ -19,13 +7,11 @@ const ALGO_TO_SUBTLE: Record<string, string> = {
sha512: 'SHA-512', sha512: 'SHA-512',
} }
export class WebCryptoProvider extends BaseCryptoProvider implements ICryptoProvider { export class WebCryptoProvider extends WasmCryptoProvider implements ICryptoProvider {
readonly subtle: SubtleCrypto readonly subtle: SubtleCrypto
readonly wasmInput?: InitInput
constructor(params?: { wasmInput?: InitInput; subtle?: SubtleCrypto }) { constructor(params?: WasmCryptoProviderOptions & { subtle?: SubtleCrypto }) {
super() super(params)
this.wasmInput = params?.wasmInput
const subtle = params?.subtle ?? globalThis.crypto?.subtle const subtle = params?.subtle ?? globalThis.crypto?.subtle
if (!subtle) { if (!subtle) {
@ -34,18 +20,6 @@ export class WebCryptoProvider extends BaseCryptoProvider implements ICryptoProv
this.subtle = subtle this.subtle = subtle
} }
initialize(): Promise<void> {
return initAsync(this.wasmInput)
}
sha1(data: Uint8Array): MaybeAsync<Uint8Array> {
return this.subtle.digest('SHA-1', data).then((result) => new Uint8Array(result))
}
sha256(data: Uint8Array): MaybeAsync<Uint8Array> {
return this.subtle.digest('SHA-256', data).then((result) => new Uint8Array(result))
}
async pbkdf2( async pbkdf2(
password: Uint8Array, password: Uint8Array,
salt: Uint8Array, salt: Uint8Array,
@ -82,28 +56,4 @@ export class WebCryptoProvider extends BaseCryptoProvider implements ICryptoProv
return new Uint8Array(res) return new Uint8Array(res)
} }
createAesCtr(key: Uint8Array, iv: Uint8Array): IAesCtr {
const ctx = createCtr256(key, iv)
return {
process: (data) => ctr256(ctx, data),
close: () => freeCtr256(ctx),
}
}
createAesIge(key: Uint8Array, iv: Uint8Array): IEncryptionScheme {
return {
encrypt: (data) => ige256Encrypt(data, key, iv),
decrypt: (data) => ige256Decrypt(data, key, iv),
}
}
gzip(data: Uint8Array, maxSize: number): Uint8Array | null {
return deflateMaxSize(data, maxSize)
}
gunzip(data: Uint8Array): Uint8Array {
return gunzip(data)
}
} }

View file

@ -21,9 +21,9 @@ describe('AuthKey', () => {
const logger = new LogManager() const logger = new LogManager()
const readerMap: TlReaderMap = {} const readerMap: TlReaderMap = {}
it('should correctly calculate derivatives', async () => { it('should correctly calculate derivatives', () => {
const key = new AuthKey(crypto, logger, readerMap) const key = new AuthKey(crypto, logger, readerMap)
await key.setup(authKey) key.setup(authKey)
expect(key.key).to.eql(authKey) expect(key.key).to.eql(authKey)
expect(key.clientSalt).to.eql( expect(key.clientSalt).to.eql(

View file

@ -11,20 +11,20 @@ import { ICryptoProvider } from '../src/utils/index.js'
export function testCryptoProvider(c: ICryptoProvider): void { export function testCryptoProvider(c: ICryptoProvider): void {
before(() => c.initialize?.()) before(() => c.initialize?.())
it('should calculate sha1', async () => { it('should calculate sha1', () => {
expect(hexEncode(await c.sha1(utf8EncodeToBuffer('')))).to.eq('da39a3ee5e6b4b0d3255bfef95601890afd80709') expect(hexEncode(c.sha1(utf8EncodeToBuffer('')))).to.eq('da39a3ee5e6b4b0d3255bfef95601890afd80709')
expect(hexEncode(await c.sha1(utf8EncodeToBuffer('hello')))).to.eq('aaf4c61ddcc5e8a2dabede0f3b482cd9aea9434d') expect(hexEncode(c.sha1(utf8EncodeToBuffer('hello')))).to.eq('aaf4c61ddcc5e8a2dabede0f3b482cd9aea9434d')
expect(hexEncode(await c.sha1(hexDecodeToBuffer('aebb1f')))).to.eq('62849d15c5dea495916c5eea8dba5f9551288850') expect(hexEncode(c.sha1(hexDecodeToBuffer('aebb1f')))).to.eq('62849d15c5dea495916c5eea8dba5f9551288850')
}) })
it('should calculate sha256', async () => { it('should calculate sha256', () => {
expect(hexEncode(await c.sha256(utf8EncodeToBuffer('')))).to.eq( expect(hexEncode(c.sha256(utf8EncodeToBuffer('')))).to.eq(
'e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855', 'e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855',
) )
expect(hexEncode(await c.sha256(utf8EncodeToBuffer('hello')))).to.eq( expect(hexEncode(c.sha256(utf8EncodeToBuffer('hello')))).to.eq(
'2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824', '2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824',
) )
expect(hexEncode(await c.sha256(hexDecodeToBuffer('aebb1f')))).to.eq( expect(hexEncode(c.sha256(hexDecodeToBuffer('aebb1f')))).to.eq(
'2d29658aba48f2b286fe8bbddb931b7ad297e5adb5b9a6fc3aab67ef7fbf4e80', '2d29658aba48f2b286fe8bbddb931b7ad297e5adb5b9a6fc3aab67ef7fbf4e80',
) )
}) })

View file

@ -7,9 +7,9 @@ import { parsePublicKey } from '../src/utils/index.js'
const crypto = new NodeCryptoProvider() const crypto = new NodeCryptoProvider()
describe('parsePublicKey', () => { describe('parsePublicKey', () => {
it('should parse telegram public keys', async () => { it('should parse telegram public keys', () => {
expect( expect(
await parsePublicKey( parsePublicKey(
crypto, crypto,
`-----BEGIN RSA PUBLIC KEY----- `-----BEGIN RSA PUBLIC KEY-----
MIIBCgKCAQEAruw2yP/BCcsJliRoW5eBVBVle9dtjJw+OYED160Wybum9SXtBBLX MIIBCgKCAQEAruw2yP/BCcsJliRoW5eBVBVle9dtjJw+OYED160Wybum9SXtBBLX

View file

@ -20,11 +20,11 @@ const authKey = Buffer.alloc(
const messageKey = Buffer.from('25d701f2a29205526757825a99eb2d32') const messageKey = Buffer.from('25d701f2a29205526757825a99eb2d32')
describe('mtproto 2.0', () => { describe('mtproto 2.0', () => {
it('should correctly derive message key and iv for client', async () => { it('should correctly derive message key and iv for client', () => {
const crypto = new NodeCryptoProvider() const crypto = new NodeCryptoProvider()
const spy = chai.spy.on(crypto, 'createAesIge') const spy = chai.spy.on(crypto, 'createAesIge')
await createAesIgeForMessage(crypto, authKey, messageKey, true) createAesIgeForMessage(crypto, authKey, messageKey, true)
expect(spy).to.have.been.called.with.exactly( expect(spy).to.have.been.called.with.exactly(
Buffer.from('7acac59ab48cd370e478daf6c64545ab9f32d5c9197f25febe052110f61875ca', 'hex'), Buffer.from('7acac59ab48cd370e478daf6c64545ab9f32d5c9197f25febe052110f61875ca', 'hex'),
@ -32,11 +32,11 @@ describe('mtproto 2.0', () => {
) )
}) })
it('should correctly derive message key and iv for server', async () => { it('should correctly derive message key and iv for server', () => {
const crypto = new NodeCryptoProvider() const crypto = new NodeCryptoProvider()
const spy = chai.spy.on(crypto, 'createAesIge') const spy = chai.spy.on(crypto, 'createAesIge')
await createAesIgeForMessage(crypto, authKey, messageKey, false) createAesIgeForMessage(crypto, authKey, messageKey, false)
expect(spy).to.have.been.called.with.exactly( expect(spy).to.have.been.called.with.exactly(
Buffer.from('c7cf179e7ebab144ba87de05415db4157d2fc66df4790b2fd405a6c8cbe4c0b3', 'hex'), Buffer.from('c7cf179e7ebab144ba87de05415db4157d2fc66df4790b2fd405a6c8cbe4c0b3', 'hex'),
@ -46,11 +46,11 @@ describe('mtproto 2.0', () => {
}) })
describe('mtproto 1.0', () => { describe('mtproto 1.0', () => {
it('should correctly derive message key and iv for client', async () => { it('should correctly derive message key and iv for client', () => {
const crypto = new NodeCryptoProvider() const crypto = new NodeCryptoProvider()
const spy = chai.spy.on(crypto, 'createAesIge') const spy = chai.spy.on(crypto, 'createAesIge')
await createAesIgeForMessageOld(crypto, authKey, messageKey, true) createAesIgeForMessageOld(crypto, authKey, messageKey, true)
expect(spy).to.have.been.called.with.exactly( expect(spy).to.have.been.called.with.exactly(
Buffer.from('aad61cb5b7be5e8435174d74665f8a978e85806d0970ad4958642ca49e3c8834', 'hex'), Buffer.from('aad61cb5b7be5e8435174d74665f8a978e85806d0970ad4958642ca49e3c8834', 'hex'),
@ -58,11 +58,11 @@ describe('mtproto 1.0', () => {
) )
}) })
it('should correctly derive message key and iv for server', async () => { it('should correctly derive message key and iv for server', () => {
const crypto = new NodeCryptoProvider() const crypto = new NodeCryptoProvider()
const spy = chai.spy.on(crypto, 'createAesIge') const spy = chai.spy.on(crypto, 'createAesIge')
await createAesIgeForMessageOld(crypto, authKey, messageKey, false) createAesIgeForMessageOld(crypto, authKey, messageKey, false)
expect(spy).to.have.been.called.with.exactly( expect(spy).to.have.been.called.with.exactly(
Buffer.from('d57682a17105e43b92bc5025ea80e88ef708240fc19450dfe072a8760f9534da', 'hex'), Buffer.from('d57682a17105e43b92bc5025ea80e88ef708240fc19450dfe072a8760f9534da', 'hex'),
@ -72,10 +72,10 @@ describe('mtproto 1.0', () => {
}) })
describe('mtproto key/iv from nonce', () => { describe('mtproto key/iv from nonce', () => {
it('should correctly derive message key and iv for given nonces', async () => { it('should correctly derive message key and iv for given nonces', () => {
const crypto = new NodeCryptoProvider() const crypto = new NodeCryptoProvider()
const res = await generateKeyAndIvFromNonce( const res = generateKeyAndIvFromNonce(
crypto, crypto,
Buffer.from('8af24c551836e5ed7002f5857e6e71b2', 'hex'), Buffer.from('8af24c551836e5ed7002f5857e6e71b2', 'hex'),
Buffer.from('3bf48b2d3152f383d82d1f2b32ac7fb5', 'hex'), Buffer.from('3bf48b2d3152f383d82d1f2b32ac7fb5', 'hex'),

View file

@ -50,7 +50,7 @@ async function main() {
const obj: Record<string, TlPublicKey> = {} const obj: Record<string, TlPublicKey> = {}
for await (const key of parseInputFile()) { for await (const key of parseInputFile()) {
const parsed = await parsePublicKey(crypto, key.pem, key.kind === 'old') const parsed = parsePublicKey(crypto, key.pem, key.kind === 'old')
obj[parsed.fingerprint] = parsed obj[parsed.fingerprint] = parsed
} }

View file

@ -5,15 +5,20 @@
Highly optimized for size & speed WASM implementation of common algorithms used in Telegram. Highly optimized for size & speed WASM implementation of common algorithms used in Telegram.
## Features ## Features
- **Super lightweight**: Only 45 KB raw, 22 KB gzipped - **Super lightweight**: Only 47 KB raw, 24 KB gzipped
- **Blazingly fast**: Up to 10x faster than pure JS implementations - **Blazingly fast**: Up to 10x faster than pure JS implementations
- Implements AES IGE and Deflate (zlib compression + gunzip), which are not available in some environments (e.g. web) - **Ready to use**: Implements almost all algos used in MTProto:
- AES IGE
- Deflate (zlib compression + gunzip)
- SHA-1, SHA-256
## Acknowledgements ## Acknowledgements
- Deflate is implemented through a modified version of [libdeflate](https://github.com/ebiggers/libdeflate), MIT license. - Deflate is implemented through a modified version of [libdeflate](https://github.com/ebiggers/libdeflate), MIT license.
- Modified by [kamillaova](https://github.com/kamillaova) to support WASM and improve bundle size - Modified by [kamillaova](https://github.com/kamillaova) to support WASM and improve bundle size
- AES IGE code is mostly based on [tgcrypto](https://github.com/pyrogram/tgcrypto), LGPL-3.0 license. - AES IGE code is mostly based on [tgcrypto](https://github.com/pyrogram/tgcrypto), LGPL-3.0 license.
- To comply with LGPL-3.0, the source code of the modified tgcrypto is available [here](./lib/crypto/) under LGPL-3.0 license. - To comply with LGPL-3.0, the source code of the modified tgcrypto is available [here](./lib/crypto/) under LGPL-3.0 license.
- SHA1 is based on [teeny-sha1](https://github.com/CTrabant/teeny-sha1)
- SHA256 is based on [lekkit/sha256](https://github.com/LekKit/sha256)
## Benchmarks ## Benchmarks
See https://github.com/mtcute/benchmarks See https://github.com/mtcute/benchmarks

View file

@ -6,9 +6,10 @@ RUN apk add --no-cache lld make clang16 binaryen
COPY crypto /src/crypto COPY crypto /src/crypto
COPY libdeflate /src/libdeflate COPY libdeflate /src/libdeflate
COPY *.h *.c Makefile /src/ COPY utils /src/utils
COPY wasm.h Makefile /src/
RUN ZLIB_COMPRESSION_API=1 GZIP_DECOMPRESSION_API=1 IGE_API=1 CTR_API=1 make RUN make
FROM scratch AS binaries FROM scratch AS binaries
COPY --from=build /src/mtcute.wasm / COPY --from=build /src/mtcute.wasm /

View file

@ -1,39 +1,17 @@
.PHONY: all clean .PHONY: all clean
DEFAULT_API ?= 0 SOURCES = utils/allocator.c \
libdeflate/allocator.c \
DEFLATE_COMPRESSION_API ?= $(DEFAULT_API) libdeflate/deflate_compress.c \
DEFLATE_DECOMPRESSION_API ?= $(DEFAULT_API) libdeflate/deflate_decompress.c \
GZIP_COMPRESSION_API ?= $(DEFAULT_API) libdeflate/gzip_decompress.c \
GZIP_DECOMPRESSION_API ?= $(DEFAULT_API) libdeflate/zlib_compress.c \
ZLIB_COMPRESSION_API ?= $(DEFAULT_API) libdeflate/adler32.c \
ZLIB_DECOMPRESSION_API ?= $(DEFAULT_API) crypto/aes256.c \
CRC32_API ?= $(DEFAULT_API) crypto/ige256.c \
ADLER32_API ?= $(DEFAULT_API) crypto/ctr256.c \
IGE_API ?= $(DEFAULT_API) hash/sha256.c \
CTR_API ?= $(DEFAULT_API) hash/sha1.c
CRC32 ?= 0
LOGGING ?= 0
_DEFLATE_COMPRESSION := 1
_DEFLATE_DECOMPRESSION := 1
_ADLER32 := $(findstring 1, $(ZLIB_COMPRESSION_API)$(ZLIB_DECOMPRESSION_API))
_AES := $(findstring 1, $(IGE_API)$(CTR_API))
SOURCES = utils.c \
$(if $(filter 1, $(_DEFLATE_COMPRESSION)), libdeflate/deflate_compress.c) \
$(if $(filter 1, $(_DEFLATE_DECOMPRESSION)), libdeflate/deflate_decompress.c) \
$(if $(filter 1, $(GZIP_COMPRESSION_API)), libdeflate/gzip_compress.c) \
$(if $(filter 1, $(GZIP_DECOMPRESSION_API)), libdeflate/gzip_decompress.c) \
$(if $(filter 1, $(ZLIB_COMPRESSION_API)), libdeflate/zlib_compress.c) \
$(if $(filter 1, $(ZLIB_DECOMPRESSION_API)), libdeflate/zlib_decompress.c) \
$(if $(filter 1, $(CRC32)), libdeflate/crc32.c) \
$(if $(filter 1, $(_ADLER32)), libdeflate/adler32.c) \
$(if $(filter 1, $(_AES)), crypto/aes256.c) \
$(if $(filter 1, $(IGE_API)), crypto/ige256.c) \
$(if $(filter 1, $(CTR_API)), crypto/ctr256.c)
WASM_CC ?= clang WASM_CC ?= clang
CC := $(WASM_CC) CC := $(WASM_CC)
@ -41,7 +19,6 @@ CC := $(WASM_CC)
CFLAGS_WASM := \ CFLAGS_WASM := \
-target wasm32-unknown-unknown \ -target wasm32-unknown-unknown \
-nostdlib -ffreestanding -DFREESTANDING \ -nostdlib -ffreestanding -DFREESTANDING \
$(if $(filter 1, $(LOGGING)), -DLOGGING) \
-mbulk-memory \ -mbulk-memory \
-Wl,--no-entry,--export-dynamic,--lto-O3 -Wl,--no-entry,--export-dynamic,--lto-O3
@ -69,7 +46,7 @@ endif
OUT := mtcute.wasm OUT := mtcute.wasm
$(OUT): $(SOURCES) $(OUT): $(SOURCES)
$(CC) $(CFLAGS) -I . -o $@ $^ $(CC) $(CFLAGS) -I . -I utils -o $@ $^
clean: clean:
rm -f $(OUT) rm -f $(OUT)

View file

@ -6,6 +6,17 @@
#define GET(p) SWAP(*((uint32_t *)(p))) #define GET(p) SWAP(*((uint32_t *)(p)))
#define PUT(ct, st) (*((uint32_t *)(ct)) = SWAP((st))) #define PUT(ct, st) (*((uint32_t *)(ct)) = SWAP((st)))
uint8_t aes_shared_key_buffer[32];
uint8_t aes_shared_iv_buffer[32];
WASM_EXPORT uint8_t* __get_shared_key_buffer() {
return aes_shared_key_buffer;
}
WASM_EXPORT uint8_t* __get_shared_iv_buffer() {
return aes_shared_iv_buffer;
}
static const uint32_t Te0[256] = { static const uint32_t Te0[256] = {
0xc66363a5, 0xf87c7c84, 0xee777799, 0xf67b7b8d, 0xfff2f20d, 0xd66b6bbd, 0xde6f6fb1, 0x91c5c554, 0xc66363a5, 0xf87c7c84, 0xee777799, 0xf67b7b8d, 0xfff2f20d, 0xd66b6bbd, 0xde6f6fb1, 0x91c5c554,
0x60303050, 0x02010103, 0xce6767a9, 0x562b2b7d, 0xe7fefe19, 0xb5d7d762, 0x4dababe6, 0xec76769a, 0x60303050, 0x02010103, 0xce6767a9, 0x562b2b7d, 0xe7fefe19, 0xb5d7d762, 0x4dababe6, 0xec76769a,

View file

@ -1,15 +1,13 @@
#include "lib_common.h" #include "wasm.h"
#ifndef AES256_H #ifndef AES256_H
#define AES256_H #define AES256_H
#define AES_BLOCK_SIZE 16 #define AES_BLOCK_SIZE 16
#define EXPANDED_KEY_SIZE 60 #define EXPANDED_KEY_SIZE 60
#define AES_EXPORT __attribute__((visibility("default")))
#ifdef __cplusplus extern uint8_t aes_shared_key_buffer[32];
extern "C" { extern uint8_t aes_shared_iv_buffer[32];
#endif
void aes256_set_encryption_key(uint8_t* key, uint32_t* expandedKey); void aes256_set_encryption_key(uint8_t* key, uint32_t* expandedKey);
void aes256_set_decryption_key(uint8_t* key, uint32_t* expandedKey); void aes256_set_decryption_key(uint8_t* key, uint32_t* expandedKey);
@ -17,9 +15,4 @@ void aes256_set_decryption_key(uint8_t* key, uint32_t* expandedKey);
void aes256_encrypt(uint8_t* in, uint8_t* out, uint32_t* expandedKey); void aes256_encrypt(uint8_t* in, uint8_t* out, uint32_t* expandedKey);
void aes256_decrypt(uint8_t* in, uint8_t* out, uint32_t* expandedKey); void aes256_decrypt(uint8_t* in, uint8_t* out, uint32_t* expandedKey);
#endif // AES256_H
#ifdef __cplusplus
}
#endif
#endif // AES256_H

View file

@ -2,27 +2,25 @@
struct ctr256_ctx { struct ctr256_ctx {
uint32_t expandedKey[EXPANDED_KEY_SIZE]; uint32_t expandedKey[EXPANDED_KEY_SIZE];
uint8_t* iv; uint8_t iv[AES_BLOCK_SIZE];
uint8_t state; uint8_t state;
}; };
AES_EXPORT struct ctr256_ctx* ctr256_alloc(uint8_t* key, uint8_t* iv) { WASM_EXPORT struct ctr256_ctx* ctr256_alloc() {
struct ctr256_ctx *state = (struct ctr256_ctx *) __malloc(sizeof(struct ctr256_ctx)); struct ctr256_ctx *state = (struct ctr256_ctx *) __malloc(sizeof(struct ctr256_ctx));
aes256_set_encryption_key(key, state->expandedKey); aes256_set_encryption_key(aes_shared_key_buffer, state->expandedKey);
__free(key);
state->iv = iv; memcpy(state->iv, aes_shared_iv_buffer, AES_BLOCK_SIZE);
state->state = 0; state->state = 0;
return state; return state;
} }
AES_EXPORT void ctr256_free(struct ctr256_ctx* ctx) { WASM_EXPORT void ctr256_free(struct ctr256_ctx* ctx) {
__free(ctx->iv);
__free(ctx); __free(ctx);
} }
AES_EXPORT void ctr256(struct ctr256_ctx* ctx, uint8_t* in, uint32_t length, uint8_t *out) { WASM_EXPORT void ctr256(struct ctr256_ctx* ctx, uint8_t* in, uint32_t length, uint8_t *out) {
uint8_t chunk[AES_BLOCK_SIZE]; uint8_t chunk[AES_BLOCK_SIZE];
uint32_t* expandedKey = ctx->expandedKey; uint32_t* expandedKey = ctx->expandedKey;
uint8_t* iv = ctx->iv; uint8_t* iv = ctx->iv;

View file

@ -1,6 +0,0 @@
#ifndef CTR256_H
#define CTR256_H
uint8_t *ctr256(const uint8_t in[], uint32_t length, const uint8_t key[32], uint8_t iv[16], uint8_t *state);
#endif

View file

@ -1,6 +1,6 @@
#include "aes256.h" #include "aes256.h"
AES_EXPORT void ige256_encrypt(uint8_t* in, uint32_t length, uint8_t* key, uint8_t* iv, uint8_t* out) { WASM_EXPORT void ige256_encrypt(uint8_t* in, uint32_t length, uint8_t* out) {
uint32_t expandedKey[EXPANDED_KEY_SIZE]; uint32_t expandedKey[EXPANDED_KEY_SIZE];
uint32_t i, j; uint32_t i, j;
@ -8,10 +8,10 @@ AES_EXPORT void ige256_encrypt(uint8_t* in, uint32_t length, uint8_t* key, uint8
uint8_t* iv2; uint8_t* iv2;
uint8_t* block; uint8_t* block;
iv1 = &iv[0]; iv1 = &aes_shared_iv_buffer[0];
iv2 = &iv[16]; iv2 = &aes_shared_iv_buffer[16];
aes256_set_encryption_key(key, expandedKey); aes256_set_encryption_key(aes_shared_key_buffer, expandedKey);
for (i = 0; i < length; i += AES_BLOCK_SIZE) { for (i = 0; i < length; i += AES_BLOCK_SIZE) {
block = &out[i]; block = &out[i];
@ -29,7 +29,7 @@ AES_EXPORT void ige256_encrypt(uint8_t* in, uint32_t length, uint8_t* key, uint8
} }
} }
AES_EXPORT void ige256_decrypt(uint8_t* in, uint32_t length, uint8_t* key, uint8_t* iv, uint8_t* out) { WASM_EXPORT void ige256_decrypt(uint8_t* in, uint32_t length, uint8_t* out) {
uint32_t expandedKey[EXPANDED_KEY_SIZE]; uint32_t expandedKey[EXPANDED_KEY_SIZE];
uint32_t i, j; uint32_t i, j;
@ -37,10 +37,10 @@ AES_EXPORT void ige256_decrypt(uint8_t* in, uint32_t length, uint8_t* key, uint8
uint8_t* iv2; uint8_t* iv2;
uint8_t* block; uint8_t* block;
iv1 = &iv[16]; iv1 = &aes_shared_iv_buffer[16];
iv2 = &iv[0]; iv2 = &aes_shared_iv_buffer[0];
aes256_set_decryption_key(key, expandedKey); aes256_set_decryption_key(aes_shared_key_buffer, expandedKey);
for (i = 0; i < length; i += AES_BLOCK_SIZE) { for (i = 0; i < length; i += AES_BLOCK_SIZE) {
block = &out[i]; block = &out[i];
@ -56,4 +56,4 @@ AES_EXPORT void ige256_decrypt(uint8_t* in, uint32_t length, uint8_t* key, uint8
iv1 = block; iv1 = block;
iv2 = &in[i]; iv2 = &in[i];
} }
} }

View file

@ -1,17 +0,0 @@
#include <stdint.h>
#ifndef IGE256_H
#define IGE256_H
#ifdef __cplusplus
extern "C" {
#endif
void ige256_encrypt(uint8_t* in, uint32_t length, uint8_t* key, uint8_t* iv, uint8_t* out);
void ige256_decrypt(uint8_t* in, uint32_t length, uint8_t* key, uint8_t* iv, uint8_t* out);
#ifdef __cplusplus
}
#endif
#endif // IGE256_H

View file

@ -0,0 +1,179 @@
/*******************************************************************************
* Teeny SHA-1
*
* The below sha1digest() calculates a SHA-1 hash value for a
* specified data buffer and generates a hex representation of the
* result. This implementation is a re-forming of the SHA-1 code at
* https://github.com/jinqiangshou/EncryptionLibrary.
*
* Copyright (c) 2017 CTrabant
*
* License: MIT, see included LICENSE file for details.
*
* To use the sha1digest() function either copy it into an existing
* project source code file or include this file in a project and put
* the declaration (example below) in the sources files where needed.
******************************************************************************/
#include "wasm.h"
/* Declaration:
extern int sha1digest(uint8_t *digest, char *hexdigest, const uint8_t *data, size_t databytes);
*/
/*******************************************************************************
* sha1digest: https://github.com/CTrabant/teeny-sha1
*
* Calculate the SHA-1 value for supplied data buffer and generate a
* text representation in hexadecimal.
*
* Based on https://github.com/jinqiangshou/EncryptionLibrary, credit
* goes to @jinqiangshou, all new bugs are mine.
*
* @input:
* data -- data to be hashed
* databytes -- bytes in data buffer to be hashed
*
* @output:
* digest -- the result, MUST be at least 20 bytes
* hexdigest -- the result in hex, MUST be at least 41 bytes
*
* At least one of the output buffers must be supplied. The other, if not
* desired, may be set to NULL.
*
* @return: 0 on success and non-zero on error.
******************************************************************************/
WASM_EXPORT void sha1(const uint8_t *data, size_t databytes) {
#define SHA1ROTATELEFT(value, bits) (((value) << (bits)) | ((value) >> (32 - (bits))))
uint32_t W[80];
uint32_t H[] = {0x67452301,
0xEFCDAB89,
0x98BADCFE,
0x10325476,
0xC3D2E1F0};
uint32_t a;
uint32_t b;
uint32_t c;
uint32_t d;
uint32_t e;
uint32_t f = 0;
uint32_t k = 0;
uint32_t idx;
uint32_t lidx;
uint32_t widx;
uint32_t didx = 0;
int32_t wcount;
uint32_t temp;
uint64_t databits = ((uint64_t)databytes) * 8;
uint32_t loopcount = (databytes + 8) / 64 + 1;
uint32_t tailbytes = 64 * loopcount - databytes;
uint8_t datatail[128] = {0};
/* Pre-processing of data tail (includes padding to fill out 512-bit chunk):
Add bit '1' to end of message (big-endian)
Add 64-bit message length in bits at very end (big-endian) */
datatail[0] = 0x80;
datatail[tailbytes - 8] = (uint8_t) (databits >> 56 & 0xFF);
datatail[tailbytes - 7] = (uint8_t) (databits >> 48 & 0xFF);
datatail[tailbytes - 6] = (uint8_t) (databits >> 40 & 0xFF);
datatail[tailbytes - 5] = (uint8_t) (databits >> 32 & 0xFF);
datatail[tailbytes - 4] = (uint8_t) (databits >> 24 & 0xFF);
datatail[tailbytes - 3] = (uint8_t) (databits >> 16 & 0xFF);
datatail[tailbytes - 2] = (uint8_t) (databits >> 8 & 0xFF);
datatail[tailbytes - 1] = (uint8_t) (databits >> 0 & 0xFF);
/* Process each 512-bit chunk */
for (lidx = 0; lidx < loopcount; lidx++)
{
/* Compute all elements in W */
memset (W, 0, 80 * sizeof (uint32_t));
/* Break 512-bit chunk into sixteen 32-bit, big endian words */
for (widx = 0; widx <= 15; widx++)
{
wcount = 24;
/* Copy byte-per byte from specified buffer */
while (didx < databytes && wcount >= 0)
{
W[widx] += (((uint32_t)data[didx]) << wcount);
didx++;
wcount -= 8;
}
/* Fill out W with padding as needed */
while (wcount >= 0)
{
W[widx] += (((uint32_t)datatail[didx - databytes]) << wcount);
didx++;
wcount -= 8;
}
}
/* Extend the sixteen 32-bit words into eighty 32-bit words, with potential optimization from:
"Improving the Performance of the Secure Hash Algorithm (SHA-1)" by Max Locktyukhin */
for (widx = 16; widx <= 31; widx++)
{
W[widx] = SHA1ROTATELEFT ((W[widx - 3] ^ W[widx - 8] ^ W[widx - 14] ^ W[widx - 16]), 1);
}
for (widx = 32; widx <= 79; widx++)
{
W[widx] = SHA1ROTATELEFT ((W[widx - 6] ^ W[widx - 16] ^ W[widx - 28] ^ W[widx - 32]), 2);
}
/* Main loop */
a = H[0];
b = H[1];
c = H[2];
d = H[3];
e = H[4];
for (idx = 0; idx <= 79; idx++)
{
if (idx <= 19)
{
f = (b & c) | ((~b) & d);
k = 0x5A827999;
}
else if (idx >= 20 && idx <= 39)
{
f = b ^ c ^ d;
k = 0x6ED9EBA1;
}
else if (idx >= 40 && idx <= 59)
{
f = (b & c) | (b & d) | (c & d);
k = 0x8F1BBCDC;
}
else if (idx >= 60 && idx <= 79)
{
f = b ^ c ^ d;
k = 0xCA62C1D6;
}
temp = SHA1ROTATELEFT (a, 5) + f + e + k + W[idx];
e = d;
d = c;
c = SHA1ROTATELEFT (b, 30);
b = a;
a = temp;
}
H[0] += a;
H[1] += b;
H[2] += c;
H[3] += d;
H[4] += e;
}
/* Store binary digest in supplied buffer */
for (idx = 0; idx < 5; idx++) {
shared_out[idx * 4 + 0] = (uint8_t) (H[idx] >> 24);
shared_out[idx * 4 + 1] = (uint8_t) (H[idx] >> 16);
shared_out[idx * 4 + 2] = (uint8_t) (H[idx] >> 8);
shared_out[idx * 4 + 3] = (uint8_t) (H[idx]);
}
#undef SHA1ROTATELEFT
} /* End of sha1digest() */

View file

@ -0,0 +1,167 @@
/*
MIT License
Copyright (c) 2020 LekKit https://github.com/LekKit
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
#include "wasm.h"
struct lekkit_sha256_buff {
uint64_t data_size;
uint32_t h[8];
uint8_t last_chunk[64];
uint8_t chunk_size;
};
void lekkit_sha256_init(struct lekkit_sha256_buff* buff) {
buff->h[0] = 0x6a09e667;
buff->h[1] = 0xbb67ae85;
buff->h[2] = 0x3c6ef372;
buff->h[3] = 0xa54ff53a;
buff->h[4] = 0x510e527f;
buff->h[5] = 0x9b05688c;
buff->h[6] = 0x1f83d9ab;
buff->h[7] = 0x5be0cd19;
buff->data_size = 0;
buff->chunk_size = 0;
}
static const uint32_t lekkit_k[64] = {
0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5,
0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174,
0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da,
0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967,
0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85,
0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070,
0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3,
0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2
};
#define rotate_r(val, bits) (val >> bits | val << (32 - bits))
static void lekkit_sha256_calc_chunk(struct lekkit_sha256_buff* buff, const uint8_t* chunk) {
uint32_t w[64];
uint32_t tv[8];
uint32_t i;
for (i=0; i<16; ++i){
w[i] = (uint32_t) chunk[0] << 24 | (uint32_t) chunk[1] << 16 | (uint32_t) chunk[2] << 8 | (uint32_t) chunk[3];
chunk += 4;
}
for (i=16; i<64; ++i){
uint32_t s0 = rotate_r(w[i-15], 7) ^ rotate_r(w[i-15], 18) ^ (w[i-15] >> 3);
uint32_t s1 = rotate_r(w[i-2], 17) ^ rotate_r(w[i-2], 19) ^ (w[i-2] >> 10);
w[i] = w[i-16] + s0 + w[i-7] + s1;
}
for (i = 0; i < 8; ++i)
tv[i] = buff->h[i];
for (i=0; i<64; ++i){
uint32_t S1 = rotate_r(tv[4], 6) ^ rotate_r(tv[4], 11) ^ rotate_r(tv[4], 25);
uint32_t ch = (tv[4] & tv[5]) ^ (~tv[4] & tv[6]);
uint32_t temp1 = tv[7] + S1 + ch + lekkit_k[i] + w[i];
uint32_t S0 = rotate_r(tv[0], 2) ^ rotate_r(tv[0], 13) ^ rotate_r(tv[0], 22);
uint32_t maj = (tv[0] & tv[1]) ^ (tv[0] & tv[2]) ^ (tv[1] & tv[2]);
uint32_t temp2 = S0 + maj;
tv[7] = tv[6];
tv[6] = tv[5];
tv[5] = tv[4];
tv[4] = tv[3] + temp1;
tv[3] = tv[2];
tv[2] = tv[1];
tv[1] = tv[0];
tv[0] = temp1 + temp2;
}
for (i = 0; i < 8; ++i)
buff->h[i] += tv[i];
}
void lekkit_sha256_update(struct lekkit_sha256_buff* buff, const void* data, uint32_t size) {
const uint8_t* ptr = (const uint8_t*)data;
buff->data_size += size;
/* If there is data left in buff, concatenate it to process as new chunk */
if (size + buff->chunk_size >= 64) {
uint8_t tmp_chunk[64];
__builtin_memcpy(tmp_chunk, buff->last_chunk, buff->chunk_size);
__builtin_memcpy(tmp_chunk + buff->chunk_size, ptr, 64 - buff->chunk_size);
ptr += (64 - buff->chunk_size);
size -= (64 - buff->chunk_size);
buff->chunk_size = 0;
lekkit_sha256_calc_chunk(buff, tmp_chunk);
}
/* Run over data chunks */
while (size >= 64) {
lekkit_sha256_calc_chunk(buff, ptr);
ptr += 64;
size -= 64;
}
/* Save remaining data in buff, will be reused on next call or finalize */
__builtin_memcpy(buff->last_chunk + buff->chunk_size, ptr, size);
buff->chunk_size += size;
}
void lekkit_sha256_finalize(struct lekkit_sha256_buff* buff) {
buff->last_chunk[buff->chunk_size] = 0x80;
buff->chunk_size++;
__builtin_memset(buff->last_chunk + buff->chunk_size, 0, 64 - buff->chunk_size);
/* If there isn't enough space to fit int64, pad chunk with zeroes and prepare next chunk */
if (buff->chunk_size > 56) {
lekkit_sha256_calc_chunk(buff, buff->last_chunk);
__builtin_memset(buff->last_chunk, 0, 64);
}
/* Add total size as big-endian int64 x8 */
uint64_t size = buff->data_size * 8;
int i;
for (i = 8; i > 0; --i) {
buff->last_chunk[55+i] = size & 255;
size >>= 8;
}
lekkit_sha256_calc_chunk(buff, buff->last_chunk);
}
void lekkit_sha256_read(const struct lekkit_sha256_buff* buff, uint8_t* hash) {
uint32_t i;
for (i = 0; i < 8; i++) {
hash[i*4] = (buff->h[i] >> 24) & 255;
hash[i*4 + 1] = (buff->h[i] >> 16) & 255;
hash[i*4 + 2] = (buff->h[i] >> 8) & 255;
hash[i*4 + 3] = buff->h[i] & 255;
}
}
struct lekkit_sha256_buff lekkit_shared_ctx;
WASM_EXPORT void sha256(const void* data, uint32_t size) {
lekkit_sha256_init(&lekkit_shared_ctx);
lekkit_sha256_update(&lekkit_shared_ctx, data, size);
lekkit_sha256_finalize(&lekkit_shared_ctx);
lekkit_sha256_read(&lekkit_shared_ctx, shared_out);
}
#undef rotate_r

View file

@ -0,0 +1,21 @@
#include "wasm.h"
void *
libdeflate_aligned_malloc(size_t alignment, size_t size)
{
void *ptr = __malloc(sizeof(void *) + alignment - 1 + size);
if (ptr) {
void *orig_ptr = ptr;
ptr = (void *)ALIGN((uintptr_t)ptr + sizeof(void *), alignment);
((void **)ptr)[-1] = orig_ptr;
}
return ptr;
}
void
libdeflate_aligned_free(void *ptr)
{
__free((((void **)ptr)[-1]));
}

View file

@ -5,14 +5,6 @@
#ifndef LIB_LIB_COMMON_H #ifndef LIB_LIB_COMMON_H
#define LIB_LIB_COMMON_H #define LIB_LIB_COMMON_H
#ifdef LIBDEFLATE_H
/*
* When building the library, LIBDEFLATEAPI needs to be defined properly before
* including libdeflate.h.
*/
# error "lib_common.h must always be included before libdeflate.h"
#endif
#if defined(LIBDEFLATE_DLL) && (defined(_WIN32) || defined(__CYGWIN__)) #if defined(LIBDEFLATE_DLL) && (defined(_WIN32) || defined(__CYGWIN__))
# define LIBDEFLATE_EXPORT_SYM __declspec(dllexport) # define LIBDEFLATE_EXPORT_SYM __declspec(dllexport)
#elif defined(__GNUC__) #elif defined(__GNUC__)
@ -38,9 +30,8 @@
#define LIBDEFLATEAPI LIBDEFLATE_EXPORT_SYM LIBDEFLATE_ALIGN_STACK #define LIBDEFLATEAPI LIBDEFLATE_EXPORT_SYM LIBDEFLATE_ALIGN_STACK
#include "common_defs.h" #include "common_defs.h"
#include "libdeflate.h"
extern void* __malloc(size_t size); #include "wasm.h"
extern void __free(void* ptr);
void *libdeflate_aligned_malloc(size_t alignment, size_t size); void *libdeflate_aligned_malloc(size_t alignment, size_t size);
void libdeflate_aligned_free(void *ptr); void libdeflate_aligned_free(void *ptr);
@ -49,14 +40,4 @@ void libdeflate_aligned_free(void *ptr);
#define CONCAT_IMPL(a, b) a##b #define CONCAT_IMPL(a, b) a##b
#define CONCAT(a, b) CONCAT_IMPL(a, b) #define CONCAT(a, b) CONCAT_IMPL(a, b)
#define ADD_SUFFIX(name) CONCAT(name, SUFFIX) #define ADD_SUFFIX(name) CONCAT(name, SUFFIX)
#ifdef LOGGING
void __debug(char* str);
#define DEBUG(str) __debug(str);
#else
#define DEBUG(str)
#endif
#endif /* LIB_LIB_COMMON_H */ #endif /* LIB_LIB_COMMON_H */

View file

@ -16,20 +16,6 @@ extern "C" {
#define LIBDEFLATE_VERSION_MINOR 19 #define LIBDEFLATE_VERSION_MINOR 19
#define LIBDEFLATE_VERSION_STRING "1.19" #define LIBDEFLATE_VERSION_STRING "1.19"
/*
* Users of libdeflate.dll on Windows can define LIBDEFLATE_DLL to cause
* __declspec(dllimport) to be used. This should be done when it's easy to do.
* Otherwise it's fine to skip it, since it is a very minor performance
* optimization that is irrelevant for most use cases of libdeflate.
*/
#ifndef LIBDEFLATEAPI
# if defined(LIBDEFLATE_DLL) && (defined(_WIN32) || defined(__CYGWIN__))
# define LIBDEFLATEAPI __declspec(dllimport)
# else
# define LIBDEFLATEAPI
# endif
#endif
/* ========================================================================== */ /* ========================================================================== */
/* Compression */ /* Compression */
/* ========================================================================== */ /* ========================================================================== */

Binary file not shown.

View file

@ -1,137 +0,0 @@
/*
* utils.c - utility functions for libdeflate
*
* Copyright 2016 Eric Biggers
*
* Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associated documentation
* files (the "Software"), to deal in the Software without
* restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following
* conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
* OTHER DEALINGS IN THE SOFTWARE.
*/
#include "lib_common.h"
extern unsigned char __heap_base;
static size_t __heap_tail = (size_t) &__heap_base;
static size_t __heap_mark = (size_t) &__heap_base;
#define memory_size() __builtin_wasm_memory_size(0)
#define memory_grow(delta) __builtin_wasm_memory_grow(0, delta)
enum {
_mem_flag_used = 0xbf82583a,
_mem_flag_free = 0xab34d705
};
__attribute__((visibility("default"))) void* __malloc(size_t n) {
n += (8 - (n % 4)) % 4;
// check if size is enough
size_t total = __heap_tail + n + 3 * sizeof(size_t);
size_t size = memory_size() << 16;
if (total > size) {
memory_grow((total >> 16) - (size >> 16) + 1);
}
unsigned int r = __heap_tail;
*((size_t*) r) = n;
r += sizeof(size_t);
*((size_t*) r) =_mem_flag_used;
r += sizeof(size_t);
__heap_tail = r + n;
*((size_t*) __heap_tail) = n;
__heap_tail += sizeof(size_t);
return (void*) r;
}
__attribute__((visibility("default"))) void __free(void* p) {
size_t n;
// null case
if (!p) return;
size_t r=(size_t)p;
r -= sizeof(size_t);
// already free
if (*((size_t*) r) != _mem_flag_used) {
return;
}
// mark it as free
size_t flag = _mem_flag_free;
*((size_t*) r) = flag;
// calc ptr_tail
r -= sizeof(size_t);
n = *(size_t*) r; // size of current block
size_t ptr_tail = ((size_t) p) + n + sizeof(size_t);
// if not at tail return without moving __heap_tail
if (__heap_tail != ptr_tail) {
return;
}
__heap_tail = r;
while (r > (size_t) &__heap_base) {
r -= sizeof(size_t);
n = *(size_t*) r; // size of previous block
r -= n;
r -= sizeof(size_t);
flag = *((size_t*) r);
if (flag != _mem_flag_free) break;
r -= sizeof(size_t);
n = *(size_t*) r; // size of current block
__heap_tail = r;
}
}
void *
libdeflate_aligned_malloc(size_t alignment, size_t size)
{
void *ptr = __malloc(sizeof(void *) + alignment - 1 + size);
if (ptr) {
void *orig_ptr = ptr;
ptr = (void *)ALIGN((uintptr_t)ptr + sizeof(void *), alignment);
((void **)ptr)[-1] = orig_ptr;
}
return ptr;
}
void
libdeflate_aligned_free(void *ptr)
{
__free((((void **)ptr)[-1]));
}
#ifdef LOGGING
char* __debug_log = 0;
char __debug_log_pos = 0;
__attribute__((visibility("default"))) char* __get_debug_log() {
return __debug_log;
}
void __debug(char* str) {
if (!__debug_log) {
__debug_log = __malloc(1024);
}
int i = 0;
while (str[i] != '\0') {
__debug_log[__debug_log_pos++] = str[i++];
}
__debug_log[__debug_log_pos++] = '\n';
__debug_log[__debug_log_pos] = '\0';
}
#endif

View file

@ -0,0 +1,74 @@
#include "wasm.h"
extern unsigned char __heap_base;
static size_t __heap_tail = (size_t) &__heap_base;
static size_t __heap_mark = (size_t) &__heap_base;
#define memory_size() __builtin_wasm_memory_size(0)
#define memory_grow(delta) __builtin_wasm_memory_grow(0, delta)
enum {
_mem_flag_used = 0xbf82583a,
_mem_flag_free = 0xab34d705
};
WASM_EXPORT void* __malloc(size_t n) {
n += (8 - (n % 4)) % 4;
// check if size is enough
size_t total = __heap_tail + n + 3 * sizeof(size_t);
size_t size = memory_size() << 16;
if (total > size) {
memory_grow((total >> 16) - (size >> 16) + 1);
}
unsigned int r = __heap_tail;
*((size_t*) r) = n;
r += sizeof(size_t);
*((size_t*) r) =_mem_flag_used;
r += sizeof(size_t);
__heap_tail = r + n;
*((size_t*) __heap_tail) = n;
__heap_tail += sizeof(size_t);
return (void*) r;
}
WASM_EXPORT void __free(void* p) {
size_t n;
// null case
if (!p) return;
size_t r=(size_t)p;
r -= sizeof(size_t);
// already free
if (*((size_t*) r) != _mem_flag_used) {
return;
}
// mark it as free
size_t flag = _mem_flag_free;
*((size_t*) r) = flag;
// calc ptr_tail
r -= sizeof(size_t);
n = *(size_t*) r; // size of current block
size_t ptr_tail = ((size_t) p) + n + sizeof(size_t);
// if not at tail return without moving __heap_tail
if (__heap_tail != ptr_tail) {
return;
}
__heap_tail = r;
while (r > (size_t) &__heap_base) {
r -= sizeof(size_t);
n = *(size_t*) r; // size of previous block
r -= n;
r -= sizeof(size_t);
flag = *((size_t*) r);
if (flag != _mem_flag_free) break;
r -= sizeof(size_t);
n = *(size_t*) r; // size of current block
__heap_tail = r;
}
}
uint8_t shared_out[256];
WASM_EXPORT uint8_t* __get_shared_out() {
return shared_out;
}

View file

@ -28,8 +28,6 @@
#ifndef COMMON_DEFS_H #ifndef COMMON_DEFS_H
#define COMMON_DEFS_H #define COMMON_DEFS_H
#include "libdeflate.h"
#include <stdbool.h> #include <stdbool.h>
#include <stddef.h> /* for size_t */ #include <stddef.h> /* for size_t */
#include <stdint.h> #include <stdint.h>

18
packages/wasm/lib/wasm.h Normal file
View file

@ -0,0 +1,18 @@
#ifndef MTCUTE_WASM_H
#define MTCUTE_WASM_H
#include "common_defs.h"
#define WASM_EXPORT __attribute__((visibility("default")))
// see utils/allocator.c
extern void* __malloc(size_t size);
extern void __free(void* ptr);
#define memset(p,v,n) __builtin_memset(p,v,n)
#define memcpy(d,s,n) __builtin_memcpy(d,s,n)
// more than enough for most of our cases
extern uint8_t shared_out[256];
#endif // MTCUTE_WASM_H

View file

@ -6,11 +6,17 @@ export * from './types.js'
let wasm!: MtcuteWasmModule let wasm!: MtcuteWasmModule
let compressor!: number let compressor!: number
let decompressor!: number let decompressor!: number
let sharedOutPtr!: number
let sharedKeyPtr!: number
let sharedIvPtr!: number
let cachedUint8Memory: Uint8Array | null = null let cachedUint8Memory: Uint8Array | null = null
function initCommon() { function initCommon() {
compressor = wasm.libdeflate_alloc_compressor(6) compressor = wasm.libdeflate_alloc_compressor(6)
decompressor = wasm.libdeflate_alloc_decompressor() decompressor = wasm.libdeflate_alloc_decompressor()
sharedOutPtr = wasm.__get_shared_out()
sharedKeyPtr = wasm.__get_shared_key_buffer()
sharedIvPtr = wasm.__get_shared_iv_buffer()
} }
function getUint8Memory() { function getUint8Memory() {
@ -58,7 +64,9 @@ export async function initAsync(input?: InitInput): Promise<void> {
export function deflateMaxSize(bytes: Uint8Array, size: number): Uint8Array | null { export function deflateMaxSize(bytes: Uint8Array, size: number): Uint8Array | null {
const outputPtr = wasm.__malloc(size) const outputPtr = wasm.__malloc(size)
const inputPtr = wasm.__malloc(bytes.length) const inputPtr = wasm.__malloc(bytes.length)
getUint8Memory().set(bytes, inputPtr)
const mem = getUint8Memory()
mem.set(bytes, inputPtr)
const written = wasm.libdeflate_zlib_compress(compressor, inputPtr, bytes.length, outputPtr, size) const written = wasm.libdeflate_zlib_compress(compressor, inputPtr, bytes.length, outputPtr, size)
wasm.__free(inputPtr) wasm.__free(inputPtr)
@ -69,7 +77,7 @@ export function deflateMaxSize(bytes: Uint8Array, size: number): Uint8Array | nu
return null return null
} }
const result = getUint8Memory().slice(outputPtr, outputPtr + written) const result = mem.slice(outputPtr, outputPtr + written)
wasm.__free(outputPtr) wasm.__free(outputPtr)
return result return result
@ -109,20 +117,18 @@ export function gunzip(bytes: Uint8Array): Uint8Array {
* @param iv initialization vector (32 bytes) * @param iv initialization vector (32 bytes)
*/ */
export function ige256Encrypt(data: Uint8Array, key: Uint8Array, iv: Uint8Array): Uint8Array { export function ige256Encrypt(data: Uint8Array, key: Uint8Array, iv: Uint8Array): Uint8Array {
const ptr = wasm.__malloc(key.length + iv.length + data.length + data.length) const ptr = wasm.__malloc(data.length + data.length)
const keyPtr = ptr const inputPtr = ptr
const ivPtr = ptr + key.length
const inputPtr = ivPtr + iv.length
const outputPtr = inputPtr + data.length const outputPtr = inputPtr + data.length
const mem = getUint8Memory() const mem = getUint8Memory()
mem.set(data, inputPtr) mem.set(data, inputPtr)
mem.set(key, keyPtr) mem.set(key, sharedKeyPtr)
mem.set(iv, ivPtr) mem.set(iv, sharedIvPtr)
wasm.ige256_encrypt(inputPtr, data.length, keyPtr, ivPtr, outputPtr) wasm.ige256_encrypt(inputPtr, data.length, outputPtr)
const result = getUint8Memory().slice(outputPtr, outputPtr + data.length) const result = mem.slice(outputPtr, outputPtr + data.length)
wasm.__free(ptr) wasm.__free(ptr)
@ -137,21 +143,19 @@ export function ige256Encrypt(data: Uint8Array, key: Uint8Array, iv: Uint8Array)
* @param iv initialization vector (32 bytes) * @param iv initialization vector (32 bytes)
*/ */
export function ige256Decrypt(data: Uint8Array, key: Uint8Array, iv: Uint8Array): Uint8Array { export function ige256Decrypt(data: Uint8Array, key: Uint8Array, iv: Uint8Array): Uint8Array {
const ptr = wasm.__malloc(key.length + iv.length + data.length + data.length) const ptr = wasm.__malloc(data.length + data.length)
const keyPtr = ptr const inputPtr = ptr
const ivPtr = ptr + key.length
const inputPtr = ivPtr + iv.length
const outputPtr = inputPtr + data.length const outputPtr = inputPtr + data.length
const mem = getUint8Memory() const mem = getUint8Memory()
mem.set(data, inputPtr) mem.set(data, inputPtr)
mem.set(key, keyPtr) mem.set(key, sharedKeyPtr)
mem.set(iv, ivPtr) mem.set(iv, sharedIvPtr)
wasm.ige256_decrypt(inputPtr, data.length, keyPtr, ivPtr, outputPtr) wasm.ige256_decrypt(inputPtr, data.length, outputPtr)
const result = mem.slice(outputPtr, outputPtr + data.length)
const result = getUint8Memory().slice(outputPtr, outputPtr + data.length)
wasm.__free(ptr) wasm.__free(ptr)
return result return result
@ -163,15 +167,10 @@ export function ige256Decrypt(data: Uint8Array, key: Uint8Array, iv: Uint8Array)
* > **Note**: `freeCtr256` must be called on the returned context when it's no longer needed * > **Note**: `freeCtr256` must be called on the returned context when it's no longer needed
*/ */
export function createCtr256(key: Uint8Array, iv: Uint8Array) { export function createCtr256(key: Uint8Array, iv: Uint8Array) {
const keyPtr = wasm.__malloc(key.length) getUint8Memory().set(key, sharedKeyPtr)
const ivPtr = wasm.__malloc(iv.length) getUint8Memory().set(iv, sharedIvPtr)
getUint8Memory().set(key, keyPtr)
getUint8Memory().set(iv, ivPtr)
const ctx = wasm.ctr256_alloc(keyPtr, ivPtr) return wasm.ctr256_alloc()
// pointers are "moved" and will be handled by c code
return ctx
} }
/** /**
@ -203,6 +202,42 @@ export function ctr256(ctx: number, data: Uint8Array): Uint8Array {
return result return result
} }
/**
* Calculate a SHA-256 hash
*
* @param data data to hash
*/
export function sha256(data: Uint8Array): Uint8Array {
const { __malloc, __free } = wasm
const inputPtr = __malloc(data.length)
const mem = getUint8Memory()
mem.set(data, inputPtr)
wasm.sha256(inputPtr, data.length)
__free(inputPtr)
return mem.slice(sharedOutPtr, sharedOutPtr + 32)
}
/**
* Calculate a SHA-1 hash
*
* @param data data to hash
*/
export function sha1(data: Uint8Array): Uint8Array {
const { __malloc, __free } = wasm
const inputPtr = __malloc(data.length)
const mem = getUint8Memory()
mem.set(data, inputPtr)
wasm.sha1(inputPtr, data.length)
__free(inputPtr)
return mem.slice(sharedOutPtr, sharedOutPtr + 20)
}
/** /**
* Get the WASM module instance. * Get the WASM module instance.
* *

View file

@ -2,6 +2,11 @@ export interface MtcuteWasmModule {
memory: WebAssembly.Memory memory: WebAssembly.Memory
__malloc: (size: number) => number __malloc: (size: number) => number
__free: (ptr: number) => void __free: (ptr: number) => void
__get_shared_out: () => number
__get_shared_key_buffer: () => number
__get_shared_iv_buffer: () => number
libdeflate_alloc_decompressor: () => number libdeflate_alloc_decompressor: () => number
libdeflate_alloc_compressor: (level: number) => number libdeflate_alloc_compressor: (level: number) => number
@ -11,13 +16,16 @@ export interface MtcuteWasmModule {
libdeflate_zlib_compress: (ctx: number, src: number, srcLen: number, dst: number, dstLen: number) => number libdeflate_zlib_compress: (ctx: number, src: number, srcLen: number, dst: number, dstLen: number) => number
ige256_encrypt: (data: number, dataLen: number, key: number, iv: number, out: number) => void ige256_encrypt: (data: number, dataLen: number, out: number) => void
ige256_decrypt: (data: number, dataLen: number, key: number, iv: number, out: number) => void ige256_decrypt: (data: number, dataLen: number, out: number) => void
ctr256_alloc: (key: number, iv: number) => number ctr256_alloc: () => number
ctr256_free: (ctx: number) => void ctr256_free: (ctx: number) => void
ctr256: (ctx: number, data: number, dataLen: number, out: number) => number ctr256: (ctx: number, data: number, dataLen: number, out: number) => number
sha256: (data: number, dataLen: number) => void
sha1: (data: number, dataLen: number) => void
} }
export type SyncInitInput = BufferSource | WebAssembly.Module export type SyncInitInput = BufferSource | WebAssembly.Module

View file

@ -0,0 +1,49 @@
/* eslint-disable no-restricted-globals */
import { expect } from 'chai'
import { before, describe } from 'mocha'
import { __getWasm, initAsync, sha1, sha256 } from '../src/index.js'
before(async () => {
await initAsync()
})
describe('sha256', () => {
it('should correctly calculate sha-256 hash', () => {
const hash = sha256(Buffer.from('abc'))
expect(Buffer.from(hash).toString('hex')).to.equal(
'ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad',
)
})
it('should not leak memory', () => {
const mem = __getWasm().memory.buffer
const memSize = mem.byteLength
for (let i = 0; i < 100; i++) {
sha256(Buffer.from('abc'))
}
expect(mem.byteLength).to.equal(memSize)
})
})
describe('sha1', () => {
it('should correctly calculate sha-1 hash', () => {
const hash = sha1(Buffer.from('abc'))
expect(Buffer.from(hash).toString('hex')).to.equal('a9993e364706816aba3e25717850c26c9cd0d89d')
})
it('should not leak memory', () => {
const mem = __getWasm().memory.buffer
const memSize = mem.byteLength
for (let i = 0; i < 100; i++) {
sha1(Buffer.from('abc'))
}
expect(mem.byteLength).to.equal(memSize)
})
})