package com.peachpasswords.sync import kotlinx.serialization.json.Json import kotlinx.serialization.json.jsonObject import kotlinx.serialization.json.jsonPrimitive import org.bouncycastle.crypto.digests.SHA256Digest import org.bouncycastle.crypto.generators.HKDFBytesGenerator import org.bouncycastle.crypto.params.HKDFParameters import org.junit.Assert.assertEquals import org.junit.Test import java.util.Base64 import javax.crypto.Cipher import javax.crypto.spec.GCMParameterSpec import javax.crypto.spec.SecretKeySpec class SyncEntryEnvelopeKatTest { @Test fun matches_cross_client_sync_entry_v1_envelope_vector() { val vector = Json.parseToJsonElement( checkNotNull(javaClass.classLoader?.getResource("fixtures/golden/sync-entry-envelope-v1.json")) .readText(), ).jsonObject val master = hexToBytes(vector.getValue("masterKeyHex").jsonPrimitive.content) val entryId = vector.getValue("entryId").jsonPrimitive.content val nonce = hexToBytes(vector.getValue("nonceHex").jsonPrimitive.content) val info = (entryId + "\u0000sync-entry-v1").toByteArray(Charsets.UTF_8) val key = ByteArray(32) HKDFBytesGenerator(SHA256Digest()).apply { init(HKDFParameters(master, ByteArray(0), info)) generateBytes(key, 0, key.size) } val cipher = Cipher.getInstance("AES/GCM/NoPadding") cipher.init(Cipher.ENCRYPT_MODE, SecretKeySpec(key, "AES"), GCMParameterSpec(128, nonce)) cipher.updateAAD("peach-sync-entry-v1".toByteArray(Charsets.UTF_8)) val ciphertext = cipher.doFinal( vector.getValue("plaintext").jsonPrimitive.content.toByteArray(Charsets.UTF_8), ) assertEquals( vector.getValue("envelopeBase64").jsonPrimitive.content, Base64.getEncoder().encodeToString(nonce + ciphertext), ) key.fill(0) master.fill(0) } }