package io.github.hapjava.server.impl.pairing; import io.github.hapjava.server.HomekitAuthInfo; import io.github.hapjava.server.impl.crypto.ChachaDecoder; import io.github.hapjava.server.impl.crypto.ChachaEncoder; import io.github.hapjava.server.impl.crypto.EdsaSigner; import io.github.hapjava.server.impl.crypto.EdsaVerifier; import io.github.hapjava.server.impl.http.HttpResponse; import io.github.hapjava.server.impl.pairing.PairSetupRequest.Stage3Request; import io.github.hapjava.server.impl.pairing.TypeLengthValueUtils.DecodeResult; import io.github.hapjava.server.impl.pairing.TypeLengthValueUtils.Encoder; import java.nio.charset.StandardCharsets; import org.bouncycastle.crypto.digests.SHA512Digest; import org.bouncycastle.crypto.generators.HKDFBytesGenerator; import org.bouncycastle.crypto.params.HKDFParameters; class FinalPairHandler { private final byte[] k; private final HomekitAuthInfo authInfo; private byte[] hkdf_enc_key; public FinalPairHandler(byte[] k, HomekitAuthInfo authInfo) { this.k = k; this.authInfo = authInfo; } public HttpResponse handle(PairSetupRequest req) throws Exception { HKDFBytesGenerator hkdf = new HKDFBytesGenerator(new SHA512Digest()); hkdf.init( new HKDFParameters( k, "Pair-Setup-Encrypt-Salt".getBytes(StandardCharsets.UTF_8), "Pair-Setup-Encrypt-Info".getBytes(StandardCharsets.UTF_8))); byte[] okm = hkdf_enc_key = new byte[32]; hkdf.generateBytes(okm, 0, 32); return decrypt((Stage3Request) req, okm); } private HttpResponse decrypt(Stage3Request req, byte[] key) throws Exception { ChachaDecoder chacha = new ChachaDecoder(key, "PS-Msg05".getBytes(StandardCharsets.UTF_8)); byte[] plaintext = chacha.decodeCiphertext(req.getAuthTagData(), req.getMessageData()); DecodeResult d = TypeLengthValueUtils.decode(plaintext); byte[] username = d.getBytes(MessageType.USERNAME); byte[] ltpk = d.getBytes(MessageType.PUBLIC_KEY); byte[] proof = d.getBytes(MessageType.SIGNATURE); return createUser(username, ltpk, proof); } private HttpResponse createUser(byte[] username, byte[] ltpk, byte[] proof) throws Exception { HKDFBytesGenerator hkdf = new HKDFBytesGenerator(new SHA512Digest()); hkdf.init( new HKDFParameters( k, "Pair-Setup-Controller-Sign-Salt".getBytes(StandardCharsets.UTF_8), "Pair-Setup-Controller-Sign-Info".getBytes(StandardCharsets.UTF_8))); byte[] okm = new byte[32]; hkdf.generateBytes(okm, 0, 32); byte[] completeData = ByteUtils.joinBytes(okm, username, ltpk); if (!new EdsaVerifier(ltpk).verify(completeData, proof)) { throw new Exception("Invalid signature"); } authInfo.createUser(authInfo.getMac() + new String(username, StandardCharsets.UTF_8), ltpk); return createResponse(); } private HttpResponse createResponse() throws Exception { HKDFBytesGenerator hkdf = new HKDFBytesGenerator(new SHA512Digest()); hkdf.init( new HKDFParameters( k, "Pair-Setup-Accessory-Sign-Salt".getBytes(StandardCharsets.UTF_8), "Pair-Setup-Accessory-Sign-Info".getBytes(StandardCharsets.UTF_8))); byte[] okm = new byte[32]; hkdf.generateBytes(okm, 0, 32); EdsaSigner signer = new EdsaSigner(authInfo.getPrivateKey()); byte[] material = ByteUtils.joinBytes( okm, authInfo.getMac().getBytes(StandardCharsets.UTF_8), signer.getPublicKey()); byte[] proof = signer.sign(material); Encoder encoder = TypeLengthValueUtils.getEncoder(); encoder.add(MessageType.USERNAME, authInfo.getMac().getBytes(StandardCharsets.UTF_8)); encoder.add(MessageType.PUBLIC_KEY, signer.getPublicKey()); encoder.add(MessageType.SIGNATURE, proof); byte[] plaintext = encoder.toByteArray(); ChachaEncoder chacha = new ChachaEncoder(hkdf_enc_key, "PS-Msg06".getBytes(StandardCharsets.UTF_8)); byte[] ciphertext = chacha.encodeCiphertext(plaintext); encoder = TypeLengthValueUtils.getEncoder(); encoder.add(MessageType.STATE, (short) 6); encoder.add(MessageType.ENCRYPTED_DATA, ciphertext); return new PairingResponse(encoder.toByteArray()); } }