Digital-Sapphire / PyUpdater

Pyinstaller auto-update library
https://www.pyupdater.org
460 stars 92 forks source link

Add an optional(?) expiration date to the app key #260

Closed gdetrez closed 2 years ago

gdetrez commented 4 years ago

Hello & thanks for writing open source software!

I've been trying to figure out the security model of PyUpdater. My current understanding is that the separation between the offline key and the app key should allow rotating the key on the build machine (e.g. developer laptop or build server) while keeping updates compatible with old version of the software. Basically, as long as you're using the same offline key, you can change the app key as often as you want and the updates will still be accepted. Is that correct?

What I couldn't find is a way to mark old app keys as revoked or expired. For example, I would like to make the keypack available on my build server but rotate the key regularly so that if there's a vulnerability in the CI server and the key is stolen, it doesn't remain valid indefinitely...

I believe that a simple way to start doing that would be to add an expiration date to the app key, which is then included in the signature and have the update client check that the key hasn't expired before accepting the update.

This is not perfect and might require regenerating keys.gz and versions.gz manually if you don't release often enough but it would at least open the possibility to make things more secure if you want to.

What do you think?

JMSwag commented 4 years ago

@gdetrez Once you add new app keys to your repo, generate new keys.gz & versions.gz, the old keys will be expired automagically.

Automagically = Old app keys are no longer in the keys.gz file.

Hope this helps.

gdetrez commented 4 years ago

Hi @JMSwag. Thanks for your answer. I understand that, if you re-generate key.gz then the old key is not included in it anymore. But I'm not really sure that solves my problem because the client has no idea that it shouldn't trust the old keys.gz any longer.

E.g. say the attacker, let's call her Eve, is able to control what files are downloaded by the client (I assume this is already part of PyUpdater's threat model and the reason why PyUpdater uses offline signing in the first place.) Now say Eve also gets hold of a valid app key, current or revoked. Isn't it then possible for Eve to sign her evil version of my application with this key, regenerate versions.gz, take the old keys.gz (the one that corresponds to her key) and serve those to the client? The client will only see that the app key is correctly signed by the offline key and accept Eve's update. Or did I miss something?

JMSwag commented 4 years ago

@gdetrez My mistake. You are correct. The TUF Project is probably what you are looking for. The original inspiration for this lib was something easier to use than TUF while sacrificing some security.

That being said, a PR is welcomed that implements expiration for app keys.

gdetrez commented 4 years ago

Thanks @JMSwag. Yes, we started looking at TUF for our use case. We'll probably go this way although it is more complex and you need to bring your own update client.

TUF offers a great deal of flexibility and I'm wondering if it might be possible to retrofit PyUpdater security model in TUF by mapping offline key to the root role and the app key to every other roles (target, timestamp and snapshots). It would then be possible to adopt TUF repository metadata. Merging roles like this is not the recommended way to implement TUF but it would solve the current issue and prevent other attacks (rollback, infinite-freze, etc) while not significantly changing the PyUpdater user experience.

JMSwag commented 4 years ago

@gdetrez Sounds great especially not changing the PyUpdater user experience!