Custom assertion possible?

There is no repo yet, not even an internal one. This is just a bunch of scripts and experiments here and there, I can share all if you want. For the signing I am currently using https://github.com/pypg/pypg

So now I have probably “fixed” the above issue. I had to prepend \x01 to the signature as it’s done here https://github.com/snapcore/snapd/blob/e0b63e07bc9bda3f76bbd6cc585f5e34140e3ec6/asserts/crypto.go#L52.

Now the issue I face is even further down the line, now I get

om26er@HomePC:~$ sudo snap ack testing.assert 
error: cannot assert: assert failed: cannot accept some assertions:
 - failed signature verification: openpgp: invalid signature: hash tag doesn't match

This is because the first two bytes of the signature that I am sending apparently are different from what snapd expects and we hit this codepath https://github.com/golang/crypto/blob/c07d793c2f9aacf728fe68cbd7acd73adbd04159/openpgp/packet/public_key.go#L512

Here is my example Python code that I use to create the signature, which snapd doesn’t like

data = """-----BEGIN PGP PRIVATE KEY BLOCK-----

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

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

from pgpy.constants import PubKeyAlgorithm, KeyFlags, HashAlgorithm, SymmetricKeyAlgorithm, CompressionAlgorithm
import pgpy
import base64
from functools import partial
from io import StringIO

key = pgpy.PGPKey()
priv = key.parse(data)
uid = pgpy.PGPUID.new('')
key.add_uid(uid, usage={KeyFlags.Sign})
signature_raw: pgpy.PGPSignature = key.sign(sign.encode('utf-8'), include_issuer_fingerprint=False)
print(signature_raw._signature.hash2)
print(signature_raw.ascii_headers)
signature = signature_raw.ascii_unarmor(signature_raw.__str__())['body']

signature_prepend = b'\x01' + signature
updated = base64.b64encode(signature_prepend)
print('\n')
print('\n'.join([l for l in iter(partial(StringIO(updated.decode()).read, 76), '')]))

Also attached the snapd patch

diff --git a/asserts/sysdb/trusted.go b/asserts/sysdb/trusted.go
index 33612a0ce4..296f7d5252 100644
--- a/asserts/sysdb/trusted.go
+++ b/asserts/sysdb/trusted.go
@@ -106,6 +106,59 @@ MxnsSusymwnYegvvvr7Xp/KBLZK1+8Djzm3fwAryp4qNo29ciVw3O9lFKmmuiIcxSY0bauXaK6kv
 pTnYkmx7XGPF7Ahb7Ov0/0FE2Lx3JZXSEKeW+VrCcpYQOY++t67b+jf0AV4rZExcLFJzP6MPMimP
 ZCd383NzlzkXK+vAdvTi40HPiM9FYOp6g8JTs5TTdx2/qs/SWFC8AkahIQmH0IpFBJep2JKl2kyr
 FZMvASkHA9bR/UuXDvbMzsUmT/xnERZosQaZgFEO
+`
+
+       encodedredactedAccount = `type: account
+authority-id: 4d6cd738e4914c50b71c87268b0cdd2c
+account-id: 4d6cd738e4914c50b71c87268b0cdd2c
+display-name: redacted
+timestamp: 2021-05-30T10:23:51+00:00
+username: redacted
+validation: unproven
+sign-key-sha3-384: Dw8e6E_xmIKKonaOWDiGK_G9kwhCRRqhaMx-t3qV8-hraLQlGuyw-QcqSbr-aoWv
+
+AcLBXAQAAQoABgUCYKWu2wAKCRAcUMfK4pmKI9xYEACJdePmSZ5RItP8XFANT+Lgd7sA25Lttp9s
+OSZYpZ9/a+lHqUQPMthDcEh8llRPDbnmGBQkYa/A2rCATuL+lAU6Lh7cILtDFx3oMHRzx62Y4bZY
+vuMV8yxNxb7BtMIAVTpJKy/xTKqwGwX7qa/8mHaXFgR2dfXH1NkepXywrByLOAQ7F28yZ16pugAD
+EAW6QH3nfO2p9MFEzurTJET2BC7zvOAAnZJ8uImMwjy5nDrmRsn/6LtgQxSzhBzcjSeC2vaU9BqX
+0yynMk8OLrAM141eMusvBOKJfNBX02AH5xs5N6VN8KXnwx23CpReebNPj16KqxVDfu5k58bcuNlE
+nNgZQQB1RYPQTzV+ynqolds6BFgPHOz88yOyqyR40jvFcnYtTAzvixwIemsaNtyxCnA742dYfxGo
+ii8Be/y5JhY3GAxH3XuVXcJ23XAl34pYaDhiLHQ+d6qVMgB2SNuVrlfc3qSgageC0xxfdK9/HjbQ
+Mv/ThZa/4QF/ypS9dv0mupzHvtkE+27coCBrcM20HTUEC072rMyk/zA3EhmrYOrRO8tN0VBlEI5S
+yBqrPsEg9PZP3hE9wIzfkp2MyoCPPoF1ef9GvP3wgphHV+C7AL9aw3yeBvAf54NXq3hKXCiaffKQ
+U2R3O6k7g0kE6TC67NVxmppEMBpQ2nFRoxnp4cztog==
+`
+
+       encodedredactedRootAccountKey = `type: account-key
+authority-id: redacted
+public-key-sha3-384: Dw8e6E_xmIKKonaOWDiGK_G9kwhCRRqhaMx-t3qV8-hraLQlGuyw-QcqSbr-aoWv
+account-id: 4d6cd738e4914c50b71c87268b0cdd2c
+name: redacted
+since: 2021-05-20T05:36:17+05:00
+body-length: 717
+sign-key-sha3-384: Dw8e6E_xmIKKonaOWDiGK_G9kwhCRRqhaMx-t3qV8-hraLQlGuyw-QcqSbr-aoWv
+
+AcbBTQRWhcGAARAAtU3PYietwSao+26yIj2q+5BFy2Mu+sj3alRZF8tZGX6PRBSWEtQ27W3MukJA
+fZKIKLUmkddwqU9AcefL5ebbai3Pb8d2lLlyaGKjapV1KEIu3ydZ48PjVQEuHPWmGkEa6G3Wdu8p
+J1veil57rZNKdScfN2bHzp6BsP0bO7bH7nZlVMqVrDVrNjSV4GqkAykmpFZgJfn65ZaNbTW9FP22
+GiK6SYElwwqi1h2WeVFUJFcmbBH0pfvDHNiR0ZIfKZ3xjzB5lDsJryZMkITlVzVEsfMKVY5K4KNG
+O9WkBGm/Fgh8l3upu+duq+HoydZTcpTGzM57knacvM+2XAZmO3793GXDOFLhPLO5z4kHllvFW9Hn
+yTs1oaNHYMhvrTmK3DBb3YUQeGUDuGSQ64orYKTrA2y0ueFCl03fFR9OZ55Xj4OoJOdWLBGDX8tz
+y0vjY/07NbTnOARECBqn02bM9+gFQ441N+scqhlUhq2yniEmgZ3S8y5uWshsndzbpQcdsjsmt5K6
+zR5VMzbMBCN4lkDIbCC3z9L24HqJrPBsB0U4hp+8bYhL1KZRz68Yt9VZgSFtiBDnNzzdtI4fgt9+
+3iArlbc0g9SRQgJkf/EEuVDzkcJC5WcNviQb+6o9AyRbiJE61GxXChp8YOit1r53F1/cKhpeQmT9
+VHq83OIdPDU5xekAEQEAAQ==
+
+AcLBUgQAAQoABgUCYKWvAQAAl4QQAITV8cFALOMExI/QPR56W5mTWRguiAYNKkSGo9C64YcpgXy1
+a/dcPegu/lb/5SZu2gtyfMLbzB2NsGtmj2NVLEQf7yNgU3SYtrsUw7OncUIbRAjbCIHBR+2gX8oh
+IUpu4lq7AA+33EnSf9qTI+YK7k6G9+aAIx4N62DW0WxKhS5z+st9NI4j5vcThY7TEuF1JvgeqNpp
+lzddVwXOwig1p6eaHDXDCs20lOWR4Hh9BV1m8TVK3mMtqNFDrYG+i8Q9slVGZuAuWnRqsmEDa8qB
+mlS6PxTC+BWqvVqVXvczVFH1+iogEQkTBn7YvaI6jmMEg579c2N7K3YaYV+u+2l/WpKibA2CLZnE
+nNgOgI+B6lOgu7X40dN6QKB07X4Yay+Cj1kjluuCl4vVyflchMgcbmEp7bTRpcZvy17/uTqpa32h
+nHobWznQlr4OVN84LkqaRSoauLpu8mlik1OK4poG3gH4UCjIL/UtrX0NPQVxYDS4SKWldZ8x07mM
+EFq8CIfFidNmoCTxCfrqsOkmeY8f7ZvyBUpTgPFKoCeIkfVXNvgTxpDXzxuhQT6AwtOuocOReyyV
+malZUzASaXGycRxE6yOj35ytc03n9fASqkjh+O42HS0jkuZGuEt+2amfUFhdURGTlNS3PLy8pzYD
+DhBlXfSqggK3B951wIeCcqVLVohu
 `
 )
 
@@ -124,7 +177,15 @@ func init() {
        if err != nil {
                panic(fmt.Sprintf("cannot decode trusted assertion: %v", err))
        }
-       trustedAssertions = []asserts.Assertion{canonicalAccount, canonicalRootAccountKey}
+       redactedAccount, err := asserts.Decode([]byte(encodedredactedAccount))
+       if err != nil {
+               panic(fmt.Sprintf("cannot decode trusted assertion: %v", err))
+       }
+       redactedRootAccountKey, err := asserts.Decode([]byte(encodedredactedRootAccountKey))
+       if err != nil {
+               panic(fmt.Sprintf("cannot decode trusted assertion: %v", err))
+       }
+       trustedAssertions = []asserts.Assertion{canonicalAccount, canonicalRootAccountKey, redactedAccount, redactedRootAccountKey}
 }
 
 // Trusted returns a copy of the current set of trusted assertions as used by Open.

Do you have the generated testing.assert available? I need to check it and do not want to bother with generating an example of my own at the moment.

Sure, here

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

AcLBXAQAAQgABgUCYKmyywAKCRAcUMfK4pmKI8FBD/kBfTitC6y8U93V7I4VcBXuyHIx8Unua6Mo
mfqMexT+I3/5rzbDWQn1dQcXeXHATbPx297Bf0SKNjY5HHpA4T7ibpKNl8JwPnF/hyeoG70HBWrU
MoU/QXyI2QLvUAWgW3Dpq2cARm0aH3BJjr0xW5U7ClfG2+uO67oJRmY0z3v5YY5axbaP4YDceLI9
5tqNAyHLdqr9rDCOGrRcYvMIuz3yHzf2U1D+N+qmctFDXLv9TRgNcvdpiafa3HOfwz9qEPey/Eea
rs0omDSC5Ln4ogCnm7J+dBQOGfMik2QV9FkDncNPRvrmyVGfro8BPm0SHiH4mMPOazNH/srrugje
d3j4B5NyfbH0PjjKN7RmpZA15rUqDqzv5W/IIP5Y6tq4yHQkZMLO5GrODnAEHg4pGAcpTWEMyKP3
PXf5rpgTzr1hvJ7jpVbXG0uUC5ER3RDweZ3wQajBNgkLeuwxpb1tPZUtH/eA4AUpMftWMltUORzm
Pm5EVlTx+lIxy0/I19j4FTYwCCplC7YOw3h09qvcVKYLGP0qIM0DB8lAmQ9zNvyaUSCM8Agr0aPW
ZzlXGyqR+Eju9ZtWPU31XNj13a0tb8e1x/uS8JQqnxNCQCR0mCyudJXhmBk92rRUn5UpG1KgAKO/
9fwhI4ItL7vdcSFKZ1FVyecxBMxjUSbw7f3TEF7l0Q==

I’m traveling at the moment but based on my local store implementation when I create the root key to start with I am using Go code that pulls in a lot of snapd, specifically the assertstest package. In any case it ends up being an RSA Private Key. Perhaps that is the issue. I’ll include some of my store initialization code here to see if it’s helpful.

func CreateKeyPair(bitsSizeOfKey int) (asserts.PrivateKey, *rsa.PrivateKey) {
	pk, rsaPK := assertstest.GenerateKey(bitsSizeOfKey)
	return pk, rsaPK
}

func ExportRsaPrivateKeyAsPemStr(privkey *rsa.PrivateKey) string {
	privkeyBytes := x509.MarshalPKCS1PrivateKey(privkey)
	privkeyPem := pem.EncodeToMemory(
		&pem.Block{
			Type:  "RSA PRIVATE KEY",
			Bytes: privkeyBytes,
		},
	)
	return string(privkeyPem)
}

func ExportRsaPublicKeyAsPemStr(pubkey *rsa.PublicKey) (string, error) {
	pubkeyBytes, err := x509.MarshalPKIXPublicKey(pubkey)
	if err != nil {
		return "", err
	}
	pubkeyPem := pem.EncodeToMemory(
		&pem.Block{
			Type:  "RSA PUBLIC KEY",
			Bytes: pubkeyBytes,
		},
	)

	return string(pubkeyPem), nil
}

So I have decided to just use the snap sign cli tool for now. That should unblock me and allow me to further experiment with things. I’ll revisit this later.

However I think feedback from @ijohnson would be interesting.

@ijohnson I am trying to port snap sign to python and have made lots of progress, however snap ack gives me

om26er@HomePC:~$ sudo snap ack testing.assert 
error: cannot assert: assert failed: cannot accept some assertions:
 - failed signature verification: openpgp: invalid signature: hash tag doesn't match

I have verified separately that I am “correctly” generating the PGP signature using Python, however it seems snapd doesn’t like that. My “hunch” is that this call https://github.com/snapcore/snapd/blob/e0b63e07bc9bda3f76bbd6cc585f5e34140e3ec6/asserts/crypto.go#L106 probably changes the structure of the signature, which I am missing in my Python implementation. (Here is the internals of Serialize https://github.com/golang/crypto/blob/c07d793c2f9aacf728fe68cbd7acd73adbd04159/openpgp/packet/signature.go#L599)

Blockquote I have verified separately that I am “correctly” generating the PGP signature using Python, however it seems snapd doesn’t like that. My “hunch” is that this call https://github.com/snapcore/snapd/blob/e0b63e07bc9bda3f76bbd6cc585f5e34140e3ec6/asserts/crypto.go#L106 probably changes the structure of the signature, which I am missing in my Python implementation. (Here is the internals of Serialize crypto/openpgp/packet/signature.go at c07d793c2f9aacf728fe68cbd7acd73adbd04159 · golang/crypto · GitHub)

hello, maybe you can change the signature at the expense of something else? have you tried it? if you find a better option through Python it will be fine

Do you mean I should try a different python library ?

Btw, I have tried signing with gpg1 cli tool but it does not allow me to prepend \x01 to the signature, which is required.

Unfortunately I don’t have much bandwidth to help you understand what needs to change about Python code to make it work like the Go code we use. If it’s really blocking you I would recommend building some sort of Glue code app so you can call a binary built with the Go code we have in snapd from Python.

1 Like

So I was able to correctly sign the assertion by using gpg1 cli tool from Python, removing the need to call the snap cli tool. While this allows me to circumvent the use of snap, it still puts a dependency on an external binary.

I am not sure if making parallel requests to the gpg1 tool will result in a disaster in production (I guess there is only one way to find that out :wink:

To answer my original question. Custom assertions are possible, with a simple patch to snapd. I’ll do a blog post about my findings in the coming days.

1 Like

Hello! Since it’s quite few years… Where we can find information/blogpost about custom assertions with snapd? :slight_smile:

Its been a while, I still run a private snap store with assertions signed by our keys. That blogpost will happen but probably in a series of blogposts when we open source our stuff (hopefully later this year).

1 Like