Version in base suite: 0.1.9-1 Base version: libjcat_0.1.9-1 Target version: libjcat_0.2.3-1~deb12u1 Base file: /srv/ftp-master.debian.org/ftp/pool/main/libj/libjcat/libjcat_0.1.9-1.dsc Target file: /srv/ftp-master.debian.org/policy/pool/main/libj/libjcat/libjcat_0.2.3-1~deb12u1.dsc .clang-tidy | 42 +++ .github/dependabot.yml | 6 .github/workflows/ccpp.yml | 28 -- .github/workflows/scorecard.yml | 73 +++++ NEWS | 95 ++++++ README.md | 43 +++ RELEASE | 22 - SECURITY.md | 12 contrib/build-certs.py | 2 contrib/generate-version-script.py | 98 ++++--- contrib/libjcat.spec.in | 11 data/tests/colorhug/firmware.bin.ed25519 | 2 data/tests/colorhug/firmware.bin.sha256 | 1 data/tests/colorhug/meson.build | 20 + data/tests/libjcat.test.in | 2 data/tests/meson.build | 11 data/tests/pki/meson.build | 5 data/tests/pki/test.ed25519 | 1 data/tests/secret.ed25519 | 1 data/tests/test.btcheckpoint | 5 data/tests/test.btverifier | 1 debian/changelog | 26 + debian/control | 2 debian/libjcat1.symbols | 30 ++ libjcat/jcat-blob-private.h | 6 libjcat/jcat-blob.c | 92 ++++++ libjcat/jcat-blob.h | 42 ++- libjcat/jcat-bt-checkpoint-private.h | 13 libjcat/jcat-bt-checkpoint.c | 310 ++++++++++++++++++++++ libjcat/jcat-bt-checkpoint.h | 32 ++ libjcat/jcat-bt-verifier-private.h | 12 libjcat/jcat-bt-verifier.c | 198 ++++++++++++++ libjcat/jcat-bt-verifier.h | 26 + libjcat/jcat-common-private.h | 20 + libjcat/jcat-common.c | 101 +++++++ libjcat/jcat-compile.h | 18 + libjcat/jcat-context-private.h | 2 libjcat/jcat-context.c | 220 +++++++++++++++ libjcat/jcat-context.h | 24 + libjcat/jcat-ed25519-engine.c | 434 +++++++++++++++++++++++++++++++ libjcat/jcat-ed25519-engine.h | 17 + libjcat/jcat-engine-private.h | 6 libjcat/jcat-engine.c | 30 ++ libjcat/jcat-engine.h | 19 - libjcat/jcat-file-private.h | 2 libjcat/jcat-file.c | 2 libjcat/jcat-file.h | 27 + libjcat/jcat-gpg-engine.h | 2 libjcat/jcat-item-private.h | 6 libjcat/jcat-item.c | 68 ++++ libjcat/jcat-item.h | 20 - libjcat/jcat-pkcs7-common.h | 14 - libjcat/jcat-pkcs7-engine.c | 38 +- libjcat/jcat-pkcs7-engine.h | 2 libjcat/jcat-result-private.h | 5 libjcat/jcat-result.h | 12 libjcat/jcat-self-test.c | 376 ++++++++++++++++++++++++++ libjcat/jcat-sha1-engine.c | 7 libjcat/jcat-sha1-engine.h | 2 libjcat/jcat-sha256-engine.c | 7 libjcat/jcat-sha256-engine.h | 2 libjcat/jcat-sha512-engine.c | 87 ++++++ libjcat/jcat-sha512-engine.h | 17 + libjcat/jcat-tool.c | 141 ++++++++-- libjcat/jcat-version.c | 25 + libjcat/jcat-version.h.in | 20 + libjcat/jcat.h | 1 libjcat/jcat.map | 46 +++ libjcat/meson.build | 38 ++ meson.build | 12 meson_options.txt | 1 71 files changed, 2907 insertions(+), 234 deletions(-) dpkg-source: warning: cannot verify inline signature for /srv/release.debian.org/tmp/tmpeflbff61/libjcat_0.1.9-1.dsc: no acceptable signature found dpkg-source: warning: cannot verify inline signature for /srv/release.debian.org/tmp/tmpeflbff61/libjcat_0.2.3-1~deb12u1.dsc: no acceptable signature found diff -Nru libjcat-0.1.9/.clang-tidy libjcat-0.2.3/.clang-tidy --- libjcat-0.1.9/.clang-tidy 1970-01-01 00:00:00.000000000 +0000 +++ libjcat-0.2.3/.clang-tidy 2025-02-03 14:10:02.000000000 +0000 @@ -0,0 +1,42 @@ +--- +Checks: "-*,\ +bugprone-*,\ +-bugprone-assignment-in-if-condition,\ +-bugprone-easily-swappable-parameters,\ +-bugprone-implicit-widening-of-multiplication-result,\ +-bugprone-macro-parentheses,\ +-bugprone-misplaced-widening-cast,\ +-bugprone-narrowing-conversions,\ +-bugprone-reserved-identifier,\ +-bugprone-too-small-loop-variable,\ +-bugprone-unchecked-optional-access,\ +misc-*,\ +-misc-confusable-identifiers,\ +-misc-const-correctness,\ +-misc-non-private-member-variables-in-classes,\ +-misc-no-recursion,\ +-misc-static-assert,\ +-misc-unused-parameters,\ +modernize-*,\ +-modernize-macro-to-enum,\ +-modernize-use-trailing-return-type,\ +-modernize-use-transparent-functors,\ +performance-*,\ +-performance-inefficient-vector-operation,\ +-performance-no-int-to-ptr,\ +readability-*,\ +-readability-avoid-const-params-in-decls,\ +-readability-braces-around-statements,\ +-readability-function-cognitive-complexity,\ +-readability-identifier-length,\ +-readability-identifier-naming,\ +-readability-implicit-bool-conversion,\ +-readability-isolate-declaration,\ +-readability-magic-numbers,\ +-readability-non-const-parameter,\ +-readability-qualified-auto,\ +-readability-redundant-declaration,\ +-readability-suspicious-call-argument,\ +-readability-uppercase-literal-suffix,\ +" +... diff -Nru libjcat-0.1.9/.github/dependabot.yml libjcat-0.2.3/.github/dependabot.yml --- libjcat-0.1.9/.github/dependabot.yml 1970-01-01 00:00:00.000000000 +0000 +++ libjcat-0.2.3/.github/dependabot.yml 2025-02-03 14:10:02.000000000 +0000 @@ -0,0 +1,6 @@ +version: 2 +updates: + - package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: "weekly" diff -Nru libjcat-0.1.9/.github/workflows/ccpp.yml libjcat-0.2.3/.github/workflows/ccpp.yml --- libjcat-0.1.9/.github/workflows/ccpp.yml 2021-11-28 15:58:08.000000000 +0000 +++ libjcat-0.2.3/.github/workflows/ccpp.yml 2025-02-03 14:10:02.000000000 +0000 @@ -8,13 +8,12 @@ jobs: gcc: - runs-on: ubuntu-18.04 + runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 - - uses: actions/setup-python@v1 + - uses: actions/checkout@v4.2.2 + - uses: actions/setup-python@v5 - name: deps run: | - sudo add-apt-repository ppa:zyga/hughsient-backports sudo apt-get update sudo apt-get install -y \ gnutls-bin \ @@ -30,27 +29,10 @@ shared-mime-info \ valac - name: meson - uses: BSFishy/meson-build@v1.0.1 + uses: BSFishy/meson-build@v1.0.3 with: action: test directory: _build setup-options: -Db_coverage=false options: --verbose - meson-version: 0.52.1 - - build-freebsd: - runs-on: macos-10.15 - steps: - - name: Checkout - uses: actions/checkout@v2 - - name: Build - id: test - uses: vmactions/freebsd-vm@v0.1.5 - with: - usesh: true - mem: 8192 - prepare: | - pkg install -y git python3 glib meson pkgconf gobject-introspection \ - json-glib gnutls gtk-doc vala - sync: rsync - run: ./contrib/ci/build-debian.sh -Dgpg=false + meson-version: 0.56.0 diff -Nru libjcat-0.1.9/.github/workflows/scorecard.yml libjcat-0.2.3/.github/workflows/scorecard.yml --- libjcat-0.1.9/.github/workflows/scorecard.yml 1970-01-01 00:00:00.000000000 +0000 +++ libjcat-0.2.3/.github/workflows/scorecard.yml 2025-02-03 14:10:02.000000000 +0000 @@ -0,0 +1,73 @@ +# This workflow uses actions that are not certified by GitHub. They are provided +# by a third-party and are governed by separate terms of service, privacy +# policy, and support documentation. + +name: Scorecard supply-chain security +on: + # For Branch-Protection check. Only the default branch is supported. See + # https://github.com/ossf/scorecard/blob/main/docs/checks.md#branch-protection + branch_protection_rule: + # To guarantee Maintained check is occasionally updated. See + # https://github.com/ossf/scorecard/blob/main/docs/checks.md#maintained + schedule: + - cron: '43 15 * * 5' + push: + branches: [ "main" ] + +# Declare default permissions as read only. +permissions: read-all + +jobs: + analysis: + name: Scorecard analysis + runs-on: ubuntu-latest + permissions: + # Needed to upload the results to code-scanning dashboard. + security-events: write + # Needed to publish results and get a badge (see publish_results below). + id-token: write + # Uncomment the permissions below if installing in a private repository. + # contents: read + # actions: read + + steps: + - name: "Checkout code" + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + with: + persist-credentials: false + + - name: "Run analysis" + uses: ossf/scorecard-action@62b2cac7ed8198b15735ed49ab1e5cf35480ba46 # v2.4.0 + with: + results_file: results.sarif + results_format: sarif + # (Optional) "write" PAT token. Uncomment the `repo_token` line below if: + # - you want to enable the Branch-Protection check on a *public* repository, or + # - you are installing Scorecard on a *private* repository + # To create the PAT, follow the steps in https://github.com/ossf/scorecard-action?tab=readme-ov-file#authentication-with-fine-grained-pat-optional. + # repo_token: ${{ secrets.SCORECARD_TOKEN }} + + # Public repositories: + # - Publish results to OpenSSF REST API for easy access by consumers + # - Allows the repository to include the Scorecard badge. + # - See https://github.com/ossf/scorecard-action#publishing-results. + # For private repositories: + # - `publish_results` will always be set to `false`, regardless + # of the value entered here. + publish_results: true + + # Upload the results as artifacts (optional). Commenting out will disable uploads of run results in SARIF + # format to the repository Actions tab. + - name: "Upload artifact" + uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0 + with: + name: SARIF file + path: results.sarif + retention-days: 5 + + # Upload the results to GitHub's code scanning dashboard (optional). + # Commenting out will disable upload of results to your repo's Code Scanning dashboard + - name: "Upload to code-scanning" + uses: github/codeql-action/upload-sarif@dd746615b3b9d728a6a37ca2045b68ca76d4841a # v3.28.8 + with: + sarif_file: results.sarif diff -Nru libjcat-0.1.9/NEWS libjcat-0.2.3/NEWS --- libjcat-0.1.9/NEWS 2021-11-28 15:58:08.000000000 +0000 +++ libjcat-0.2.3/NEWS 2025-02-03 14:10:02.000000000 +0000 @@ -1,3 +1,98 @@ +Version 0.2.3 +~~~~~~~~~~~~~ +Released: 2025-02-03 + +Bugfixes: + - Do not close the base stream when using jcat_file_import_stream() (Richard Hughes) + - Skip ed25519 part of a test with -Ded25519=false (Heiko Becker) + +Version 0.2.2 +~~~~~~~~~~~~~ +Released: 2024-10-14 + +New Features: + - Add bt-logindex blob kind (Richard Hughes) + +Bugfixes: + - Increase test coverage for ED25519 support (Daiki Ueno) + - Save the auto-generated private key with 0600 file permissions (Richard Hughes) + - Switch ED25519 support to not directly using Nettle (Daiki Ueno) + +Version 0.2.1 +~~~~~~~~~~~~~ +Released: 2024-01-20 + +Bugfixes: + - Do not dedupe sig and sig-of-checksum when loading (Richard Hughes) + - Fix the installed tests (Mario Limonciello) + - Show the sig-of-checksum results clearer on the CLI (Richard Hughes) + +Version 0.2.0 +~~~~~~~~~~~~~ +Released: 2024-01-02 + +New Features: + - Add support for verifying firmware transparency checkpoints (Richard Hughes) + - Add various bitcounting functions for future use (Joe Qian) + - Allow creating and validating SHA512 checksums (Richard Hughes) + - Allow verifying the checksum of a payload (Richard Hughes) + +Bugfixes: + - Sprinkle __attribute__((nonnull)) to give a little more compile-time safety (Richard Hughes) + +Version 0.1.14 +~~~~~~~~~~~~~~ +Released: 2023-06-08 + +Bugfixes: + - Fix header includes (Daisuke Fujimura) + - Fix prefix of LIBJCAT_CHECK_VERSION (Richard Hughes) + - Use project_source_root to fix building as a subproject (Richard Hughes) + +Version 0.1.13 +~~~~~~~~~~~~~~ +Released: 2023-02-22 + +New Features: + - Add support for SHA512 checksums (Richard Hughes) + - Add the ability to add and remove support for blob types (#72) (Richard Hughes) + +Bugfixes: + - Fix header includes for clang-tidy (Richard Hughes) + - Show the expected SHA checksum in the error (Richard Hughes) + +Version 0.1.12 +~~~~~~~~~~~~~~ +Released: 2022-09-11 + +Bugfixes: + - Correctly export the AliasIds in all cases (Richard Hughes) + - Install installed-test firmware.bin.ed25519 (Jan Tojnar) + - Predate test cert activation date by 1 day (David Bonner) + +Version 0.1.11 +~~~~~~~~~~~~~~ +Released: 2022-03-22 + +New Features: + - Allow the user to get the runtime library version (Richard Hughes) + +Bugfixes: + - Fix incorrect certtool being called on macOS (Richard Hughes) + +Version 0.1.10 +~~~~~~~~~~~~~~ +Released: 2022-02-16 + +New Features: + - Add ED25519 support (Richard Hughes) + - Define three more types used for the firmware transparency log (Richard Hughes) + +Bugfixes: + - Include the pkgconfig variables in the subproject dependency (Richard Hughes) + - Drop the use of setuptools in the test script for regenerating ld version file (Eli Schwartz) + - Use the correct lookup method for the python3 script interpreter (Eli Schwartz) + Version 0.1.9 ~~~~~~~~~~~~~ Released: 2021-11-28 diff -Nru libjcat-0.1.9/README.md libjcat-0.2.3/README.md --- libjcat-0.1.9/README.md 2021-11-28 15:58:08.000000000 +0000 +++ libjcat-0.2.3/README.md 2025-02-03 14:10:02.000000000 +0000 @@ -121,6 +121,47 @@ 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 ======= @@ -131,7 +172,7 @@ Lets create a Jcat file with a single checksum: - $ jcat-tool sign test.jcat firmware.bin sha256 + $ jcat-tool self-sign test.jcat firmware.bin --kind sha256 $ jcat-tool info test.jcat JcatFile: Version: 0.1 diff -Nru libjcat-0.1.9/RELEASE libjcat-0.2.3/RELEASE --- libjcat-0.1.9/RELEASE 2021-11-28 15:58:08.000000000 +0000 +++ libjcat-0.2.3/RELEASE 2025-02-03 14:10:02.000000000 +0000 @@ -2,11 +2,11 @@ 1. Write NEWS entries for libjcat in the same format as usual. -git shortlog 0.1.8.. | grep -i -v trivial | grep -v Merge > NEWS.new +git shortlog 0.2.2.. | grep -i -v trivial | grep -v Merge > NEWS.new -Version 0.1.9 +Version 0.2.3 ~~~~~~~~~~~~~ -Released: 2021-xx-xx +Released: 2025-xx-xx New Features: Bugfixes: @@ -15,24 +15,16 @@ Commit changes to git: # MAKE SURE THESE ARE CORRECT -export release_ver="0.1.9" +export release_ver="0.2.3" git commit -a -m "Release libjcat ${release_ver}" git tag -s -f -m "Release libjcat ${release_ver}" "${release_ver}" -git push --tags -git push - -Generate the tarball: - ninja dist - -Generate the additional verification metadata - gpg -b -a meson-dist/libjcat-${release_ver}.tar.xz +git push --tags +git push -Upload tarball: - -scp meson-dist/libjcat-${release_ver}.tar.* hughsient@people.freedesktop.org:~/public_html/releases +Upload release artifacts via https://github.com/hughsie/libjcat/tags Do post release version bump in meson.build diff -Nru libjcat-0.1.9/SECURITY.md libjcat-0.2.3/SECURITY.md --- libjcat-0.1.9/SECURITY.md 1970-01-01 00:00:00.000000000 +0000 +++ libjcat-0.2.3/SECURITY.md 2025-02-03 14:10:02.000000000 +0000 @@ -0,0 +1,12 @@ +# Security Policy + +## Supported Versions + +| Version | Supported | +| ------- | ------------------ | +| 0.2.x | :white_check_mark: | +| 0.1.x | :x: | + +## Reporting a Vulnerability + +We have enabled private reporting in GitHub, so please [follow these steps](https://github.com/hughsie/libjcat/security) to report vulnerabilities. diff -Nru libjcat-0.1.9/contrib/build-certs.py libjcat-0.2.3/contrib/build-certs.py --- libjcat-0.1.9/contrib/build-certs.py 2021-11-28 15:58:08.000000000 +0000 +++ libjcat-0.2.3/contrib/build-certs.py 2025-02-03 14:10:02.000000000 +0000 @@ -11,7 +11,7 @@ def _build_certs(): # expire in 7 days to avoid people using these in production - dt_activation = datetime.utcnow().isoformat() + dt_activation = (datetime.utcnow() - timedelta(days=1)).isoformat() dt_expiration = (datetime.utcnow() + timedelta(days=7)).isoformat() # certificate authority diff -Nru libjcat-0.1.9/contrib/generate-version-script.py libjcat-0.2.3/contrib/generate-version-script.py --- libjcat-0.1.9/contrib/generate-version-script.py 2021-11-28 15:58:08.000000000 +0000 +++ libjcat-0.2.3/contrib/generate-version-script.py 2025-02-03 14:10:02.000000000 +0000 @@ -6,15 +6,19 @@ # SPDX-License-Identifier: LGPL-2.1+ import sys +import argparse import xml.etree.ElementTree as ET -from pkg_resources import parse_version +XMLNS = "{http://www.gtk.org/introspection/core/1.0}" +XMLNS_C = "{http://www.gtk.org/introspection/c/1.0}" + + +def parse_version(ver): + return tuple(map(int, ver.split("."))) -XMLNS = '{http://www.gtk.org/introspection/core/1.0}' -XMLNS_C = '{http://www.gtk.org/introspection/c/1.0}' def usage(return_code): - """ print usage and exit with the supplied return code """ + """print usage and exit with the supplied return code""" if return_code == 0: out = sys.stdout else: @@ -22,19 +26,25 @@ out.write("usage: %s \n" % sys.argv[0]) sys.exit(return_code) + class LdVersionScript: - """ Rasterize some text """ + """Rasterize some text""" def __init__(self, library_name): self.library_name = library_name self.releases = {} + self.overrides = {} def _add_node(self, node): - identifier = node.attrib[XMLNS_C + 'identifier'] - if 'version' not in node.attrib: - print('No version for', identifier) + identifier = node.attrib[XMLNS_C + "identifier"] + introspectable = int(node.get("introspectable", 1)) + version = node.get("version", None) + if introspectable and not version: + print("No version for", identifier) sys.exit(1) - version = node.attrib['version'] + if not version: + return None + version = node.attrib["version"] if version not in self.releases: self.releases[version] = [] release = self.releases[version] @@ -45,42 +55,48 @@ def _add_cls(self, cls): # add all class functions - for node in cls.findall(XMLNS + 'function'): + for node in cls.findall(XMLNS + "function"): self._add_node(node) # choose the lowest version method for the _get_type symbol version_lowest = None - if '{http://www.gtk.org/introspection/glib/1.0}get-type' not in cls.attrib: - return - type_name = cls.attrib['{http://www.gtk.org/introspection/glib/1.0}get-type'] # add all class methods - for node in cls.findall(XMLNS + 'method'): + for node in cls.findall(XMLNS + "method"): version_tmp = self._add_node(node) if version_tmp: - if not version_lowest or parse_version(version_tmp) < parse_version(version_lowest): + if not version_lowest or parse_version(version_tmp) < parse_version( + version_lowest + ): version_lowest = version_tmp # add the constructor - for node in cls.findall(XMLNS + 'constructor'): + for node in cls.findall(XMLNS + "constructor"): version_tmp = self._add_node(node) if version_tmp: - if not version_lowest or parse_version(version_tmp) < parse_version(version_lowest): + if not version_lowest or parse_version(version_tmp) < parse_version( + version_lowest + ): version_lowest = version_tmp + if "{http://www.gtk.org/introspection/glib/1.0}get-type" not in cls.attrib: + return + type_name = cls.attrib["{http://www.gtk.org/introspection/glib/1.0}get-type"] + # finally add the get_type symbol - if version_lowest: - self.releases[version_lowest].append(type_name) + version = self.overrides.get(type_name, version_lowest) + if version: + self.releases[version].append(type_name) def import_gir(self, filename): tree = ET.parse(filename) root = tree.getroot() - for ns in root.findall(XMLNS + 'namespace'): - for node in ns.findall(XMLNS + 'function'): + for ns in root.findall(XMLNS + "namespace"): + for node in ns.findall(XMLNS + "function"): self._add_node(node) - for cls in ns.findall(XMLNS + 'record'): + for cls in ns.findall(XMLNS + "record"): self._add_cls(cls) - for cls in ns.findall(XMLNS + 'class'): + for cls in ns.findall(XMLNS + "class"): self._add_cls(cls) def render(self): @@ -91,28 +107,36 @@ versions.append(version) # output the version data to a file - verout = '# generated automatically, do not edit!\n' + verout = "# generated automatically, do not edit!\n" oldversion = None for version in sorted(versions, key=parse_version): symbols = sorted(self.releases[version]) - verout += '\n%s_%s {\n' % (self.library_name, version) - verout += ' global:\n' + verout += "\n%s_%s {\n" % (self.library_name, version) + verout += " global:\n" for symbol in symbols: - verout += ' %s;\n' % symbol - verout += ' local: *;\n' + verout += " %s;\n" % symbol + verout += " local: *;\n" if oldversion: - verout += '} %s_%s;\n' % (self.library_name, oldversion) + verout += "} %s_%s;\n" % (self.library_name, oldversion) else: - verout += '};\n' + verout += "};\n" oldversion = version return verout -if __name__ == '__main__': - if {'-?', '--help', '--usage'}.intersection(set(sys.argv)): - usage(0) - if len(sys.argv) != 4: + +if __name__ == "__main__": + + parser = argparse.ArgumentParser() + parser.add_argument( + "-r", "--override", action="append", nargs=2, metavar=("symbol", "version") + ) + args, argv = parser.parse_known_args() + if len(argv) != 3: usage(1) - ld = LdVersionScript(library_name=sys.argv[1]) - ld.import_gir(sys.argv[2]) - open(sys.argv[3], 'w').write(ld.render()) + ld = LdVersionScript(library_name=argv[0]) + if args.override: + for override_symbol, override_version in args.override: + ld.overrides[override_symbol] = override_version + ld.import_gir(argv[1]) + open(argv[2], "w").write(ld.render()) diff -Nru libjcat-0.1.9/contrib/libjcat.spec.in libjcat-0.2.3/contrib/libjcat.spec.in --- libjcat-0.1.9/contrib/libjcat.spec.in 2021-11-28 15:58:08.000000000 +0000 +++ libjcat-0.2.3/contrib/libjcat.spec.in 2025-02-03 14:10:02.000000000 +0000 @@ -7,9 +7,9 @@ Name: libjcat Version: #VERSION# Release: 0.#BUILD#%{?alphatag}%{?dist} -License: LGPLv2+ -URL: https://github.com/hughsie/libjcat -Source0: https://people.freedesktop.org/~hughsient/releases/%{name}-%{version}.tar.xz +License: LGPL-2.1-or-later +URL: https://github.com/hughsie/%{name} +Source0: https://github.com/hughsie/%{name}/releases/download/%{version}/%{name}-%{version}.tar.xz BuildRequires: gtk-doc BuildRequires: meson @@ -44,7 +44,7 @@ Executable and data files for installed tests. %prep -%setup -q +%autosetup -p0 %build @@ -90,6 +90,5 @@ %dir %{_datadir}/installed-tests/libjcat %changelog -* #LONGDATE# Richard Hughes #VERSION#-0.#BUILD##ALPHATAG# -- Update from git +%autochangelog diff -Nru libjcat-0.1.9/data/tests/colorhug/firmware.bin.ed25519 libjcat-0.2.3/data/tests/colorhug/firmware.bin.ed25519 --- libjcat-0.1.9/data/tests/colorhug/firmware.bin.ed25519 1970-01-01 00:00:00.000000000 +0000 +++ libjcat-0.2.3/data/tests/colorhug/firmware.bin.ed25519 2025-02-03 14:10:02.000000000 +0000 @@ -0,0 +1,2 @@ +. ])g?j ^QqR +oAۈ*Ik%yQZ \ No newline at end of file diff -Nru libjcat-0.1.9/data/tests/colorhug/firmware.bin.sha256 libjcat-0.2.3/data/tests/colorhug/firmware.bin.sha256 --- libjcat-0.1.9/data/tests/colorhug/firmware.bin.sha256 1970-01-01 00:00:00.000000000 +0000 +++ libjcat-0.2.3/data/tests/colorhug/firmware.bin.sha256 2025-02-03 14:10:02.000000000 +0000 @@ -0,0 +1 @@ +a196504d09871da4f7d83b874b500f8ee6e0619ab799f074814b316d88f96f7f \ No newline at end of file diff -Nru libjcat-0.1.9/data/tests/colorhug/meson.build libjcat-0.2.3/data/tests/colorhug/meson.build --- libjcat-0.1.9/data/tests/colorhug/meson.build 2021-11-28 15:58:08.000000000 +0000 +++ libjcat-0.2.3/data/tests/colorhug/meson.build 2025-02-03 14:10:02.000000000 +0000 @@ -1,9 +1,10 @@ install_data([ 'firmware.bin', 'firmware.bin.asc', + 'firmware.bin.ed25519', 'firmware.bin.p7b', ], - install_dir: join_paths(installed_test_bindir, 'colorhug'), + install_dir: join_paths(installed_test_datadir, 'colorhug'), ) if get_option('pkcs7') @@ -18,6 +19,21 @@ '--infile', '@INPUT@', '--outfile', '@OUTPUT@'], install: true, - install_dir: join_paths(installed_test_bindir, 'colorhug'), + install_dir: join_paths(installed_test_datadir, 'colorhug'), ) + + # generate self-signed detached signature *of the checksum* + colorhug_pkcs7_signature_hash = custom_target('firmware.bin.sha256.p7c', + input: 'firmware.bin.sha256', + output: 'firmware.bin.sha256.p7c', + command: [certtool, '--p7-detached-sign', + '--p7-time', + '--load-privkey', pkcs7_privkey, + '--load-certificate', pkcs7_certificate, + '--infile', '@INPUT@', + '--outfile', '@OUTPUT@'], + install: true, + install_dir: join_paths(installed_test_datadir, 'colorhug'), + ) + endif diff -Nru libjcat-0.1.9/data/tests/libjcat.test.in libjcat-0.2.3/data/tests/libjcat.test.in --- libjcat-0.1.9/data/tests/libjcat.test.in 2021-11-28 15:58:08.000000000 +0000 +++ libjcat-0.2.3/data/tests/libjcat.test.in 2025-02-03 14:10:02.000000000 +0000 @@ -1,3 +1,3 @@ [Test] Type=session -Exec=@installed_test_bindir@/jcat-self-test +Exec=sh -c "G_TEST_SRCDIR=@installed_test_datadir@ G_TEST_BUILDDIR=@installed_test_datadir@ @installed_test_bindir@/jcat-self-test" diff -Nru libjcat-0.1.9/data/tests/meson.build libjcat-0.2.3/data/tests/meson.build --- libjcat-0.1.9/data/tests/meson.build 2021-11-28 15:58:08.000000000 +0000 +++ libjcat-0.2.3/data/tests/meson.build 2025-02-03 14:10:02.000000000 +0000 @@ -1,3 +1,11 @@ +install_data([ + 'secret.ed25519', + 'test.btcheckpoint', + 'test.btverifier', + ], + install_dir: installed_test_datadir, +) + configure_file( input : 'libjcat.test.in', output : 'libjcat.test', @@ -7,7 +15,8 @@ ) # generate private PKCS7 key -certtool = find_program('certtool') +certtool = find_program('gnutls-certtool', 'certtool') + pkcs7_privkey = custom_target('test-privkey.pem', output: 'test-privkey.pem', command: [certtool, '--generate-privkey', '--outfile', '@OUTPUT@'], diff -Nru libjcat-0.1.9/data/tests/pki/meson.build libjcat-0.2.3/data/tests/pki/meson.build --- libjcat-0.1.9/data/tests/pki/meson.build 2021-11-28 15:58:08.000000000 +0000 +++ libjcat-0.2.3/data/tests/pki/meson.build 2025-02-03 14:10:02.000000000 +0000 @@ -1,8 +1,9 @@ install_data([ 'GPG-KEY-Linux-Vendor-Firmware-Service', 'LVFS-CA.pem', + 'test.ed25519', ], - install_dir: join_paths(installed_test_bindir, 'pki'), + install_dir: join_paths(installed_test_datadir, 'pki'), ) # generate certificate @@ -14,4 +15,6 @@ '--template', pkcs7_config, '--load-privkey', '@INPUT@', '--outfile', '@OUTPUT@'], + install: true, + install_dir: join_paths(installed_test_datadir, 'pki'), ) diff -Nru libjcat-0.1.9/data/tests/pki/test.ed25519 libjcat-0.2.3/data/tests/pki/test.ed25519 --- libjcat-0.1.9/data/tests/pki/test.ed25519 1970-01-01 00:00:00.000000000 +0000 +++ libjcat-0.2.3/data/tests/pki/test.ed25519 2025-02-03 14:10:02.000000000 +0000 @@ -0,0 +1 @@ +V6Ͽ?T3g,pF \ No newline at end of file diff -Nru libjcat-0.1.9/data/tests/secret.ed25519 libjcat-0.2.3/data/tests/secret.ed25519 --- libjcat-0.1.9/data/tests/secret.ed25519 1970-01-01 00:00:00.000000000 +0000 +++ libjcat-0.2.3/data/tests/secret.ed25519 2025-02-03 14:10:02.000000000 +0000 @@ -0,0 +1 @@ +MO8+5|t\B\j7l SNvƳ-a'c~ \ No newline at end of file diff -Nru libjcat-0.1.9/data/tests/test.btcheckpoint libjcat-0.2.3/data/tests/test.btcheckpoint --- libjcat-0.1.9/data/tests/test.btcheckpoint 1970-01-01 00:00:00.000000000 +0000 +++ libjcat-0.2.3/data/tests/test.btcheckpoint 2025-02-03 14:10:02.000000000 +0000 @@ -0,0 +1,5 @@ +lvfsqa +4 +KdF0yX/GfFEPKXB1+v0pnT0lxdvquyYRybl5/HhmgWk= + +— lvfsqa xGPwhE1lSYI9j14ckjbUMmPII56Niamhq+x3IpeeEPMiYU6x+iGDXUkvorVzvK9sOY6zeQYZhr91qAkqprEaJaZpWQw= diff -Nru libjcat-0.1.9/data/tests/test.btverifier libjcat-0.2.3/data/tests/test.btverifier --- libjcat-0.1.9/data/tests/test.btverifier 1970-01-01 00:00:00.000000000 +0000 +++ libjcat-0.2.3/data/tests/test.btverifier 2025-02-03 14:10:02.000000000 +0000 @@ -0,0 +1 @@ +lvfsqa+c463f084+AbAFMnhYEhBWzVlO0eGRA6KtPP3FCWpdg/FRRPMIlys6 \ No newline at end of file diff -Nru libjcat-0.1.9/debian/changelog libjcat-0.2.3/debian/changelog --- libjcat-0.1.9/debian/changelog 2022-01-14 20:57:07.000000000 +0000 +++ libjcat-0.2.3/debian/changelog 2026-06-05 00:04:57.000000000 +0000 @@ -1,3 +1,29 @@ +libjcat (0.2.3-1~deb12u1) bookworm; urgency=medium + + * Backport to bookworm to support fwupd backport. + + -- Mario Limonciello Thu, 04 Jun 2026 19:04:57 -0500 + +libjcat (0.2.3-1) unstable; urgency=medium + + * New upstream version. + * Drop patches upstream + * d/control: B-d on pkgconf instead of pkg-config + + -- Mario Limonciello Wed, 19 Mar 2025 09:47:18 -0500 + +libjcat (0.2.0-2) unstable; urgency=medium + + * Backport a patch from upstream to fix the installed tests. + + -- Mario Limonciello Wed, 17 Jan 2024 08:52:30 -0600 + +libjcat (0.2.0-1) unstable; urgency=medium + + * Upgrade to 0.2.0 release. + + -- Mario Limonciello Sun, 14 Jan 2024 10:50:14 -0600 + libjcat (0.1.9-1) unstable; urgency=low [ Debian Janitor ] diff -Nru libjcat-0.1.9/debian/control libjcat-0.2.3/debian/control --- libjcat-0.1.9/debian/control 2022-01-14 20:57:00.000000000 +0000 +++ libjcat-0.2.3/debian/control 2025-03-19 14:18:42.000000000 +0000 @@ -18,7 +18,7 @@ libjson-glib-dev, meson, ninja-build, - pkg-config, + pkgconf, valac Standards-Version: 4.5.0 Homepage: https://github.com/hughsie/libjcat diff -Nru libjcat-0.1.9/debian/libjcat1.symbols libjcat-0.2.3/debian/libjcat1.symbols --- libjcat-0.1.9/debian/libjcat1.symbols 2022-01-14 20:57:00.000000000 +0000 +++ libjcat-0.2.3/debian/libjcat1.symbols 2025-03-19 14:00:35.000000000 +0000 @@ -1,12 +1,17 @@ libjcat.so.1 libjcat1 #MINVER# * Build-Depends-Package: libjcat-dev LIBJCAT_0.1.0@LIBJCAT_0.1.0 0.1.0 + LIBJCAT_0.1.11@LIBJCAT_0.1.11 0.1.11 + LIBJCAT_0.1.12@LIBJCAT_0.1.12 0.1.12 LIBJCAT_0.1.1@LIBJCAT_0.1.1 0.1.1 LIBJCAT_0.1.3@LIBJCAT_0.1.3 0.1.3 + LIBJCAT_0.1.9@LIBJCAT_0.1.9 0.1.9 + LIBJCAT_0.2.0@LIBJCAT_0.2.0 0.2.0 jcat_blob_get_appstream_id@LIBJCAT_0.1.0 0.1.0 jcat_blob_get_data@LIBJCAT_0.1.0 0.1.0 jcat_blob_get_data_as_string@LIBJCAT_0.1.0 0.1.0 jcat_blob_get_kind@LIBJCAT_0.1.0 0.1.0 + jcat_blob_get_target@LIBJCAT_0.2.0 0.2.0 jcat_blob_get_timestamp@LIBJCAT_0.1.0 0.1.0 jcat_blob_get_type@LIBJCAT_0.1.0 0.1.0 jcat_blob_kind_from_string@LIBJCAT_0.1.0 0.1.0 @@ -16,10 +21,30 @@ jcat_blob_new_full@LIBJCAT_0.1.0 0.1.0 jcat_blob_new_utf8@LIBJCAT_0.1.0 0.1.0 jcat_blob_set_appstream_id@LIBJCAT_0.1.0 0.1.0 + jcat_blob_set_target@LIBJCAT_0.2.0 0.2.0 jcat_blob_set_timestamp@LIBJCAT_0.1.0 0.1.0 jcat_blob_to_string@LIBJCAT_0.1.0 0.1.0 + jcat_bt_checkpoint_get_hash@LIBJCAT_0.2.0 0.2.0 + jcat_bt_checkpoint_get_identity@LIBJCAT_0.2.0 0.2.0 + jcat_bt_checkpoint_get_log_size@LIBJCAT_0.2.0 0.2.0 + jcat_bt_checkpoint_get_origin@LIBJCAT_0.2.0 0.2.0 + jcat_bt_checkpoint_get_payload@LIBJCAT_0.2.0 0.2.0 + jcat_bt_checkpoint_get_pubkey@LIBJCAT_0.2.0 0.2.0 + jcat_bt_checkpoint_get_signature@LIBJCAT_0.2.0 0.2.0 + jcat_bt_checkpoint_get_type@LIBJCAT_0.2.0 0.2.0 + jcat_bt_checkpoint_new@LIBJCAT_0.2.0 0.2.0 + jcat_bt_checkpoint_to_string@LIBJCAT_0.2.0 0.2.0 + jcat_bt_verifier_get_alg@LIBJCAT_0.2.0 0.2.0 + jcat_bt_verifier_get_hash@LIBJCAT_0.2.0 0.2.0 + jcat_bt_verifier_get_key@LIBJCAT_0.2.0 0.2.0 + jcat_bt_verifier_get_name@LIBJCAT_0.2.0 0.2.0 + jcat_bt_verifier_get_type@LIBJCAT_0.2.0 0.2.0 + jcat_bt_verifier_new@LIBJCAT_0.2.0 0.2.0 + jcat_bt_verifier_to_string@LIBJCAT_0.2.0 0.2.0 jcat_context_add_public_key@LIBJCAT_0.1.0 0.1.0 jcat_context_add_public_keys@LIBJCAT_0.1.0 0.1.0 + jcat_context_blob_kind_allow@LIBJCAT_0.1.12 0.1.12 + jcat_context_blob_kind_disallow@LIBJCAT_0.1.12 0.1.12 jcat_context_get_engine@LIBJCAT_0.1.0 0.1.0 jcat_context_get_keyring_path@LIBJCAT_0.1.0 0.1.0 jcat_context_get_type@LIBJCAT_0.1.0 0.1.0 @@ -27,6 +52,8 @@ jcat_context_set_keyring_path@LIBJCAT_0.1.0 0.1.0 jcat_context_verify_blob@LIBJCAT_0.1.0 0.1.0 jcat_context_verify_item@LIBJCAT_0.1.0 0.1.0 + jcat_context_verify_target@LIBJCAT_0.2.0 0.2.0 + jcat_engine_add_public_key_raw@LIBJCAT_0.1.9 0.1.9 jcat_engine_get_kind@LIBJCAT_0.1.3 0.1.3 jcat_engine_get_method@LIBJCAT_0.1.3 0.1.3 jcat_engine_get_type@LIBJCAT_0.1.0 0.1.0 @@ -52,10 +79,12 @@ jcat_item_add_alias_id@LIBJCAT_0.1.1 0.1.1 jcat_item_add_blob@LIBJCAT_0.1.0 0.1.0 jcat_item_get_alias_ids@LIBJCAT_0.1.1 0.1.1 + jcat_item_get_blob_by_kind@LIBJCAT_0.2.0 0.2.0 jcat_item_get_blobs@LIBJCAT_0.1.0 0.1.0 jcat_item_get_blobs_by_kind@LIBJCAT_0.1.0 0.1.0 jcat_item_get_id@LIBJCAT_0.1.0 0.1.0 jcat_item_get_type@LIBJCAT_0.1.0 0.1.0 + jcat_item_has_target@LIBJCAT_0.2.0 0.2.0 jcat_item_new@LIBJCAT_0.1.0 0.1.0 jcat_item_remove_alias_id@LIBJCAT_0.1.1 0.1.1 jcat_item_to_string@LIBJCAT_0.1.0 0.1.0 @@ -65,3 +94,4 @@ jcat_result_get_timestamp@LIBJCAT_0.1.0 0.1.0 jcat_result_get_type@LIBJCAT_0.1.0 0.1.0 jcat_result_to_string@LIBJCAT_0.1.0 0.1.0 + jcat_version_string@LIBJCAT_0.1.11 0.1.11 diff -Nru libjcat-0.1.9/libjcat/jcat-blob-private.h libjcat-0.2.3/libjcat/jcat-blob-private.h --- libjcat-0.1.9/libjcat/jcat-blob-private.h 2021-11-28 15:58:08.000000000 +0000 +++ libjcat-0.2.3/libjcat/jcat-blob-private.h 2025-02-03 14:10:02.000000000 +0000 @@ -12,8 +12,8 @@ #include "jcat-common.h" JcatBlob * -jcat_blob_import(JsonObject *obj, JcatImportFlags flags, GError **error); +jcat_blob_import(JsonObject *obj, JcatImportFlags flags, GError **error) G_GNUC_NON_NULL(1); void -jcat_blob_export(JcatBlob *self, JcatExportFlags flags, JsonBuilder *builder); +jcat_blob_export(JcatBlob *self, JcatExportFlags flags, JsonBuilder *builder) G_GNUC_NON_NULL(1, 3); void -jcat_blob_add_string(JcatBlob *self, guint idt, GString *str); +jcat_blob_add_string(JcatBlob *self, guint idt, GString *str) G_GNUC_NON_NULL(1, 3); diff -Nru libjcat-0.1.9/libjcat/jcat-blob.c libjcat-0.2.3/libjcat/jcat-blob.c --- libjcat-0.1.9/libjcat/jcat-blob.c 2021-11-28 15:58:08.000000000 +0000 +++ libjcat-0.2.3/libjcat/jcat-blob.c 2025-02-03 14:10:02.000000000 +0000 @@ -13,6 +13,7 @@ typedef struct { JcatBlobKind kind; + JcatBlobKind target; JcatBlobFlags flags; GBytes *data; gchar *appstream_id; @@ -67,6 +68,20 @@ return JCAT_BLOB_KIND_SHA256; if (g_strcmp0(kind, "sha1") == 0) return JCAT_BLOB_KIND_SHA1; + if (g_strcmp0(kind, "bt-manifest") == 0) + return JCAT_BLOB_KIND_BT_MANIFEST; + if (g_strcmp0(kind, "bt-checkpoint") == 0) + return JCAT_BLOB_KIND_BT_CHECKPOINT; + if (g_strcmp0(kind, "bt-inclusion-proof") == 0) + return JCAT_BLOB_KIND_BT_INCLUSION_PROOF; + if (g_strcmp0(kind, "bt-verifier") == 0) + return JCAT_BLOB_KIND_BT_VERIFIER; + if (g_strcmp0(kind, "ed25519") == 0) + return JCAT_BLOB_KIND_ED25519; + if (g_strcmp0(kind, "sha512") == 0) + return JCAT_BLOB_KIND_SHA512; + if (g_strcmp0(kind, "bt-logindex") == 0) + return JCAT_BLOB_KIND_BT_LOGINDEX; return JCAT_BLOB_KIND_UNKNOWN; } @@ -91,6 +106,20 @@ return "sha256"; if (kind == JCAT_BLOB_KIND_SHA1) return "sha1"; + if (kind == JCAT_BLOB_KIND_BT_MANIFEST) + return "bt-manifest"; + if (kind == JCAT_BLOB_KIND_BT_CHECKPOINT) + return "bt-checkpoint"; + if (kind == JCAT_BLOB_KIND_BT_INCLUSION_PROOF) + return "bt-inclusion-proof"; + if (kind == JCAT_BLOB_KIND_BT_VERIFIER) + return "bt-verifier"; + if (kind == JCAT_BLOB_KIND_ED25519) + return "ed25519"; + if (kind == JCAT_BLOB_KIND_SHA512) + return "sha512"; + if (kind == JCAT_BLOB_KIND_BT_LOGINDEX) + return "bt-logindex"; return NULL; } @@ -115,6 +144,20 @@ return "sha256"; if (kind == JCAT_BLOB_KIND_SHA1) return "sha1"; + if (kind == JCAT_BLOB_KIND_BT_MANIFEST) + return "btmanifest"; + if (kind == JCAT_BLOB_KIND_BT_CHECKPOINT) + return "btcheckpoint"; + if (kind == JCAT_BLOB_KIND_BT_INCLUSION_PROOF) + return "btinclusionproof"; + if (kind == JCAT_BLOB_KIND_BT_VERIFIER) + return "btverifier"; + if (kind == JCAT_BLOB_KIND_ED25519) + return "ed25519"; + if (kind == JCAT_BLOB_KIND_SHA512) + return "sha512"; + if (kind == JCAT_BLOB_KIND_BT_LOGINDEX) + return "btlogindex"; return NULL; } @@ -125,6 +168,12 @@ JcatBlobPrivate *priv = GET_PRIVATE(self); jcat_string_append_kv(str, idt, G_OBJECT_TYPE_NAME(self), NULL); jcat_string_append_kv(str, idt + 1, "Kind", jcat_blob_kind_to_string(priv->kind)); + if (priv->target != JCAT_BLOB_KIND_UNKNOWN) { + jcat_string_append_kv(str, + idt + 1, + "Target", + jcat_blob_kind_to_string(priv->target)); + } jcat_string_append_kv(str, idt + 1, "Flags", @@ -192,11 +241,13 @@ priv->kind = json_object_get_int_member(obj, "Kind"); priv->flags = json_object_get_int_member(obj, "Flags"); - /* both optional */ + /* all optional */ if (json_object_has_member(obj, "Timestamp")) priv->timestamp = json_object_get_int_member(obj, "Timestamp"); if (json_object_has_member(obj, "AppstreamId")) priv->appstream_id = g_strdup(json_object_get_string_member(obj, "AppstreamId")); + if (json_object_has_member(obj, "Target")) + priv->target = json_object_get_int_member(obj, "Target"); /* get compressed data */ data_str = json_object_get_string_member(obj, "Data"); @@ -222,6 +273,10 @@ /* add metadata */ json_builder_set_member_name(builder, "Kind"); json_builder_add_int_value(builder, priv->kind); + if (priv->target != JCAT_BLOB_KIND_UNKNOWN) { + json_builder_set_member_name(builder, "Target"); + json_builder_add_int_value(builder, priv->target); + } json_builder_set_member_name(builder, "Flags"); json_builder_add_int_value(builder, priv->flags); if (priv->appstream_id != NULL) { @@ -395,6 +450,41 @@ } /** + * jcat_blob_get_target: + * @self: #JcatBlob + * + * Gets the blob target. + * + * Returns: #JcatBlobKind, e.g. %JCAT_BLOB_KIND_SHA256 + * + * Since: 0.2.0 + **/ +JcatBlobKind +jcat_blob_get_target(JcatBlob *self) +{ + JcatBlobPrivate *priv = GET_PRIVATE(self); + g_return_val_if_fail(JCAT_IS_BLOB(self), 0); + return priv->target; +} + +/** + * jcat_blob_set_target: + * @self: #JcatBlob + * @target: a #JcatBlobKind, e.g. %JCAT_BLOB_KIND_SHA256 + * + * Sets the blob target. + * + * Since: 0.2.0 + **/ +void +jcat_blob_set_target(JcatBlob *self, JcatBlobKind target) +{ + JcatBlobPrivate *priv = GET_PRIVATE(self); + g_return_if_fail(JCAT_IS_BLOB(self)); + priv->target = target; +} + +/** * jcat_blob_new: * @kind: #JcatBlobKind, e.g. %JCAT_BLOB_KIND_SHA256 * @data: #GBytes diff -Nru libjcat-0.1.9/libjcat/jcat-blob.h libjcat-0.2.3/libjcat/jcat-blob.h --- libjcat-0.1.9/libjcat/jcat-blob.h 2021-11-28 15:58:08.000000000 +0000 +++ libjcat-0.2.3/libjcat/jcat-blob.h 2025-02-03 14:10:02.000000000 +0000 @@ -6,7 +6,7 @@ #pragma once -#include +#include "jcat-compile.h" #define JCAT_TYPE_BLOB jcat_blob_get_type() @@ -19,6 +19,13 @@ * @JCAT_BLOB_KIND_GPG: GPG detached signature * @JCAT_BLOB_KIND_PKCS7: PKCS-7 detached signature * @JCAT_BLOB_KIND_SHA1: SHA-1 checksum + * @JCAT_BLOB_KIND_BT_MANIFEST: Binary transparency manifest + * @JCAT_BLOB_KIND_BT_CHECKPOINT: Binary transparency checkpoint + * @JCAT_BLOB_KIND_BT_INCLUSION_PROOF: Binary transparency inclusion proof + * @JCAT_BLOB_KIND_BT_VERIFIER: Binary transparency verifier + * @JCAT_BLOB_KIND_ED25519: ED25519 signature + * @JCAT_BLOB_KIND_SHA512: SHA-512 checksum + * @JCAT_BLOB_KIND_BT_LOGINDEX: Binary transparency log index * * The kind of blob stored as a signature on the item. **/ @@ -28,6 +35,13 @@ JCAT_BLOB_KIND_GPG, JCAT_BLOB_KIND_PKCS7, JCAT_BLOB_KIND_SHA1, + JCAT_BLOB_KIND_BT_MANIFEST, /* Since: 0.1.9 */ + JCAT_BLOB_KIND_BT_CHECKPOINT, /* Since: 0.1.9 */ + JCAT_BLOB_KIND_BT_INCLUSION_PROOF, /* Since: 0.1.9 */ + JCAT_BLOB_KIND_BT_VERIFIER, /* Since: 0.1.9 */ + JCAT_BLOB_KIND_ED25519, /* Since: 0.1.9 */ + JCAT_BLOB_KIND_SHA512, /* Since: 0.1.13 */ + JCAT_BLOB_KIND_BT_LOGINDEX, /* Since: 0.2.2 */ /*< private >*/ JCAT_BLOB_KIND_LAST } JcatBlobKind; @@ -75,24 +89,28 @@ jcat_blob_kind_to_filename_ext(JcatBlobKind kind); JcatBlob * -jcat_blob_new(JcatBlobKind kind, GBytes *data); +jcat_blob_new(JcatBlobKind kind, GBytes *data) G_GNUC_NON_NULL(2); JcatBlob * -jcat_blob_new_full(JcatBlobKind kind, GBytes *data, JcatBlobFlags flags); +jcat_blob_new_full(JcatBlobKind kind, GBytes *data, JcatBlobFlags flags) G_GNUC_NON_NULL(2); JcatBlob * -jcat_blob_new_utf8(JcatBlobKind kind, const gchar *data); +jcat_blob_new_utf8(JcatBlobKind kind, const gchar *data) G_GNUC_NON_NULL(2); gchar * -jcat_blob_to_string(JcatBlob *self); +jcat_blob_to_string(JcatBlob *self) G_GNUC_NON_NULL(1); GBytes * -jcat_blob_get_data(JcatBlob *self); +jcat_blob_get_data(JcatBlob *self) G_GNUC_NON_NULL(1); gchar * -jcat_blob_get_data_as_string(JcatBlob *self); +jcat_blob_get_data_as_string(JcatBlob *self) G_GNUC_NON_NULL(1); JcatBlobKind -jcat_blob_get_kind(JcatBlob *self); +jcat_blob_get_kind(JcatBlob *self) G_GNUC_NON_NULL(1); +JcatBlobKind +jcat_blob_get_target(JcatBlob *self) G_GNUC_NON_NULL(1); +void +jcat_blob_set_target(JcatBlob *self, JcatBlobKind target) G_GNUC_NON_NULL(1); gint64 -jcat_blob_get_timestamp(JcatBlob *self); +jcat_blob_get_timestamp(JcatBlob *self) G_GNUC_NON_NULL(1); void -jcat_blob_set_timestamp(JcatBlob *self, gint64 timestamp); +jcat_blob_set_timestamp(JcatBlob *self, gint64 timestamp) G_GNUC_NON_NULL(1); const gchar * -jcat_blob_get_appstream_id(JcatBlob *self); +jcat_blob_get_appstream_id(JcatBlob *self) G_GNUC_NON_NULL(1); void -jcat_blob_set_appstream_id(JcatBlob *self, const gchar *appstream_id); +jcat_blob_set_appstream_id(JcatBlob *self, const gchar *appstream_id) G_GNUC_NON_NULL(1); diff -Nru libjcat-0.1.9/libjcat/jcat-bt-checkpoint-private.h libjcat-0.2.3/libjcat/jcat-bt-checkpoint-private.h --- libjcat-0.1.9/libjcat/jcat-bt-checkpoint-private.h 1970-01-01 00:00:00.000000000 +0000 +++ libjcat-0.2.3/libjcat/jcat-bt-checkpoint-private.h 2025-02-03 14:10:02.000000000 +0000 @@ -0,0 +1,13 @@ +/* + * Copyright (C) 2021 Richard Hughes + * + * SPDX-License-Identifier: LGPL-2.1+ + */ + +#pragma once + +#include "jcat-bt-checkpoint.h" + +void +jcat_bt_checkpoint_add_string(JcatBtCheckpoint *self, guint idt, GString *str) + G_GNUC_NON_NULL(1, 3); diff -Nru libjcat-0.1.9/libjcat/jcat-bt-checkpoint.c libjcat-0.2.3/libjcat/jcat-bt-checkpoint.c --- libjcat-0.1.9/libjcat/jcat-bt-checkpoint.c 1970-01-01 00:00:00.000000000 +0000 +++ libjcat-0.2.3/libjcat/jcat-bt-checkpoint.c 2025-02-03 14:10:02.000000000 +0000 @@ -0,0 +1,310 @@ +/* + * Copyright (C) 2021 Richard Hughes + * + * SPDX-License-Identifier: LGPL-2.1+ + */ + +#include "config.h" + +#include "jcat-bt-checkpoint-private.h" +#include "jcat-common-private.h" + +struct _JcatBtCheckpoint { + GObject parent_instance; + gchar *origin; + gchar *hash; + guint log_size; + GBytes *blob_pubkey; + gchar *identity; + GBytes *blob_signature; + GBytes *blob_payload; +}; + +G_DEFINE_TYPE(JcatBtCheckpoint, jcat_bt_checkpoint, G_TYPE_OBJECT) + +/** + * jcat_bt_checkpoint_get_log_size: + * @self: #JcatBtCheckpoint + * + * Gets the log_size. + * + * Returns: integer + * + * Since: 0.2.0 + **/ +guint +jcat_bt_checkpoint_get_log_size(JcatBtCheckpoint *self) +{ + g_return_val_if_fail(JCAT_IS_BT_CHECKPOINT(self), 0); + return self->log_size; +} + +/** + * jcat_bt_checkpoint_get_origin: + * @self: #JcatBtCheckpoint + * + * Gets the unique identifier for the log identity which issued the checkpoint. + * + * Returns: string, or %NULL + * + * Since: 0.2.0 + **/ +const gchar * +jcat_bt_checkpoint_get_origin(JcatBtCheckpoint *self) +{ + g_return_val_if_fail(JCAT_IS_BT_CHECKPOINT(self), NULL); + return self->origin; +} + +/** + * jcat_bt_checkpoint_get_identity: + * @self: #JcatBtCheckpoint + * + * Gets a human-readable representation of the signing ID. + * + * Returns: string, or %NULL + * + * Since: 0.2.0 + **/ +const gchar * +jcat_bt_checkpoint_get_identity(JcatBtCheckpoint *self) +{ + g_return_val_if_fail(JCAT_IS_BT_CHECKPOINT(self), NULL); + return self->identity; +} + +/** + * jcat_bt_checkpoint_get_hash: + * @self: #JcatBtCheckpoint + * + * Gets the first 4 bytes of the SHA256 hash of the associated public key to act as a hint in + * identifying the correct key to verify with. + * + * Returns: string, or %NULL + * + * Since: 0.2.0 + **/ +const gchar * +jcat_bt_checkpoint_get_hash(JcatBtCheckpoint *self) +{ + g_return_val_if_fail(JCAT_IS_BT_CHECKPOINT(self), NULL); + return self->hash; +} + +/** + * jcat_bt_checkpoint_get_pubkey: + * @self: #JcatBtCheckpoint + * + * Gets the ED25519 public key blob. + * + * Returns: (transfer none): blob, or %NULL + * + * Since: 0.2.0 + **/ +GBytes * +jcat_bt_checkpoint_get_pubkey(JcatBtCheckpoint *self) +{ + g_return_val_if_fail(JCAT_IS_BT_CHECKPOINT(self), NULL); + return self->blob_pubkey; +} + +/** + * jcat_bt_checkpoint_get_signature: + * @self: #JcatBtCheckpoint + * + * Gets the ED25519 public key blob. + * + * Returns: (transfer none): blob, or %NULL + * + * Since: 0.2.0 + **/ +GBytes * +jcat_bt_checkpoint_get_signature(JcatBtCheckpoint *self) +{ + g_return_val_if_fail(JCAT_IS_BT_CHECKPOINT(self), NULL); + return self->blob_signature; +} + +/** + * jcat_bt_checkpoint_get_payload: + * @self: #JcatBtCheckpoint + * + * Gets the ED25519 public key blob. + * + * Returns: (transfer none): blob, or %NULL + * + * Since: 0.2.0 + **/ +GBytes * +jcat_bt_checkpoint_get_payload(JcatBtCheckpoint *self) +{ + g_return_val_if_fail(JCAT_IS_BT_CHECKPOINT(self), NULL); + return self->blob_payload; +} + +/* private */ +void +jcat_bt_checkpoint_add_string(JcatBtCheckpoint *self, guint idt, GString *str) +{ + jcat_string_append_kv(str, idt, G_OBJECT_TYPE_NAME(self), NULL); + if (self->origin != NULL) + jcat_string_append_kv(str, idt + 1, "Origin", self->origin); + if (self->identity != NULL) + jcat_string_append_kv(str, idt + 1, "OriginSignature", self->identity); + if (self->log_size != 0) + jcat_string_append_kx(str, idt + 1, "TreeSize", self->log_size); + if (self->blob_pubkey != 0) { + jcat_string_append_kx(str, + idt + 1, + "BlobPubkeySz", + g_bytes_get_size(self->blob_pubkey)); + } + if (self->blob_signature != 0) { + jcat_string_append_kx(str, + idt + 1, + "BlobSignatureSz", + g_bytes_get_size(self->blob_signature)); + } + if (self->blob_payload != 0) { + jcat_string_append_kx(str, + idt + 1, + "BlobPayloadSz", + g_bytes_get_size(self->blob_payload)); + } +} + +/** + * jcat_bt_checkpoint_to_string: + * @self: #JcatBtCheckpoint + * + * Converts the #JcatBtCheckpoint to a string. + * + * Returns: string + * + * Since: 0.2.0 + **/ +gchar * +jcat_bt_checkpoint_to_string(JcatBtCheckpoint *self) +{ + GString *str = g_string_new(NULL); + jcat_bt_checkpoint_add_string(self, 0, str); + return g_string_free(str, FALSE); +} + +/** + * jcat_bt_checkpoint_new: + * @blob: a #GBytes + * @error: (nullable): a #GError + * + * Converts the #JcatBtCheckpoint to a string. + * + * Returns: (transfer full): a #JcatBtCheckpoint, or %NULL on error + * + * Since: 0.2.0 + **/ +JcatBtCheckpoint * +jcat_bt_checkpoint_new(GBytes *blob, GError **error) +{ + g_autofree gchar *blob_str = NULL; + g_autofree guchar *pubkey = NULL; + g_autofree guchar *sig = NULL; + g_auto(GStrv) lines = NULL; + g_auto(GStrv) sections = NULL; + g_autoptr(GByteArray) payload = g_byte_array_new(); + g_autoptr(JcatBtCheckpoint) self = g_object_new(JCAT_TYPE_BT_CHECKPOINT, NULL); + gsize pubkeysz = 0; + gsize sigsz = 0; + + g_return_val_if_fail(blob != NULL, NULL); + + /* this is not always a NUL-terminated string */ + blob_str = g_strndup(g_bytes_get_data(blob, NULL), g_bytes_get_size(blob)); + lines = g_strsplit(blob_str, "\n", -1); + if (g_strv_length(lines) != 6) { + g_set_error(error, + G_IO_ERROR, + G_IO_ERROR_INVALID_DATA, + "invalid checkpoint format, lines %u", + g_strv_length(lines)); + return NULL; + } + + /* add as strings */ + g_byte_array_append(payload, (const guint8 *)lines[0], strlen(lines[0])); + g_byte_array_append(payload, (const guint8 *)"\n", 1); + g_byte_array_append(payload, (const guint8 *)lines[1], strlen(lines[1])); + g_byte_array_append(payload, (const guint8 *)"\n", 1); + g_byte_array_append(payload, (const guint8 *)lines[2], strlen(lines[2])); + g_byte_array_append(payload, (const guint8 *)"\n", 1); + self->blob_payload = g_byte_array_free_to_bytes(g_steal_pointer(&payload)); + + /* first two lines are trivial strings */ + self->origin = g_strdup(lines[0]); + self->log_size = g_ascii_strtoull(lines[1], NULL, 10); + + /* ED25519 public key */ + pubkey = g_base64_decode(lines[2], &pubkeysz); + if (pubkeysz != 32) { + g_set_error(error, + G_IO_ERROR, + G_IO_ERROR_INVALID_DATA, + "invalid pubkey format, pubkeysz 0x%x", + (guint)pubkeysz); + return NULL; + } + self->blob_pubkey = g_bytes_new(pubkey, pubkeysz); + + /* — ORIGIN BASE64 */ + sections = g_strsplit(lines[4], " ", 3); + if (g_strv_length(sections) != 3 || g_strcmp0(sections[0], "—") != 0) { + g_set_error(error, + G_IO_ERROR, + G_IO_ERROR_INVALID_DATA, + "invalid checkpoint format, sections %x", + g_strv_length(sections)); + return NULL; + } + self->identity = g_strdup(sections[1]); + sig = g_base64_decode(sections[2], &sigsz); + if (sigsz != 64 + 4) { + g_set_error(error, + G_IO_ERROR, + G_IO_ERROR_INVALID_DATA, + "invalid pubkey format, sigsz was 0x%x", + (guint)sigsz); + return NULL; + } + self->hash = g_strdup_printf("%02x%02x%02x%02x", sig[0], sig[1], sig[2], sig[3]); + self->blob_signature = g_bytes_new(sig + 0x4, sigsz - 0x4); + + /* success */ + return g_steal_pointer(&self); +} + +static void +jcat_bt_checkpoint_finalize(GObject *object) +{ + JcatBtCheckpoint *self = JCAT_BT_CHECKPOINT(object); + g_free(self->origin); + g_free(self->identity); + g_free(self->hash); + if (self->blob_pubkey != NULL) + g_bytes_unref(self->blob_pubkey); + if (self->blob_signature != NULL) + g_bytes_unref(self->blob_signature); + if (self->blob_payload != NULL) + g_bytes_unref(self->blob_payload); + G_OBJECT_CLASS(jcat_bt_checkpoint_parent_class)->finalize(object); +} + +static void +jcat_bt_checkpoint_class_init(JcatBtCheckpointClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS(klass); + object_class->finalize = jcat_bt_checkpoint_finalize; +} + +static void +jcat_bt_checkpoint_init(JcatBtCheckpoint *self) +{ +} diff -Nru libjcat-0.1.9/libjcat/jcat-bt-checkpoint.h libjcat-0.2.3/libjcat/jcat-bt-checkpoint.h --- libjcat-0.1.9/libjcat/jcat-bt-checkpoint.h 1970-01-01 00:00:00.000000000 +0000 +++ libjcat-0.2.3/libjcat/jcat-bt-checkpoint.h 2025-02-03 14:10:02.000000000 +0000 @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2021 Richard Hughes + * + * SPDX-License-Identifier: LGPL-2.1+ + */ + +#pragma once + +#include "jcat-compile.h" + +#define JCAT_TYPE_BT_CHECKPOINT (jcat_bt_checkpoint_get_type()) + +G_DECLARE_FINAL_TYPE(JcatBtCheckpoint, jcat_bt_checkpoint, JCAT, BT_CHECKPOINT, GObject) + +JcatBtCheckpoint * +jcat_bt_checkpoint_new(GBytes *blob, GError **error); +gchar * +jcat_bt_checkpoint_to_string(JcatBtCheckpoint *self) G_GNUC_NON_NULL(1); +const gchar * +jcat_bt_checkpoint_get_origin(JcatBtCheckpoint *self) G_GNUC_NON_NULL(1); +const gchar * +jcat_bt_checkpoint_get_identity(JcatBtCheckpoint *self) G_GNUC_NON_NULL(1); +const gchar * +jcat_bt_checkpoint_get_hash(JcatBtCheckpoint *self) G_GNUC_NON_NULL(1); +GBytes * +jcat_bt_checkpoint_get_pubkey(JcatBtCheckpoint *self) G_GNUC_NON_NULL(1); +GBytes * +jcat_bt_checkpoint_get_signature(JcatBtCheckpoint *self) G_GNUC_NON_NULL(1); +GBytes * +jcat_bt_checkpoint_get_payload(JcatBtCheckpoint *self) G_GNUC_NON_NULL(1); +guint +jcat_bt_checkpoint_get_log_size(JcatBtCheckpoint *self) G_GNUC_NON_NULL(1); diff -Nru libjcat-0.1.9/libjcat/jcat-bt-verifier-private.h libjcat-0.2.3/libjcat/jcat-bt-verifier-private.h --- libjcat-0.1.9/libjcat/jcat-bt-verifier-private.h 1970-01-01 00:00:00.000000000 +0000 +++ libjcat-0.2.3/libjcat/jcat-bt-verifier-private.h 2025-02-03 14:10:02.000000000 +0000 @@ -0,0 +1,12 @@ +/* + * Copyright (C) 2021 Richard Hughes + * + * SPDX-License-Identifier: LGPL-2.1+ + */ + +#pragma once + +#include "jcat-bt-verifier.h" + +void +jcat_bt_verifier_add_string(JcatBtVerifier *self, guint idt, GString *str) G_GNUC_NON_NULL(1, 3); diff -Nru libjcat-0.1.9/libjcat/jcat-bt-verifier.c libjcat-0.2.3/libjcat/jcat-bt-verifier.c --- libjcat-0.1.9/libjcat/jcat-bt-verifier.c 1970-01-01 00:00:00.000000000 +0000 +++ libjcat-0.2.3/libjcat/jcat-bt-verifier.c 2025-02-03 14:10:02.000000000 +0000 @@ -0,0 +1,198 @@ +/* + * Copyright (C) 2021 Richard Hughes + * + * SPDX-License-Identifier: LGPL-2.1+ + */ + +#include "config.h" + +#include "jcat-bt-verifier-private.h" +#include "jcat-common-private.h" + +struct _JcatBtVerifier { + GObject parent_instance; + gchar *name; + gchar *hash; + guint8 alg; + GBytes *blob_key; +}; + +G_DEFINE_TYPE(JcatBtVerifier, jcat_bt_verifier, G_TYPE_OBJECT) + +/** + * jcat_bt_verifier_get_alg: + * @self: #JcatBtVerifier + * + * Gets the algorithm ID. + * + * Returns: ID, typically 1 + * + * Since: 0.2.0 + **/ +guint8 +jcat_bt_verifier_get_alg(JcatBtVerifier *self) +{ + g_return_val_if_fail(JCAT_IS_BT_VERIFIER(self), 0); + return self->alg; +} + +/** + * jcat_bt_verifier_get_name: + * @self: #JcatBtVerifier + * + * Gets the name. + * + * Returns: string, or %NULL + * + * Since: 0.2.0 + **/ +const gchar * +jcat_bt_verifier_get_name(JcatBtVerifier *self) +{ + g_return_val_if_fail(JCAT_IS_BT_VERIFIER(self), NULL); + return self->name; +} + +/** + * jcat_bt_verifier_get_hash: + * @self: #JcatBtVerifier + * + * Gets the hash. + * + * Returns: string, or %NULL + * + * Since: 0.2.0 + **/ +const gchar * +jcat_bt_verifier_get_hash(JcatBtVerifier *self) +{ + g_return_val_if_fail(JCAT_IS_BT_VERIFIER(self), NULL); + return self->hash; +} + +/** + * jcat_bt_verifier_get_key: + * @self: #JcatBtVerifier + * + * Gets the ED25519 public key blob. + * + * Returns: (transfer none): blob, or %NULL + * + * Since: 0.2.0 + **/ +GBytes * +jcat_bt_verifier_get_key(JcatBtVerifier *self) +{ + g_return_val_if_fail(JCAT_IS_BT_VERIFIER(self), NULL); + return self->blob_key; +} + +/* private */ +void +jcat_bt_verifier_add_string(JcatBtVerifier *self, guint idt, GString *str) +{ + jcat_string_append_kv(str, idt, G_OBJECT_TYPE_NAME(self), NULL); + if (self->name != NULL) + jcat_string_append_kv(str, idt + 1, "Name", self->name); + if (self->hash != NULL) + jcat_string_append_kv(str, idt + 1, "Hash", self->hash); + if (self->alg != 0) + jcat_string_append_kx(str, idt + 1, "AlgoId", self->alg); + if (self->blob_key != 0) { + jcat_string_append_kx(str, idt + 1, "KeySz", g_bytes_get_size(self->blob_key)); + } +} + +/** + * jcat_bt_verifier_to_string: + * @self: #JcatBtVerifier + * + * Converts the #JcatBtVerifier to a string. + * + * Returns: string + * + * Since: 0.2.0 + **/ +gchar * +jcat_bt_verifier_to_string(JcatBtVerifier *self) +{ + GString *str = g_string_new(NULL); + jcat_bt_verifier_add_string(self, 0, str); + return g_string_free(str, FALSE); +} + +/** + * jcat_bt_verifier_new: + * @blob: a #GBytes + * @error: (nullable): a #GError + * + * Converts the #JcatBtVerifier to a string. + * + * Returns: (transfer full): a #JcatBtVerifier, or %NULL on error + * + * Since: 0.2.0 + **/ +JcatBtVerifier * +jcat_bt_verifier_new(GBytes *blob, GError **error) +{ + gsize pubkey_rawsz = 0; + g_autofree gchar *blob_str = NULL; + g_autofree guchar *pubkey_raw = NULL; + g_auto(GStrv) sections = NULL; + g_autoptr(JcatBtVerifier) self = g_object_new(JCAT_TYPE_BT_VERIFIER, NULL); + + g_return_val_if_fail(blob != NULL, NULL); + + /* this is not a NUL-terminated string */ + blob_str = g_strndup(g_bytes_get_data(blob, NULL), g_bytes_get_size(blob)); + sections = g_strsplit(blob_str, "+", 3); + if (g_strv_length(sections) != 3) { + g_set_error_literal(error, + G_IO_ERROR, + G_IO_ERROR_INVALID_DATA, + "invalid pubkey format"); + return NULL; + } + + /* first two sections are trivial strings */ + self->name = g_strdup(sections[0]); + self->hash = g_strdup(sections[1]); + + /* algorithm ID then ED25519 public key */ + pubkey_raw = g_base64_decode(sections[2], &pubkey_rawsz); + if (pubkey_rawsz != 33) { + g_set_error_literal(error, + G_IO_ERROR, + G_IO_ERROR_INVALID_DATA, + "invalid pubkey format"); + return NULL; + } + self->alg = pubkey_raw[0]; + self->blob_key = g_bytes_new(pubkey_raw + 1, pubkey_rawsz - 1); + + /* success */ + return g_steal_pointer(&self); +} + +static void +jcat_bt_verifier_finalize(GObject *object) +{ + JcatBtVerifier *self = JCAT_BT_VERIFIER(object); + g_free(self->name); + g_free(self->hash); + if (self->blob_key != NULL) + g_bytes_unref(self->blob_key); + G_OBJECT_CLASS(jcat_bt_verifier_parent_class)->finalize(object); +} + +static void +jcat_bt_verifier_class_init(JcatBtVerifierClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS(klass); + object_class->finalize = jcat_bt_verifier_finalize; +} + +static void +jcat_bt_verifier_init(JcatBtVerifier *self) +{ +} diff -Nru libjcat-0.1.9/libjcat/jcat-bt-verifier.h libjcat-0.2.3/libjcat/jcat-bt-verifier.h --- libjcat-0.1.9/libjcat/jcat-bt-verifier.h 1970-01-01 00:00:00.000000000 +0000 +++ libjcat-0.2.3/libjcat/jcat-bt-verifier.h 2025-02-03 14:10:02.000000000 +0000 @@ -0,0 +1,26 @@ +/* + * Copyright (C) 2021 Richard Hughes + * + * SPDX-License-Identifier: LGPL-2.1+ + */ + +#pragma once + +#include "jcat-compile.h" + +#define JCAT_TYPE_BT_VERIFIER (jcat_bt_verifier_get_type()) + +G_DECLARE_FINAL_TYPE(JcatBtVerifier, jcat_bt_verifier, JCAT, BT_VERIFIER, GObject) + +JcatBtVerifier * +jcat_bt_verifier_new(GBytes *blob, GError **error); +gchar * +jcat_bt_verifier_to_string(JcatBtVerifier *self) G_GNUC_NON_NULL(1); +const gchar * +jcat_bt_verifier_get_name(JcatBtVerifier *self) G_GNUC_NON_NULL(1); +const gchar * +jcat_bt_verifier_get_hash(JcatBtVerifier *self) G_GNUC_NON_NULL(1); +GBytes * +jcat_bt_verifier_get_key(JcatBtVerifier *self) G_GNUC_NON_NULL(1); +guint8 +jcat_bt_verifier_get_alg(JcatBtVerifier *self) G_GNUC_NON_NULL(1); diff -Nru libjcat-0.1.9/libjcat/jcat-common-private.h libjcat-0.2.3/libjcat/jcat-common-private.h --- libjcat-0.1.9/libjcat/jcat-common-private.h 2021-11-28 15:58:08.000000000 +0000 +++ libjcat-0.2.3/libjcat/jcat-common-private.h 2025-02-03 14:10:02.000000000 +0000 @@ -1,5 +1,6 @@ /* * Copyright (C) 2020 Richard Hughes + * Copyright (C) 2022 Joe Qian * * SPDX-License-Identifier: LGPL-2.1+ */ @@ -9,12 +10,23 @@ #include #include "jcat-common.h" +#include "jcat-compile.h" gboolean -jcat_mkdir_parent(const gchar *filename, GError **error); +jcat_mkdir_parent(const gchar *filename, GError **error) G_GNUC_NON_NULL(1); gboolean -jcat_set_contents_bytes(const gchar *filename, GBytes *bytes, GError **error); +jcat_set_contents_bytes(const gchar *filename, GBytes *bytes, gint mode, GError **error) + G_GNUC_NON_NULL(1, 2); GBytes * -jcat_get_contents_bytes(const gchar *filename, GError **error); +jcat_get_contents_bytes(const gchar *filename, GError **error) G_GNUC_NON_NULL(1); void -jcat_string_append_kv(GString *str, guint idt, const gchar *key, const gchar *value); +jcat_string_append_kv(GString *str, guint idt, const gchar *key, const gchar *value) + G_GNUC_NON_NULL(1); +void +jcat_string_append_kx(GString *str, guint idt, const gchar *key, guint value) G_GNUC_NON_NULL(1); +guint +jcat_bits_ones_count64(guint64 val); +guint +jcat_bits_trailing_zeros64(guint64 val); +guint +jcat_bits_length64(guint64 val); diff -Nru libjcat-0.1.9/libjcat/jcat-common.c libjcat-0.2.3/libjcat/jcat-common.c --- libjcat-0.1.9/libjcat/jcat-common.c 2021-11-28 15:58:08.000000000 +0000 +++ libjcat-0.2.3/libjcat/jcat-common.c 2025-02-03 14:10:02.000000000 +0000 @@ -1,5 +1,6 @@ /* * Copyright (C) 2017 Richard Hughes + * Copyright (C) 2022 Joe Qian * * SPDX-License-Identifier: LGPL-2.1+ */ @@ -24,7 +25,7 @@ /* private */ gboolean -jcat_set_contents_bytes(const gchar *filename, GBytes *bytes, GError **error) +jcat_set_contents_bytes(const gchar *filename, GBytes *bytes, gint mode, GError **error) { const gchar *data; gsize size; @@ -39,7 +40,16 @@ } data = g_bytes_get_data(bytes, &size); g_debug("writing %s with %" G_GSIZE_FORMAT " bytes", filename, size); +#if GLIB_CHECK_VERSION(2, 66, 0) + return g_file_set_contents_full(filename, + data, + size, + G_FILE_SET_CONTENTS_CONSISTENT, + mode, + error); +#else return g_file_set_contents(filename, data, size, error); +#endif } /* private */ @@ -108,3 +118,92 @@ g_string_append(str, "\n"); } } + +/* private */ +void +jcat_string_append_kx(GString *str, guint idt, const gchar *key, guint value) +{ + g_autofree gchar *tmp = g_strdup_printf("0x%x", value); + jcat_string_append_kv(str, idt, key, tmp); +} + +#ifndef __has_builtin +#define __has_builtin(x) 0 +#endif + +/** + * jcat_bits_ones_count64: + * @val: input + * + * Count the number of 1's set. + * + * Returns: integer + * + * Since: 0.2.0 + **/ +guint +jcat_bits_ones_count64(guint64 val) +{ +#if __has_builtin(__builtin_popcountll) + return __builtin_popcountll(val); +#else + guint c = 0; + for (guint i = 0; i < 64; i++) { + if (val & ((guint64)0b1 << i)) + c += 1; + } + return c; +#endif +} + +/** + * jcat_bits_trailing_zeros64: + * @val: input + * + * Count the number of trailing zero bits. + * + * Returns: integer + * + * Since: 0.2.0 + **/ +guint +jcat_bits_trailing_zeros64(guint64 val) +{ +#if __has_builtin(__builtin_ctzll) + if (val == 0) + return 64; + return __builtin_ctzll(val); +#else + for (guint i = 0; i < 64; i++) { + if (val & ((guint64)0b1 << i)) + return i; + } + return 64; +#endif +} + +/** + * jcat_bits_length64: + * @val: input + * + * Find the minimum number of bits required to represent a number. + * + * Returns: integer + * + * Since: 0.2.0 + **/ +guint +jcat_bits_length64(guint64 val) +{ +#if __has_builtin(__builtin_clzll) + if (val == 0) + return 0; + return 64 - __builtin_clzll(val); +#else + for (guint i = 0; i < 64; i++) { + if (((guint64)1 << i) > val) + return i; + } + return 64; +#endif +} diff -Nru libjcat-0.1.9/libjcat/jcat-compile.h libjcat-0.2.3/libjcat/jcat-compile.h --- libjcat-0.1.9/libjcat/jcat-compile.h 1970-01-01 00:00:00.000000000 +0000 +++ libjcat-0.2.3/libjcat/jcat-compile.h 2025-02-03 14:10:02.000000000 +0000 @@ -0,0 +1,18 @@ +/* + * Copyright (C) 2023 Richard Hughes + * + * SPDX-License-Identifier: LGPL-2.1+ + */ + +#pragma once + +#include + +/* see https://bugzilla.gnome.org/show_bug.cgi?id=113075 */ +#ifndef G_GNUC_NON_NULL +#if !defined(_WIN32) && (__GNUC__ > 3) || (__GNUC__ == 3 && __GNUC_MINOR__ >= 3) +#define G_GNUC_NON_NULL(params...) __attribute__((nonnull(params))) +#else +#define G_GNUC_NON_NULL(params...) +#endif +#endif diff -Nru libjcat-0.1.9/libjcat/jcat-context-private.h libjcat-0.2.3/libjcat/jcat-context-private.h --- libjcat-0.1.9/libjcat/jcat-context-private.h 2021-11-28 15:58:08.000000000 +0000 +++ libjcat-0.2.3/libjcat/jcat-context-private.h 2025-02-03 14:10:02.000000000 +0000 @@ -9,4 +9,4 @@ #include "jcat-context.h" GPtrArray * -jcat_context_get_public_keys(JcatContext *self); +jcat_context_get_public_keys(JcatContext *self) G_GNUC_NON_NULL(1); diff -Nru libjcat-0.1.9/libjcat/jcat-context.c libjcat-0.2.3/libjcat/jcat-context.c --- libjcat-0.1.9/libjcat/jcat-context.c 2021-11-28 15:58:08.000000000 +0000 +++ libjcat-0.2.3/libjcat/jcat-context.c 2025-02-03 14:10:02.000000000 +0000 @@ -12,6 +12,7 @@ #include "jcat-result-private.h" #include "jcat-sha1-engine.h" #include "jcat-sha256-engine.h" +#include "jcat-sha512-engine.h" #ifdef ENABLE_GPG #include "jcat-gpg-engine.h" @@ -19,11 +20,15 @@ #ifdef ENABLE_PKCS7 #include "jcat-pkcs7-engine.h" #endif +#ifdef ENABLE_ED25519 +#include "jcat-ed25519-engine.h" +#endif typedef struct { GPtrArray *engines; GPtrArray *public_keys; gchar *keyring_path; + guint32 blob_kinds; } JcatContextPrivate; G_DEFINE_TYPE_WITH_PRIVATE(JcatContext, jcat_context, G_TYPE_OBJECT) @@ -51,18 +56,24 @@ jcat_context_init(JcatContext *self) { JcatContextPrivate *priv = GET_PRIVATE(self); + + priv->blob_kinds = G_MAXUINT32; priv->keyring_path = g_build_filename(g_get_user_data_dir(), PACKAGE_NAME, NULL); priv->engines = g_ptr_array_new_with_free_func((GDestroyNotify)g_object_unref); priv->public_keys = g_ptr_array_new_with_free_func(g_free); g_ptr_array_add(priv->engines, jcat_sha1_engine_new(self)); g_ptr_array_add(priv->engines, jcat_sha256_engine_new(self)); + g_ptr_array_add(priv->engines, jcat_sha512_engine_new(self)); #ifdef ENABLE_GPG g_ptr_array_add(priv->engines, jcat_gpg_engine_new(self)); #endif #ifdef ENABLE_PKCS7 g_ptr_array_add(priv->engines, jcat_pkcs7_engine_new(self)); #endif +#ifdef ENABLE_ED25519 + g_ptr_array_add(priv->engines, jcat_ed25519_engine_new(self)); +#endif } /** @@ -156,6 +167,13 @@ return priv->keyring_path; } +static gboolean +jcat_context_is_blob_kind_allowed(JcatContext *self, JcatBlobKind kind) +{ + JcatContextPrivate *priv = GET_PRIVATE(self); + return (priv->blob_kinds & (1ull << kind)) > 0; +} + /** * jcat_context_get_engine: * @self: #JcatContext @@ -176,6 +194,14 @@ g_return_val_if_fail(JCAT_IS_CONTEXT(self), NULL); + if (!jcat_context_is_blob_kind_allowed(self, kind)) { + g_set_error(error, + G_IO_ERROR, + G_IO_ERROR_NOT_SUPPORTED, + "Jcat engine kind '%s' not allowed", + jcat_blob_kind_to_string(kind)); + return NULL; + } for (guint i = 0; i < priv->engines->len; i++) { JcatEngine *engine = g_ptr_array_index(priv->engines, i); if (jcat_engine_get_kind(engine) == kind) @@ -228,6 +254,56 @@ } /** + * jcat_context_blob_kind_allow: + * @self: #JcatContext + * @kind: #JcatBlobKind, e.g. %JCAT_BLOB_KIND_GPG + * + * Adds a blob kind to the allowlist. By default, JCat will use all signature and checksum schemes + * compiled in at build time. Once this function has been called only specific blob kinds will be + * used in functions like jcat_context_verify_blob(). + * + * Since: 0.1.12 + **/ +void +jcat_context_blob_kind_allow(JcatContext *self, JcatBlobKind kind) +{ + JcatContextPrivate *priv = GET_PRIVATE(self); + + g_return_if_fail(JCAT_IS_CONTEXT(self)); + g_return_if_fail(kind < JCAT_BLOB_KIND_LAST); + + /* clear all */ + if (priv->blob_kinds == G_MAXUINT32) + priv->blob_kinds = 0x0; + + /* enable this */ + priv->blob_kinds |= 1ull << kind; +} + +/** + * jcat_context_blob_kind_disallow: + * @self: #JcatContext + * @kind: #JcatBlobKind, e.g. %JCAT_BLOB_KIND_GPG + * + * Removes a blob kind from the allowlist. By default, JCat will use all signature and checksum + * schemes compiled in at build time. Once this function has been called this @kind will not be + * used in functions like jcat_context_verify_blob(). + * + * Since: 0.1.12 + **/ +void +jcat_context_blob_kind_disallow(JcatContext *self, JcatBlobKind kind) +{ + JcatContextPrivate *priv = GET_PRIVATE(self); + + g_return_if_fail(JCAT_IS_CONTEXT(self)); + g_return_if_fail(kind < JCAT_BLOB_KIND_LAST); + + /* disable this */ + priv->blob_kinds &= ~(1ull << kind); +} + +/** * jcat_context_verify_item: * @self: #JcatContext * @data: #GBytes @@ -321,6 +397,150 @@ jcat_blob_get_data(blob), flags, &error_local); + if (result == NULL) { + g_debug("signature failure: %s", error_local->message); + continue; + } + result_str = jcat_result_to_string(result); + g_debug("verified: %s", result_str); + g_ptr_array_add(results, g_steal_pointer(&result)); + nr_signature++; + } + if (flags & JCAT_VERIFY_FLAG_REQUIRE_SIGNATURE && nr_signature == 0) { + g_set_error_literal(error, + G_IO_ERROR, + G_IO_ERROR_NOT_SUPPORTED, + "signatures were required, but none supplied"); + return NULL; + } + + /* success */ + return g_steal_pointer(&results); +} + +/** + * jcat_context_verify_target: + * @self: #JcatContext + * @item_target: #JcatItem containing checksums of the data + * @item: #JcatItem + * @flags: #JcatVerifyFlags, e.g. %JCAT_VERIFY_FLAG_REQUIRE_SIGNATURE + * @error: #GError, or %NULL + * + * Verifies a #JcatItem using the target to an item. At least one `verify=CHECKSUM` (e.g. SHA256) + * must exist and all checksum types that do exist must verify correctly. + * + * Returns: (transfer container) (element-type JcatResult): results, or %NULL for failed + * + * Since: 0.2.0 + **/ +GPtrArray * +jcat_context_verify_target(JcatContext *self, + JcatItem *item_target, + JcatItem *item, + JcatVerifyFlags flags, + GError **error) +{ + guint nr_signature = 0; + g_autoptr(GPtrArray) blobs = NULL; + g_autoptr(GPtrArray) results = + g_ptr_array_new_with_free_func((GDestroyNotify)g_object_unref); + + g_return_val_if_fail(JCAT_IS_CONTEXT(self), NULL); + g_return_val_if_fail(JCAT_IS_ITEM(item_target), NULL); + g_return_val_if_fail(JCAT_IS_ITEM(item), NULL); + + /* no blobs */ + blobs = jcat_item_get_blobs(item); + if (blobs->len == 0) { + g_set_error_literal(error, + G_IO_ERROR, + G_IO_ERROR_NOT_SUPPORTED, + "no blobs in item"); + return NULL; + } + + /* all checksum engines must verify */ + for (guint i = 0; i < blobs->len; i++) { + JcatBlob *blob = g_ptr_array_index(blobs, i); + g_autoptr(GError) error_local = NULL; + g_autoptr(JcatEngine) engine = NULL; + g_autoptr(JcatResult) result = NULL; + g_autoptr(JcatBlob) blob_target = NULL; + g_autofree gchar *checksum = NULL; + g_autofree gchar *checksum_target = NULL; + + /* get engine */ + engine = jcat_context_get_engine(self, jcat_blob_get_kind(blob), &error_local); + if (engine == NULL) { + g_debug("%s", error_local->message); + continue; + } + if (jcat_engine_get_method(engine) != JCAT_BLOB_METHOD_CHECKSUM) + continue; + blob_target = + jcat_item_get_blob_by_kind(item_target, jcat_blob_get_kind(blob), &error_local); + if (blob_target == NULL) { + g_debug("no target value: %s", error_local->message); + continue; + } + + /* checksum is as expected */ + checksum = jcat_blob_get_data_as_string(blob); + checksum_target = jcat_blob_get_data_as_string(blob_target); + if (g_strcmp0(checksum, checksum_target) != 0) { + g_set_error(error, + G_IO_ERROR, + G_IO_ERROR_NOT_SUPPORTED, + "%s checksum was %s but target is %s", + jcat_blob_kind_to_string(jcat_blob_get_kind(blob)), + checksum, + checksum_target); + return NULL; + } + g_ptr_array_add(results, g_object_new(JCAT_TYPE_RESULT, "engine", engine, NULL)); + } + if (flags & JCAT_VERIFY_FLAG_REQUIRE_CHECKSUM && results->len == 0) { + g_set_error_literal(error, + G_IO_ERROR, + G_IO_ERROR_NOT_SUPPORTED, + "checksums were required, but none supplied"); + return NULL; + } + + /* we only have to have one non-checksum method to verify */ + for (guint i = 0; i < blobs->len; i++) { + JcatBlob *blob = g_ptr_array_index(blobs, i); + g_autofree gchar *result_str = NULL; + g_autoptr(GError) error_local = NULL; + g_autoptr(JcatBlob) blob_target = NULL; + g_autoptr(JcatEngine) engine = NULL; + g_autoptr(JcatResult) result = NULL; + + engine = jcat_context_get_engine(self, jcat_blob_get_kind(blob), &error_local); + if (engine == NULL) { + g_debug("%s", error_local->message); + continue; + } + if (jcat_engine_get_method(engine) != JCAT_BLOB_METHOD_SIGNATURE) + continue; + if (jcat_blob_get_target(blob) == JCAT_BLOB_KIND_UNKNOWN) { + g_debug("blob has no target"); + continue; + } + blob_target = jcat_item_get_blob_by_kind(item_target, + jcat_blob_get_target(blob), + &error_local); + if (blob_target == NULL) { + g_debug("no target for %s: %s", + jcat_blob_kind_to_string(jcat_blob_get_target(blob)), + error_local->message); + continue; + } + result = jcat_engine_pubkey_verify(engine, + jcat_blob_get_data(blob_target), + jcat_blob_get_data(blob), + flags, + &error_local); if (result == NULL) { g_debug("signature failure: %s", error_local->message); continue; diff -Nru libjcat-0.1.9/libjcat/jcat-context.h libjcat-0.2.3/libjcat/jcat-context.h --- libjcat-0.1.9/libjcat/jcat-context.h 2021-11-28 15:58:08.000000000 +0000 +++ libjcat-0.2.3/libjcat/jcat-context.h 2025-02-03 14:10:02.000000000 +0000 @@ -24,24 +24,34 @@ JcatContext * jcat_context_new(void); void -jcat_context_add_public_key(JcatContext *self, const gchar *filename); +jcat_context_add_public_key(JcatContext *self, const gchar *filename) G_GNUC_NON_NULL(1, 2); void -jcat_context_add_public_keys(JcatContext *self, const gchar *path); +jcat_context_add_public_keys(JcatContext *self, const gchar *path) G_GNUC_NON_NULL(1, 2); JcatEngine * -jcat_context_get_engine(JcatContext *self, JcatBlobKind kind, GError **error); +jcat_context_get_engine(JcatContext *self, JcatBlobKind kind, GError **error) G_GNUC_NON_NULL(1); void -jcat_context_set_keyring_path(JcatContext *self, const gchar *path); +jcat_context_set_keyring_path(JcatContext *self, const gchar *path) G_GNUC_NON_NULL(1); const gchar * -jcat_context_get_keyring_path(JcatContext *self); +jcat_context_get_keyring_path(JcatContext *self) G_GNUC_NON_NULL(1); JcatResult * jcat_context_verify_blob(JcatContext *self, GBytes *data, JcatBlob *blob, JcatVerifyFlags flags, - GError **error); + GError **error) G_GNUC_NON_NULL(1, 2, 3); GPtrArray * jcat_context_verify_item(JcatContext *self, GBytes *data, JcatItem *item, JcatVerifyFlags flags, - GError **error); + GError **error) G_GNUC_NON_NULL(1, 2, 3); +GPtrArray * +jcat_context_verify_target(JcatContext *self, + JcatItem *item_target, + JcatItem *item, + JcatVerifyFlags flags, + GError **error) G_GNUC_NON_NULL(1, 2, 3); +void +jcat_context_blob_kind_allow(JcatContext *self, JcatBlobKind kind) G_GNUC_NON_NULL(1); +void +jcat_context_blob_kind_disallow(JcatContext *self, JcatBlobKind kind) G_GNUC_NON_NULL(1); diff -Nru libjcat-0.1.9/libjcat/jcat-ed25519-engine.c libjcat-0.2.3/libjcat/jcat-ed25519-engine.c --- libjcat-0.1.9/libjcat/jcat-ed25519-engine.c 1970-01-01 00:00:00.000000000 +0000 +++ libjcat-0.2.3/libjcat/jcat-ed25519-engine.c 2025-02-03 14:10:02.000000000 +0000 @@ -0,0 +1,434 @@ +/* + * Copyright (C) 2021 Richard Hughes + * + * SPDX-License-Identifier: LGPL-2.1+ + */ + +#include "config.h" + +#include +#include +#include +#include + +#include "jcat-common-private.h" +#include "jcat-ed25519-engine.h" +#include "jcat-engine-private.h" + +struct _JcatEd25519Engine { + JcatEngine parent_instance; + GPtrArray *pubkeys; /* of gnutls_pubkey_t */ +}; + +G_DEFINE_TYPE(JcatEd25519Engine, jcat_ed25519_engine, JCAT_TYPE_ENGINE) + +static void +jcat_ed25519_datum_clear(gnutls_datum_t *data) +{ + gnutls_free(data->data); +} + +G_DEFINE_AUTO_CLEANUP_FREE_FUNC(gnutls_pubkey_t, gnutls_pubkey_deinit, NULL) +G_DEFINE_AUTO_CLEANUP_FREE_FUNC(gnutls_privkey_t, gnutls_privkey_deinit, NULL) +G_DEFINE_AUTO_CLEANUP_CLEAR_FUNC(gnutls_datum_t, jcat_ed25519_datum_clear) + +static GBytes * +jcat_ed25519_pubkey_to_bytes(const gnutls_pubkey_t pubkey, GError **error) +{ + gint rc; + g_auto(gnutls_datum_t) x = {NULL, 0}; + + rc = gnutls_pubkey_export_ecc_raw(pubkey, NULL, &x, NULL); + if (rc < 0) { + g_set_error(error, + G_IO_ERROR, + G_IO_ERROR_FAILED, + "unable to export pubkey: %s", + gnutls_strerror(rc)); + return NULL; + } + return g_bytes_new(x.data, x.size); +} + +static gboolean +jcat_ed25519_pubkey_from_bytes(GBytes *blob, gnutls_pubkey_t pubkey, GError **error) +{ + gint rc; + gnutls_datum_t x = {NULL, 0}; + + x.data = (guchar *)g_bytes_get_data(blob, NULL); + x.size = g_bytes_get_size(blob); + + rc = gnutls_pubkey_import_ecc_raw(pubkey, GNUTLS_ECC_CURVE_ED25519, &x, NULL); + if (rc < 0) { + g_set_error(error, + G_IO_ERROR, + G_IO_ERROR_FAILED, + "unable to import pubkey: %s", + gnutls_strerror(rc)); + return FALSE; + } + + return TRUE; +} + +static GBytes * +jcat_ed25519_privkey_to_bytes(const gnutls_privkey_t privkey, GError **error) +{ + gint rc; + g_auto(gnutls_datum_t) k = {NULL, 0}; + + rc = gnutls_privkey_export_ecc_raw2(privkey, NULL, NULL, NULL, &k, 0); + if (rc < 0) { + g_set_error(error, + G_IO_ERROR, + G_IO_ERROR_FAILED, + "unable to export pubkey: %s", + gnutls_strerror(rc)); + return NULL; + } + return g_bytes_new(k.data, k.size); +} + +static gboolean +jcat_ed25519_privkey_from_bytes(GBytes *blob_public, + GBytes *blob_privkey, + gnutls_privkey_t privkey, + GError **error) +{ + gint rc; + gnutls_datum_t x = {NULL, 0}; + gnutls_datum_t k = {NULL, 0}; + + x.data = (guchar *)g_bytes_get_data(blob_public, NULL); + x.size = g_bytes_get_size(blob_public); + + k.data = (guchar *)g_bytes_get_data(blob_privkey, NULL); + k.size = g_bytes_get_size(blob_privkey); + + rc = gnutls_privkey_import_ecc_raw(privkey, GNUTLS_ECC_CURVE_ED25519, &x, NULL, &k); + if (rc < 0) { + g_set_error(error, + G_IO_ERROR, + G_IO_ERROR_FAILED, + "unable to import privkey: %s", + gnutls_strerror(rc)); + return FALSE; + } + + return TRUE; +} + +static gboolean +jcat_ed25519_engine_add_public_key_raw(JcatEngine *engine, GBytes *blob, GError **error) +{ + JcatEd25519Engine *self = JCAT_ED25519_ENGINE(engine); + gint rc; + g_auto(gnutls_pubkey_t) pubkey = NULL; + + rc = gnutls_pubkey_init(&pubkey); + if (rc < 0) { + g_set_error(error, + G_IO_ERROR, + G_IO_ERROR_FAILED, + "unable to allocate pubkey: %s", + gnutls_strerror(rc)); + return FALSE; + } + + if (!jcat_ed25519_pubkey_from_bytes(blob, pubkey, error)) + return FALSE; + + g_ptr_array_add(self->pubkeys, g_steal_pointer(&pubkey)); + return TRUE; +} + +static gboolean +jcat_ed25519_engine_add_public_key(JcatEngine *engine, const gchar *filename, GError **error) +{ + g_autoptr(GBytes) blob = NULL; + + /* ignore */ + if (!g_str_has_suffix(filename, ".ed25519")) + return TRUE; + + blob = jcat_get_contents_bytes(filename, error); + if (blob == NULL) + return FALSE; + return jcat_ed25519_engine_add_public_key_raw(engine, blob, error); +} + +static JcatResult * +jcat_ed25519_engine_pubkey_verify(JcatEngine *engine, + GBytes *blob, + GBytes *blob_signature, + JcatVerifyFlags flags, + GError **error) +{ + JcatEd25519Engine *self = JCAT_ED25519_ENGINE(engine); + + /* sanity check */ + if (self->pubkeys->len == 0) { + g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT, "no keys in keyring"); + return NULL; + } + + /* verifies against any of the public keys */ + for (guint i = 0; i < self->pubkeys->len; i++) { + gint rc; + gnutls_pubkey_t pubkey = g_ptr_array_index(self->pubkeys, i); + gnutls_datum_t data = {NULL, 0}; + gnutls_datum_t sig = {NULL, 0}; + + data.data = (guchar *)g_bytes_get_data(blob, NULL); + data.size = g_bytes_get_size(blob); + sig.data = (guchar *)g_bytes_get_data(blob_signature, NULL); + sig.size = g_bytes_get_size(blob_signature); + rc = gnutls_pubkey_verify_data2(pubkey, GNUTLS_SIGN_EDDSA_ED25519, 0, &data, &sig); + if (rc == GNUTLS_E_SUCCESS) + return JCAT_RESULT(g_object_new(JCAT_TYPE_RESULT, "engine", engine, NULL)); + } + + /* nothing found */ + g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "failed to verify data"); + return NULL; +} + +static JcatBlob * +jcat_ed25519_engine_pubkey_sign(JcatEngine *engine, + GBytes *blob, + GBytes *blob_cert, + GBytes *blob_privkey, + JcatSignFlags flags, + GError **error) +{ + gint rc; + gnutls_datum_t data = {NULL, 0}; + g_autoptr(GBytes) blob_sig = NULL; + g_auto(gnutls_pubkey_t) pubkey = NULL; + g_auto(gnutls_privkey_t) privkey = NULL; + g_auto(gnutls_datum_t) sig = {NULL, 0}; + + /* nothing to do */ + if (g_bytes_get_size(blob) == 0) { + g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT, "nothing to do"); + return NULL; + } + + /* load */ + rc = gnutls_privkey_init(&privkey); + if (rc < 0) { + g_set_error(error, + G_IO_ERROR, + G_IO_ERROR_FAILED, + "unable to allocate privkey: %s", + gnutls_strerror(rc)); + return NULL; + } + if (!jcat_ed25519_privkey_from_bytes(blob_cert, blob_privkey, privkey, error)) + return NULL; + + /* sign */ + data.data = (guchar *)g_bytes_get_data(blob, NULL); + data.size = g_bytes_get_size(blob); + rc = gnutls_privkey_sign_data2(privkey, GNUTLS_SIGN_EDDSA_ED25519, 0, &data, &sig); + if (rc < 0) { + g_set_error(error, + G_IO_ERROR, + G_IO_ERROR_FAILED, + "unable to sign data: %s", + gnutls_strerror(rc)); + return NULL; + } + blob_sig = g_bytes_new(sig.data, sig.size); + return jcat_blob_new(JCAT_BLOB_KIND_ED25519, blob_sig); +} + +static JcatResult * +jcat_ed25519_engine_self_verify(JcatEngine *engine, + GBytes *blob, + GBytes *blob_signature, + JcatVerifyFlags flags, + GError **error) +{ + gint rc; + gnutls_datum_t data = {NULL, 0}; + gnutls_datum_t sig = {NULL, 0}; + const gchar *keyring_path = jcat_engine_get_keyring_path(engine); + g_autofree gchar *fn_pubkey = NULL; + g_autoptr(GBytes) blob_pubkey = NULL; + g_auto(gnutls_pubkey_t) pubkey = NULL; + + fn_pubkey = g_build_filename(keyring_path, "pki", "public.ed25519", NULL); + blob_pubkey = jcat_get_contents_bytes(fn_pubkey, error); + if (blob_pubkey == NULL) + return NULL; + rc = gnutls_pubkey_init(&pubkey); + if (rc < 0) { + g_set_error(error, + G_IO_ERROR, + G_IO_ERROR_FAILED, + "unable to allocate pubkey: %s", + gnutls_strerror(rc)); + return NULL; + } + if (!jcat_ed25519_pubkey_from_bytes(blob_pubkey, pubkey, error)) + return NULL; + + data.data = (guchar *)g_bytes_get_data(blob, NULL); + data.size = g_bytes_get_size(blob); + sig.data = (guchar *)g_bytes_get_data(blob_signature, NULL); + sig.size = g_bytes_get_size(blob_signature); + rc = gnutls_pubkey_verify_data2(pubkey, GNUTLS_SIGN_EDDSA_ED25519, 0, &data, &sig); + if (rc < 0) { + g_set_error(error, + G_IO_ERROR, + G_IO_ERROR_INVALID_DATA, + "failed to verify data: %s", + gnutls_strerror(rc)); + return NULL; + } + + /* success */ + return JCAT_RESULT(g_object_new(JCAT_TYPE_RESULT, "engine", engine, NULL)); +} + +static JcatBlob * +jcat_ed25519_engine_self_sign(JcatEngine *engine, GBytes *blob, JcatSignFlags flags, GError **error) +{ + gint rc; + gnutls_datum_t data = {NULL, 0}; + const gchar *keyring_path = jcat_engine_get_keyring_path(engine); + g_autofree gchar *fn_privkey = NULL; + g_autofree gchar *fn_pubkey = NULL; + g_autoptr(GBytes) blob_privkey = NULL; + g_autoptr(GBytes) blob_pubkey = NULL; + g_auto(gnutls_pubkey_t) pubkey = NULL; + g_auto(gnutls_privkey_t) privkey = NULL; + g_autoptr(GBytes) blob_sig = NULL; + g_auto(gnutls_datum_t) sig = {NULL, 0}; + + rc = gnutls_privkey_init(&privkey); + if (rc < 0) { + g_set_error(error, + G_IO_ERROR, + G_IO_ERROR_FAILED, + "unable to allocate privkey: %s", + gnutls_strerror(rc)); + return NULL; + } + + rc = gnutls_pubkey_init(&pubkey); + if (rc < 0) { + g_set_error(error, + G_IO_ERROR, + G_IO_ERROR_FAILED, + "unable to allocate pubkey: %s", + gnutls_strerror(rc)); + return NULL; + } + + /* check keypair exists, otherwise generate and save */ + fn_privkey = g_build_filename(keyring_path, "pki", "secret.ed25519", NULL); + fn_pubkey = g_build_filename(keyring_path, "pki", "public.ed25519", NULL); + if (!g_file_test(fn_privkey, G_FILE_TEST_EXISTS)) { + rc = gnutls_privkey_generate2(privkey, GNUTLS_PK_EDDSA_ED25519, 0, 0, NULL, 0); + if (rc < 0) { + g_set_error(error, + G_IO_ERROR, + G_IO_ERROR_INVALID_DATA, + "failed to generate private key: %s [%i]", + gnutls_strerror(rc), + rc); + return NULL; + } + rc = gnutls_pubkey_import_privkey(pubkey, privkey, 0, 0); + if (rc < 0) { + g_set_error(error, + G_IO_ERROR, + G_IO_ERROR_FAILED, + "unable to import pubkey from privkey: %s", + gnutls_strerror(rc)); + return NULL; + } + if (!jcat_mkdir_parent(fn_privkey, error)) + return NULL; + blob_pubkey = jcat_ed25519_pubkey_to_bytes(pubkey, error); + if (!blob_pubkey) + return NULL; + if (!jcat_set_contents_bytes(fn_pubkey, blob_pubkey, 0666, error)) + return NULL; + blob_privkey = jcat_ed25519_privkey_to_bytes(privkey, error); + if (!blob_privkey) + return NULL; + if (!jcat_set_contents_bytes(fn_privkey, blob_privkey, 0600, error)) + return NULL; + } else { + blob_pubkey = jcat_get_contents_bytes(fn_pubkey, error); + if (blob_pubkey == NULL) + return NULL; + if (!jcat_ed25519_pubkey_from_bytes(blob_pubkey, pubkey, error)) + return NULL; + blob_privkey = jcat_get_contents_bytes(fn_privkey, error); + if (blob_privkey == NULL) + return NULL; + if (!jcat_ed25519_privkey_from_bytes(blob_pubkey, blob_privkey, privkey, error)) + return NULL; + } + + data.data = (guchar *)g_bytes_get_data(blob, NULL); + data.size = g_bytes_get_size(blob); + rc = gnutls_privkey_sign_data2(privkey, GNUTLS_SIGN_EDDSA_ED25519, 0, &data, &sig); + if (rc < 0) { + g_set_error(error, + G_IO_ERROR, + G_IO_ERROR_FAILED, + "unable to sign data: %s", + gnutls_strerror(rc)); + return NULL; + } + blob_sig = g_bytes_new(sig.data, sig.size); + return jcat_blob_new(JCAT_BLOB_KIND_ED25519, blob_sig); +} + +static void +jcat_ed25519_engine_finalize(GObject *object) +{ + JcatEd25519Engine *self = JCAT_ED25519_ENGINE(object); + g_ptr_array_unref(self->pubkeys); + G_OBJECT_CLASS(jcat_ed25519_engine_parent_class)->finalize(object); +} + +static void +jcat_ed25519_engine_class_init(JcatEd25519EngineClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS(klass); + JcatEngineClass *klass_app = JCAT_ENGINE_CLASS(klass); + klass_app->add_public_key = jcat_ed25519_engine_add_public_key; + klass_app->add_public_key_raw = jcat_ed25519_engine_add_public_key_raw; + klass_app->pubkey_verify = jcat_ed25519_engine_pubkey_verify; + klass_app->pubkey_sign = jcat_ed25519_engine_pubkey_sign; + klass_app->self_verify = jcat_ed25519_engine_self_verify; + klass_app->self_sign = jcat_ed25519_engine_self_sign; + object_class->finalize = jcat_ed25519_engine_finalize; +} + +static void +jcat_ed25519_engine_init(JcatEd25519Engine *self) +{ + self->pubkeys = g_ptr_array_new_with_free_func(g_free); +} + +JcatEngine * +jcat_ed25519_engine_new(JcatContext *context) +{ + g_return_val_if_fail(JCAT_IS_CONTEXT(context), NULL); + return JCAT_ENGINE(g_object_new(JCAT_TYPE_ED25519_ENGINE, + "context", + context, + "kind", + JCAT_BLOB_KIND_ED25519, + "method", + JCAT_BLOB_METHOD_SIGNATURE, + NULL)); +} diff -Nru libjcat-0.1.9/libjcat/jcat-ed25519-engine.h libjcat-0.2.3/libjcat/jcat-ed25519-engine.h --- libjcat-0.1.9/libjcat/jcat-ed25519-engine.h 1970-01-01 00:00:00.000000000 +0000 +++ libjcat-0.2.3/libjcat/jcat-ed25519-engine.h 2025-02-03 14:10:02.000000000 +0000 @@ -0,0 +1,17 @@ +/* + * Copyright (C) 2021 Richard Hughes + * + * SPDX-License-Identifier: LGPL-2.1+ + */ + +#pragma once + +#include "jcat-context.h" +#include "jcat-engine.h" + +#define JCAT_TYPE_ED25519_ENGINE (jcat_ed25519_engine_get_type()) + +G_DECLARE_FINAL_TYPE(JcatEd25519Engine, jcat_ed25519_engine, JCAT, ED25519_ENGINE, JcatEngine) + +JcatEngine * +jcat_ed25519_engine_new(JcatContext *context) G_GNUC_NON_NULL(1); diff -Nru libjcat-0.1.9/libjcat/jcat-engine-private.h libjcat-0.2.3/libjcat/jcat-engine-private.h --- libjcat-0.1.9/libjcat/jcat-engine-private.h 2021-11-28 15:58:08.000000000 +0000 +++ libjcat-0.2.3/libjcat/jcat-engine-private.h 2025-02-03 14:10:02.000000000 +0000 @@ -9,8 +9,8 @@ #include "jcat-engine.h" const gchar * -jcat_engine_get_keyring_path(JcatEngine *self); +jcat_engine_get_keyring_path(JcatEngine *self) G_GNUC_NON_NULL(1); void -jcat_engine_add_string(JcatEngine *self, guint idt, GString *str); +jcat_engine_add_string(JcatEngine *self, guint idt, GString *str) G_GNUC_NON_NULL(1, 3); gchar * -jcat_engine_to_string(JcatEngine *self); +jcat_engine_to_string(JcatEngine *self) G_GNUC_NON_NULL(1); diff -Nru libjcat-0.1.9/libjcat/jcat-engine.c libjcat-0.2.3/libjcat/jcat-engine.c --- libjcat-0.1.9/libjcat/jcat-engine.c 2021-11-28 15:58:08.000000000 +0000 +++ libjcat-0.2.3/libjcat/jcat-engine.c 2025-02-03 14:10:02.000000000 +0000 @@ -231,6 +231,36 @@ } /** + * jcat_engine_add_public_key_raw: + * @self: #JcatEngine + * @blob: #GBytes + * @error: #GError, or %NULL + * + * Adds a public key manually. + * + * Returns: % + * + * Since: 0.1.9 + **/ +gboolean +jcat_engine_add_public_key_raw(JcatEngine *self, GBytes *blob, GError **error) +{ + JcatEngineClass *klass = JCAT_ENGINE_GET_CLASS(self); + g_return_val_if_fail(JCAT_IS_ENGINE(self), FALSE); + g_return_val_if_fail(blob != NULL, FALSE); + if (klass->add_public_key_raw == NULL) { + g_set_error_literal(error, + G_IO_ERROR, + G_IO_ERROR_NOT_SUPPORTED, + "adding public keys manually is not supported"); + return FALSE; + } + if (!jcat_engine_setup(self, error)) + return FALSE; + return klass->add_public_key_raw(self, blob, error); +} + +/** * jcat_engine_get_kind: * @self: #JcatEngine * diff -Nru libjcat-0.1.9/libjcat/jcat-engine.h libjcat-0.2.3/libjcat/jcat-engine.h --- libjcat-0.1.9/libjcat/jcat-engine.h 2021-11-28 15:58:08.000000000 +0000 +++ libjcat-0.2.3/libjcat/jcat-engine.h 2025-02-03 14:10:02.000000000 +0000 @@ -37,31 +37,36 @@ JcatVerifyFlags flags, GError **error); JcatBlob *(*self_sign)(JcatEngine *self, GBytes *blob, JcatSignFlags flags, GError **error); - gpointer padding[9]; + gboolean (*add_public_key_raw)(JcatEngine *self, GBytes *blob, GError **error); + gpointer padding[8]; }; JcatBlobKind -jcat_engine_get_kind(JcatEngine *self); +jcat_engine_get_kind(JcatEngine *self) G_GNUC_NON_NULL(1); JcatBlobMethod -jcat_engine_get_method(JcatEngine *self); +jcat_engine_get_method(JcatEngine *self) G_GNUC_NON_NULL(1); JcatResult * jcat_engine_pubkey_verify(JcatEngine *self, GBytes *blob, GBytes *blob_signature, JcatVerifyFlags flags, - GError **error); + GError **error) G_GNUC_NON_NULL(1, 2, 3); JcatBlob * jcat_engine_pubkey_sign(JcatEngine *self, GBytes *blob, GBytes *cert, GBytes *privkey, JcatSignFlags flags, - GError **error); + GError **error) G_GNUC_NON_NULL(1, 2, 3, 4); JcatResult * jcat_engine_self_verify(JcatEngine *self, GBytes *blob, GBytes *blob_signature, JcatVerifyFlags flags, - GError **error); + GError **error) G_GNUC_NON_NULL(1, 2, 3); JcatBlob * -jcat_engine_self_sign(JcatEngine *self, GBytes *blob, JcatSignFlags flags, GError **error); +jcat_engine_self_sign(JcatEngine *self, GBytes *blob, JcatSignFlags flags, GError **error) + G_GNUC_NON_NULL(1, 2); +gboolean +jcat_engine_add_public_key_raw(JcatEngine *self, GBytes *blob, GError **error) + G_GNUC_NON_NULL(1, 2); diff -Nru libjcat-0.1.9/libjcat/jcat-file-private.h libjcat-0.2.3/libjcat/jcat-file-private.h --- libjcat-0.1.9/libjcat/jcat-file-private.h 2021-11-28 15:58:08.000000000 +0000 +++ libjcat-0.2.3/libjcat/jcat-file-private.h 2025-02-03 14:10:02.000000000 +0000 @@ -9,4 +9,4 @@ #include "jcat-file.h" void -jcat_file_add_string(JcatFile *self, guint idt, GString *str); +jcat_file_add_string(JcatFile *self, guint idt, GString *str) G_GNUC_NON_NULL(1, 3); diff -Nru libjcat-0.1.9/libjcat/jcat-file.c libjcat-0.2.3/libjcat/jcat-file.c --- libjcat-0.1.9/libjcat/jcat-file.c 2021-11-28 15:58:08.000000000 +0000 +++ libjcat-0.2.3/libjcat/jcat-file.c 2025-02-03 14:10:02.000000000 +0000 @@ -209,6 +209,8 @@ conv = G_CONVERTER(g_zlib_decompressor_new(G_ZLIB_COMPRESSOR_FORMAT_GZIP)); istream_uncompressed = g_converter_input_stream_new(istream, conv); + g_filter_input_stream_set_close_base_stream(G_FILTER_INPUT_STREAM(istream_uncompressed), + FALSE); if (!json_parser_load_from_stream(parser, istream_uncompressed, cancellable, error)) return FALSE; return jcat_file_import_parser(self, parser, flags, error); diff -Nru libjcat-0.1.9/libjcat/jcat-file.h libjcat-0.2.3/libjcat/jcat-file.h --- libjcat-0.1.9/libjcat/jcat-file.h 2021-11-28 15:58:08.000000000 +0000 +++ libjcat-0.2.3/libjcat/jcat-file.h 2025-02-03 14:10:02.000000000 +0000 @@ -23,44 +23,45 @@ JcatFile * jcat_file_new(void); gchar * -jcat_file_to_string(JcatFile *self); +jcat_file_to_string(JcatFile *self) G_GNUC_NON_NULL(1); gboolean jcat_file_import_stream(JcatFile *self, GInputStream *istream, JcatImportFlags flags, GCancellable *cancellable, - GError **error); + GError **error) G_GNUC_NON_NULL(1, 2); gboolean jcat_file_import_file(JcatFile *self, GFile *gfile, JcatImportFlags flags, GCancellable *cancellable, - GError **error); + GError **error) G_GNUC_NON_NULL(1, 2); gboolean -jcat_file_import_json(JcatFile *self, const gchar *json, JcatImportFlags flags, GError **error); +jcat_file_import_json(JcatFile *self, const gchar *json, JcatImportFlags flags, GError **error) + G_GNUC_NON_NULL(1, 2); gboolean jcat_file_export_stream(JcatFile *self, GOutputStream *ostream, JcatExportFlags flags, GCancellable *cancellable, - GError **error); + GError **error) G_GNUC_NON_NULL(1, 2); gboolean jcat_file_export_file(JcatFile *self, GFile *gfile, JcatExportFlags flags, GCancellable *cancellable, - GError **error); + GError **error) G_GNUC_NON_NULL(1, 2); gchar * -jcat_file_export_json(JcatFile *self, JcatExportFlags flags, GError **error); +jcat_file_export_json(JcatFile *self, JcatExportFlags flags, GError **error) G_GNUC_NON_NULL(1); GPtrArray * -jcat_file_get_items(JcatFile *self); +jcat_file_get_items(JcatFile *self) G_GNUC_NON_NULL(1); JcatItem * -jcat_file_get_item_by_id(JcatFile *self, const gchar *id, GError **error); +jcat_file_get_item_by_id(JcatFile *self, const gchar *id, GError **error) G_GNUC_NON_NULL(1, 2); JcatItem * -jcat_file_get_item_default(JcatFile *self, GError **error); +jcat_file_get_item_default(JcatFile *self, GError **error) G_GNUC_NON_NULL(1); void -jcat_file_add_item(JcatFile *self, JcatItem *item); +jcat_file_add_item(JcatFile *self, JcatItem *item) G_GNUC_NON_NULL(1); guint32 -jcat_file_get_version_major(JcatFile *self); +jcat_file_get_version_major(JcatFile *self) G_GNUC_NON_NULL(1); guint32 -jcat_file_get_version_minor(JcatFile *self); +jcat_file_get_version_minor(JcatFile *self) G_GNUC_NON_NULL(1); diff -Nru libjcat-0.1.9/libjcat/jcat-gpg-engine.h libjcat-0.2.3/libjcat/jcat-gpg-engine.h --- libjcat-0.1.9/libjcat/jcat-gpg-engine.h 2021-11-28 15:58:08.000000000 +0000 +++ libjcat-0.2.3/libjcat/jcat-gpg-engine.h 2025-02-03 14:10:02.000000000 +0000 @@ -14,4 +14,4 @@ G_DECLARE_FINAL_TYPE(JcatGpgEngine, jcat_gpg_engine, JCAT, GPG_ENGINE, JcatEngine) JcatEngine * -jcat_gpg_engine_new(JcatContext *context); +jcat_gpg_engine_new(JcatContext *context) G_GNUC_NON_NULL(1); diff -Nru libjcat-0.1.9/libjcat/jcat-item-private.h libjcat-0.2.3/libjcat/jcat-item-private.h --- libjcat-0.1.9/libjcat/jcat-item-private.h 2021-11-28 15:58:08.000000000 +0000 +++ libjcat-0.2.3/libjcat/jcat-item-private.h 2025-02-03 14:10:02.000000000 +0000 @@ -12,8 +12,8 @@ #include "jcat-item.h" JcatItem * -jcat_item_import(JsonObject *obj, JcatImportFlags flags, GError **error); +jcat_item_import(JsonObject *obj, JcatImportFlags flags, GError **error) G_GNUC_NON_NULL(1); void -jcat_item_export(JcatItem *self, JcatExportFlags flags, JsonBuilder *builder); +jcat_item_export(JcatItem *self, JcatExportFlags flags, JsonBuilder *builder) G_GNUC_NON_NULL(1, 3); void -jcat_item_add_string(JcatItem *self, guint idt, GString *str); +jcat_item_add_string(JcatItem *self, guint idt, GString *str) G_GNUC_NON_NULL(1, 3); diff -Nru libjcat-0.1.9/libjcat/jcat-item.c libjcat-0.2.3/libjcat/jcat-item.c --- libjcat-0.1.9/libjcat/jcat-item.c 2021-11-28 15:58:08.000000000 +0000 +++ libjcat-0.2.3/libjcat/jcat-item.c 2025-02-03 14:10:02.000000000 +0000 @@ -162,7 +162,7 @@ json_builder_add_string_value(builder, priv->id); /* add alias_ids */ - if (priv->blobs->len > 0) { + if (priv->alias_ids->len > 0) { json_builder_set_member_name(builder, "AliasIds"); json_builder_begin_array(builder); for (guint i = 0; i < priv->alias_ids->len; i++) { @@ -233,6 +233,46 @@ } /** + * jcat_item_get_blob_by_kind: + * @self: #JcatItem + * @kind: #JcatBlobKind, e.g. %JCAT_BLOB_KIND_SHA256 + * @error: #GError, or %NULL + * + * Gets the item blobs by a specific kind. + * + * Returns: (transfer full): a blob, or %NULL + * + * Since: 0.2.0 + **/ +JcatBlob * +jcat_item_get_blob_by_kind(JcatItem *self, JcatBlobKind kind, GError **error) +{ + g_autoptr(GPtrArray) target_blobs = NULL; + + g_return_val_if_fail(JCAT_IS_ITEM(self), NULL); + g_return_val_if_fail(kind != JCAT_BLOB_KIND_UNKNOWN, NULL); + + target_blobs = jcat_item_get_blobs_by_kind(self, kind); + if (target_blobs->len == 0) { + g_set_error(error, + G_IO_ERROR, + G_IO_ERROR_INVALID_DATA, + "no existing checksum of type %s", + jcat_blob_kind_to_string(kind)); + return NULL; + } + if (target_blobs->len > 1) { + g_set_error(error, + G_IO_ERROR, + G_IO_ERROR_INVALID_DATA, + "multiple checksums of type %s", + jcat_blob_kind_to_string(kind)); + return NULL; + } + return g_object_ref(JCAT_BLOB(g_ptr_array_index(target_blobs, 0))); +} + +/** * jcat_item_add_blob: * @self: #JcatItem * @blob: #JcatBlob @@ -253,6 +293,7 @@ for (guint i = 0; i < priv->blobs->len; i++) { JcatBlob *blob_tmp = g_ptr_array_index(priv->blobs, i); if (jcat_blob_get_kind(blob_tmp) == jcat_blob_get_kind(blob) && + jcat_blob_get_target(blob_tmp) == jcat_blob_get_target(blob) && g_strcmp0(jcat_blob_get_appstream_id(blob_tmp), jcat_blob_get_appstream_id(blob)) == 0) { g_ptr_array_remove(priv->blobs, blob_tmp); @@ -347,6 +388,31 @@ } /** + * jcat_item_has_target: + * @self: #JcatItem + * + * Finds out if any of the blobs are targeting an internal checksum. + * If this returns with success then the caller might be able to use functions like + * jcat_context_verify_target() supplying some target checksums. + * + * Returns: %TRUE on success + * + * Since: 0.2.0 + **/ +gboolean +jcat_item_has_target(JcatItem *self) +{ + JcatItemPrivate *priv = GET_PRIVATE(self); + g_return_val_if_fail(JCAT_IS_ITEM(self), FALSE); + for (guint i = 0; i < priv->blobs->len; i++) { + JcatBlob *blob_tmp = g_ptr_array_index(priv->blobs, i); + if (jcat_blob_get_target(blob_tmp) != JCAT_BLOB_KIND_UNKNOWN) + return TRUE; + } + return FALSE; +} + +/** * jcat_item_new: * @id: An item ID, typically a file basename * diff -Nru libjcat-0.1.9/libjcat/jcat-item.h libjcat-0.2.3/libjcat/jcat-item.h --- libjcat-0.1.9/libjcat/jcat-item.h 2021-11-28 15:58:08.000000000 +0000 +++ libjcat-0.2.3/libjcat/jcat-item.h 2025-02-03 14:10:02.000000000 +0000 @@ -22,18 +22,22 @@ JcatItem * jcat_item_new(const gchar *id); gchar * -jcat_item_to_string(JcatItem *self); +jcat_item_to_string(JcatItem *self) G_GNUC_NON_NULL(1); GPtrArray * -jcat_item_get_blobs(JcatItem *self); +jcat_item_get_blobs(JcatItem *self) G_GNUC_NON_NULL(1); GPtrArray * -jcat_item_get_blobs_by_kind(JcatItem *self, JcatBlobKind kind); +jcat_item_get_blobs_by_kind(JcatItem *self, JcatBlobKind kind) G_GNUC_NON_NULL(1); +JcatBlob * +jcat_item_get_blob_by_kind(JcatItem *self, JcatBlobKind kind, GError **error) G_GNUC_NON_NULL(1); void -jcat_item_add_blob(JcatItem *self, JcatBlob *blob); +jcat_item_add_blob(JcatItem *self, JcatBlob *blob) G_GNUC_NON_NULL(1, 2); const gchar * -jcat_item_get_id(JcatItem *self); +jcat_item_get_id(JcatItem *self) G_GNUC_NON_NULL(1); void -jcat_item_add_alias_id(JcatItem *self, const gchar *id); +jcat_item_add_alias_id(JcatItem *self, const gchar *id) G_GNUC_NON_NULL(1, 2); void -jcat_item_remove_alias_id(JcatItem *self, const gchar *id); +jcat_item_remove_alias_id(JcatItem *self, const gchar *id) G_GNUC_NON_NULL(1, 2); GPtrArray * -jcat_item_get_alias_ids(JcatItem *self); +jcat_item_get_alias_ids(JcatItem *self) G_GNUC_NON_NULL(1); +gboolean +jcat_item_has_target(JcatItem *self) G_GNUC_NON_NULL(1); diff -Nru libjcat-0.1.9/libjcat/jcat-pkcs7-common.h libjcat-0.2.3/libjcat/jcat-pkcs7-common.h --- libjcat-0.1.9/libjcat/jcat-pkcs7-common.h 2021-11-28 15:58:08.000000000 +0000 +++ libjcat-0.2.3/libjcat/jcat-pkcs7-common.h 2025-02-03 14:10:02.000000000 +0000 @@ -6,11 +6,12 @@ #pragma once -#include #include #include #include +#include "jcat-compile.h" + typedef guchar gnutls_data_t; static void @@ -35,15 +36,16 @@ #pragma clang diagnostic pop gchar * -jcat_pkcs7_datum_to_dn_str(const gnutls_datum_t *raw); +jcat_pkcs7_datum_to_dn_str(const gnutls_datum_t *raw) G_GNUC_NON_NULL(1); gnutls_x509_crt_t -jcat_pkcs7_load_crt_from_blob(GBytes *blob, gnutls_x509_crt_fmt_t format, GError **error); +jcat_pkcs7_load_crt_from_blob(GBytes *blob, gnutls_x509_crt_fmt_t format, GError **error) + G_GNUC_NON_NULL(1); gnutls_privkey_t -jcat_pkcs7_load_privkey_from_blob(GBytes *blob, GError **error); +jcat_pkcs7_load_privkey_from_blob(GBytes *blob, GError **error) G_GNUC_NON_NULL(1); gnutls_pubkey_t -jcat_pkcs7_load_pubkey_from_privkey(gnutls_privkey_t privkey, GError **error); +jcat_pkcs7_load_pubkey_from_privkey(gnutls_privkey_t privkey, GError **error) G_GNUC_NON_NULL(1); GBytes * jcat_pkcs7_create_private_key(GError **error); GBytes * -jcat_pkcs7_create_client_certificate(gnutls_privkey_t privkey, GError **error); +jcat_pkcs7_create_client_certificate(gnutls_privkey_t privkey, GError **error) G_GNUC_NON_NULL(1); diff -Nru libjcat-0.1.9/libjcat/jcat-pkcs7-engine.c libjcat-0.2.3/libjcat/jcat-pkcs7-engine.c --- libjcat-0.1.9/libjcat/jcat-pkcs7-engine.c 2021-11-28 15:58:08.000000000 +0000 +++ libjcat-0.2.3/libjcat/jcat-pkcs7-engine.c 2025-02-03 14:10:02.000000000 +0000 @@ -19,21 +19,16 @@ G_DEFINE_TYPE(JcatPkcs7Engine, jcat_pkcs7_engine, JCAT_TYPE_ENGINE) static gboolean -jcat_pkcs7_engine_add_pubkey(JcatPkcs7Engine *self, - const gchar *filename, - gnutls_x509_crt_fmt_t format, - GError **error) +jcat_pkcs7_engine_add_pubkey_blob_fmt(JcatPkcs7Engine *self, + GBytes *blob, + gnutls_x509_crt_fmt_t format, + GError **error) { guint key_usage = 0; int rc; g_auto(gnutls_x509_crt_t) crt = NULL; - g_autoptr(GBytes) blob = NULL; /* load file and add to the trust list */ - g_debug("trying to load certificate from %s", filename); - blob = jcat_get_contents_bytes(filename, error); - if (blob == NULL) - return FALSE; crt = jcat_pkcs7_load_crt_from_blob(blob, format, error); if (crt == NULL) return FALSE; @@ -52,8 +47,7 @@ g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, - "certificate %s not suitable for use [0x%x]", - filename, + "certificate not suitable for use [0x%x]", key_usage); return FALSE; } @@ -75,17 +69,30 @@ } static gboolean +jcat_pkcs7_engine_add_public_key_raw(JcatEngine *engine, GBytes *blob, GError **error) +{ + JcatPkcs7Engine *self = JCAT_PKCS7_ENGINE(engine); + return jcat_pkcs7_engine_add_pubkey_blob_fmt(self, blob, GNUTLS_X509_FMT_PEM, error); +} + +static gboolean jcat_pkcs7_engine_add_public_key(JcatEngine *engine, const gchar *filename, GError **error) { JcatPkcs7Engine *self = JCAT_PKCS7_ENGINE(engine); /* search all the public key files */ if (g_str_has_suffix(filename, ".pem")) { - if (!jcat_pkcs7_engine_add_pubkey(self, filename, GNUTLS_X509_FMT_PEM, error)) + g_autoptr(GBytes) blob = jcat_get_contents_bytes(filename, error); + if (blob == NULL) + return FALSE; + if (!jcat_pkcs7_engine_add_pubkey_blob_fmt(self, blob, GNUTLS_X509_FMT_PEM, error)) return FALSE; } else if (g_str_has_suffix(filename, ".cer") || g_str_has_suffix(filename, ".crt") || g_str_has_suffix(filename, ".der")) { - if (!jcat_pkcs7_engine_add_pubkey(self, filename, GNUTLS_X509_FMT_DER, error)) + g_autoptr(GBytes) blob = jcat_get_contents_bytes(filename, error); + if (blob == NULL) + return FALSE; + if (!jcat_pkcs7_engine_add_pubkey_blob_fmt(self, blob, GNUTLS_X509_FMT_DER, error)) return FALSE; } else { g_autofree gchar *basename = g_path_get_basename(filename); @@ -411,7 +418,7 @@ return NULL; if (!jcat_mkdir_parent(fn_privkey, error)) return NULL; - if (!jcat_set_contents_bytes(fn_privkey, privkey, error)) + if (!jcat_set_contents_bytes(fn_privkey, privkey, 0600, error)) return NULL; } @@ -431,7 +438,7 @@ return NULL; if (!jcat_mkdir_parent(fn_cert, error)) return NULL; - if (!jcat_set_contents_bytes(fn_cert, cert, error)) + if (!jcat_set_contents_bytes(fn_cert, cert, 0666, error)) return NULL; } @@ -454,6 +461,7 @@ JcatEngineClass *klass_app = JCAT_ENGINE_CLASS(klass); klass_app->setup = jcat_pkcs7_engine_setup; klass_app->add_public_key = jcat_pkcs7_engine_add_public_key; + klass_app->add_public_key_raw = jcat_pkcs7_engine_add_public_key_raw; klass_app->pubkey_verify = jcat_pkcs7_engine_pubkey_verify; klass_app->pubkey_sign = jcat_pkcs7_engine_pubkey_sign; klass_app->self_verify = jcat_pkcs7_engine_self_verify; diff -Nru libjcat-0.1.9/libjcat/jcat-pkcs7-engine.h libjcat-0.2.3/libjcat/jcat-pkcs7-engine.h --- libjcat-0.1.9/libjcat/jcat-pkcs7-engine.h 2021-11-28 15:58:08.000000000 +0000 +++ libjcat-0.2.3/libjcat/jcat-pkcs7-engine.h 2025-02-03 14:10:02.000000000 +0000 @@ -14,4 +14,4 @@ G_DECLARE_FINAL_TYPE(JcatPkcs7Engine, jcat_pkcs7_engine, JCAT, PKCS7_ENGINE, JcatEngine) JcatEngine * -jcat_pkcs7_engine_new(JcatContext *context); +jcat_pkcs7_engine_new(JcatContext *context) G_GNUC_NON_NULL(1); diff -Nru libjcat-0.1.9/libjcat/jcat-result-private.h libjcat-0.2.3/libjcat/jcat-result-private.h --- libjcat-0.1.9/libjcat/jcat-result-private.h 2021-11-28 15:58:08.000000000 +0000 +++ libjcat-0.2.3/libjcat/jcat-result-private.h 2025-02-03 14:10:02.000000000 +0000 @@ -6,9 +6,10 @@ #pragma once +#include "jcat-engine.h" #include "jcat-result.h" void -jcat_result_add_string(JcatResult *self, guint idt, GString *str); +jcat_result_add_string(JcatResult *self, guint idt, GString *str) G_GNUC_NON_NULL(1, 3); JcatEngine * -jcat_result_get_engine(JcatResult *self); +jcat_result_get_engine(JcatResult *self) G_GNUC_NON_NULL(1); diff -Nru libjcat-0.1.9/libjcat/jcat-result.h libjcat-0.2.3/libjcat/jcat-result.h --- libjcat-0.1.9/libjcat/jcat-result.h 2021-11-28 15:58:08.000000000 +0000 +++ libjcat-0.2.3/libjcat/jcat-result.h 2025-02-03 14:10:02.000000000 +0000 @@ -6,19 +6,19 @@ #pragma once -#include +#include "jcat-blob.h" #define JCAT_TYPE_RESULT (jcat_result_get_type()) G_DECLARE_FINAL_TYPE(JcatResult, jcat_result, JCAT, RESULT, GObject) gchar * -jcat_result_to_string(JcatResult *self); +jcat_result_to_string(JcatResult *self) G_GNUC_NON_NULL(1); gint64 -jcat_result_get_timestamp(JcatResult *self); +jcat_result_get_timestamp(JcatResult *self) G_GNUC_NON_NULL(1); const gchar * -jcat_result_get_authority(JcatResult *self); +jcat_result_get_authority(JcatResult *self) G_GNUC_NON_NULL(1); JcatBlobKind -jcat_result_get_kind(JcatResult *self); +jcat_result_get_kind(JcatResult *self) G_GNUC_NON_NULL(1); JcatBlobMethod -jcat_result_get_method(JcatResult *self); +jcat_result_get_method(JcatResult *self) G_GNUC_NON_NULL(1); diff -Nru libjcat-0.1.9/libjcat/jcat-self-test.c libjcat-0.2.3/libjcat/jcat-self-test.c --- libjcat-0.1.9/libjcat/jcat-self-test.c 2021-11-28 15:58:08.000000000 +0000 +++ libjcat-0.2.3/libjcat/jcat-self-test.c 2025-02-03 14:10:02.000000000 +0000 @@ -1,5 +1,6 @@ /* * Copyright (C) 2020 Richard Hughes + * Copyright (C) 2022 Joe Qian * * SPDX-License-Identifier: LGPL-2.1+ */ @@ -9,6 +10,8 @@ #include #include "jcat-blob-private.h" +#include "jcat-bt-checkpoint-private.h" +#include "jcat-bt-verifier-private.h" #include "jcat-common-private.h" #include "jcat-context.h" #include "jcat-engine-private.h" @@ -210,6 +213,7 @@ g_autoptr(JcatBlob) blob_sig2 = NULL; g_autoptr(JcatContext) context = jcat_context_new(); g_autoptr(JcatEngine) engine = NULL; + g_autoptr(JcatEngine) engine_none = NULL; g_autoptr(JcatResult) result_fail = NULL; g_autoptr(JcatResult) result_pass = NULL; const gchar *sig_actual = "7c0ae84b191822bcadbdcbe2f74a011695d783c7"; @@ -251,6 +255,12 @@ g_assert_nonnull(blob_sig2); sig = jcat_blob_get_data_as_string(blob_sig2); g_assert_cmpstr(sig, ==, sig_actual); + + /* not supported */ + jcat_context_blob_kind_disallow(context, JCAT_BLOB_KIND_SHA1); + engine_none = jcat_context_get_engine(context, JCAT_BLOB_KIND_SHA1, &error); + g_assert_error(error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED); + g_assert_null(engine_none); } static void @@ -446,7 +456,7 @@ g_autofree gchar *fn_fail = NULL; g_autofree gchar *fn_pass = NULL; g_autofree gchar *fn_sig = NULL; - g_autofree gchar *pki_dir = NULL; + g_autofree gchar *pki_f = NULL; g_autofree gchar *sig_fn2 = NULL; g_autoptr(GBytes) blob_sig2 = NULL; g_autoptr(GBytes) data_fail = NULL; @@ -460,8 +470,8 @@ /* set up context */ jcat_context_set_keyring_path(context, "/tmp/libjcat-self-test/var"); - pki_dir = g_test_build_filename(G_TEST_DIST, "pki", NULL); - jcat_context_add_public_keys(context, pki_dir); + pki_f = g_test_build_filename(G_TEST_DIST, "pki", "LVFS-CA.pem", NULL); + jcat_context_add_public_key(context, pki_f); /* get engine */ engine = jcat_context_get_engine(context, JCAT_BLOB_KIND_PKCS7, &error); @@ -572,6 +582,130 @@ } static void +jcat_ed25519_engine_func(void) +{ +#ifdef ENABLE_ED25519 + g_autofree gchar *fn_fail = NULL; + g_autofree gchar *fn_pass = NULL; + g_autofree gchar *fn_sig = NULL; + g_autofree gchar *pki_dir = NULL; + g_autoptr(GBytes) data_fail = NULL; + g_autoptr(GBytes) data_fwbin = NULL; + g_autoptr(GBytes) data_sig = NULL; + g_autoptr(GError) error = NULL; + g_autoptr(JcatContext) context = jcat_context_new(); + g_autoptr(JcatEngine) engine = NULL; + g_autoptr(JcatResult) result_fail = NULL; + g_autoptr(JcatResult) result_pass = NULL; + + /* set up context */ + jcat_context_set_keyring_path(context, "/tmp/libjcat-self-test/var"); + pki_dir = g_test_build_filename(G_TEST_DIST, "pki", NULL); + jcat_context_add_public_keys(context, pki_dir); + + /* get engine */ + engine = jcat_context_get_engine(context, JCAT_BLOB_KIND_ED25519, &error); + g_assert_no_error(error); + g_assert_nonnull(engine); + g_assert_cmpint(jcat_engine_get_kind(engine), ==, JCAT_BLOB_KIND_ED25519); + g_assert_cmpint(jcat_engine_get_method(engine), ==, JCAT_BLOB_METHOD_SIGNATURE); + + /* verify with a manually generated signature */ + fn_pass = g_test_build_filename(G_TEST_DIST, "colorhug", "firmware.bin", NULL); + data_fwbin = jcat_get_contents_bytes(fn_pass, &error); + g_assert_no_error(error); + g_assert_nonnull(data_fwbin); + fn_sig = g_test_build_filename(G_TEST_DIST, "colorhug", "firmware.bin.ed25519", NULL); + data_sig = jcat_get_contents_bytes(fn_sig, &error); + g_assert_no_error(error); + g_assert_nonnull(data_sig); + result_pass = + jcat_engine_pubkey_verify(engine, data_fwbin, data_sig, JCAT_VERIFY_FLAG_NONE, &error); + g_assert_no_error(error); + g_assert_nonnull(result_pass); + + /* verify will fail with valid signature and different data */ + fn_fail = g_test_build_filename(G_TEST_DIST, "colorhug", "firmware.bin.asc", NULL); + data_fail = jcat_get_contents_bytes(fn_fail, &error); + g_assert_no_error(error); + g_assert_nonnull(data_fail); + result_fail = + jcat_engine_pubkey_verify(engine, data_fail, data_sig, JCAT_VERIFY_FLAG_NONE, &error); + g_assert_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA); + g_assert_null(result_fail); + g_clear_error(&error); +#else + g_test_skip("no GnuTLS support enabled"); +#endif +} + +static void +jcat_ed25519_engine_self_signed_func(void) +{ +#ifdef ENABLE_ED25519 + static const char payload_str[] = "Hello, world!"; + g_autofree gchar *tmp_dir = NULL; + g_autoptr(JcatContext) context = jcat_context_new(); + g_autoptr(JcatEngine) engine = NULL; + g_autoptr(GBytes) payload = NULL; + g_autoptr(GError) error = NULL; + const gchar *str_perfect = "JcatResult:\n" + " Timestamp: 1970-01-01T03:25:45Z\n" + " JcatEd25519Engine:\n" + " Kind: ed25519\n" + " VerifyKind: signature\n"; + + /* set up context */ + tmp_dir = g_dir_make_tmp(NULL, &error); + g_assert_no_error(error); + jcat_context_set_keyring_path(context, tmp_dir); + + /* get engine */ + engine = jcat_context_get_engine(context, JCAT_BLOB_KIND_ED25519, &error); + g_assert_no_error(error); + g_assert_nonnull(engine); + + payload = g_bytes_new_static(payload_str, sizeof(payload_str)); + g_assert_nonnull(payload); + + /* do signing and verification twice: first with no keys exist + * (thus new keys are generated), secondly with keys already + * exist. */ + for (gsize i = 0; i < 2; i++) { + g_autofree gchar *str = NULL; + g_autoptr(JcatBlob) signature = NULL; + g_autoptr(JcatEngine) engine2 = NULL; + g_autoptr(JcatResult) result = NULL; + g_autoptr(JcatResult) result2 = NULL; + + signature = + jcat_engine_self_sign(engine, payload, JCAT_SIGN_FLAG_ADD_TIMESTAMP, &error); + g_assert_no_error(error); + g_assert_nonnull(signature); + result = jcat_engine_self_verify(engine, + payload, + jcat_blob_get_data(signature), + JCAT_VERIFY_FLAG_NONE, + &error); + g_assert_no_error(error); + g_assert_nonnull(result); + + /* verify engine set */ + engine2 = jcat_result_get_engine(result); + g_assert(engine == engine2); + + /* to string */ + g_object_set(result, "timestamp", (gint64)12345, NULL); + str = jcat_result_to_string(result); + g_print("%s", str); + g_assert_cmpstr(str, ==, str_perfect); + } +#else + g_test_skip("no GnuTLS support enabled"); +#endif +} + +static void jcat_context_verify_blob_func(void) { #ifdef ENABLE_PKCS7 @@ -590,6 +724,7 @@ g_autoptr(JcatEngine) engine3 = NULL; g_autoptr(JcatEngine) engine4 = NULL; g_autoptr(JcatResult) result = NULL; + g_autoptr(JcatResult) result_disallow = NULL; /* set up context */ jcat_context_set_keyring_path(context, "/tmp"); @@ -634,6 +769,16 @@ g_assert_cmpstr(jcat_result_get_authority(result), ==, "O=Linux Vendor Firmware Project,CN=LVFS CA"); + + /* not supported */ + jcat_context_blob_kind_disallow(context, JCAT_BLOB_KIND_PKCS7); + result_disallow = jcat_context_verify_blob(context, + data_fwbin, + blob, + JCAT_VERIFY_FLAG_DISABLE_TIME_CHECKS, + &error); + g_assert_error(error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED); + g_assert_null(result_disallow); #else g_test_skip("no GnuTLS support enabled"); #endif @@ -726,6 +871,76 @@ } static void +jcat_context_verify_item_target_func(void) +{ +#ifdef ENABLE_PKCS7 + JcatResult *result; + g_autofree gchar *fn_pass = NULL; + g_autofree gchar *fn_sig = NULL; + g_autofree gchar *pki_f = NULL; + g_autoptr(GBytes) data_fwbin = NULL; + g_autoptr(GBytes) data_sig = NULL; + g_autoptr(GError) error = NULL; + g_autoptr(JcatBlob) blob_pkcs7 = NULL; + g_autoptr(JcatBlob) blob_target_sha256 = NULL; + g_autoptr(JcatItem) item = jcat_item_new("filename.bin"); + g_autoptr(JcatItem) item_target = jcat_item_new("filename.bin"); + g_autoptr(JcatContext) context = jcat_context_new(); + g_autoptr(JcatEngine) engine_sha256 = NULL; + g_autoptr(GPtrArray) results_fail = NULL; + g_autoptr(GPtrArray) results_pass = NULL; + + /* set up context */ + jcat_context_set_keyring_path(context, "/tmp"); + pki_f = g_test_build_filename(G_TEST_BUILT, "pki", "test.pem", NULL); + jcat_context_add_public_key(context, pki_f); + + /* get all engines */ + engine_sha256 = jcat_context_get_engine(context, JCAT_BLOB_KIND_SHA256, &error); + g_assert_no_error(error); + g_assert_nonnull(engine_sha256); + + /* add SHA256 hash as a target blob */ + fn_pass = g_test_build_filename(G_TEST_DIST, "colorhug", "firmware.bin", NULL); + data_fwbin = jcat_get_contents_bytes(fn_pass, &error); + g_assert_no_error(error); + g_assert_nonnull(data_fwbin); + blob_target_sha256 = + jcat_engine_self_sign(engine_sha256, data_fwbin, JCAT_SIGN_FLAG_NONE, &error); + g_assert_no_error(error); + g_assert_nonnull(blob_target_sha256); + jcat_item_add_blob(item_target, blob_target_sha256); + + /* create the item to verify, with a checksum and the PKCS#7 signature *of the hash* */ + jcat_item_add_blob(item, blob_target_sha256); + fn_sig = g_test_build_filename(G_TEST_BUILT, "colorhug", "firmware.bin.sha256.p7c", NULL); + data_sig = jcat_get_contents_bytes(fn_sig, &error); + g_assert_no_error(error); + g_assert_nonnull(data_sig); + blob_pkcs7 = jcat_blob_new_full(JCAT_BLOB_KIND_PKCS7, data_sig, JCAT_BLOB_FLAG_IS_UTF8); + jcat_blob_set_target(blob_pkcs7, JCAT_BLOB_KIND_SHA256); + jcat_item_add_blob(item, blob_pkcs7); + + /* enforce a checksum and signature match */ + results_pass = jcat_context_verify_target(context, + item_target, + item, + JCAT_VERIFY_FLAG_DISABLE_TIME_CHECKS | + JCAT_VERIFY_FLAG_REQUIRE_CHECKSUM | + JCAT_VERIFY_FLAG_REQUIRE_SIGNATURE, + &error); + g_assert_no_error(error); + g_assert_nonnull(results_pass); + g_assert_cmpint(results_pass->len, ==, 2); + result = g_ptr_array_index(results_pass, 1); + g_assert_cmpint(jcat_result_get_timestamp(result), >=, 1502871248); + g_assert_cmpstr(jcat_result_get_authority(result), ==, "O=Hughski Limited"); +#else + g_test_skip("no GnuTLS support enabled"); +#endif +} + +static void jcat_context_verify_item_csum_func(void) { #ifdef ENABLE_PKCS7 @@ -805,26 +1020,181 @@ #endif } +static void +jcat_bits_func(void) +{ + g_assert_cmpint(jcat_bits_ones_count64(0), ==, 0); + g_assert_cmpint(jcat_bits_ones_count64(1), ==, 1); + g_assert_cmpint(jcat_bits_ones_count64(5), ==, 2); + g_assert_cmpint(jcat_bits_ones_count64(5), ==, 2); + g_assert_cmpint(jcat_bits_ones_count64(0x8000000000000000), ==, 1); + g_assert_cmpint(jcat_bits_ones_count64(0xFFFFFFFFFFFFFFFF), ==, 64); + + g_assert_cmpint(jcat_bits_trailing_zeros64(0), ==, 64); + g_assert_cmpint(jcat_bits_trailing_zeros64(8), ==, 3); + g_assert_cmpint(jcat_bits_trailing_zeros64(24), ==, 3); + g_assert_cmpint(jcat_bits_trailing_zeros64(25), ==, 0); + g_assert_cmpint(jcat_bits_trailing_zeros64(0x8000000000000000), ==, 63); + g_assert_cmpint(jcat_bits_trailing_zeros64(0xFFFFFFFFFFFFFFFF), ==, 0); + + g_assert_cmpint(jcat_bits_length64(0), ==, 0); + g_assert_cmpint(jcat_bits_length64(1), ==, 1); + g_assert_cmpint(jcat_bits_length64(7), ==, 3); + g_assert_cmpint(jcat_bits_length64(16), ==, 5); + g_assert_cmpint(jcat_bits_length64(64), ==, 7); + g_assert_cmpint(jcat_bits_length64(0x8000000000000000), ==, 64); + g_assert_cmpint(jcat_bits_length64(0xFFFFFFFFFFFFFFFF), ==, 64); +} + +static void +jcat_bt_verifier_func(void) +{ + GBytes *buf; + g_autofree gchar *fn = NULL; + g_autofree gchar *str = NULL; + g_autoptr(GBytes) blob = NULL; + g_autoptr(GError) error = NULL; + g_autoptr(JcatBtVerifier) btverifier = NULL; + + fn = g_test_build_filename(G_TEST_DIST, "test.btverifier", NULL); + blob = jcat_get_contents_bytes(fn, &error); + g_assert_no_error(error); + g_assert_nonnull(blob); + + btverifier = jcat_bt_verifier_new(blob, &error); + g_assert_no_error(error); + g_assert_nonnull(btverifier); + + str = jcat_bt_verifier_to_string(btverifier); + g_print("%s\n", str); + + g_assert_cmpstr(jcat_bt_verifier_get_name(btverifier), ==, "lvfsqa"); + g_assert_cmpstr(jcat_bt_verifier_get_hash(btverifier), ==, "c463f084"); + g_assert_cmpint(jcat_bt_verifier_get_alg(btverifier), ==, 1); + buf = jcat_bt_verifier_get_key(btverifier); + g_assert_cmpint(g_bytes_get_size(buf), ==, 32); +} + +static void +jcat_bt_checkpoint_func(void) +{ + GBytes *buf; + g_autofree gchar *fn = NULL; + g_autofree gchar *str = NULL; + g_autoptr(GBytes) blob = NULL; + g_autoptr(GError) error = NULL; + g_autoptr(JcatBtCheckpoint) btcheckpoint = NULL; + + fn = g_test_build_filename(G_TEST_DIST, "test.btcheckpoint", NULL); + blob = jcat_get_contents_bytes(fn, &error); + g_assert_no_error(error); + g_assert_nonnull(blob); + + btcheckpoint = jcat_bt_checkpoint_new(blob, &error); + g_assert_no_error(error); + g_assert_nonnull(btcheckpoint); + + str = jcat_bt_checkpoint_to_string(btcheckpoint); + g_print("%s\n", str); + + g_assert_cmpstr(jcat_bt_checkpoint_get_origin(btcheckpoint), ==, "lvfsqa"); + g_assert_cmpstr(jcat_bt_checkpoint_get_identity(btcheckpoint), ==, "lvfsqa"); + g_assert_cmpstr(jcat_bt_checkpoint_get_hash(btcheckpoint), ==, "c463f084"); + g_assert_cmpint(jcat_bt_checkpoint_get_log_size(btcheckpoint), ==, 4); + buf = jcat_bt_checkpoint_get_pubkey(btcheckpoint); + g_assert_cmpint(g_bytes_get_size(buf), ==, 32); + buf = jcat_bt_checkpoint_get_signature(btcheckpoint); + g_assert_cmpint(g_bytes_get_size(buf), ==, 64); + buf = jcat_bt_checkpoint_get_payload(btcheckpoint); + g_assert_cmpint(g_bytes_get_size(buf), ==, 54); +} + +static void +jcat_bt_common_func(void) +{ + gboolean ret; + g_autofree gchar *fn_btcheckpoint = NULL; + g_autofree gchar *fn_btverifier = NULL; + g_autoptr(GBytes) blob_btcheckpoint = NULL; + g_autoptr(GBytes) blob_btverifier = NULL; + g_autoptr(JcatBtCheckpoint) btcheckpoint = NULL; + g_autoptr(JcatBtVerifier) btverifier = NULL; + g_autoptr(JcatContext) context = jcat_context_new(); + g_autoptr(JcatEngine) engine = NULL; + g_autoptr(JcatResult) result = NULL; + g_autoptr(GError) error = NULL; + + fn_btverifier = g_test_build_filename(G_TEST_DIST, "test.btverifier", NULL); + blob_btverifier = jcat_get_contents_bytes(fn_btverifier, &error); + g_assert_no_error(error); + g_assert_nonnull(blob_btverifier); + + btverifier = jcat_bt_verifier_new(blob_btverifier, &error); + g_assert_no_error(error); + g_assert_nonnull(btverifier); + + fn_btcheckpoint = g_test_build_filename(G_TEST_DIST, "test.btcheckpoint", NULL); + blob_btcheckpoint = jcat_get_contents_bytes(fn_btcheckpoint, &error); + g_assert_no_error(error); + g_assert_nonnull(blob_btcheckpoint); + + btcheckpoint = jcat_bt_checkpoint_new(blob_btcheckpoint, &error); + g_assert_no_error(error); + g_assert_nonnull(btcheckpoint); + +#ifdef ENABLE_ED25519 + /* get engine */ + engine = jcat_context_get_engine(context, JCAT_BLOB_KIND_ED25519, &error); + g_assert_no_error(error); + g_assert_nonnull(engine); + g_assert_cmpint(jcat_engine_get_kind(engine), ==, JCAT_BLOB_KIND_ED25519); + g_assert_cmpint(jcat_engine_get_method(engine), ==, JCAT_BLOB_METHOD_SIGNATURE); + + ret = jcat_engine_add_public_key_raw(engine, jcat_bt_verifier_get_key(btverifier), &error); + g_assert_no_error(error); + g_assert_true(ret); + + // TODO: check jcat_bt_checkpoint_get_origin == jcat_bt_verifier_get_name + // TODO: check jcat_bt_checkpoint_get_identity == jcat_bt_verifier_get_name + result = jcat_engine_pubkey_verify(engine, + jcat_bt_checkpoint_get_payload(btcheckpoint), + jcat_bt_checkpoint_get_signature(btcheckpoint), + JCAT_VERIFY_FLAG_NONE, + &error); + g_assert_no_error(error); + g_assert_nonnull(result); +#endif +} + int main(int argc, char **argv) { + g_setenv("G_TEST_SRCDIR", SRCDIR, FALSE); + g_setenv("G_TEST_BUILDDIR", BUILDDIR, FALSE); g_test_init(&argc, &argv, NULL); /* only critical and error are fatal */ g_log_set_fatal_mask(NULL, G_LOG_LEVEL_ERROR | G_LOG_LEVEL_CRITICAL); g_setenv("G_MESSAGES_DEBUG", "all", TRUE); + g_test_add_func("/jcat/bits", jcat_bits_func); g_test_add_func("/jcat/blob", jcat_blob_func); g_test_add_func("/jcat/item", jcat_item_func); g_test_add_func("/jcat/file", jcat_file_func); + g_test_add_func("/jcat/bt-verifier", jcat_bt_verifier_func); + g_test_add_func("/jcat/bt-checkpoint", jcat_bt_checkpoint_func); + g_test_add_func("/jcat/bt-common", jcat_bt_common_func); g_test_add_func("/jcat/engine{sha1}", jcat_sha1_engine_func); g_test_add_func("/jcat/engine{sha256}", jcat_sha256_engine_func); g_test_add_func("/jcat/engine{gpg}", jcat_gpg_engine_func); g_test_add_func("/jcat/engine{gpg-msg}", jcat_gpg_engine_msg_func); g_test_add_func("/jcat/engine{pkcs7}", jcat_pkcs7_engine_func); g_test_add_func("/jcat/engine{pkcs7-self-signed}", jcat_pkcs7_engine_self_signed_func); + g_test_add_func("/jcat/engine{ed25519}", jcat_ed25519_engine_func); + g_test_add_func("/jcat/engine{ed25519-self-signed}", jcat_ed25519_engine_self_signed_func); g_test_add_func("/jcat/context{verify-blob}", jcat_context_verify_blob_func); g_test_add_func("/jcat/context{verify-item-sign}", jcat_context_verify_item_sign_func); g_test_add_func("/jcat/context{verify-item-csum}", jcat_context_verify_item_csum_func); + g_test_add_func("/jcat/context{verify-item-target}", jcat_context_verify_item_target_func); return g_test_run(); } diff -Nru libjcat-0.1.9/libjcat/jcat-sha1-engine.c libjcat-0.2.3/libjcat/jcat-sha1-engine.c --- libjcat-0.1.9/libjcat/jcat-sha1-engine.c 2021-11-28 15:58:08.000000000 +0000 +++ libjcat-0.2.3/libjcat/jcat-sha1-engine.c 2025-02-03 14:10:02.000000000 +0000 @@ -38,11 +38,14 @@ tmp = g_compute_checksum_for_bytes(G_CHECKSUM_SHA1, data); data_tmp = g_bytes_new(tmp, strlen(tmp)); if (g_bytes_compare(data_tmp, blob_signature) != 0) { + g_autofree gchar *sig = g_strndup(g_bytes_get_data(blob_signature, NULL), + g_bytes_get_size(blob_signature)); g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, - "failed to verify data, expected %s", - tmp); + "failed to verify data, expected %s and got %s", + tmp, + sig); return NULL; } return JCAT_RESULT(g_object_new(JCAT_TYPE_RESULT, "engine", engine, NULL)); diff -Nru libjcat-0.1.9/libjcat/jcat-sha1-engine.h libjcat-0.2.3/libjcat/jcat-sha1-engine.h --- libjcat-0.1.9/libjcat/jcat-sha1-engine.h 2021-11-28 15:58:08.000000000 +0000 +++ libjcat-0.2.3/libjcat/jcat-sha1-engine.h 2025-02-03 14:10:02.000000000 +0000 @@ -14,4 +14,4 @@ G_DECLARE_FINAL_TYPE(JcatSha1Engine, jcat_sha1_engine, JCAT, SHA1_ENGINE, JcatEngine) JcatEngine * -jcat_sha1_engine_new(JcatContext *context); +jcat_sha1_engine_new(JcatContext *context) G_GNUC_NON_NULL(1); diff -Nru libjcat-0.1.9/libjcat/jcat-sha256-engine.c libjcat-0.2.3/libjcat/jcat-sha256-engine.c --- libjcat-0.1.9/libjcat/jcat-sha256-engine.c 2021-11-28 15:58:08.000000000 +0000 +++ libjcat-0.2.3/libjcat/jcat-sha256-engine.c 2025-02-03 14:10:02.000000000 +0000 @@ -38,11 +38,14 @@ tmp = g_compute_checksum_for_bytes(G_CHECKSUM_SHA256, data); data_tmp = g_bytes_new(tmp, strlen(tmp)); if (g_bytes_compare(data_tmp, blob_signature) != 0) { + g_autofree gchar *sig = g_strndup(g_bytes_get_data(blob_signature, NULL), + g_bytes_get_size(blob_signature)); g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, - "failed to verify data, expected %s", - tmp); + "failed to verify data, expected %s and got %s", + tmp, + sig); return NULL; } return JCAT_RESULT(g_object_new(JCAT_TYPE_RESULT, "engine", engine, NULL)); diff -Nru libjcat-0.1.9/libjcat/jcat-sha256-engine.h libjcat-0.2.3/libjcat/jcat-sha256-engine.h --- libjcat-0.1.9/libjcat/jcat-sha256-engine.h 2021-11-28 15:58:08.000000000 +0000 +++ libjcat-0.2.3/libjcat/jcat-sha256-engine.h 2025-02-03 14:10:02.000000000 +0000 @@ -14,4 +14,4 @@ G_DECLARE_FINAL_TYPE(JcatSha256Engine, jcat_sha256_engine, JCAT, SHA256_ENGINE, JcatEngine) JcatEngine * -jcat_sha256_engine_new(JcatContext *context); +jcat_sha256_engine_new(JcatContext *context) G_GNUC_NON_NULL(1); diff -Nru libjcat-0.1.9/libjcat/jcat-sha512-engine.c libjcat-0.2.3/libjcat/jcat-sha512-engine.c --- libjcat-0.1.9/libjcat/jcat-sha512-engine.c 1970-01-01 00:00:00.000000000 +0000 +++ libjcat-0.2.3/libjcat/jcat-sha512-engine.c 2025-02-03 14:10:02.000000000 +0000 @@ -0,0 +1,87 @@ +/* + * Copyright (C) 2020 Richard Hughes + * + * SPDX-License-Identifier: LGPL-2.1+ + */ + +#include "config.h" + +#include + +#include "jcat-engine-private.h" +#include "jcat-sha512-engine.h" + +struct _JcatSha512Engine { + JcatEngine parent_instance; +}; + +G_DEFINE_TYPE(JcatSha512Engine, jcat_sha512_engine, JCAT_TYPE_ENGINE) + +static JcatBlob * +jcat_sha512_engine_self_sign(JcatEngine *engine, GBytes *data, JcatSignFlags flags, GError **error) +{ + g_autofree gchar *tmp = NULL; + tmp = g_compute_checksum_for_bytes(G_CHECKSUM_SHA512, data); + return jcat_blob_new_utf8(JCAT_BLOB_KIND_SHA512, tmp); +} + +static JcatResult * +jcat_sha512_engine_self_verify(JcatEngine *engine, + GBytes *data, + GBytes *blob_signature, + JcatVerifyFlags flags, + GError **error) +{ + g_autofree gchar *tmp = NULL; + g_autoptr(GBytes) data_tmp = NULL; + + tmp = g_compute_checksum_for_bytes(G_CHECKSUM_SHA512, data); + data_tmp = g_bytes_new(tmp, strlen(tmp)); + if (g_bytes_compare(data_tmp, blob_signature) != 0) { + g_autofree gchar *sig = g_strndup(g_bytes_get_data(blob_signature, NULL), + g_bytes_get_size(blob_signature)); + g_set_error(error, + G_IO_ERROR, + G_IO_ERROR_INVALID_DATA, + "failed to verify data, expected %s and got %s", + tmp, + sig); + return NULL; + } + return JCAT_RESULT(g_object_new(JCAT_TYPE_RESULT, "engine", engine, NULL)); +} + +static void +jcat_sha512_engine_finalize(GObject *object) +{ + G_OBJECT_CLASS(jcat_sha512_engine_parent_class)->finalize(object); +} + +static void +jcat_sha512_engine_class_init(JcatSha512EngineClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS(klass); + JcatEngineClass *klass_app = JCAT_ENGINE_CLASS(klass); + klass_app->self_sign = jcat_sha512_engine_self_sign; + klass_app->self_verify = jcat_sha512_engine_self_verify; + object_class->finalize = jcat_sha512_engine_finalize; +} + +static void +jcat_sha512_engine_init(JcatSha512Engine *self) +{ +} + +JcatEngine * +jcat_sha512_engine_new(JcatContext *context) +{ + g_return_val_if_fail(JCAT_IS_CONTEXT(context), NULL); + return JCAT_ENGINE(g_object_new(JCAT_TYPE_SHA512_ENGINE, + "context", + context, + "kind", + JCAT_BLOB_KIND_SHA512, + "method", + JCAT_BLOB_METHOD_CHECKSUM, + NULL)); +} diff -Nru libjcat-0.1.9/libjcat/jcat-sha512-engine.h libjcat-0.2.3/libjcat/jcat-sha512-engine.h --- libjcat-0.1.9/libjcat/jcat-sha512-engine.h 1970-01-01 00:00:00.000000000 +0000 +++ libjcat-0.2.3/libjcat/jcat-sha512-engine.h 2025-02-03 14:10:02.000000000 +0000 @@ -0,0 +1,17 @@ +/* + * Copyright (C) 2020 Richard Hughes + * + * SPDX-License-Identifier: LGPL-2.1+ + */ + +#pragma once + +#include "jcat-context.h" +#include "jcat-engine.h" + +#define JCAT_TYPE_SHA512_ENGINE (jcat_sha512_engine_get_type()) + +G_DECLARE_FINAL_TYPE(JcatSha512Engine, jcat_sha512_engine, JCAT, SHA512_ENGINE, JcatEngine) + +JcatEngine * +jcat_sha512_engine_new(JcatContext *context) G_GNUC_NON_NULL(1); diff -Nru libjcat-0.1.9/libjcat/jcat-tool.c libjcat-0.2.3/libjcat/jcat-tool.c --- libjcat-0.1.9/libjcat/jcat-tool.c 2021-11-28 15:58:08.000000000 +0000 +++ libjcat-0.2.3/libjcat/jcat-tool.c 2025-02-03 14:10:02.000000000 +0000 @@ -30,6 +30,7 @@ gchar *prefix; gchar *appstream_id; JcatBlobKind kind; + JcatBlobKind target; } JcatToolPrivate; static void @@ -302,14 +303,39 @@ /* guess format */ if (g_str_has_suffix(values[2], ".asc")) { blob = jcat_blob_new_full(JCAT_BLOB_KIND_GPG, data_sig, JCAT_BLOB_FLAG_IS_UTF8); + } else if (g_str_has_suffix(values[2], ".btmanifest")) { + blob = jcat_blob_new_full(JCAT_BLOB_KIND_BT_MANIFEST, + data_sig, + JCAT_BLOB_FLAG_IS_UTF8); + } else if (g_str_has_suffix(values[2], ".btcheckpoint")) { + blob = jcat_blob_new_full(JCAT_BLOB_KIND_BT_CHECKPOINT, + data_sig, + JCAT_BLOB_FLAG_IS_UTF8); + } else if (g_str_has_suffix(values[2], ".btinclusionproof")) { + blob = jcat_blob_new_full(JCAT_BLOB_KIND_BT_INCLUSION_PROOF, + data_sig, + JCAT_BLOB_FLAG_IS_UTF8); + } else if (g_str_has_suffix(values[2], ".btverifier")) { + blob = jcat_blob_new_full(JCAT_BLOB_KIND_BT_VERIFIER, + data_sig, + JCAT_BLOB_FLAG_IS_UTF8); + } else if (g_str_has_suffix(values[2], ".btlogindex")) { + blob = jcat_blob_new_full(JCAT_BLOB_KIND_BT_LOGINDEX, + data_sig, + JCAT_BLOB_FLAG_IS_UTF8); } else if (g_str_has_suffix(values[2], ".p7b") || g_str_has_suffix(values[2], ".p7c") || g_str_has_suffix(values[2], ".pem")) { blob = jcat_blob_new_full(JCAT_BLOB_KIND_PKCS7, data_sig, JCAT_BLOB_FLAG_IS_UTF8); } else if (g_str_has_suffix(values[2], ".der")) { blob = jcat_blob_new_full(JCAT_BLOB_KIND_PKCS7, data_sig, JCAT_BLOB_FLAG_NONE); + } else if (g_str_has_suffix(values[2], ".ed25519")) { + blob = jcat_blob_new_full(JCAT_BLOB_KIND_ED25519, data_sig, JCAT_BLOB_FLAG_NONE); } else if (g_str_has_suffix(values[2], ".sha256") || g_str_has_suffix(values[2], ".SHA256")) { blob = jcat_blob_new_full(JCAT_BLOB_KIND_SHA256, data_sig, JCAT_BLOB_FLAG_IS_UTF8); + } else if (g_str_has_suffix(values[2], ".sha512") || + g_str_has_suffix(values[2], ".SHA512")) { + blob = jcat_blob_new_full(JCAT_BLOB_KIND_SHA512, data_sig, JCAT_BLOB_FLAG_IS_UTF8); } else { g_autoptr(GString) tmp = g_string_new(NULL); for (guint i = 1; i < JCAT_BLOB_KIND_LAST; i++) @@ -372,12 +398,7 @@ return FALSE; } - /* load source */ - source = jcat_get_contents_bytes(values[1], error); - if (source == NULL) - return FALSE; - - /* sign the file using the engine */ + /* create item if required */ id_safe = jcat_tool_import_convert_id_safe(priv, values[1]); item = jcat_file_get_item_by_id(file, id_safe, NULL); if (item == NULL) { @@ -385,6 +406,19 @@ jcat_file_add_item(file, item); } + /* load source */ + if (priv->target == JCAT_BLOB_KIND_UNKNOWN) { + source = jcat_get_contents_bytes(values[1], error); + if (source == NULL) + return FALSE; + } else { + g_autoptr(JcatBlob) blob_target = NULL; + blob_target = jcat_item_get_blob_by_kind(item, priv->target, error); + if (blob_target == NULL) + return FALSE; + source = g_bytes_ref(jcat_blob_get_data(blob_target)); + } + /* sign with this kind */ if (priv->kind == JCAT_BLOB_KIND_UNKNOWN) priv->kind = JCAT_BLOB_KIND_PKCS7; @@ -396,6 +430,8 @@ return FALSE; if (priv->appstream_id != NULL) jcat_blob_set_appstream_id(blob, priv->appstream_id); + if (priv->target != JCAT_BLOB_KIND_UNKNOWN) + jcat_blob_set_target(blob, priv->target); jcat_item_add_blob(item, blob); /* export new file */ @@ -436,18 +472,7 @@ return FALSE; } - /* load source, certificate and privatekey */ - source = jcat_get_contents_bytes(values[1], error); - if (source == NULL) - return FALSE; - cert = jcat_get_contents_bytes(values[2], error); - if (cert == NULL) - return FALSE; - privkey = jcat_get_contents_bytes(values[3], error); - if (privkey == NULL) - return FALSE; - - /* sign the file using the engine */ + /* create item if required */ id_safe = jcat_tool_import_convert_id_safe(priv, values[1]); item = jcat_file_get_item_by_id(file, id_safe, NULL); if (item == NULL) { @@ -455,6 +480,27 @@ jcat_file_add_item(file, item); } + /* load source */ + if (priv->target == JCAT_BLOB_KIND_UNKNOWN) { + source = jcat_get_contents_bytes(values[1], error); + if (source == NULL) + return FALSE; + } else { + g_autoptr(JcatBlob) blob_target = NULL; + blob_target = jcat_item_get_blob_by_kind(item, priv->target, error); + if (blob_target == NULL) + return FALSE; + source = g_bytes_ref(jcat_blob_get_data(blob_target)); + } + + /* certificate and privatekey */ + cert = jcat_get_contents_bytes(values[2], error); + if (cert == NULL) + return FALSE; + privkey = jcat_get_contents_bytes(values[3], error); + if (privkey == NULL) + return FALSE; + /* sign with this kind */ if (priv->kind == JCAT_BLOB_KIND_UNKNOWN) priv->kind = JCAT_BLOB_KIND_PKCS7; @@ -471,6 +517,8 @@ return FALSE; if (priv->appstream_id != NULL) jcat_blob_set_appstream_id(blob, priv->appstream_id); + if (priv->target != JCAT_BLOB_KIND_UNKNOWN) + jcat_blob_set_target(blob, priv->target); jcat_item_add_blob(item, blob); /* export new file */ @@ -512,6 +560,8 @@ g_set_error(error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND, "Could not find %s", str); return FALSE; } + + /* load source */ source = jcat_get_contents_bytes(fn_safe, error); if (source == NULL) return FALSE; @@ -519,36 +569,56 @@ /* verify blob */ for (guint j = 0; j < blobs->len; j++) { JcatBlob *blob = g_ptr_array_index(blobs, j); + JcatBlobKind target = jcat_blob_get_target(blob); JcatVerifyFlags flags = JCAT_VERIFY_FLAG_NONE; const gchar *authority; g_autoptr(GError) error_verify = NULL; g_autoptr(JcatResult) result = NULL; + g_autoptr(GBytes) blob_source = NULL; + g_autoptr(GString) kind_str = g_string_new(NULL); /* skip */ if (priv->kind != JCAT_BLOB_KIND_UNKNOWN && priv->kind != jcat_blob_get_kind(blob)) continue; + /* get correct source */ + if (target == JCAT_BLOB_KIND_UNKNOWN) { + blob_source = g_bytes_ref(source); + } else { + g_autoptr(JcatBlob) blob_target = NULL; + blob_target = jcat_item_get_blob_by_kind(item, target, error); + if (blob_target == NULL) + return FALSE; + blob_source = g_bytes_ref(jcat_blob_get_data(blob_target)); + } + + g_string_append(kind_str, jcat_blob_kind_to_string(jcat_blob_get_kind(blob))); + if (jcat_blob_get_target(blob) != JCAT_BLOB_KIND_UNKNOWN) { + g_string_append_printf( + kind_str, + "-of-%s", + jcat_blob_kind_to_string(jcat_blob_get_target(blob))); + } if (priv->disable_time_checks) flags |= JCAT_VERIFY_FLAG_DISABLE_TIME_CHECKS; - result = - jcat_context_verify_blob(priv->context, source, blob, flags, &error_verify); + result = jcat_context_verify_blob(priv->context, + blob_source, + blob, + flags, + &error_verify); if (result == NULL) { if (g_error_matches(error_verify, G_IO_ERROR, G_IO_ERROR_NOT_FOUND)) { g_print(" SKIPPED %s: %s\n", - jcat_blob_kind_to_string(jcat_blob_get_kind(blob)), + kind_str->str, error_verify->message); continue; } - g_print(" FAILED %s: %s\n", - jcat_blob_kind_to_string(jcat_blob_get_kind(blob)), - error_verify->message); + g_print(" FAILED %s: %s\n", kind_str->str, error_verify->message); ret = FALSE; continue; } authority = jcat_result_get_authority(result); - g_print(" PASSED %s: %s\n", - jcat_blob_kind_to_string(jcat_blob_get_kind(blob)), - authority != NULL ? authority : "OK"); + g_print(" PASSED %s: %s\n", kind_str->str, authority != NULL ? authority : "OK"); } if (!ret) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "Validation failed"); @@ -606,7 +676,7 @@ ".%s", jcat_blob_kind_to_filename_ext(jcat_blob_get_kind(blob))); fn = g_build_filename(priv->prefix, str->str, NULL); - if (!jcat_set_contents_bytes(fn, jcat_blob_get_data(blob), error)) + if (!jcat_set_contents_bytes(fn, jcat_blob_get_data(blob), 0666, error)) return FALSE; g_print("Wrote %s\n", fn); } @@ -689,6 +759,7 @@ g_autofree gchar *cmd_descriptions = NULL; g_autofree gchar *keyring_path = NULL; g_autofree gchar *kind = NULL; + g_autofree gchar *target = NULL; g_autofree gchar *prefix = NULL; g_autofree gchar *public_key = NULL; g_autofree gchar *public_keys = NULL; @@ -740,6 +811,13 @@ _("Prefix for import and output files"), NULL}, {"kind", '\0', 0, G_OPTION_ARG_STRING, &kind, _("Kind for blob, e.g. `gpg`"), NULL}, + {"target", + '\0', + 0, + G_OPTION_ARG_STRING, + &target, + _("Target for blob, e.g. `sha256`"), + NULL}, {"appstream-id", '\0', 0, @@ -865,6 +943,13 @@ return EXIT_FAILURE; } } + if (target != NULL) { + priv->target = jcat_blob_kind_from_string(target); + if (priv->target == JCAT_BLOB_KIND_UNKNOWN) { + g_printerr("Failed to parse target '%s', expected checksum", kind); + return EXIT_FAILURE; + } + } /* set verbose? */ if (verbose) diff -Nru libjcat-0.1.9/libjcat/jcat-version.c libjcat-0.2.3/libjcat/jcat-version.c --- libjcat-0.1.9/libjcat/jcat-version.c 1970-01-01 00:00:00.000000000 +0000 +++ libjcat-0.2.3/libjcat/jcat-version.c 2025-02-03 14:10:02.000000000 +0000 @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2022 Richard Hughes + * + * SPDX-License-Identifier: LGPL-2.1+ + */ + +#include "config.h" + +#include "jcat-version.h" + +/** + * jcat_version_string: + * + * Gets the JCat installed runtime version. + * + * Returns: a version number, e.g. "0.1.11" + * + * Since: 0.1.11 + **/ +const gchar * +jcat_version_string(void) +{ + return G_STRINGIFY(JCAT_MAJOR_VERSION) "." G_STRINGIFY(JCAT_MINOR_VERSION) "." G_STRINGIFY( + JCAT_MICRO_VERSION); +} diff -Nru libjcat-0.1.9/libjcat/jcat-version.h.in libjcat-0.2.3/libjcat/jcat-version.h.in --- libjcat-0.1.9/libjcat/jcat-version.h.in 2021-11-28 15:58:08.000000000 +0000 +++ libjcat-0.2.3/libjcat/jcat-version.h.in 2025-02-03 14:10:02.000000000 +0000 @@ -6,10 +6,14 @@ #pragma once +#include + /** * JCAT_MAJOR_VERSION: * * The compile-time major version + * + * Since: 0.1.0 */ #ifndef JCAT_MAJOR_VERSION #define JCAT_MAJOR_VERSION (@JCAT_MAJOR_VERSION@) @@ -19,6 +23,8 @@ * JCAT_MINOR_VERSION: * * The compile-time minor version + * + * Since: 0.1.0 */ #ifndef JCAT_MINOR_VERSION #define JCAT_MINOR_VERSION (@JCAT_MINOR_VERSION@) @@ -28,22 +34,32 @@ * JCAT_MICRO_VERSION: * * The compile-time micro version + * + * Since: 0.1.0 */ #ifndef JCAT_MICRO_VERSION #define JCAT_MICRO_VERSION (@JCAT_MICRO_VERSION@) #endif /** - * LIBJCAT_CHECK_VERSION: + * JCAT_CHECK_VERSION: * @major: Major version number * @minor: Minor version number * @micro: Micro version number * * Check whether a libjcat version equal to or greater than * major.minor.micro. + * + * Since: 0.1.14 */ -#define LIBJCAT_CHECK_VERSION(major, minor, micro) \ +#define JCAT_CHECK_VERSION(major, minor, micro) \ (JCAT_MAJOR_VERSION > (major) || \ (JCAT_MAJOR_VERSION == (major) && JCAT_MINOR_VERSION > (minor)) || \ (JCAT_MAJOR_VERSION == (major) && JCAT_MINOR_VERSION == (minor) && \ JCAT_MICRO_VERSION >= (micro))) + +#ifndef __GI_SCANNER__ +#define LIBJCAT_CHECK_VERSION(major, minor, micro) JCAT_CHECK_VERSION(major, minor, micro) +#endif + +const gchar *jcat_version_string (void); diff -Nru libjcat-0.1.9/libjcat/jcat.h libjcat-0.2.3/libjcat/jcat.h --- libjcat-0.1.9/libjcat/jcat.h 2021-11-28 15:58:08.000000000 +0000 +++ libjcat-0.2.3/libjcat/jcat.h 2025-02-03 14:10:02.000000000 +0000 @@ -10,6 +10,7 @@ #include #include +#include #include #include #include diff -Nru libjcat-0.1.9/libjcat/jcat.map libjcat-0.2.3/libjcat/jcat.map --- libjcat-0.1.9/libjcat/jcat.map 2021-11-28 15:58:08.000000000 +0000 +++ libjcat-0.2.3/libjcat/jcat.map 2025-02-03 14:10:02.000000000 +0000 @@ -76,3 +76,49 @@ jcat_result_get_method; local: *; } LIBJCAT_0.1.1; + +LIBJCAT_0.1.9 { + global: + jcat_engine_add_public_key_raw; + local: *; +} LIBJCAT_0.1.3; + +LIBJCAT_0.1.11 { + global: + jcat_version_string; + local: *; +} LIBJCAT_0.1.9; + +LIBJCAT_0.1.12 { + global: + jcat_context_blob_kind_allow; + jcat_context_blob_kind_disallow; + local: *; +} LIBJCAT_0.1.11; + +LIBJCAT_0.2.0 { + global: + jcat_blob_get_target; + jcat_blob_set_target; + jcat_bt_checkpoint_get_hash; + jcat_bt_checkpoint_get_identity; + jcat_bt_checkpoint_get_log_size; + jcat_bt_checkpoint_get_origin; + jcat_bt_checkpoint_get_payload; + jcat_bt_checkpoint_get_pubkey; + jcat_bt_checkpoint_get_signature; + jcat_bt_checkpoint_get_type; + jcat_bt_checkpoint_new; + jcat_bt_checkpoint_to_string; + jcat_bt_verifier_get_alg; + jcat_bt_verifier_get_hash; + jcat_bt_verifier_get_key; + jcat_bt_verifier_get_name; + jcat_bt_verifier_get_type; + jcat_bt_verifier_new; + jcat_bt_verifier_to_string; + jcat_context_verify_target; + jcat_item_get_blob_by_kind; + jcat_item_has_target; + local: *; +} LIBJCAT_0.1.12; diff -Nru libjcat-0.1.9/libjcat/meson.build libjcat-0.2.3/libjcat/meson.build --- libjcat-0.1.9/libjcat/meson.build 2021-11-28 15:58:08.000000000 +0000 +++ libjcat-0.2.3/libjcat/meson.build 2025-02-03 14:10:02.000000000 +0000 @@ -14,10 +14,13 @@ 'jcat-blob.h', 'jcat-common.h', 'jcat-context.h', + 'jcat-compile.h', 'jcat-engine.h', 'jcat-file.h', 'jcat-item.h', 'jcat-result.h', + 'jcat-bt-verifier.h', + 'jcat-bt-checkpoint.h', ) + [libjcat_version_h] install_headers( @@ -33,6 +36,9 @@ jcat_src += 'jcat-pkcs7-common.c' jcat_src += 'jcat-pkcs7-engine.c' endif +if get_option('ed25519') + jcat_src += 'jcat-ed25519-engine.c' +endif jcat_mapfile = 'jcat.map' libjcat_ldflags = cc.get_supported_link_arguments([ @@ -47,9 +53,13 @@ 'jcat-engine.c', 'jcat-sha1-engine.c', 'jcat-sha256-engine.c', + 'jcat-sha512-engine.c', 'jcat-result.c', + 'jcat-bt-verifier.c', + 'jcat-bt-checkpoint.c', 'jcat-file.c', 'jcat-item.c', + 'jcat-version.c', jcat_src, ], soversion : lt_current, @@ -68,6 +78,9 @@ if get_option('pkcs7') pkgg_variables += 'supported_pkcs7=1' endif +if get_option('ed25519') + pkgg_variables += 'supported_ed25519=1' +endif pkgg = import('pkgconfig') pkgg.generate(libjcat, @@ -89,6 +102,7 @@ include_directories('.'), configinc, ], + variables : pkgg_variables, dependencies : libjcat_deps ) @@ -114,6 +128,12 @@ 'jcat-engine.h', 'jcat-result.c', 'jcat-result.h', + 'jcat-bt-verifier.c', + 'jcat-bt-verifier.h', + 'jcat-bt-checkpoint.c', + 'jcat-bt-checkpoint.h', + 'jcat-version.c', + libjcat_version_h, ], nsversion : '1.0', namespace : 'Jcat', @@ -146,13 +166,7 @@ ) endif - python = import('python') - python_interpreter = python.find_installation( - modules: [ - 'xml.etree.ElementTree', - 'pkg_resources', - ], - ) + python_interpreter = find_program('python3') # Verify the map file is correct -- note we can't actually use the generated # file for two reasons: @@ -167,7 +181,7 @@ output: 'jcat.map', command: [ python_interpreter, - join_paths(meson.source_root(), 'contrib', 'generate-version-script.py'), + join_paths(meson.project_source_root(), 'contrib', 'generate-version-script.py'), 'LIBJCAT', '@INPUT@', '@OUTPUT@', @@ -218,6 +232,7 @@ test_deps = [] if get_option('pkcs7') test_deps += colorhug_pkcs7_signature + test_deps += colorhug_pkcs7_signature_hash endif testdatadirs = environment() testdatadirs.set('G_TEST_SRCDIR', testdatadir_src) @@ -234,13 +249,20 @@ 'jcat-file.c', 'jcat-item.c', 'jcat-result.c', + 'jcat-bt-verifier.c', + 'jcat-bt-checkpoint.c', 'jcat-sha1-engine.c', 'jcat-sha256-engine.c', + 'jcat-sha512-engine.c', jcat_src, ], include_directories : [ configinc, ], + c_args: [ + '-DSRCDIR="' + testdatadir_src + '"', + '-DBUILDDIR="' + testdatadir_dst + '"', + ], dependencies : [ libjcat_deps, ], diff -Nru libjcat-0.1.9/meson.build libjcat-0.2.3/meson.build --- libjcat-0.1.9/meson.build 2021-11-28 15:58:08.000000000 +0000 +++ libjcat-0.2.3/meson.build 2025-02-03 14:10:02.000000000 +0000 @@ -1,7 +1,7 @@ project('libjcat', 'c', - version : '0.1.9', + version : '0.2.3', license : 'LGPL-2.1+', - meson_version : '>=0.49.2', + meson_version : '>=0.56.0', default_options : ['warning_level=2', 'c_std=c99'], ) @@ -27,6 +27,8 @@ # get supported warning flags warning_flags = [ + '-Wfatal-errors', + '-Wno-nonnull-compare', '-Waggregate-return', '-Wunused', '-Warray-bounds', @@ -140,6 +142,11 @@ libjcat_deps += gnutls endif +if get_option('ed25519') + conf.set('ENABLE_ED25519', '1') + libjcat_deps += dependency('gnutls') +endif + if get_option('gpg') gpgme = cc.find_library('gpgme') gpgerror = cc.find_library('gpg-error') @@ -151,6 +158,7 @@ gnome = import('gnome') conf.set('installed_test_bindir', installed_test_bindir) +conf.set('installed_test_datadir', installed_test_datadir) conf.set_quoted('PACKAGE_NAME', meson.project_name()) conf.set_quoted('GETTEXT_PACKAGE', meson.project_name()) conf.set_quoted('JCAT_LOCALSTATEDIR', localstatedir) diff -Nru libjcat-0.1.9/meson_options.txt libjcat-0.2.3/meson_options.txt --- libjcat-0.1.9/meson_options.txt 2021-11-28 15:58:08.000000000 +0000 +++ libjcat-0.2.3/meson_options.txt 2025-02-03 14:10:02.000000000 +0000 @@ -4,5 +4,6 @@ option('tests', type : 'boolean', value : true, description : 'enable tests') option('gpg', type : 'boolean', value : true, description : 'enable the GPG verification support') option('pkcs7', type : 'boolean', value : true, description : 'enable the PKCS7 verification support') +option('ed25519', type : 'boolean', value : true, description : 'enable the ED25519 verification support') option('man', type : 'boolean', value : true, description : 'enable man pages') option('cli', type : 'boolean', value : true, description : 'build and install the jcat-tool CLI')