1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253
|
libjcat
=======
This library allows reading and writing gzip-compressed JSON catalog files,
which can be used to store GPG, PKCS-7 and SHA-256 checksums for each file.
This provides equivalent functionality to the catalog files supported in
Microsoft Windows.
Design
======
Each JSON file is gzipped, and is logically divided into three structures:
JcatBlob
--------
The 'signature' blob, which can be a proper detached sigature like PKCS-7 or
just a checksum like SHA-256.
JcatItem
--------
Items roughly approximate single files, and can have multiple JcatBlobs assigned.
In a typical firmware archive you would have two items, with IDs `firmware.bin`
and `firmware.metainfo.xml`
JcatFile
--------
The container which contains one or multiple JcatItems.
Self Signing
============
Jcat files can be signed using a certificate and key that are automatically
generated on your local computer. This means you can only verify the Jcat
archive on the same computer (and probably the same user) that you use to sign
the archive.
It does however mean you can skip manually generating a secret key and public
key pair. If you do upload the public certificate up to a web service (for
instance the LVFS) it does mean it can verify your signatures.
$ jcat-tool --appstream-id localhost self-sign firmware.jcat firmware.bin
$ jcat-tool info firmware.jcat
JcatFile:
Version: 0.1
JcatItem:
ID: firmware.bin
JcatBlob:
Kind: pkcs7
Flags: is-utf8
AppstreamId: localhost
Timestamp: 2020-03-05T12:06:42Z
Size: 0x2d9
Data: -----BEGIN PKCS7-----
MIIB9wYJKoZIhvcNAQcCoIIB6DCCAeQCAQExDTALBglghkgBZQMEAgEwCwYJKoZI
...
oDd2UcfqgdQnihpYf0NaPDYhpcP5r7dmH1XN
-----END PKCS7-----
Public Key Signing
==================
Jcat can of course sign the archive with proper keys too. Here we will generate
a private and public key ourselves, but you should probably talk to your IT
department security team and ask them how to get a user certificate that's been
signed by the corporate CA certificate.
Lets create our own certificate authority (CA) and issue a per-user key for
local testing. Never use these in any kind of production system!
$ ../contrib/build-certs.py
$ ls ACME-CA.* rhughes*
ACME-CA.key ACME-CA.pem rhughes.csr rhughes.key rhughes.pem rhughes_signed.pem
Then we can actually use both files:
$ jcat-tool --appstream-id com.redhat.rhughes sign firmware.jcat firmware.bin rhughes_signed.pem rhughes.key
JcatFile:
Version: 0.1
JcatItem:
ID: firmware.bin
JcatBlob:
Kind: pkcs7
Flags: is-utf8
AppstreamId: com.redhat.rhughes
Timestamp: 2020-03-05T12:16:30Z
Size: 0x373
Data: -----BEGIN PKCS7-----
MIICZwYJKoZIhvcNAQcCoIICWDCCAlQCAQExDTALBglghkgBZQMEAgEwCwYJKoZI
...
8jggo0FbhDSs8frXhr1BHKBktOPKEbA3sETxlbHViYt6oldpi1uszV0kHA==
-----END PKCS7-----
Lets verify this new signature:
$ jcat-tool --appstream-id com.redhat.rhughes verify firmware.jcat
firmware.bin:
FAILED pkcs7: failed to verify data for O=ACME Corp.,CN=ACME CA: Public key signature verification has failed. [-89]
FAILED: Validation failed
Validation failed
Ahh, of course; we need to tell Jcat to load our generated CA certificate:
$ jcat-tool --appstream-id com.redhat.rhughes verify firmware.jcat --public-key ACME-CA.pem
firmware.bin:
PASSED pkcs7: O=ACME Corp.,CN=ACME CA
We can then check the result using
$ jcat-tool export firmware.jcat
Wrote ./firmware.bin-com.redhat.rhughes.p7b
$ certtool --p7-verify --infile firmware.bin-com.redhat.rhughes.p7b --load-data firmware.bin --load-ca-certificate=ACME-CA.pem
Loaded CAs (1 available)
eContent Type: 1.2.840.113549.1.7.1
Signers:
Signer's issuer DN: O=ACME Corp.,CN=ACME CA
Signer's serial: 4df758978d0601c6500ab6f266963916d8b7ab33
Signature Algorithm: RSA-SHA256
Signature status: ok
Large Payloads
==============
It may be impractical to load the entire binary into RAM for verification.
For this usercase, jcat supports signing the *checksum of the payload* as the target rather than the payload itself.
$ jcat-tool self-sign firmware.jcat firmware.bin --kind sha256
$ jcat-tool --appstream-id com.redhat.rhughes sign firmware.jcat firmware.bin rhughes_signed.pem rhughes.key --target sha256
$ jcat-tool info firmware.jcat
JcatFile:
Version: 0.1
JcatItem:
ID: firmware.bin
JcatBlob:
Kind: sha256
Flags: is-utf8
Timestamp: 2023-12-15T16:38:11Z
Size: 0x40
Data: a948904f2f0f479b8f8197694b30184b0d2ed1c1cd2a1ec0fb85d299a192a447
JcatBlob:
Kind: pkcs7
Target: sha256
Flags: is-utf8
AppstreamId: com.redhat.rhughes
Timestamp: 2023-12-15T16:38:15Z
Size: 0xdcc
Data: -----BEGIN PKCS7-----
MIIKCwYJKoZIhvcNAQcCoIIJ/DCCCfgCAQExDTALBglghkgBZQMEAgEwCwYJKoZI
...
Zjb6fuKL5Rr/ouoImn+x1cYJyqRMmCxpLG9GrXR9Ag==
-----END PKCS7-----
$ jcat-tool --appstream-id com.redhat.rhughes verify firmware.jcat --public-key ACME-CA.pem
firmware.bin:
PASSED pkcs7: O=ACME Corp.,CN=ACME CA
NOTE: Only JCat v2.0.0 and newer supports the *checksum of the payload* functionality, and you
should also add signatures **without** using `--target` if you need to support older versions.
Additionally, older JCat versions deduplicate the blobs by just the blob kind, so you want to make
sure that the signature added with `--target` is added **before** the signature added without.
Testing
=======
Download a firmware from the LVFS, and decompress with `gcab -x` -- we can now
validate the signatures are valid:
certtool --p7-verify --infile=firmware.bin.p7b --load-ca-certificate=/etc/pki/fwupd/LVFS-CA.pem --load-data=firmware.bin
Lets create a Jcat file with a single checksum:
$ jcat-tool self-sign test.jcat firmware.bin --kind sha256
$ jcat-tool info test.jcat
JcatFile:
Version: 0.1
JcatItem:
ID: firmware.bin
JcatBlob:
Kind: sha256
Flags: is-utf8
Timestamp: 2020-03-04T13:59:57Z
Size: 0x40
Data: bd598c9019baee65373da1963fbce7478d6e9e8963bd837d12896f53b03be83e
Now we can import both existing signatures into a Jcat file, and then validate
it again.
$ jcat-tool import test.jcat firmware.bin firmware.bin.asc
$ jcat-tool import test.jcat firmware.bin firmware.bin.p7b
$ jcat-tool info test.jcat
JcatFile:
Version: 0.1
JcatItem:
ID: firmware.bin
JcatBlob:
Kind: sha256
Flags: is-utf8
Timestamp: 2020-03-04T13:59:57Z
Size: 0x40
Data: bd598c9019baee65373da1963fbce7478d6e9e8963bd837d12896f53b03be83e
JcatBlob:
Kind: gpg
Flags: is-utf8
Timestamp: 2020-03-04T14:00:30Z
Size: 0x1ea
Data: -----BEGIN PGP SIGNATURE-----
Version: GnuPG v2.0.22 (GNU/Linux)
iQEcBAABAgAGBQJeVoylAAoJEEim2A5FOLrCagQIAIb6uDCzwUBBoZRqRzekxf0E
...
=0GGy
-----END PGP SIGNATURE-----
JcatBlob:
Kind: pkcs7
Flags: is-utf8
Timestamp: 2020-03-04T14:00:34Z
Size: 0x8c0
Data: -----BEGIN PKCS7-----
MIIGUgYJKoZIhvcNAQcCoIIGQzCCBj8CAQExDTALBglghkgBZQMEAgEwCwYJKoZI
...
EYOqoEV8PaVQZW3ndWEaQfyo6MgZ/WqpO6Gv2zTx1CXk0APIGG8=
-----END PKCS7-----
$ jcat-tool verify test.jcat --public-keys /etc/pki/fwupd
firmware.bin:
PASSED sha256: OK
PASSED gpg: 3FC6B804410ED0840D8F2F9748A6D80E4538BAC2
PASSED pkcs7: O=Linux Vendor Firmware Project,CN=LVFS CA
Security
========
Unlike Microsoft catalog files which are a signed manifest of hashes, a Jcat file
is a manifest of signatures. This means it's possible (and positively encouraged)
to modify the `.jcat` file to add new signatures or replace existing ones.
This means Jcat does not verify that the set of file has not been modified, only
that the individual files and signatures themselves have not been changed.
If you require some trust in that file A was signed at the same time, or by the
same person as file B then then best way to do this is to embed a checksum (e.g
SHA-256) into one file and then verify it in the client software.
For instance, when installing firmware we need to know if a metadata file was
provided by the LVFS with the vendor firmware file. To do this, we add the
SHA-256 checksum of the `firmware.bin` in the `firmware.metainfo.xml` file itself,
and then add both files to a Jcat archive.
The client software (e.g. fwupd) then needs to check the firmware checksum as
an additional step of verifying the signatures in the Jcat file.
|