Closed WFT closed 10 months ago
The basic approach I’ve used (which I would be willing to donate to this project if it would be useful) is to have a JWSMocker
class which contains its own newly-generated public/private key pair. That class has two methods:
sign_jws
, which takes one of the payload types (e.g. JWSTransactionDecodedPayload
) and signs it with the private keyverifier
, which produces a SignedDataVerifier
configured to verify data using the generated public key. This is implemented by swapping out the _chain_verifier
with a custom subclass.The usage is pretty verbose (see example below), but I think it creates a nice building block on which other testing facilities can be built.
mock = JWSMocker()
now = datetime.datetime.now()
transaction = mock.sign_jws(
JWSTransactionDecodedPayload(
originalTransactionId="123456",
transactionId="123456",
webOrderLineItemId="0000001",
bundleId="com.example.app",
productId="com.example.app.premium.annual",
subscriptionGroupIdentifier="com.example.app.premium",
purchaseDate=int(now.timestamp() * 1000),
originalPurchaseDate=int(now.timestamp() * 1000),
expiresDate=int((now + datetime.timedelta(days=365)).timestamp() * 1000),
quantity=1,
type=Type.AUTO_RENEWABLE_SUBSCRIPTION,
appAccountToken=None,
inAppOwnershipType=InAppOwnershipType.PURCHASED,
signedDate=int(now.timestamp() * 1000),
revocationReason=None,
revocationDate=None,
isUpgraded=False,
offerType=None,
offerIdentifier=None,
environment=Environment.SANDBOX,
storefront="USA",
storefrontId="143441",
transactionReason=TransactionReason.PURCHASE,
)
)
print(transaction)
verifier = mock.verifier(
environment=Environment.SANDBOX, bundle_id="com.example.app"
)
decoded = verifier.verify_and_decode_signed_transaction(transaction)
print(decoded)
^ It would be useful to me 🙏
@pnico Hey sorry I’ve been pretty busy the last few months.
I’ve put the full code (& a small example usage) I’m proposing in this gist: https://gist.github.com/WFT/3d06c2048cac4a3da9ebad6b31962928
We’ve used it internally with success.
It’s a pretty small patch, but it’s also very closely tied to the internal implementation of SignedDataVerifier
. So it could easily break in a future update of this library. That’s why I think this is better off as part of this library.
Hello @WFT @pnico I am working on a testing improvement that will allow verifying signed data in a unit test, similar to what you have proposed, but without requiring changes to the internals of SignedDataVerifier (it will actually form the appropriate chain, etc) and come with unit tests for the models. I hope to have a PR out in a week or two with this change. See https://github.com/apple/app-store-server-library-java/tree/main/src/test/resources for the appropriate keys. Does this meet your need or are you looking for an embedded certificate in the library without it needing to be loaded?
@alexanderjordanbaker Thanks for the update!
Just to make sure I understand the concept: the difference is your PR would require copying testCA.pem & testSigningKey.p8 into our test suite? Then just hooking up SignedDataVerifier to use testCA.pem as a root certificate and signing our own JWS with testSigningKey.p8?
That seems like it would fulfill my needs!
The rest of this comment only makes sense if I’m understanding that correctly.
Without seeing the PR it’s a little hard to give a sure yes/no, but my concern overall with that approach would be that without a slightly higher level API it’s easier to misuse.
For example — it would be easy enough for someone to configure their SignedDataVerifier to accept both the testing root CA certificate & the actual production root certificate. It would cause no errors at any point & it would make setting up unit tests slightly easier. But they would be using a well known certificate with a well known private key. That’s a pretty serious error!
My JWSMocker proposed API above makes it harder to write such broken code in two ways:
That wall of text said, there are clearly upsides to the approach of not changing the internals & just configuring everything in the normal way. And these are just quibbles about the shape of the API, really. Excited to see what you’ve got! No matter what shape it takes, it'll be a large improvement over the current state.
@WFT Would 100% disablement of the cert validation work or are you looking for actually validating something? I could add a new environment to the Environment enum and then allowlist only that environment, like TEST, to bypass validation while confirming that the environment of the signed data also matches TEST (to prevent the hardcoding test cert case you were describing bypassing validation on Production/Sandbox environment data)
@WFT a new environment, LOCAL_TESTING, now exists which will bypass all signature checks, by setting the expected environment of SignedDataVerifier to LOCAL_TESTING, this now effectively does
Below in my unit tests is an example of creating arbitrary signed data signed by a random ES256 key
Would 100% disablement of the cert validation work or are you looking for actually validating something
Yeah, I think this is fine! Frankly I think just calling it LOCAL_TESTING
resolves most of my fear about developer mistakes.
I haven't tried it out yet, but from the tests in that commit (https://github.com/apple/app-store-server-library-python/commit/96eae1a23dfe69a43de5c9188826c7fe777ddf36) it looks like exactly what I need.
Thanks @alexanderjordanbaker !
I'm going to close this without trying it because it really does look like what I was hoping for.
Feature request: Support creating and verifying mock JWS values, such as transactions and notifications.
I'd like the ability to create mock JWS data that will be accepted by a SignedDataVerifier which is specifically configured for testing.
This is very similar to #22, but instead of the JWS data coming from Xcode StoreKit testing I want it to be generated in my python unit tests.
I don’t think this would need to actually generate a certificate, sign the tokens, etc. But that would certainly work.
Use case
I would like to do unit testing for my server's use of the library. I want to test the following features:
Example code
Here are some tests one could write, with example API: