Background
Accrescent's backend (specifically Parcelo) accepts app uploads in the APK set format which at a high level contains:
- Developer-signed split APKs (and standalone APKs if appropriate)
- A table of contents (a binary
BuildApksResult protobuf message)
To ensure uploaded apps are installable and otherwise valid, we run custom validation steps such as verifying that all APKs in the APK set are signed with the same certificate and have the same application ID. We also parse certain security-sensitive data such as application ID and version code from APKs directly instead of from the table of contents because the table of contents could be modified by a developer before upload. However, this approach of accepting APK sets and parsing them directly has significant limitations, namely:
- Developers must upload APK sets which are often significantly larger than their corresponding app bundles, using more upload bandwidth, increasing storage usage, and causing more upload timeout errors compared to other app stores
- We must write custom validation code for APK sets since bundletool doesn't have any (whereas it does for app bundles), which is very error-prone due to the large number of properties to check in the table of contents
- In practice, our APK set validation code is incomplete, allowing developers to e.g. upload an app which is publishable but which clients can't install due to a tampered-with table of contents
We initially chose to accept APK sets because they include developer-signed APKs, whereas app bundles traditionally require generating APKs signed by the app store they're uploaded to — a requirement we cannot accept to ensure Accrescent's security properties. However, there is a way we can inherit all of the benefits of app bundles while still serving only developer-signed APKs.
Proposal
Instead of APK sets, we should accept a new, custom package format which contains at a minimum the following elements:
- An Android app bundle
- Split APK signatures
App developers could create this package from an app bundle by performing the following steps:
- Generate signed APKs in an APK set using bundletool
- Extract APK signatures from the signed APKs
- Compile the original app bundle and the APK signatures from step 2 into the custom package format
Accrescent's servers could process the new package format by following these steps:
- Generate unsigned APKs in an APK set from the uploaded app bundle using bundletool
- Copy APK signatures from the uploaded package and patch them onto the unsigned APKs
- Use apksigner to verify that the signatures of the now-signed APKs are valid
Adopting this process would solve all of the aforementioned issues with accepting APK sets directly, that is:
- Developers would need to upload only app bundles and a small amount of extra metadata, saving large amounts of upload bandwidth, reducing storage usage, and reducing the number of upload timeout errors developers encounter
- We can leverage bundletool's robust app bundle validation code, saving us significant code complexity
- Our package validation can be comprehensive by allowing us to trust the APK set table of contents since we would generate it ourselves
In this way, we can accept app bundles as an app upload format while still keeping APKs signed by their respective developers.
Considerations
The proposed process for re-producing valid developer-signed APKs from an app bundle depends on bundletool's APK set output being reproducible, which isn't guaranteed. However, it appears to be reproducible from some basic testing, meaning we could rely upon this data at least for specific versions of bundletool. If future versions of bundletool generate different APKs than previous versions, we can generate APKs server-side using a compatible bundletool version to compensate. If future versions of bundletool generate different APKs across invocations, we can find where the discrepancies are and encode that metadata into our custom package format so that the server can modify server-generated APKs appropriately before adding the developer's signature.
A custom package format requires a custom tool. Before requiring developers to upload the new package format, we should develop, at a minimum, an Accrescent CLI which can generate valid Accrescent packages from an app bundle. We could improve the DX of this tool in the future by developing a Gradle plugin and other developer integrations.
Tasks
To implement this proposal, we would need to execute at least the following steps:
Background
Accrescent's backend (specifically Parcelo) accepts app uploads in the APK set format which at a high level contains:
BuildApksResultprotobuf message)To ensure uploaded apps are installable and otherwise valid, we run custom validation steps such as verifying that all APKs in the APK set are signed with the same certificate and have the same application ID. We also parse certain security-sensitive data such as application ID and version code from APKs directly instead of from the table of contents because the table of contents could be modified by a developer before upload. However, this approach of accepting APK sets and parsing them directly has significant limitations, namely:
We initially chose to accept APK sets because they include developer-signed APKs, whereas app bundles traditionally require generating APKs signed by the app store they're uploaded to — a requirement we cannot accept to ensure Accrescent's security properties. However, there is a way we can inherit all of the benefits of app bundles while still serving only developer-signed APKs.
Proposal
Instead of APK sets, we should accept a new, custom package format which contains at a minimum the following elements:
App developers could create this package from an app bundle by performing the following steps:
Accrescent's servers could process the new package format by following these steps:
Adopting this process would solve all of the aforementioned issues with accepting APK sets directly, that is:
In this way, we can accept app bundles as an app upload format while still keeping APKs signed by their respective developers.
Considerations
The proposed process for re-producing valid developer-signed APKs from an app bundle depends on bundletool's APK set output being reproducible, which isn't guaranteed. However, it appears to be reproducible from some basic testing, meaning we could rely upon this data at least for specific versions of bundletool. If future versions of bundletool generate different APKs than previous versions, we can generate APKs server-side using a compatible bundletool version to compensate. If future versions of bundletool generate different APKs across invocations, we can find where the discrepancies are and encode that metadata into our custom package format so that the server can modify server-generated APKs appropriately before adding the developer's signature.
A custom package format requires a custom tool. Before requiring developers to upload the new package format, we should develop, at a minimum, an Accrescent CLI which can generate valid Accrescent packages from an app bundle. We could improve the DX of this tool in the future by developing a Gradle plugin and other developer integrations.
Tasks
To implement this proposal, we would need to execute at least the following steps: