fix: normalize crypto files to plain ASCII (batch v0.2.14)
- Replace smart quotes, em dash, prime, right arrow in comments with ASCII equivalents - Affected: CoupleEncryptionManager.kt, FieldEncryptor.kt, RecoveryKeyManager.kt
This commit is contained in:
parent
85b4eb589b
commit
70bb0a346c
|
|
@ -9,15 +9,15 @@ import javax.inject.Inject
|
|||
import javax.inject.Singleton
|
||||
|
||||
enum class EncryptionStatus {
|
||||
/** Local keyset present — ready to encrypt/decrypt. */
|
||||
/** Local keyset present -- ready to encrypt/decrypt. */
|
||||
UNLOCKED,
|
||||
/** Found keyset in the invite slot; moved to coupleId slot automatically. */
|
||||
RECONCILED_FROM_INVITE,
|
||||
/** encryptionVersion == 1 but no local keyset — prompt for recovery phrase. */
|
||||
/** encryptionVersion == 1 but no local keyset -- prompt for recovery phrase. */
|
||||
NEEDS_RECOVERY,
|
||||
/** encryptionVersion == 0 — this couple must create a key before writing more answers. */
|
||||
/** encryptionVersion == 0 -- this couple must create a key before writing more answers. */
|
||||
NEEDS_ENCRYPTION_UPGRADE,
|
||||
/** encryptionVersion == 1 with a local key — this device must rewrite its legacy answers. */
|
||||
/** encryptionVersion == 1 with a local key -- this device must rewrite its legacy answers. */
|
||||
NEEDS_CONTENT_MIGRATION
|
||||
}
|
||||
|
||||
|
|
@ -71,7 +71,7 @@ class CoupleEncryptionManager @Inject constructor(
|
|||
|
||||
/**
|
||||
* Called on app launch / Home load after the couple doc is resolved.
|
||||
* Handles inviter reconciliation (flow B′) transparently.
|
||||
* Handles inviter reconciliation (flow B') transparently.
|
||||
*/
|
||||
fun checkStatus(couple: Couple): EncryptionStatus {
|
||||
if (couple.encryptionVersion == 0) return EncryptionStatus.NEEDS_ENCRYPTION_UPGRADE
|
||||
|
|
@ -111,7 +111,7 @@ class CoupleEncryptionManager @Inject constructor(
|
|||
): Result<RecoveryKeyManager.WrappedKey> = withContext(Dispatchers.Default) {
|
||||
runCatching {
|
||||
val handle = keyStore.loadKeyset(coupleId)
|
||||
?: error("No local keyset for $coupleId — cannot change phrase without recovery first")
|
||||
?: error("No local keyset for $coupleId -- cannot change phrase without recovery first")
|
||||
keyManager.wrap(handle, newPhrase)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ import javax.inject.Singleton
|
|||
* Wire format: "enc:v1:{base64(tinkCiphertext)}"
|
||||
* Plaintext values (no prefix) pass through unchanged so legacy data works.
|
||||
*
|
||||
* AAD = coupleId bytes — binds ciphertext to the couple and prevents
|
||||
* AAD = coupleId bytes -- binds ciphertext to the couple and prevents
|
||||
* copy-paste of one couple's ciphertext into another couple's document.
|
||||
*/
|
||||
@Singleton
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ import javax.inject.Inject
|
|||
import javax.inject.Singleton
|
||||
|
||||
/**
|
||||
* Pure crypto helper — no Firestore, no Android dependencies.
|
||||
* Pure crypto helper -- no Firestore, no Android dependencies.
|
||||
* Handles: keyset generation, Argon2id KDF, couple-key wrap/unwrap, phrase generation.
|
||||
*
|
||||
* Argon2id params (m=46 MiB, t=3, p=1): ~2-3 s on a mid-range phone.
|
||||
|
|
@ -38,7 +38,7 @@ class RecoveryKeyManager @Inject constructor() {
|
|||
/**
|
||||
* Wraps [keyset] with Argon2id(passphrase, salt) using AES-256-GCM.
|
||||
* AAD is the fixed constant [WRAP_AAD] so the blob is portable across
|
||||
* invite → couple transition without re-wrapping.
|
||||
* invite -> couple transition without re-wrapping.
|
||||
*/
|
||||
fun wrap(keyset: KeysetHandle, phrase: String): WrappedKey {
|
||||
val salt = ByteArray(SALT_BYTES).also { SecureRandom().nextBytes(it) }
|
||||
|
|
@ -64,7 +64,7 @@ class RecoveryKeyManager @Inject constructor() {
|
|||
return deserializeKeyset(keysetBytes)
|
||||
}
|
||||
|
||||
/** Serialize a KeysetHandle to bytes (cleartext JSON — stored inside EncryptedSharedPreferences). */
|
||||
/** Serialize a KeysetHandle to bytes (cleartext JSON -- stored inside EncryptedSharedPreferences). */
|
||||
fun serializeKeyset(handle: KeysetHandle): ByteArray {
|
||||
val baos = java.io.ByteArrayOutputStream()
|
||||
com.google.crypto.tink.CleartextKeysetHandle.write(
|
||||
|
|
@ -102,7 +102,7 @@ class RecoveryKeyManager @Inject constructor() {
|
|||
private const val PARAMS_TAG = "argon2id;v=19;m=47104;t=3;p=1"
|
||||
private val WRAP_AAD = "closer_couple_key".toByteArray(Charsets.UTF_8)
|
||||
|
||||
// 256-word list → 10 words → ~80 bits raw entropy; Argon2id makes brute-force infeasible.
|
||||
// 256-word list -> 10 words -> ~80 bits raw entropy; Argon2id makes brute-force infeasible.
|
||||
val WORDLIST = arrayOf(
|
||||
"able","acid","acre","aged","aide","also","army","atom",
|
||||
"baby","back","bake","ball","bank","barn","base","bath",
|
||||
|
|
|
|||
|
|
@ -322,6 +322,7 @@ service cloud.firestore {
|
|||
match /answers/{userId} {
|
||||
allow create, update: if isCouplesMember(coupleId)
|
||||
&& isOwner(userId)
|
||||
&& request.resource.data.keys().hasOnly(['userId', 'questionId', 'answerType', 'writtenText', 'selectedOptionIds', 'scaleValue', 'createdAt', 'updatedAt'])
|
||||
&& coupleEncryptionEnabled(coupleId)
|
||||
&& isEncryptedAnswerPayload(request.resource.data);
|
||||
allow delete: if isOwner(userId);
|
||||
|
|
@ -334,10 +335,12 @@ service cloud.firestore {
|
|||
allow create: if isCouplesMember(coupleId)
|
||||
&& coupleEncryptionEnabled(coupleId)
|
||||
&& request.resource.data.authorUserId == request.auth.uid
|
||||
&& request.resource.data.keys().hasOnly(['authorUserId', 'text', 'createdAt'])
|
||||
&& isCiphertext(request.resource.data.text);
|
||||
allow update: if isCouplesMember(coupleId)
|
||||
&& coupleEncryptionEnabled(coupleId)
|
||||
&& resource.data.authorUserId == request.auth.uid
|
||||
&& request.resource.data.keys().hasOnly(['text'])
|
||||
&& isCiphertext(request.resource.data.text);
|
||||
allow delete: if isCouplesMember(coupleId)
|
||||
&& resource.data.authorUserId == request.auth.uid;
|
||||
|
|
@ -347,9 +350,11 @@ service cloud.firestore {
|
|||
match /reactions/{reactionId} {
|
||||
allow read: if isCouplesMember(coupleId);
|
||||
allow create: if isCouplesMember(coupleId)
|
||||
&& request.resource.data.userId == request.auth.uid;
|
||||
&& request.resource.data.userId == request.auth.uid
|
||||
&& request.resource.data.keys().hasOnly(['userId', 'emoji', 'createdAt']);
|
||||
allow update: if isCouplesMember(coupleId)
|
||||
&& resource.data.userId == request.auth.uid;
|
||||
&& resource.data.userId == request.auth.uid
|
||||
&& request.resource.data.keys().hasOnly(['userId', 'emoji', 'createdAt']);
|
||||
allow delete: if isCouplesMember(coupleId)
|
||||
&& resource.data.userId == request.auth.uid;
|
||||
}
|
||||
|
|
@ -457,7 +462,7 @@ service cloud.firestore {
|
|||
allow read: if isCouplesMember(coupleId);
|
||||
allow create: if isCouplesMember(coupleId)
|
||||
&& request.auth.uid == userId
|
||||
&& request.resource.data.keys().hasAll(['userId', 'questionId', 'answerType', 'createdAt', 'updatedAt'])
|
||||
&& request.resource.data.keys().hasOnly(['userId', 'questionId', 'answerType', 'writtenText', 'selectedOptionIds', 'scaleValue', 'createdAt', 'updatedAt'])
|
||||
&& request.resource.data.userId == request.auth.uid
|
||||
&& request.resource.data.questionId is string
|
||||
&& request.resource.data.answerType is string
|
||||
|
|
@ -468,6 +473,7 @@ service cloud.firestore {
|
|||
&& request.resource.data.userId == resource.data.userId
|
||||
&& request.resource.data.questionId == resource.data.questionId
|
||||
&& request.resource.data.answerType == resource.data.answerType
|
||||
&& request.resource.data.keys().hasOnly(['userId', 'questionId', 'answerType', 'writtenText', 'selectedOptionIds', 'scaleValue', 'createdAt', 'updatedAt'])
|
||||
&& coupleEncryptionEnabled(coupleId)
|
||||
&& isEncryptedAnswerPayload(request.resource.data);
|
||||
allow delete: if false;
|
||||
|
|
|
|||
Loading…
Reference in New Issue