1package com.peachpasswords.sync23import kotlinx.serialization.json.Json4import kotlinx.serialization.json.jsonObject5import kotlinx.serialization.json.jsonPrimitive6import org.bouncycastle.crypto.digests.SHA256Digest7import org.bouncycastle.crypto.generators.HKDFBytesGenerator8import org.bouncycastle.crypto.params.HKDFParameters9import org.junit.Assert.assertEquals10import org.junit.Test11import java.util.Base6412import javax.crypto.Cipher13import javax.crypto.spec.GCMParameterSpec14import javax.crypto.spec.SecretKeySpec1516class SyncEntryEnvelopeKatTest {17 @Test fun matches_cross_client_sync_entry_v1_envelope_vector() {18 val vector = Json.parseToJsonElement(19 checkNotNull(javaClass.classLoader?.getResource("fixtures/golden/sync-entry-envelope-v1.json"))20 .readText(),21 ).jsonObject22 val master = hexToBytes(vector.getValue("masterKeyHex").jsonPrimitive.content)23 val entryId = vector.getValue("entryId").jsonPrimitive.content24 val nonce = hexToBytes(vector.getValue("nonceHex").jsonPrimitive.content)25 val info = (entryId + "\u0000sync-entry-v1").toByteArray(Charsets.UTF_8)26 val key = ByteArray(32)27 HKDFBytesGenerator(SHA256Digest()).apply {28 init(HKDFParameters(master, ByteArray(0), info))29 generateBytes(key, 0, key.size)30 }31 val cipher = Cipher.getInstance("AES/GCM/NoPadding")32 cipher.init(Cipher.ENCRYPT_MODE, SecretKeySpec(key, "AES"), GCMParameterSpec(128, nonce))33 cipher.updateAAD("peach-sync-entry-v1".toByteArray(Charsets.UTF_8))34 val ciphertext = cipher.doFinal(35 vector.getValue("plaintext").jsonPrimitive.content.toByteArray(Charsets.UTF_8),36 )3738 assertEquals(39 vector.getValue("envelopeBase64").jsonPrimitive.content,40 Base64.getEncoder().encodeToString(nonce + ciphertext),41 )42 key.fill(0)43 master.fill(0)44 }45}