Permalink
Cannot retrieve contributors at this time
Name already in use
A tag already exists with the provided branch name. Many Git commands accept both tag and branch names, so creating this branch may cause unexpected behavior. Are you sure you want to create this branch?
node-appletv/src/lib/verifier.ts
Go to fileThis commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
134 lines (117 sloc)
4.41 KB
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
import { load } from 'protobufjs'; | |
import * as path from 'path'; | |
import * as ed25519 from 'ed25519'; | |
import * as crypto from 'crypto'; | |
import * as curve25519 from 'curve25519-n2'; | |
import { AppleTV } from './appletv'; | |
import { Credentials } from './credentials'; | |
import { Message } from './message'; | |
import tlv from './util/tlv'; | |
import enc from './util/encryption'; | |
type PairingData = { | |
sessionPublicKey: Buffer; | |
sharedSecret: Buffer; | |
encryptionKey: Buffer; | |
pairingData: Buffer; | |
} | |
export class Verifier { | |
constructor(public device: AppleTV) { | |
} | |
async verify(): Promise<{}> { | |
var verifyPrivate = Buffer.alloc(32); | |
curve25519.makeSecretKey(verifyPrivate); | |
let verifyPublic = curve25519.derivePublicKey(verifyPrivate); | |
let { sessionPublicKey, encryptionKey, sharedSecret, pairingData } = await this.requestPairingData(verifyPublic, verifyPrivate); | |
let tlvData = tlv.decode(pairingData); | |
let identifier = tlvData[tlv.Tag.Username]; | |
let signature = tlvData[tlv.Tag.Signature]; | |
if (!identifier.equals(this.device.credentials.identifier)) { | |
throw new Error("Identifier mismatch"); | |
} | |
let deviceInfo = Buffer.concat([sessionPublicKey, Buffer.from(identifier), verifyPublic]); | |
if (!ed25519.Verify(deviceInfo, signature, this.device.credentials.publicKey)) { | |
throw new Error("Signature verification failed"); | |
} | |
return await this.completeVerification(verifyPublic, sessionPublicKey, encryptionKey, sharedSecret); | |
} | |
private async requestPairingData(verifyPublic: Buffer, verifyPrivate: Buffer): Promise<PairingData> { | |
let encodedData = tlv.encode( | |
tlv.Tag.Sequence, 0x01, | |
tlv.Tag.PublicKey, verifyPublic | |
); | |
let message = { | |
status: 0, | |
state: 3, | |
isRetrying: true, | |
isUsingSystemPairing: true, | |
pairingData: encodedData | |
}; | |
await this.device.sendMessage('CryptoPairingMessage', 'CryptoPairingMessage', message, false); | |
let pairingDataResponse = await this.device.waitForSequence(0x02); | |
let pairingData = pairingDataResponse.payload.pairingData; | |
let decodedData = tlv.decode(pairingData); | |
let sessionPublicKey = decodedData[tlv.Tag.PublicKey]; | |
let encryptedData = decodedData[tlv.Tag.EncryptedData]; | |
if (sessionPublicKey.length != 32) { | |
throw new Error(`sessionPublicKey must be 32 bytes (but was ${sessionPublicKey.length})`); | |
} | |
let cipherText = encryptedData.slice(0, -16); | |
let hmac = encryptedData.slice(-16); | |
let sharedSecret = curve25519.deriveSharedSecret(verifyPrivate, sessionPublicKey); | |
let encryptionKey = enc.HKDF( | |
"sha512", | |
Buffer.from("Pair-Verify-Encrypt-Salt"), | |
sharedSecret, | |
Buffer.from("Pair-Verify-Encrypt-Info"), | |
32 | |
); | |
let decryptedData = enc.verifyAndDecrypt(cipherText, hmac, null, Buffer.from('PV-Msg02'), encryptionKey); | |
return { | |
sessionPublicKey: sessionPublicKey, | |
sharedSecret: sharedSecret, | |
encryptionKey: encryptionKey, | |
pairingData: decryptedData | |
}; | |
} | |
private async completeVerification(verifyPublic: Buffer, sessionPublicKey: Buffer, encryptionKey: Buffer, sharedSecret: Buffer): Promise<{}> { | |
let material = Buffer.concat([verifyPublic, Buffer.from(this.device.credentials.pairingId), sessionPublicKey]); | |
let keyPair = ed25519.MakeKeypair(this.device.credentials.encryptionKey); | |
let signed = ed25519.Sign(material, keyPair); | |
let plainTLV = tlv.encode( | |
tlv.Tag.Username, Buffer.from(this.device.credentials.pairingId), | |
tlv.Tag.Signature, signed | |
); | |
let encryptedTLV = Buffer.concat(enc.encryptAndSeal(plainTLV, null, Buffer.from('PV-Msg03'), encryptionKey)); | |
let tlvData = tlv.encode( | |
tlv.Tag.Sequence, 0x03, | |
tlv.Tag.EncryptedData, encryptedTLV | |
); | |
let message = { | |
status: 0, | |
state: 3, | |
isRetrying: false, | |
isUsingSystemPairing: true, | |
pairingData: tlvData | |
}; | |
await this.device.sendMessage('CryptoPairingMessage', 'CryptoPairingMessage', message, false); | |
await this.device.waitForSequence(0x04); | |
let readKey = enc.HKDF( | |
"sha512", | |
Buffer.from("MediaRemote-Salt"), | |
sharedSecret, | |
Buffer.from("MediaRemote-Read-Encryption-Key"), | |
32 | |
); | |
let writeKey = enc.HKDF( | |
"sha512", | |
Buffer.from("MediaRemote-Salt"), | |
sharedSecret, | |
Buffer.from("MediaRemote-Write-Encryption-Key"), | |
32 | |
); | |
return { | |
readKey: readKey, | |
writeKey: writeKey | |
}; | |
} | |
} |