// SPDX-License-Identifier: MIT // Copyright (c) 2026 Thibault Ducray // // This file is part of MyPwdTool's open-source sync/encryption core โ€” see // LICENSE-SYNC-CRYPTO.md at the repo root and https://tducray.fr/mypwdtool/open-source/. // The rest of this application is proprietary and NOT covered by this license. package fr.tducray.mypwdtool.relay import javax.crypto.Mac import javax.crypto.spec.SecretKeySpec /** * Port of `RelayAPIClient.computeChallengeResponse(challenge:platform:clientVersion:)` in * `Sync/RelayAPIClient.swift` โ€” the two-level HMAC registration-challenge response (ยง3.3 of the * E2EE Relay API spec). Kept separate from the network client so it's testable without a live * server or HTTP mocking; the master key itself is passed in rather than read from a constant * here, deliberately โ€” see `ChallengeResponseTest` for why the test vector uses a dummy key * rather than the real production one. */ object ChallengeResponse { /** * `daily_key = HMAC-SHA256(master_key, version + ":" + server_date)` * `response = HMAC-SHA256(daily_key, nonce + ":" + version + ":" + platform + ":" + client_version)` * Returns the response as base64url without padding. */ fun compute( masterKeyB64Url: String, nonce: String, version: String, serverDate: String, platform: String, clientVersion: String, ): String { val masterKeyBytes = Base64Url.decode(masterKeyB64Url) require(masterKeyBytes != null && masterKeyBytes.isNotEmpty()) { "invalid master key" } val dailyMessage = "$version:$serverDate".encodeToByteArray() val dailyKeyBytes = hmacSha256(masterKeyBytes, dailyMessage) val responseMessage = "$nonce:$version:$platform:$clientVersion".encodeToByteArray() val responseBytes = hmacSha256(dailyKeyBytes, responseMessage) return Base64Url.encode(responseBytes) } private fun hmacSha256(key: ByteArray, message: ByteArray): ByteArray { val mac = Mac.getInstance("HmacSHA256") mac.init(SecretKeySpec(key, "HmacSHA256")) return mac.doFinal(message) } }