Files
purchases-ios/Sources/Networking/RemoteConfigSignatureContextProvider.swift
Rick f0cbdcaf84 Fix fallback config signature verification for 304 responses (#7753)
* Fix fallback config signature verification for 304 responses

* Add fallback config 304 integration coverage

* Strengthen fallback config 304 verification coverage
2026-09-17 14:55:27 +02:00

74 lines
2.4 KiB
Swift

//
// RemoteConfigSignatureContextProvider.swift
// RevenueCat
//
// Created by RevenueCat.
// Copyright © 2026 RevenueCat, Inc. All rights reserved.
import Foundation
/// Provides the signature inputs for remote config RC Container responses.
///
/// The response signature covers the config element's decoded payload bytes. A `204 No Content`
/// response verifies the request context with an empty payload.
struct RemoteConfigSignatureContextProvider: ResponseSignatureContextProvider {
func responsePayloadForSignature(from body: Data?, statusCode: HTTPStatusCode) throws -> Data? {
guard statusCode != .noContent else {
return Data()
}
return try Self.configPayload(from: body)
}
func requestBodyForSignature(for request: HTTPRequest) -> HTTPRequestBody? {
return nil
}
}
/// Provides signature inputs for static JSON remote config fallback responses.
///
/// Fallback config signs the JSON response body directly. It does not include request body
/// parameters because the fallback endpoint is a fallback `GET`.
struct FallbackSignatureContextProvider: ResponseSignatureContextProvider {
func responsePayloadForSignature(from body: Data?, statusCode: HTTPStatusCode) throws -> Data? {
guard statusCode != .notModified else {
return nil
}
guard let body else {
throw RCContainer.Parser.FormatError.missingBody
}
return body
}
func requestBodyForSignature(for request: HTTPRequest) -> HTTPRequestBody? {
return nil
}
}
private extension RemoteConfigSignatureContextProvider {
/// Extracts the signed payload from a remote config RC Container response.
///
/// Remote config defines the first RC Container element as the config element. The backend signs
/// that element's payload bytes, not the full container body or stored checksum. This intentionally
/// parses only the first element so verification can fail before the full response is decoded later.
static func configPayload(from data: Data?) throws -> Data {
guard let data = data else {
throw RCContainer.Parser.FormatError.missingBody
}
let configElement = try RemoteConfigContainer.configElement(from: data)
return try configElement.withDecodedPayloadBytes { bytes in
Data(bytes)
}
}
}