Support Xcode-signed JWS in SignedDataVerifier
WFT opened this issue · comments
Feature request: I think SignedDataVerifier should support an Environment.XCODE
environment which verifies JWS values signed by Xcode StoreKit testing.
The problem
Transaction JWS produced by StoreKit testing have a few differences from normal signed JWS:
- Environment is
Xcode
x5c
claim is 1 certificate long (because the Xcode certificate is a root cert)
So trying to use SignedDataVerifier to verify such a claim will immediately throw an INVALID_CHAIN_LENGTH
error. If this were to be bypassed, I imagine we'd run into an error about the JWS having the wrong environment.
Use case
When testing our iOS app during development, we use a local development server. To really test this well I want to use almost exactly the same code paths for StoreKit testing as I do for production.
Right now there’s no way to get a SignedDataVerifier to actually verify & decode a transaction or renewal info JWS.
I want code that looks like this:
def _get_verifier() -> SignedDataVerifier:
if is_in_local_testing_environment():
return SignedDataVerifier(
root_certificates=[load_xcode_testing_certificate()],
enable_online_checks=False,
environment=Environment.XCODE,
bundle_id='my.bundle.id'
)
else:
# Production & Sandbox/staging cases
return SignedDataVerifier(...)
@app.post('/api/app-store/transaction')
def process_transaction():
new_transaction = request.json['transaction']
verifier = _get_verifier()
verified_transaction = verifier.verify_and_decode_signed_transaction(new_transaction)
response = deliver_content(verified_transaction)
return response
Workaround
Right now my test server can't actually just use the same code for both cases, so we'll have to do something like the following:
import jwt
import cattrs
def _verify_transaction(signed_transaction: str) -> JWSTransactionDecodedPayload:
if is_in_local_testing_environment():
# Just for simplicity; really I should load the signing key & verify the whole chain.
# It doesn't really matter in my case, but it may matter in general.
data = jwt.decode(signed_transaction, options={'verify_signature':False})
return cattrs.structure(data, JWSTransactionDecodedPayload)
else:
# Production & Sandbox/staging cases
verifier = SignedDataVerifier(...)
return verifier.verify_and_decode_signed_transaction(signed_transaction)
@app.post('/api/app-store/transaction')
def process_transaction():
new_transaction = request.json['transaction']
verifier = _get_verifier()
verified_transaction = verifier.verify_and_decode_signed_transaction(new_transaction)
response = deliver_content(verified_transaction)
return response
This looks not too bad, but there are a couple problems:
- I have to repeat this for each kind of JWS (except notifications since I can't get those on my local server anyway)
- Now I'm not testing my use of the library, I'm testing my use of the
cattrs
andjwt
libraries.
Proposed solutions
- Support an
Environment.XCODE
, which disables checks for chain length & whatever else would normally fail with Xcode testing - Alternatively, add a subclass of
SignedDataVerifier
calledStoreKitTestingSignedDataVerifier
which overrides the relevant functions (at least_decode_signed_object
).
@WFT Xcode support has now been added and unit tests now exist with Xcode data of various types
Thank you!