shorebirdtech / shorebird

Code Push for Flutter and other tools for Flutter businesses.
https://shorebird.dev
Other
2.36k stars 142 forks source link

Artifact (e.g. patch) signing #112

Closed felangel closed 6 months ago

felangel commented 1 year ago

The goal here is artifact security. If we put a public key in the APK/IPA (which we can trust the integrity of), we can then expect all downloaded artifacts to be signed with the the private key.

Managing the keys itself could be somewhat of a pain, but gets easier if Shorebird provides the CI/CD, or we sit on top of existing signing mechanisms from platforms perhaps?

faustobdls commented 1 year ago

Regarding this, I talked to some security colleagues, and many believe that a double integrity check ( hash ) is enough, when sending the artifact it first sends the hash using mtls, from there it only accepts running the external file ( downloaded by shorebird ) if it contains that signature hash

eseidel commented 1 year ago

Regarding this, I talked to some security colleagues, and many believe that a double integrity check ( hash ) is enough, when sending the artifact it first sends the hash using mtls, from there it only accepts running the external file ( downloaded by shorebird ) if it contains that signature hash

That's pretty much what we do today. The hash is contained in the patch update response: https://github.com/shorebirdtech/updater/blob/main/library/src/network.rs#L113 Which we then verify as part of installing: https://github.com/shorebirdtech/updater/blob/main/library/src/updater.rs#L235

That system was built for verifying integrity of the downloaded patch (mostly to guard against network errors, or wrong-patch errors), rather than security of the patch and I suspect there is more we could do if customers need.

eseidel commented 10 months ago

Got another request for this this morning. I don't think this would be particularly hard for us to add. The hard part will be making it easy for users to understand/use since key-management is non-trivial and different between the various platforms. The general design would be:

  1. Get access to some sort of signing key on the host (doesn't matter a ton which, since all we're checking for is integrity of the asset). We could even provide a signing key if needed. Ideally we would use an existing one the user has.
  2. Embed the public key in the app (public keys are OK to be public) and include with the app for distribution. Alternatively if there was a way to get access to a public key on the device we could use that, but probably easier to just embed one.
  3. Change the shorebird command to sign the resulting patch file with the private key on the host.
  4. Change the updater library to verify the signature of the patch file when installing on the device. If the public key is inside the apk/ipa we can presumably trust existing tamper-proof mechanisms. I don't know how this interacts with jail-braking (if at all) or if extra protections are needed there to prevent apps which depend on signed updates.
loic-sharma commented 10 months ago

Some suggestions:

  1. Customers should be able to register public keys on their Shorebird account. Shorebird's servers should verify uploaded patches' signatures match the expected public keys. Modifications to signing configuration should be guarded by MFA.
  2. The signing tool should be able to run in a locked down, offline environment. Ideally signing and uploading patches can be done in separate steps on separate machines.
  3. Shorebird should sign all artifacts with its own code signing certificate. Patches signed by the user would have two signatures.

... since all we're checking for is integrity of the asset

Artifact signing isn't just integrity, it should also give apps non-repudiation: the app should be able to verify the patch was created using the app's certificate. Apps should not be compromised if Shorebird is hacked and an attacker is able to tamper patches.

I would recommend .NET's package signing as prior art. It's a key piece of Microsoft's supply chain and went through rigorous security reviews.

I talked to some security colleagues, and many believe that a double integrity check ( hash ) is enough

I disagree. Many enterprises will want code signing before using Shorebird. Without artifact signing, patches downloaded from Shorebird is untrusted code that is susceptible to tampering.

eseidel commented 10 months ago

I thought about this more, my current proposal for a first implementation:

  1. We can use an existing signing and key storage solution, e.g. https://docs.aws.amazon.com/signer/latest/developerguide/Welcome.html. This would allow Shorebird to never have to see/know the private keys.
  2. Each Release in our DB would point to a public/private key pair (presumably via just an id which maps to the service storing these keys). Probably by default, we would just generate a single pair for the App and use it for all Releases. Tying the keypair to a release would optionally (later) make it possible for customers to choose to have a unique key per release or allow us/them to reset the key for future releases, etc.
  3. As part of shorebird release we would download and embed the public key into the release (possibly just as a flutter_asset, or possibly separate, to avoid our eventual asset support accidentally making it possible to replace they key in a patch, etc.)
  4. We would continue hash the patch file during upload on the developer's device (as we do today). Except now we would take that hash (after they send it to us) and sign it (probably plus a small header that explained what it was, e.g. "hash for patch X for release version Y for app Z, etc." with the private key (via a service like linked above) and store that signed hash in our db.
  5. When customers have opted into this signed behavior, we then hash the patch file on launch and check the hash against the signed hash we got from our server (and cached locally on the device) to make sure it matches.

We can of course optionally provide the ability for customers to maintain their own keys and do their own hash signing if needed.

loic-sharma commented 9 months ago

I would also allow the Flutter app to provide an allowlist of code signing certificates so that it can reject signed updates from an unexpected certificate.

Ideal flow: I create a patch, I sign it with my certificate, I upload the patch to Shorebird, Shorebird pushes my patch to my users, my app verifies the patch was signed with my certificate. If Shorebird is compromised, my app would reject malicious updates as they weren't signed with my certificate.

Note that Shorebird would ideally also sign all patches with its own key. In other words, artifacts could have more than one signature. This has many benefits, including improving Shorebird's ability to recover if it is compromised (Shorebird would revoke its code signing certificates to invalidate previously signed artifacts).

loic-sharma commented 9 months ago

If you'd like, I'd be happy to do a call to brain dump .NET's package signing.

eseidel commented 9 months ago

I appreciate the offer. We're not yet ready to work on this, but when we do we will likely write up a design doc, possibly publicly and happy to engage more with others then.

eseidel commented 7 months ago

We've now begun work on this. Bryan and I wrote up a design doc today which I will share later (once we've rolled out more of this work).

eseidel commented 7 months ago

Customers who are blocked on this feature, should feel welcome to reach out to me (via email or Discord). We'd be happy to discuss with you early to make sure we're meeting your needs.

eseidel commented 6 months ago

We have a basic version of this implemented: https://docs.shorebird.dev/guides/patch-signing/

We would love to work with any customers interested in this to improve it for your needs.

eseidel commented 5 months ago

Blog post up: https://shorebird.dev/blog/patch-signing-beta/