accrescent / meta

Umbrella Accrescent issue tracker
6 stars 1 forks source link

RFC: Repository metadata reorganization #31

Open lberrymage opened 6 months ago

lberrymage commented 6 months ago

Problem

Accrescent's repository metadata format and organization was not designed for cacheability, internationalization, or atomicity. As a result, Accrescent has limited scalability, target audience, and robustness. These obstacles must be overcome for Accrescent to become a viable alternative to existing app stores with comparable features and a scalable foundation to build on.

Purpose and scope

This RFC aims to thoroughly define the Accrescent repository metadata format(s), directory tree structure, and caching strategies to maximize cacheability, internationalization support, and availability. This design will incorporate both current features of Accrescent's repository metadata and future features which are known to be desired. Undeveloped features need not be fully defined; however, the design must not create any obvious conflicts or challenges for implementing future features.

These proposed changes are being made public to gather feedback on design to ensure the system as implemented meets the needs of Accrescent and won't require significant changes to the repository organization for the foreseeable future. If you have any questions, suggestions, comments, or concerns, please make them in this issue thread.

The repository metadata should be representable exclusively via static files, so it is assumed that no dynamic server calls are required. The decision to use a static file structure for the repository is out-of-scope for this RFC.

Organization

This section describes the proposed organization at a high level.

Directory structure

Below is the proposed directory structure for the repository metadata.

index.sjson
apps
└── {app_id} (e.g. "app.accrescent.client")
    ├── version
    ├── metadata.json
    ├── {locale} (e.g. "en-US")
    │   ├── listing.json
    │   └── icon.png
    └── {version_code} (e.g. "13085")
        ├── compat.json
        ├── metadata.json
        ├── base.apk
        └── *.apk

File contents

This section describes the contents of each file in the above directory structure.

Caching strategies

The below caching strategies apply both to the Accrescent client and caching proxies.

Usage diagrams

---
title: Index update
---

flowchart LR
    n0((Start)) --> n1(Download index)
    n1 --> n2{Does sig verify?}
    n2 --> |No| n3((Quit))
    n2 --> |Yes| n4{Is version less than current?}
    n4 --> |Yes| n3
    n4 --> |No| n5(Update local index)
    n5 --> n3
---
title: App Update (for a single app)
---

flowchart LR
    n0((Start)) --> n1
    n1(Download version file) --> n2{Is greater than installed?}
    n2 --> |No| n3((Quit))
    n2 --> |Yes| n4(Download compat info)
    n4 --> n5{Is compatible?}
    n5 --> |No| n3
    n5 --> |Yes| n6[[Download and install update]]
    n6 --> n3
---
title: Download and install update
---

flowchart LR
    n0((Start)) --> n1
    n1(Download APKs) --> n2{Does sig verify?}
    n2 --> |No| n3((Quit))
    n2 --> |Yes| n4{Is sig identity in repo metadata?}
    n4 --> |No| n3
    n4 --> |Yes| n5{Is newer than min_version_code?}
    n5 --> |No| n3
    n5 --> |Yes| n6(Install app)
    n6 --> n3

Known issues

Below are some unresolved questions about how certain metadata should be represented. They don't all need to be addressed before adopting a new metadata organization, but should at least be considered and possibly addressed.

soupslurpr commented 6 months ago

Question: How does the way the signing cert hashes get verified not prevent a compromised key that was rotated from being used to update the app? Since it says it must only contain one in the lineage. I know the Android docs say to do this but I'm a little confused and would like an explanation if you know :D

lberrymage commented 6 months ago

Sorry, can you clarify? I can't quite picture the situation you're describing.

soupslurpr commented 6 months ago

An app is signed with a key. Let's say it gets compromised or it just has to be rotated to a newer key that is more secure. If the old key is cracked what's stopping it being put in the signing_cert_hashes and being accepted by the client?

lberrymage commented 6 months ago

Let's say the first certificate is "aaaa" and the second certificate is "bbbb".

The app developer rotates their app's cert from "aaaa" to "bbbb" and uploads a new update to the developer console. The console sees that the cert has been rotated, so it keeps the update from being published temporarily. The repository maintainer (myself) is notified of this, verifies the rotation locally (so the server isn't trusted), and updates signing_cert_hashes for the app from "aaaa" to "bbbb". The index is then signed and uploaded to the repository.

Someone cracks the key for cert "aaaa", gains access to Accrescent's servers, and replaces the legitimate app with a malicious copy signed with cert "aaaa".

When Accrescent downloads the app, it'll see that the app was signed with cert "aaaa". Provided it has an up-to-date repository index, it knows that signing_cert_hashes contains "bbbb", not "aaaa", so it rejects the update as invalid.

Does that answer your question?

soupslurpr commented 6 months ago

Thanks, that answered my question. The wording seems to say that the app certificate must only be contained in the certificate lineage stored in signing_cert_hashes, no?

"If it contains exactly one item, the app was signed with one certificate, and the signing certificate lineage must contain the hash in signing_cert_hashes."

lberrymage commented 6 months ago

The certificate in signing_cert_hashes must exist somewhere in the app's certificate lineage. The full lineage isn't contained in signing_cert_hashes, just the latest cert in the lineage that Accrescent is aware of.

soupslurpr commented 6 months ago

How should update changelogs be represented?

Can't it be in {version_code}/metadata.json or another file in {version_code}? Since it would be specific to the version_code. Is there a reason that won't work?

lberrymage commented 6 months ago

That would make sense if we only want to show the latest changelog entry in the UI. We may want to have the option to view past changelog entries, but I think only being able to view the latest makes sense for now.

soupslurpr commented 6 months ago

If we ever want to view past changelog entries there could just be version code directories in one versions directory or something.

soupslurpr commented 4 months ago

Regarding delta updates, is there anything wrong with just placing the delta for the latest published version in {version_code}/delta or so? I don't think there is a need for storing past deltas?

lberrymage commented 4 months ago

Yes. A delta is tied to two app versions: the one it's updating from and the one it's updating to. Thus, it would be unusable by clients with a different "from" version installed than the "from" version the delta targets.

Storing past deltas may be desirable because not every client can be expected to perform every update. If, say, 100 clients are on v1.0 of an app, 100 are on v2.0, and a new v3.0 update is pushed out, making a delta for only the v2.0 -> v3.0 update still leaves 100 clients performing an update from v1.0 -> v3.0 without a delta.

soupslurpr commented 4 months ago

Maybe it could be a time-based approach? Like storing the deltas for the past week or more of updates to the latest version.