Custom assertion possible?

I want to install snaps from our S3 bucket without using the --dangerous flag. We’d sign the snap packages with our own private key and generate an assertion.

If it requires patching snapd, I can look into that as well.

Is something like that possible ?

You won’t be able to do this without patching for sure (afaik).

I have done a good portion of what’s required to make this work but it’s a tad complicated and you need a variety of assertions.

I’ve also started a store implementation but it’s only the most minimal to test some assumptions and start validating my ideas.

I am working on getting it in a state that is more ready to release as an open-source project but I’m not quite there yet. And even then it would be in just a “toy” state.

Just do a snap download on any snap and then look at the .assert file that comes with it and you can start to get an idea that it’s not quite so straight-forward as at least you described. Also, if you have interfaces that need to be connected those require assertions.

If you’re intent on pulling on this thread (like I was), I believe this is where you want to start:

https://github.com/snapcore/snapd/blob/5ab26a192094e0f6478a48ce698d703515888658/asserts/sysdb/generic.go#L30

And that will start you down a lot of different paths to understanding. :slight_smile:

Let me rephrase what we are looking to achieve. We want to only install snapd and core20 snaps from the public store. All other snaps will come from our own store. Snap interface connections will be dealt separately with our custom logic.

We want to add some sort of “trust” into snapd so it trusts our keys (I guess I am a bit off here in my understanding of things).

I would be quite interested in that, maybe we could collaborate on that eventually (I am investigating currently on our available options. Exploring Brand Store as well)

you will need to loop your snaps at least once through a (brand)store to get a proper gpg signature (and the matching assertion) … then you do indeed not need the --dangerous option

i guess while you could hack snapd to accept additional keys, this is likely non-trivial to achieve… not to mention that you will need to quickly adapt your hack for each and every new snapd release to not have production devices run with an outdated snapd …

Just having snapd trust other accounts/account-keys is easy and not really hacky. In fact with Go 1.16+ and embed it could be very straight-forward to extract the default account/account-key assertions for the trust and have them as separate files. Then it wouldn’t be a hack at all to have a directory of “trusted” keys when you build. Or you could just load them on the fly from a separate location. There are reasons that all of this might not be desirable of course.

The non-trivial part comes in making all of that useful. That is definitely, 100% non-trivial. :slight_smile:

1 Like

That is the ultimate goal (to start collaborating with others). A brand store will be the most straight-forward and timely, if expensive, approach.

You would always have a patched/forked snapd with the other approach. That may be viable down the road but it definitely is not right now.

One approach that I have been investigating is to implement a totally custom logic and deliver snap updates via a custom daemon (inspired by @ogra’s script in another thread) . And being able to generate a custom assertion is one important part of that.

  1. Implement a way so that snaps uploaded to our “custom” store can be installed without using the --dangerous flag i.e. custom assertion
  2. Write a daemon in python that
    • Checks the currently installed snap packages and their versions
    • Inquires the server if there is an update available
    • Downloads and installs

Automatic interface connections could also be handled by that daemon. Of course there will be some server side logic for this as well

This kind of becomes the simplest approach out there, with much less engineering effort.

Going back to my original comment, did you inspect the contents of an assert file from a snap you downloaded? Try snap download hello-world and then look at hello-world.assert. You will need an assertion for the snap declaration and an assertion for each revision you want to load.

So you will need to patch snapd (to add your trusted key) and then you will need a way to push each new snap revision to your store and have it generate the declaration and then promote it / push it on the channel the snap is tracking on your device.

1 Like

I looked into the assert file in details and also went through https://snapcraft.io/docs/assertions. This does help understand quite a lot of things.

With effort, we should be able to implement something that generates the assert file on the server side, that we can pull on the host alongside a snap.

  1. Implement a basic “account” system in the backend for (type: account assertion)
  2. Allow that “account” to upload a snap and generate a type: snap-revision assertion from that
  3. Need to do a similar treatment for type: snap-declaration

I assume this all isn’t going to be straightforward as we need to figure out how to create a signature for each assertion.

And of course this all depends on patching snapd to allow “our” account-key alongside canonical’s

At a later stage we could update our store backend API to implement all the endpoints required by snapd.

So once we have this stuff working, the next step would be to deliver the snap with assert to the device and install it using an external daemon that talks to the snapd sock. This will allow the device to only install “trusted” packages and avoid the complexity of writing a full-fledged store with all the API endpoints that snapd calls.

Thoughts ?

That seems possible. One thing that immediately comes to mind is refreshing snaps. I don’t know what the behavior would be of snapd if you were to do this and it went looking for information about these snaps.

You would effectively being doing a snap download, snap ack and then snap install as a side-channel with your trusted key.

Seem plausible. :slight_smile:

Might be a place to start to answer those questions:

https://github.com/snapcore/snapd/blob/737be661ec6ad549602ad840d7986ed397778588/overlord/snapstate/autorefresh.go#L456

1 Like

So I have this stuff working now. Here is what I had to do

  1. Add account and account-key assertions for our ORG in trusted.go and include them in trustedAssertions in that file.
  2. created account, snap-declaration and snap-revision asserts by creating a json file for each and signing them with snap sign
  3. create account-key assertion with a custom script inspired by https://gist.github.com/anonymouse64/f644d32f8fc24b98399cf2cb36891931 (still need to make effort to port this stuff over to Python)
  4. Hacked snapd to not chicken out on “refresh” of snaps not available in store.

What I still haven’t figured out is how to generate the snap-sha3-384 of a file. running sha384sum produces a hex string, running openssl dgst -sha384 -binary < myassertion.snap | openssl enc -base64 also doesn’t produce desired result. Can anyone help with that please ?

I am sure there will be more roadblocks along the way, however this thing works in principle, which is exciting for me.

Sha3 is a different algorithm (for example sha384sum doesn’t tell you this but it uses the sha2 algorithm). Find something that can handle sha3 (OpenSSL can with the right invocation - google it, or there are python libraries for sha3) and you might make a bit more progress.

1 Like

Thanks, it worked. This is what I did in Python

import hashlib
import base64
import sys

def sha3_384(filename, blocksize=65536):
    hash = hashlib.sha3_384()
    with open(filename, "rb") as f:
        for block in iter(lambda: f.read(blocksize), b""):
            hash.update(block)

    return base64.urlsafe_b64encode(hash.digest())


print(sha3_384(sys.argv[1]))
1 Like

So now I am looking to create my own implementation of snap sign but written in Python. This should obviously be doable as I mostly need to find a way to sign the json string. However one thing I am having a bit of a hard time figuring is how is the sign-key-sha3-384 generated. Is that something to extract from the fingerprint ?

om26er@HomePC:~/work/src/github.com/snapcore/snapd$ gpg2  --homedir /home/om26er/.snap/gnupg/ --list-keys
/home/om26er/.snap/gnupg/pubring.gpg
------------------------------------
pub   rsa4096 2016-01-01 [SCEA]
      71D820A8945C231AD5948F529DADA9FEC015A12F
uid           [ultimate] omer

Here is what I tried

>>> import binascii
>>> import base64
>>> import hashlib
>>> raw = binascii.a2b_hex("71D820A8945C231AD5948F529DADA9FEC015A12F")
>>> hash = hashlib.sha3_384()
>>> hash.update(raw)
>>> print(base64.urlsafe_b64encode(hash.digest()))
b'w8kg8wjjFGKq11wqkl7GfIUN8F-_4hAUgk6vyB2CpTiNU9Mx90b0t3Ewwlv42Q81'


om26er@HomePC:~/work/src/github.com/snapcore/snapd$ snap keys
Name     SHA3-384
omer     IjxSYf7DvHlK6ArTRvPRfj4vDjSk0ughEnLWm9nZLbnJCvmXuNwUsnRk2-1g2agL

And the output is clearly different

Ok, found a way. I had to read the base64 of the public key, decode that (to get bytes) and then pass to the hash function.

So the last missing piece that I have been struggling with is to port signing of the document. I am trying to use gpg cli tool to sign the document that looks like this

type: account
authority-id: 4d6cd738e4914c50b71c87268b0cdd2c
account-id: 4d6cd738e4914c50b71c87268b0cdd2d
display-name: Omer Akram
timestamp: 2021-05-15T10:23:51+00:00
username: om26er
validation: unproven
sign-key-sha3-384: Dw8e6E_xmIKKonaOWDiGK_G9kwhCRRqhaMx-t3qV8-hraLQlGuyw-QcqSbr-aoWv

However the output result is “different” and snap ack doesn’t accept it. Am I missing something or is there something special that I need to take care of ?

I have also attached the sample private key

-----BEGIN PGP PRIVATE KEY BLOCK-----

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
=i6dy
-----END PGP PRIVATE KEY BLOCK-----

Could anyone help with that please ?

PS: While I am just getting started with the snapd code (a week or so), my initial impression is that it’s quite complicated code to “understand” and lacks comments at many places :slight_smile:

Do you have open-source repos were contributors could help? :wink: