Skip to content

Commit

Permalink
cleanup
Browse files Browse the repository at this point in the history
  • Loading branch information
evandcoleman committed Feb 2, 2020
1 parent 144d772 commit 90fb5dc
Show file tree
Hide file tree
Showing 12 changed files with 404 additions and 415 deletions.
1 change: 1 addition & 0 deletions dist/lib/appletv.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ export declare class AppleTV extends EventEmitter {
* @returns A promise that resolves to the AppleTV object after the message has been sent.
*/
sendKeyCommand(key: AppleTV.Key): Promise<AppleTV>;
waitForSequence(sequence: number, timeout?: number): Promise<Message>;
private sendKeyPressAndRelease;
private sendKeyPress;
private requestPlaybackQueueWithWait;
Expand Down
3 changes: 3 additions & 0 deletions dist/lib/appletv.js
Original file line number Diff line number Diff line change
Expand Up @@ -228,6 +228,9 @@ class AppleTV extends events_1.EventEmitter /* <AppleTV.Events> */ {
return this.sendKeyPressAndRelease(1, 0x89);
}
}
waitForSequence(sequence, timeout = 3) {
return this.connection.waitForSequence(sequence, timeout);
}
sendKeyPressAndRelease(usePage, usage) {
let that = this;
return this.sendKeyPress(usePage, usage, true)
Expand Down
1 change: 1 addition & 0 deletions dist/lib/connection.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ export declare class Connection extends EventEmitter {
send(message: ProtoMessage<{}>, waitForResponse: boolean, priority: number, credentials?: Credentials): Promise<Message>;
private sendProtocolMessage;
private decodeMessage;
waitForSequence(sequence: number, timeout?: number): Promise<Message>;
}
export declare module Connection {
interface Events {
Expand Down
24 changes: 24 additions & 0 deletions dist/lib/connection.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ const varint = require("varint");
const snake = require("snake-case");
const camelcase = require("camelcase");
const events_1 = require("events");
const tlv_1 = require("./util/tlv");
const message_1 = require("./message");
class Connection extends events_1.EventEmitter /* <Connection.Events> */ {
constructor(device, socket) {
Expand Down Expand Up @@ -183,5 +184,28 @@ class Connection extends events_1.EventEmitter /* <Connection.Events> */ {
});
});
}
waitForSequence(sequence, timeout = 3) {
let that = this;
let handler = (message, resolve) => {
let tlvData = tlv_1.default.decode(message.payload.pairingData);
if (Buffer.from([sequence]).equals(tlvData[tlv_1.default.Tag.Sequence])) {
resolve(message);
}
};
return new Promise((resolve, reject) => {
that.on('message', (message) => {
if (message.type == message_1.Message.Type.CryptoPairingMessage) {
handler(message, resolve);
}
});
setTimeout(() => {
reject(new Error("Timed out waiting for crypto sequence " + sequence));
}, timeout * 1000);
})
.then(value => {
that.removeListener('message', handler);
return value;
});
}
}
exports.Connection = Connection;
3 changes: 2 additions & 1 deletion dist/lib/pairing.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,5 +15,6 @@ export declare class Pairing {
*/
initiatePair(): Promise<(pin: string) => Promise<AppleTV>>;
private completePairing;
private waitForSequence;
private sendThirdSequence;
private sendFifthSequence;
}
163 changes: 73 additions & 90 deletions dist/lib/pairing.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,18 @@
"use strict";
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
Object.defineProperty(exports, "__esModule", { value: true });
const srp = require("fast-srp-hap");
const crypto = require("crypto");
const ed25519 = require("ed25519");
const credentials_1 = require("./credentials");
const message_1 = require("./message");
const tlv_1 = require("./util/tlv");
const encryption_1 = require("./util/encryption");
class Pairing {
Expand All @@ -17,123 +25,98 @@ class Pairing {
* @returns A promise that resolves to a callback which takes in the pairing pin from the Apple TV.
*/
initiatePair() {
let that = this;
let tlvData = tlv_1.default.encode(tlv_1.default.Tag.PairingMethod, 0x00, tlv_1.default.Tag.Sequence, 0x01);
let message = {
status: 0,
isUsingSystemPairing: true,
isRetrying: true,
state: 2,
pairingData: tlvData
};
return this.device
.sendMessage('CryptoPairingMessage', 'CryptoPairingMessage', message, false)
.then(() => {
return that.waitForSequence(0x02);
})
.then(message => {
return __awaiter(this, void 0, void 0, function* () {
let that = this;
let tlvData = tlv_1.default.encode(tlv_1.default.Tag.PairingMethod, 0x00, tlv_1.default.Tag.Sequence, 0x01);
let requestMessage = {
status: 0,
isUsingSystemPairing: true,
isRetrying: true,
state: 2,
pairingData: tlvData
};
yield this.device.sendMessage('CryptoPairingMessage', 'CryptoPairingMessage', requestMessage, false);
let message = yield this.device.waitForSequence(0x02);
let pairingData = message.payload.pairingData;
let tlvData = tlv_1.default.decode(pairingData);
if (tlvData[tlv_1.default.Tag.BackOff]) {
let backOff = tlvData[tlv_1.default.Tag.BackOff];
let decodedData = tlv_1.default.decode(pairingData);
if (decodedData[tlv_1.default.Tag.BackOff]) {
let backOff = decodedData[tlv_1.default.Tag.BackOff];
let seconds = backOff.readIntBE(0, backOff.byteLength);
if (seconds > 0) {
throw new Error("You've attempt to pair too recently. Try again in " + seconds + " seconds.");
}
}
if (tlvData[tlv_1.default.Tag.ErrorCode]) {
let buffer = tlvData[tlv_1.default.Tag.ErrorCode];
throw new Error(that.device.name + " responded with error code " + buffer.readIntBE(0, buffer.byteLength) + ". Try rebooting your Apple TV.");
if (decodedData[tlv_1.default.Tag.ErrorCode]) {
let buffer = decodedData[tlv_1.default.Tag.ErrorCode];
throw new Error(this.device.name + " responded with error code " + buffer.readIntBE(0, buffer.byteLength) + ". Try rebooting your Apple TV.");
}
that.deviceSalt = tlvData[tlv_1.default.Tag.Salt];
that.devicePublicKey = tlvData[tlv_1.default.Tag.PublicKey];
if (that.deviceSalt.byteLength != 16) {
throw new Error(`salt must be 16 bytes (but was ${that.deviceSalt.byteLength})`);
this.deviceSalt = decodedData[tlv_1.default.Tag.Salt];
this.devicePublicKey = decodedData[tlv_1.default.Tag.PublicKey];
if (this.deviceSalt.byteLength != 16) {
throw new Error(`salt must be 16 bytes (but was ${this.deviceSalt.byteLength})`);
}
if (that.devicePublicKey.byteLength !== 384) {
throw new Error(`serverPublicKey must be 384 bytes (but was ${that.devicePublicKey.byteLength})`);
if (this.devicePublicKey.byteLength !== 384) {
throw new Error(`serverPublicKey must be 384 bytes (but was ${this.devicePublicKey.byteLength})`);
}
return Promise.resolve((pin) => {
return (pin) => {
return that.completePairing(pin);
});
};
});
}
completePairing(pin) {
this.srp = srp.Client(srp.params['3072'], this.deviceSalt, Buffer.from('Pair-Setup'), Buffer.from(pin), this.key);
this.srp.setB(this.devicePublicKey);
this.publicKey = this.srp.computeA();
this.proof = this.srp.computeM1();
// console.log("DEBUG: Client Public Key=" + this.publicKey.toString('hex') + "\nProof=" + this.proof.toString('hex'));
let that = this;
let tlvData = tlv_1.default.encode(tlv_1.default.Tag.Sequence, 0x03, tlv_1.default.Tag.PublicKey, that.publicKey, tlv_1.default.Tag.Proof, that.proof);
let message = {
status: 0,
pairingData: tlvData
};
return this.device
.sendMessage('CryptoPairingMessage', 'CryptoPairingMessage', message, false)
.then(() => {
return that.waitForSequence(0x04);
})
.then(message => {
return __awaiter(this, void 0, void 0, function* () {
yield this.sendThirdSequence(pin);
let message = yield this.device.waitForSequence(0x04);
let pairingData = message.payload.pairingData;
that.deviceProof = tlv_1.default.decode(pairingData)[tlv_1.default.Tag.Proof];
// console.log("DEBUG: Device Proof=" + that.deviceProof.toString('hex'));
that.srp.checkM2(that.deviceProof);
this.deviceProof = tlv_1.default.decode(pairingData)[tlv_1.default.Tag.Proof];
// console.log("DEBUG: Device Proof=" + this.deviceProof.toString('hex'));
this.srp.checkM2(this.deviceProof);
let seed = crypto.randomBytes(32);
let keyPair = ed25519.MakeKeypair(seed);
let privateKey = keyPair.privateKey;
let publicKey = keyPair.publicKey;
let sharedSecret = that.srp.computeK();
let sharedSecret = this.srp.computeK();
let deviceHash = encryption_1.default.HKDF("sha512", Buffer.from("Pair-Setup-Controller-Sign-Salt"), sharedSecret, Buffer.from("Pair-Setup-Controller-Sign-Info"), 32);
let deviceInfo = Buffer.concat([deviceHash, Buffer.from(that.device.pairingId), publicKey]);
let deviceInfo = Buffer.concat([deviceHash, Buffer.from(this.device.pairingId), publicKey]);
let deviceSignature = ed25519.Sign(deviceInfo, privateKey);
let encryptionKey = encryption_1.default.HKDF("sha512", Buffer.from("Pair-Setup-Encrypt-Salt"), sharedSecret, Buffer.from("Pair-Setup-Encrypt-Info"), 32);
let tlvData = tlv_1.default.encode(tlv_1.default.Tag.Username, Buffer.from(that.device.pairingId), tlv_1.default.Tag.PublicKey, publicKey, tlv_1.default.Tag.Signature, deviceSignature);
yield this.sendFifthSequence(publicKey, deviceSignature, encryptionKey);
let newMessage = yield this.device.waitForSequence(0x06);
let encryptedData = tlv_1.default.decode(newMessage.payload.pairingData)[tlv_1.default.Tag.EncryptedData];
let cipherText = encryptedData.slice(0, -16);
let hmac = encryptedData.slice(-16);
let decrpytedData = encryption_1.default.verifyAndDecrypt(cipherText, hmac, null, Buffer.from('PS-Msg06'), encryptionKey);
let tlvData = tlv_1.default.decode(decrpytedData);
this.device.credentials = new credentials_1.Credentials(this.device.uid, tlvData[tlv_1.default.Tag.Username], this.device.pairingId, tlvData[tlv_1.default.Tag.PublicKey], seed);
return this.device;
});
}
sendThirdSequence(pin) {
return __awaiter(this, void 0, void 0, function* () {
this.srp = srp.Client(srp.params['3072'], this.deviceSalt, Buffer.from('Pair-Setup'), Buffer.from(pin), this.key);
this.srp.setB(this.devicePublicKey);
this.publicKey = this.srp.computeA();
this.proof = this.srp.computeM1();
// console.log("DEBUG: Client Public Key=" + this.publicKey.toString('hex') + "\nProof=" + this.proof.toString('hex'));
let tlvData = tlv_1.default.encode(tlv_1.default.Tag.Sequence, 0x03, tlv_1.default.Tag.PublicKey, this.publicKey, tlv_1.default.Tag.Proof, this.proof);
let message = {
status: 0,
pairingData: tlvData
};
return yield this.device.sendMessage('CryptoPairingMessage', 'CryptoPairingMessage', message, false);
});
}
sendFifthSequence(publicKey, signature, encryptionKey) {
return __awaiter(this, void 0, void 0, function* () {
let tlvData = tlv_1.default.encode(tlv_1.default.Tag.Username, Buffer.from(this.device.pairingId), tlv_1.default.Tag.PublicKey, publicKey, tlv_1.default.Tag.Signature, signature);
let encryptedTLV = Buffer.concat(encryption_1.default.encryptAndSeal(tlvData, null, Buffer.from('PS-Msg05'), encryptionKey));
// console.log("DEBUG: Encrypted Data=" + encryptedTLV.toString('hex'));
let outerTLV = tlv_1.default.encode(tlv_1.default.Tag.Sequence, 0x05, tlv_1.default.Tag.EncryptedData, encryptedTLV);
let nextMessage = {
status: 0,
pairingData: outerTLV
};
return that.device
.sendMessage('CryptoPairingMessage', 'CryptoPairingMessage', nextMessage, false)
.then(() => {
return that.waitForSequence(0x06);
})
.then(message => {
let encryptedData = tlv_1.default.decode(message.payload.pairingData)[tlv_1.default.Tag.EncryptedData];
let cipherText = encryptedData.slice(0, -16);
let hmac = encryptedData.slice(-16);
let decrpytedData = encryption_1.default.verifyAndDecrypt(cipherText, hmac, null, Buffer.from('PS-Msg06'), encryptionKey);
let tlvData = tlv_1.default.decode(decrpytedData);
that.device.credentials = new credentials_1.Credentials(that.device.uid, tlvData[tlv_1.default.Tag.Username], that.device.pairingId, tlvData[tlv_1.default.Tag.PublicKey], seed);
return that.device;
});
});
}
waitForSequence(sequence, timeout = 3) {
let that = this;
let handler = (message, resolve) => {
let tlvData = tlv_1.default.decode(message.payload.pairingData);
if (Buffer.from([sequence]).equals(tlvData[tlv_1.default.Tag.Sequence])) {
resolve(message);
}
};
return new Promise((resolve, reject) => {
that.device.on('message', (message) => {
if (message.type == message_1.Message.Type.CryptoPairingMessage) {
handler(message, resolve);
}
});
setTimeout(() => {
reject(new Error("Timed out waiting for crypto sequence " + sequence));
}, timeout * 1000);
})
.then(value => {
that.device.removeListener('message', handler);
return value;
return yield this.device.sendMessage('CryptoPairingMessage', 'CryptoPairingMessage', nextMessage, false);
});
}
}
Expand Down
3 changes: 2 additions & 1 deletion dist/lib/verifier.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,6 @@ export declare class Verifier {
device: AppleTV;
constructor(device: AppleTV);
verify(): Promise<{}>;
private waitForSequence;
private requestPairingData;
private completeVerification;
}
Loading

0 comments on commit 90fb5dc

Please sign in to comment.