Accept bounded StoreKit signing clock skew
Allow legitimate App Store transaction timestamps to differ within the existing verification tolerance while preserving rejection beyond that boundary.
This commit is contained in:
@@ -123,7 +123,7 @@ class StoreKitService(
|
|||||||
}
|
}
|
||||||
val now = clock.instant()
|
val now = clock.instant()
|
||||||
if (
|
if (
|
||||||
transaction.purchasedAt > transaction.signedAt ||
|
transaction.purchasedAt > transaction.signedAt.plus(MAX_CLOCK_SKEW) ||
|
||||||
transaction.signedAt > now.plus(MAX_CLOCK_SKEW)
|
transaction.signedAt > now.plus(MAX_CLOCK_SKEW)
|
||||||
) {
|
) {
|
||||||
throw StoreKitPurchaseConflict()
|
throw StoreKitPurchaseConflict()
|
||||||
|
|||||||
@@ -33,14 +33,16 @@ class StoreKitServiceTest : FunSpec({
|
|||||||
accountToken: UUID = userId,
|
accountToken: UUID = userId,
|
||||||
productId: String = product.productId,
|
productId: String = product.productId,
|
||||||
revokedAt: Instant? = null,
|
revokedAt: Instant? = null,
|
||||||
|
purchasedAt: Instant = now.minusSeconds(10),
|
||||||
|
signedAt: Instant = now.minusSeconds(5),
|
||||||
) = VerifiedStoreKitTransaction(
|
) = VerifiedStoreKitTransaction(
|
||||||
transactionId = transactionId,
|
transactionId = transactionId,
|
||||||
originalTransactionId = transactionId,
|
originalTransactionId = transactionId,
|
||||||
appAccountToken = accountToken,
|
appAccountToken = accountToken,
|
||||||
productId = productId,
|
productId = productId,
|
||||||
environment = StoreKitEnvironment.SANDBOX,
|
environment = StoreKitEnvironment.SANDBOX,
|
||||||
purchasedAt = now.minusSeconds(10),
|
purchasedAt = purchasedAt,
|
||||||
signedAt = now.minusSeconds(5),
|
signedAt = signedAt,
|
||||||
revokedAt = revokedAt,
|
revokedAt = revokedAt,
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -115,6 +117,33 @@ class StoreKitServiceTest : FunSpec({
|
|||||||
store.ledger shouldHaveSize 0
|
store.ledger shouldHaveSize 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
test("small App Store signing clock skew is accepted") {
|
||||||
|
val store = TestBillingStore()
|
||||||
|
val verified = transaction(
|
||||||
|
purchasedAt = now.minusSeconds(10),
|
||||||
|
signedAt = now.minusSeconds(70),
|
||||||
|
)
|
||||||
|
|
||||||
|
val result = service(store, verified).submit(userId, signedTransaction)
|
||||||
|
|
||||||
|
result.balanceAfter shouldBeExactly 3_000
|
||||||
|
store.ledger shouldHaveSize 1
|
||||||
|
}
|
||||||
|
|
||||||
|
test("App Store signing clock skew beyond the tolerance is rejected") {
|
||||||
|
val store = TestBillingStore()
|
||||||
|
val verified = transaction(
|
||||||
|
purchasedAt = now.minusSeconds(10),
|
||||||
|
signedAt = now.minusSeconds(311),
|
||||||
|
)
|
||||||
|
|
||||||
|
shouldThrow<StoreKitPurchaseConflict> {
|
||||||
|
service(store, verified).submit(userId, signedTransaction)
|
||||||
|
}
|
||||||
|
|
||||||
|
store.ledger shouldHaveSize 0
|
||||||
|
}
|
||||||
|
|
||||||
test("unknown or revoked products never grant credits") {
|
test("unknown or revoked products never grant credits") {
|
||||||
val unknownStore = TestBillingStore()
|
val unknownStore = TestBillingStore()
|
||||||
shouldThrow<StoreKitPurchaseConflict> {
|
shouldThrow<StoreKitPurchaseConflict> {
|
||||||
|
|||||||
Reference in New Issue
Block a user