pax_global_header00006660000000000000000000000064147501470720014521gustar00rootroot0000000000000052 comment=f284d18a694ed98f49ddb06e6920265781a30125 libjcat-0.2.3/000077500000000000000000000000001475014707200131335ustar00rootroot00000000000000libjcat-0.2.3/.clang-format000066400000000000000000000025131475014707200155070ustar00rootroot00000000000000--- AlignAfterOpenBracket: 'Align' AlignConsecutiveAssignments: 'false' AlignConsecutiveDeclarations: 'false' AlignConsecutiveMacros: 'true' AlignOperands: 'true' AlignTrailingComments: 'true' AllowAllArgumentsOnNextLine: 'false' AllowAllParametersOfDeclarationOnNextLine: 'false' AllowShortBlocksOnASingleLine: 'false' AllowShortCaseLabelsOnASingleLine: 'false' AllowShortFunctionsOnASingleLine: 'Inline' AllowShortIfStatementsOnASingleLine: 'false' AlwaysBreakAfterReturnType: 'All' BinPackParameters: 'false' BinPackArguments: 'false' BreakBeforeBraces: 'Linux' ColumnLimit: '100' DerivePointerAlignment: 'false' IndentCaseLabels: 'false' IndentWidth: '8' IncludeBlocks: 'Regroup' KeepEmptyLinesAtTheStartOfBlocks: 'false' MaxEmptyLinesToKeep: '1' PointerAlignment: 'Right' SortIncludes: 'true' SpaceAfterCStyleCast: 'false' SpaceBeforeAssignmentOperators : 'true' SpaceBeforeParens: 'ControlStatements' SpaceInEmptyParentheses: 'false' SpacesInSquareBrackets: 'false' TabWidth: '8' UseTab: 'Always' PenaltyBreakAssignment: '3' PenaltyBreakBeforeFirstCallParameter: '15' --- Language: 'Proto' --- Language: 'Cpp' IncludeCategories: - Regex: '^"config.h"$' Priority: '0' - Regex: '^<' Priority: '1' - Regex: 'jcat' Priority: '2' - Regex: '.*' Priority: '3' ... libjcat-0.2.3/.clang-tidy000066400000000000000000000023561475014707200151750ustar00rootroot00000000000000--- 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,\ " ... libjcat-0.2.3/.git-blame-ignore-revs000066400000000000000000000000511475014707200172270ustar00rootroot0000000000000028feaa000465059b38525cbfb1388a8d2ae9dc38 libjcat-0.2.3/.gitconfig000066400000000000000000000000611475014707200151020ustar00rootroot00000000000000[blame] ignoreRevsFile = .git-blame-ignore-revs libjcat-0.2.3/.github/000077500000000000000000000000001475014707200144735ustar00rootroot00000000000000libjcat-0.2.3/.github/dependabot.yml000066400000000000000000000001661475014707200173260ustar00rootroot00000000000000version: 2 updates: - package-ecosystem: "github-actions" directory: "/" schedule: interval: "weekly" libjcat-0.2.3/.github/workflows/000077500000000000000000000000001475014707200165305ustar00rootroot00000000000000libjcat-0.2.3/.github/workflows/ccpp.yml000066400000000000000000000014701475014707200202020ustar00rootroot00000000000000name: libjcat on: push: branches: [ main ] pull_request: branches: [ main ] jobs: gcc: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4.2.2 - uses: actions/setup-python@v5 - name: deps run: | sudo apt-get update sudo apt-get install -y \ gnutls-bin \ gobject-introspection \ gtk-doc-tools \ libgirepository1.0-dev \ libglib2.0-dev \ libglib2.0-dev-bin \ libgnutls28-dev \ libgpgme11-dev \ libjson-glib-dev \ pkg-config \ shared-mime-info \ valac - name: meson uses: BSFishy/meson-build@v1.0.3 with: action: test directory: _build setup-options: -Db_coverage=false options: --verbose meson-version: 0.56.0 libjcat-0.2.3/.github/workflows/scorecard.yml000066400000000000000000000060641475014707200212260ustar00rootroot00000000000000# 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 libjcat-0.2.3/.gitignore000066400000000000000000000000061475014707200151170ustar00rootroot00000000000000build libjcat-0.2.3/.lgtm.yml000066400000000000000000000003631475014707200147010ustar00rootroot00000000000000extraction: cpp: prepare: packages: - python3-pip - python3-setuptools - python3-wheel after_prepare: - python3 -m pip install --user "meson >= 0.52.0" - export PATH="$HOME/.local/bin:$PATH" libjcat-0.2.3/CODE_OF_CONDUCT.md000066400000000000000000000062201475014707200157320ustar00rootroot00000000000000# Contributor Covenant Code of Conduct ## Our Pledge In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation. ## Our Standards Examples of behavior that contributes to creating a positive environment include: * Using welcoming and inclusive language * Being respectful of differing viewpoints and experiences * Gracefully accepting constructive criticism * Focusing on what is best for the community * Showing empathy towards other community members Examples of unacceptable behavior by participants include: * The use of sexualized language or imagery and unwelcome sexual attention or advances * Trolling, insulting/derogatory comments, and personal or political attacks * Public or private harassment * Publishing others' private information, such as a physical or electronic address, without explicit permission * Other conduct which could reasonably be considered inappropriate in a professional setting ## Our Responsibilities Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. ## Scope This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. ## Enforcement Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at richard@hughsie.com. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. ## Attribution This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version] [homepage]: http://contributor-covenant.org [version]: http://contributor-covenant.org/version/1/4/ libjcat-0.2.3/LICENSE000066400000000000000000000636361475014707200141560ustar00rootroot00000000000000 GNU LESSER GENERAL PUBLIC LICENSE Version 2.1, February 1999 Copyright (C) 1991, 1999 Free Software Foundation, Inc. 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. [This is the first released version of the Lesser GPL. It also counts as the successor of the GNU Library Public License, version 2, hence the version number 2.1.] Preamble The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public Licenses are intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. This license, the Lesser General Public License, applies to some specially designated software packages--typically libraries--of the Free Software Foundation and other authors who decide to use it. You can use it too, but we suggest you first think carefully about whether this license or the ordinary General Public License is the better strategy to use in any particular case, based on the explanations below. When we speak of free software, we are referring to freedom of use, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for this service if you wish); that you receive source code or can get it if you want it; that you can change the software and use pieces of it in new free programs; and that you are informed that you can do these things. To protect your rights, we need to make restrictions that forbid distributors to deny you these rights or to ask you to surrender these rights. These restrictions translate to certain responsibilities for you if you distribute copies of the library or if you modify it. For example, if you distribute copies of the library, whether gratis or for a fee, you must give the recipients all the rights that we gave you. You must make sure that they, too, receive or can get the source code. If you link other code with the library, you must provide complete object files to the recipients, so that they can relink them with the library after making changes to the library and recompiling it. And you must show them these terms so they know their rights. We protect your rights with a two-step method: (1) we copyright the library, and (2) we offer you this license, which gives you legal permission to copy, distribute and/or modify the library. To protect each distributor, we want to make it very clear that there is no warranty for the free library. Also, if the library is modified by someone else and passed on, the recipients should know that what they have is not the original version, so that the original author's reputation will not be affected by problems that might be introduced by others. Finally, software patents pose a constant threat to the existence of any free program. We wish to make sure that a company cannot effectively restrict the users of a free program by obtaining a restrictive license from a patent holder. Therefore, we insist that any patent license obtained for a version of the library must be consistent with the full freedom of use specified in this license. Most GNU software, including some libraries, is covered by the ordinary GNU General Public License. This license, the GNU Lesser General Public License, applies to certain designated libraries, and is quite different from the ordinary General Public License. We use this license for certain libraries in order to permit linking those libraries into non-free programs. When a program is linked with a library, whether statically or using a shared library, the combination of the two is legally speaking a combined work, a derivative of the original library. The ordinary General Public License therefore permits such linking only if the entire combination fits its criteria of freedom. The Lesser General Public License permits more lax criteria for linking other code with the library. We call this license the "Lesser" General Public License because it does Less to protect the user's freedom than the ordinary General Public License. It also provides other free software developers Less of an advantage over competing non-free programs. These disadvantages are the reason we use the ordinary General Public License for many libraries. However, the Lesser license provides advantages in certain special circumstances. For example, on rare occasions, there may be a special need to encourage the widest possible use of a certain library, so that it becomes a de-facto standard. To achieve this, non-free programs must be allowed to use the library. A more frequent case is that a free library does the same job as widely used non-free libraries. In this case, there is little to gain by limiting the free library to free software only, so we use the Lesser General Public License. In other cases, permission to use a particular library in non-free programs enables a greater number of people to use a large body of free software. For example, permission to use the GNU C Library in non-free programs enables many more people to use the whole GNU operating system, as well as its variant, the GNU/Linux operating system. Although the Lesser General Public License is Less protective of the users' freedom, it does ensure that the user of a program that is linked with the Library has the freedom and the wherewithal to run that program using a modified version of the Library. The precise terms and conditions for copying, distribution and modification follow. Pay close attention to the difference between a "work based on the library" and a "work that uses the library". The former contains code derived from the library, whereas the latter must be combined with the library in order to run. GNU LESSER GENERAL PUBLIC LICENSE TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 0. This License Agreement applies to any software library or other program which contains a notice placed by the copyright holder or other authorized party saying it may be distributed under the terms of this Lesser General Public License (also called "this License"). Each licensee is addressed as "you". A "library" means a collection of software functions and/or data prepared so as to be conveniently linked with application programs (which use some of those functions and data) to form executables. The "Library", below, refers to any such software library or work which has been distributed under these terms. A "work based on the Library" means either the Library or any derivative work under copyright law: that is to say, a work containing the Library or a portion of it, either verbatim or with modifications and/or translated straightforwardly into another language. (Hereinafter, translation is included without limitation in the term "modification".) "Source code" for a work means the preferred form of the work for making modifications to it. For a library, complete source code means all the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of the library. Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running a program using the Library is not restricted, and output from such a program is covered only if its contents constitute a work based on the Library (independent of the use of the Library in a tool for writing it). Whether that is true depends on what the Library does and what the program that uses the Library does. 1. You may copy and distribute verbatim copies of the Library's complete source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and distribute a copy of this License along with the Library. You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee. 2. You may modify your copy or copies of the Library or any portion of it, thus forming a work based on the Library, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions: a) The modified work must itself be a software library. b) You must cause the files modified to carry prominent notices stating that you changed the files and the date of any change. c) You must cause the whole of the work to be licensed at no charge to all third parties under the terms of this License. d) If a facility in the modified Library refers to a function or a table of data to be supplied by an application program that uses the facility, other than as an argument passed when the facility is invoked, then you must make a good faith effort to ensure that, in the event an application does not supply such function or table, the facility still operates, and performs whatever part of its purpose remains meaningful. (For example, a function in a library to compute square roots has a purpose that is entirely well-defined independent of the application. Therefore, Subsection 2d requires that any application-supplied function or table used by this function must be optional: if the application does not supply it, the square root function must still compute square roots.) These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Library, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Library, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it. Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Library. In addition, mere aggregation of another work not based on the Library with the Library (or with a work based on the Library) on a volume of a storage or distribution medium does not bring the other work under the scope of this License. 3. You may opt to apply the terms of the ordinary GNU General Public License instead of this License to a given copy of the Library. To do this, you must alter all the notices that refer to this License, so that they refer to the ordinary GNU General Public License, version 2, instead of to this License. (If a newer version than version 2 of the ordinary GNU General Public License has appeared, then you can specify that version instead if you wish.) Do not make any other change in these notices. Once this change is made in a given copy, it is irreversible for that copy, so the ordinary GNU General Public License applies to all subsequent copies and derivative works made from that copy. This option is useful when you wish to copy part of the code of the Library into a program that is not a library. 4. You may copy and distribute the Library (or a portion or derivative of it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange. If distribution of object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place satisfies the requirement to distribute the source code, even though third parties are not compelled to copy the source along with the object code. 5. A program that contains no derivative of any portion of the Library, but is designed to work with the Library by being compiled or linked with it, is called a "work that uses the Library". Such a work, in isolation, is not a derivative work of the Library, and therefore falls outside the scope of this License. However, linking a "work that uses the Library" with the Library creates an executable that is a derivative of the Library (because it contains portions of the Library), rather than a "work that uses the library". The executable is therefore covered by this License. Section 6 states terms for distribution of such executables. When a "work that uses the Library" uses material from a header file that is part of the Library, the object code for the work may be a derivative work of the Library even though the source code is not. Whether this is true is especially significant if the work can be linked without the Library, or if the work is itself a library. The threshold for this to be true is not precisely defined by law. If such an object file uses only numerical parameters, data structure layouts and accessors, and small macros and small inline functions (ten lines or less in length), then the use of the object file is unrestricted, regardless of whether it is legally a derivative work. (Executables containing this object code plus portions of the Library will still fall under Section 6.) Otherwise, if the work is a derivative of the Library, you may distribute the object code for the work under the terms of Section 6. Any executables containing that work also fall under Section 6, whether or not they are linked directly with the Library itself. 6. As an exception to the Sections above, you may also combine or link a "work that uses the Library" with the Library to produce a work containing portions of the Library, and distribute that work under terms of your choice, provided that the terms permit modification of the work for the customer's own use and reverse engineering for debugging such modifications. You must give prominent notice with each copy of the work that the Library is used in it and that the Library and its use are covered by this License. You must supply a copy of this License. If the work during execution displays copyright notices, you must include the copyright notice for the Library among them, as well as a reference directing the user to the copy of this License. Also, you must do one of these things: a) Accompany the work with the complete corresponding machine-readable source code for the Library including whatever changes were used in the work (which must be distributed under Sections 1 and 2 above); and, if the work is an executable linked with the Library, with the complete machine-readable "work that uses the Library", as object code and/or source code, so that the user can modify the Library and then relink to produce a modified executable containing the modified Library. (It is understood that the user who changes the contents of definitions files in the Library will not necessarily be able to recompile the application to use the modified definitions.) b) Use a suitable shared library mechanism for linking with the Library. A suitable mechanism is one that (1) uses at run time a copy of the library already present on the user's computer system, rather than copying library functions into the executable, and (2) will operate properly with a modified version of the library, if the user installs one, as long as the modified version is interface-compatible with the version that the work was made with. c) Accompany the work with a written offer, valid for at least three years, to give the same user the materials specified in Subsection 6a, above, for a charge no more than the cost of performing this distribution. d) If distribution of the work is made by offering access to copy from a designated place, offer equivalent access to copy the above specified materials from the same place. e) Verify that the user has already received a copy of these materials or that you have already sent this user a copy. For an executable, the required form of the "work that uses the Library" must include any data and utility programs needed for reproducing the executable from it. However, as a special exception, the materials to be distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable. It may happen that this requirement contradicts the license restrictions of other proprietary libraries that do not normally accompany the operating system. Such a contradiction means you cannot use both them and the Library together in an executable that you distribute. 7. You may place library facilities that are a work based on the Library side-by-side in a single library together with other library facilities not covered by this License, and distribute such a combined library, provided that the separate distribution of the work based on the Library and of the other library facilities is otherwise permitted, and provided that you do these two things: a) Accompany the combined library with a copy of the same work based on the Library, uncombined with any other library facilities. This must be distributed under the terms of the Sections above. b) Give prominent notice with the combined library of the fact that part of it is a work based on the Library, and explaining where to find the accompanying uncombined form of the same work. 8. You may not copy, modify, sublicense, link with, or distribute the Library except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense, link with, or distribute the Library is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance. 9. You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or distribute the Library or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Library (or any work based on the Library), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Library or works based on it. 10. Each time you redistribute the Library (or any work based on the Library), the recipient automatically receives a license from the original licensor to copy, distribute, link with or modify the Library subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties with this License. 11. If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Library at all. For example, if a patent license would not permit royalty-free redistribution of the Library by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Library. If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply, and the section as a whole is intended to apply in other circumstances. It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the integrity of the free software distribution system which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice. This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License. 12. If the distribution and/or use of the Library is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Library under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License. 13. The Free Software Foundation may publish revised and/or new versions of the Lesser General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Library specifies a version number of this License which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Library does not specify a license version number, you may choose any version ever published by the Free Software Foundation. 14. If you wish to incorporate parts of the Library into other free programs whose distribution conditions are incompatible with these, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally. NO WARRANTY 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Libraries If you develop a new library, and you want it to be of the greatest possible use to the public, we recommend making it free software that everyone can redistribute and change. You can do so by permitting redistribution under these terms (or, alternatively, under the terms of the ordinary General Public License). To apply these terms, attach the following notices to the library. It is safest to attach them to the start of each source file to most effectively convey the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Copyright (C) This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA Also add information on how to contact you by electronic and paper mail. You should also get your employer (if you work as a programmer) or your school, if any, to sign a "copyright disclaimer" for the library, if necessary. Here is a sample; alter the names: Yoyodyne, Inc., hereby disclaims all copyright interest in the library `Frob' (a library for tweaking knobs) written by James Random Hacker. , 1 April 1990 Ty Coon, President of Vice That's all there is to it! libjcat-0.2.3/MAINTAINERS000066400000000000000000000000451475014707200146270ustar00rootroot00000000000000Richard Hughes libjcat-0.2.3/NEWS000066400000000000000000000120421475014707200136310ustar00rootroot00000000000000Version 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 New Features: - Set which backends are supported in the pkgconfig file (Richard Hughes) - Use -Dcli=false to reduce the install size (Richard Hughes) Bugfixes: - Return an error if we try to self-sign no bytes of data (Richard Hughes) - Show a more accurate output when not all engines are enabled (Richard Hughes) Version 0.1.8 ~~~~~~~~~~~~~ Released: 2021-05-24 Bugfixes: - Fix a warning when used in a subproject (Richard Hughes) - Fix compilation on FreeBSD (Richard Hughes) Version 0.1.7 ~~~~~~~~~~~~~ Released: 2021-05-06 New Features: - Do not use help2man to build manual pages (Richard Hughes) Version 0.1.6 ~~~~~~~~~~~~~ Released: 2021-02-08 New Features: - Fall back to the AliasId for validation (Richard Hughes) Bugfixes: - Fix jcat_context_verify_blob() to use self verify for checksums (Richard Hughes) Version 0.1.5 ~~~~~~~~~~~~~ Released: 2021-01-08 New Features: - Allow verifying expired certificates with an additional argument (Richard Hughes) - Allow compiling json-glib as a subproject (Richard Hughes) Version 0.1.4 ~~~~~~~~~~~~~ Released: 2020-10-23 Bugfixes: - Export the old JCatEngine property to preserve internal ABI (Richard Hughes) - Do not fail verification if compiled without an engine (Mario Limonciello) Version 0.1.3 ~~~~~~~~~~~~~ Released: 2020-06-16 New Features: - Export the JcatBlobKind and JcatBlobMethod on the result (Richard Hughes) Bugfixes: - Validate that gpgme_op_verify_result() returned at least one signature (Richard Hughes) Version 0.1.2 ~~~~~~~~~~~~~ Released: 2020-04-27 Bugfixes: - Lower the meson dep version for RHEL 8 (Richard Hughes) - Check for Python modules explicitly during build (Jan Tojnar) Version 0.1.1 ~~~~~~~~~~~~~ Released: 2020-04-14 New Features: - Allow adding an item ID 'alias' (Richard Hughes) Bugfixes: - Make the installed tests actually work (Richard Hughes) - Run generate-version-script.py using the same Python as meson itself (Marek Szuba) - Only pass --version-script to linker when supported (Jan Tojnar) - Sign a simple string instead of /etc/machine-id (Simon McVittie) Version 0.1.0 ~~~~~~~~~~~~~ Released: 2020-03-23 Notes: - This is the first release of libjcat, a library for creating a modifying detached signature collections, a.k.a. Jcat files. - See https://github.com/hughsie/libjcat/blob/main/README.md for information. libjcat-0.2.3/README.md000066400000000000000000000234561475014707200144240ustar00rootroot00000000000000libjcat ======= This library allows reading and writing gzip-compressed JSON catalog files, which can be used to store GPG, PKCS-7 and SHA-256 checksums for each file. This provides equivalent functionality to the catalog files supported in Microsoft Windows. Design ====== Each JSON file is gzipped, and is logically divided into three structures: JcatBlob -------- The 'signature' blob, which can be a proper detached sigature like PKCS-7 or just a checksum like SHA-256. JcatItem -------- Items roughly approximate single files, and can have multiple JcatBlobs assigned. In a typical firmware archive you would have two items, with IDs `firmware.bin` and `firmware.metainfo.xml` JcatFile -------- The container which contains one or multiple JcatItems. Self Signing ============ Jcat files can be signed using a certificate and key that are automatically generated on your local computer. This means you can only verify the Jcat archive on the same computer (and probably the same user) that you use to sign the archive. It does however mean you can skip manually generating a secret key and public key pair. If you do upload the public certificate up to a web service (for instance the LVFS) it does mean it can verify your signatures. $ jcat-tool --appstream-id localhost self-sign firmware.jcat firmware.bin $ jcat-tool info firmware.jcat JcatFile: Version: 0.1 JcatItem: ID: firmware.bin JcatBlob: Kind: pkcs7 Flags: is-utf8 AppstreamId: localhost Timestamp: 2020-03-05T12:06:42Z Size: 0x2d9 Data: -----BEGIN PKCS7----- MIIB9wYJKoZIhvcNAQcCoIIB6DCCAeQCAQExDTALBglghkgBZQMEAgEwCwYJKoZI ... oDd2UcfqgdQnihpYf0NaPDYhpcP5r7dmH1XN -----END PKCS7----- Public Key Signing ================== Jcat can of course sign the archive with proper keys too. Here we will generate a private and public key ourselves, but you should probably talk to your IT department security team and ask them how to get a user certificate that's been signed by the corporate CA certificate. Lets create our own certificate authority (CA) and issue a per-user key for local testing. Never use these in any kind of production system! $ ../contrib/build-certs.py $ ls ACME-CA.* rhughes* ACME-CA.key ACME-CA.pem rhughes.csr rhughes.key rhughes.pem rhughes_signed.pem Then we can actually use both files: $ jcat-tool --appstream-id com.redhat.rhughes sign firmware.jcat firmware.bin rhughes_signed.pem rhughes.key JcatFile: Version: 0.1 JcatItem: ID: firmware.bin JcatBlob: Kind: pkcs7 Flags: is-utf8 AppstreamId: com.redhat.rhughes Timestamp: 2020-03-05T12:16:30Z Size: 0x373 Data: -----BEGIN PKCS7----- MIICZwYJKoZIhvcNAQcCoIICWDCCAlQCAQExDTALBglghkgBZQMEAgEwCwYJKoZI ... 8jggo0FbhDSs8frXhr1BHKBktOPKEbA3sETxlbHViYt6oldpi1uszV0kHA== -----END PKCS7----- Lets verify this new signature: $ jcat-tool --appstream-id com.redhat.rhughes verify firmware.jcat firmware.bin: FAILED pkcs7: failed to verify data for O=ACME Corp.,CN=ACME CA: Public key signature verification has failed. [-89] FAILED: Validation failed Validation failed Ahh, of course; we need to tell Jcat to load our generated CA certificate: $ jcat-tool --appstream-id com.redhat.rhughes verify firmware.jcat --public-key ACME-CA.pem firmware.bin: PASSED pkcs7: O=ACME Corp.,CN=ACME CA We can then check the result using $ jcat-tool export firmware.jcat Wrote ./firmware.bin-com.redhat.rhughes.p7b $ certtool --p7-verify --infile firmware.bin-com.redhat.rhughes.p7b --load-data firmware.bin --load-ca-certificate=ACME-CA.pem Loaded CAs (1 available) eContent Type: 1.2.840.113549.1.7.1 Signers: Signer's issuer DN: O=ACME Corp.,CN=ACME CA Signer's serial: 4df758978d0601c6500ab6f266963916d8b7ab33 Signature Algorithm: RSA-SHA256 Signature status: ok Large Payloads ============== It may be impractical to load the entire binary into RAM for verification. For this usercase, jcat supports signing the *checksum of the payload* as the target rather than the payload itself. $ jcat-tool self-sign firmware.jcat firmware.bin --kind sha256 $ jcat-tool --appstream-id com.redhat.rhughes sign firmware.jcat firmware.bin rhughes_signed.pem rhughes.key --target sha256 $ jcat-tool info firmware.jcat JcatFile: Version: 0.1 JcatItem: ID: firmware.bin JcatBlob: Kind: sha256 Flags: is-utf8 Timestamp: 2023-12-15T16:38:11Z Size: 0x40 Data: a948904f2f0f479b8f8197694b30184b0d2ed1c1cd2a1ec0fb85d299a192a447 JcatBlob: Kind: pkcs7 Target: sha256 Flags: is-utf8 AppstreamId: com.redhat.rhughes Timestamp: 2023-12-15T16:38:15Z Size: 0xdcc Data: -----BEGIN PKCS7----- MIIKCwYJKoZIhvcNAQcCoIIJ/DCCCfgCAQExDTALBglghkgBZQMEAgEwCwYJKoZI ... Zjb6fuKL5Rr/ouoImn+x1cYJyqRMmCxpLG9GrXR9Ag== -----END PKCS7----- $ jcat-tool --appstream-id com.redhat.rhughes verify firmware.jcat --public-key ACME-CA.pem firmware.bin: PASSED pkcs7: O=ACME Corp.,CN=ACME CA NOTE: Only JCat v2.0.0 and newer supports the *checksum of the payload* functionality, and you should also add signatures **without** using `--target` if you need to support older versions. Additionally, older JCat versions deduplicate the blobs by just the blob kind, so you want to make sure that the signature added with `--target` is added **before** the signature added without. Testing ======= Download a firmware from the LVFS, and decompress with `gcab -x` -- we can now validate the signatures are valid: certtool --p7-verify --infile=firmware.bin.p7b --load-ca-certificate=/etc/pki/fwupd/LVFS-CA.pem --load-data=firmware.bin Lets create a Jcat file with a single checksum: $ jcat-tool self-sign test.jcat firmware.bin --kind sha256 $ jcat-tool info test.jcat JcatFile: Version: 0.1 JcatItem: ID: firmware.bin JcatBlob: Kind: sha256 Flags: is-utf8 Timestamp: 2020-03-04T13:59:57Z Size: 0x40 Data: bd598c9019baee65373da1963fbce7478d6e9e8963bd837d12896f53b03be83e Now we can import both existing signatures into a Jcat file, and then validate it again. $ jcat-tool import test.jcat firmware.bin firmware.bin.asc $ jcat-tool import test.jcat firmware.bin firmware.bin.p7b $ jcat-tool info test.jcat JcatFile: Version: 0.1 JcatItem: ID: firmware.bin JcatBlob: Kind: sha256 Flags: is-utf8 Timestamp: 2020-03-04T13:59:57Z Size: 0x40 Data: bd598c9019baee65373da1963fbce7478d6e9e8963bd837d12896f53b03be83e JcatBlob: Kind: gpg Flags: is-utf8 Timestamp: 2020-03-04T14:00:30Z Size: 0x1ea Data: -----BEGIN PGP SIGNATURE----- Version: GnuPG v2.0.22 (GNU/Linux) iQEcBAABAgAGBQJeVoylAAoJEEim2A5FOLrCagQIAIb6uDCzwUBBoZRqRzekxf0E ... =0GGy -----END PGP SIGNATURE----- JcatBlob: Kind: pkcs7 Flags: is-utf8 Timestamp: 2020-03-04T14:00:34Z Size: 0x8c0 Data: -----BEGIN PKCS7----- MIIGUgYJKoZIhvcNAQcCoIIGQzCCBj8CAQExDTALBglghkgBZQMEAgEwCwYJKoZI ... EYOqoEV8PaVQZW3ndWEaQfyo6MgZ/WqpO6Gv2zTx1CXk0APIGG8= -----END PKCS7----- $ jcat-tool verify test.jcat --public-keys /etc/pki/fwupd firmware.bin: PASSED sha256: OK PASSED gpg: 3FC6B804410ED0840D8F2F9748A6D80E4538BAC2 PASSED pkcs7: O=Linux Vendor Firmware Project,CN=LVFS CA Security ======== Unlike Microsoft catalog files which are a signed manifest of hashes, a Jcat file is a manifest of signatures. This means it's possible (and positively encouraged) to modify the `.jcat` file to add new signatures or replace existing ones. This means Jcat does not verify that the set of file has not been modified, only that the individual files and signatures themselves have not been changed. If you require some trust in that file A was signed at the same time, or by the same person as file B then then best way to do this is to embed a checksum (e.g SHA-256) into one file and then verify it in the client software. For instance, when installing firmware we need to know if a metadata file was provided by the LVFS with the vendor firmware file. To do this, we add the SHA-256 checksum of the `firmware.bin` in the `firmware.metainfo.xml` file itself, and then add both files to a Jcat archive. The client software (e.g. fwupd) then needs to check the firmware checksum as an additional step of verifying the signatures in the Jcat file. libjcat-0.2.3/RELEASE000066400000000000000000000012721475014707200141400ustar00rootroot00000000000000libjcat Release Notes 1. Write NEWS entries for libjcat in the same format as usual. git shortlog 0.2.2.. | grep -i -v trivial | grep -v Merge > NEWS.new Version 0.2.3 ~~~~~~~~~~~~~ Released: 2025-xx-xx New Features: Bugfixes: Commit changes to git: # MAKE SURE THESE ARE CORRECT 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}" ninja dist gpg -b -a meson-dist/libjcat-${release_ver}.tar.xz git push --tags git push Upload release artifacts via https://github.com/hughsie/libjcat/tags Do post release version bump in meson.build git commit -a -m "trivial: post release version bump" git push libjcat-0.2.3/SECURITY.md000066400000000000000000000005411475014707200147240ustar00rootroot00000000000000# 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. libjcat-0.2.3/contrib/000077500000000000000000000000001475014707200145735ustar00rootroot00000000000000libjcat-0.2.3/contrib/build-certs.py000077500000000000000000000114341475014707200173700ustar00rootroot00000000000000#!/usr/bin/python3 # SPDX-License-Identifier: LGPL-2.1+ import os import sys import subprocess import tempfile from datetime import datetime, timedelta def _build_certs(): # expire in 7 days to avoid people using these in production dt_activation = (datetime.utcnow() - timedelta(days=1)).isoformat() dt_expiration = (datetime.utcnow() + timedelta(days=7)).isoformat() # certificate authority ca = "ACME" ca_privkey = f"{ca}-CA.key" ca_certificate = f"{ca}-CA.pem" if not os.path.exists(ca_privkey): print("generating private key...") argv = ["certtool", "--generate-privkey", "--outfile", ca_privkey] rc = subprocess.run(argv, check=True) if rc.returncode != 0: return 1 if not os.path.exists(ca_certificate): print("generating self-signed certificate...") # build config lines = [] lines.append('organization = "ACME Corp."') lines.append('cn = "ACME CA"') lines.append('uri = "http://www.example.com/"') lines.append('email = "admin@example.com"') lines.append('crl_dist_points = "http://www.example.com/pki/"') lines.append("serial = 1") lines.append("crl_number = 1") lines.append("path_len = 1") lines.append('activation_date = "{}"'.format(dt_activation)) lines.append('expiration_date = "{}"'.format(dt_expiration)) lines.append("ca") lines.append("cert_signing_key") lines.append("crl_signing_key") lines.append("code_signing_key") cfg = tempfile.NamedTemporaryFile( mode="w", prefix="cert_", suffix=".cfg", dir=None, delete=True ) cfg.write("\n".join(lines)) cfg.flush() argv = [ "certtool", "--generate-self-signed", "--load-privkey", ca_privkey, "--template", cfg.name, "--outfile", ca_certificate, ] rc = subprocess.run(argv, check=True) if rc.returncode != 0: return 1 # per-user key user = "rhughes" user_privkey = f"{user}.key" user_certificate = f"{user}.pem" user_certificate_signed = f"{user}_signed.pem" user_request = f"{user}.csr" # build config lines = [] lines.append('cn = "Richard Hughes"') lines.append('uri = "https://hughsie.com/"') lines.append('email = "richard@hughsie.com"') lines.append('activation_date = "{}"'.format(dt_activation)) lines.append('expiration_date = "{}"'.format(dt_expiration)) lines.append("signing_key") lines.append("code_signing_key") cfg = tempfile.NamedTemporaryFile( mode="w", prefix="cert_", suffix=".cfg", dir=None, delete=True ) cfg.write("\n".join(lines)) cfg.flush() if not os.path.exists(user_privkey): print("generating user private key...") argv = [ "certtool", "--generate-privkey", "--rsa", "--bits", "2048", "--outfile", user_privkey, ] rc = subprocess.run(argv, check=True) if rc.returncode != 0: return 1 if not os.path.exists(user_certificate): print("generating self-signed certificate...") argv = [ "certtool", "--generate-self-signed", "--rsa", "--bits", "2048", "--load-privkey", user_privkey, "--template", cfg.name, "--outfile", user_certificate, ] rc = subprocess.run(argv, check=True) if rc.returncode != 0: return 1 if not os.path.exists(user_request): print("generating certificate...") argv = [ "certtool", "--generate-request", "--load-privkey", user_privkey, "--template", cfg.name, "--outfile", user_request, ] rc = subprocess.run(argv, check=True) if rc.returncode != 0: return 1 # sign the user if not os.path.exists(user_certificate_signed): print("generating CA-signed certificate...") argv = [ "certtool", "--generate-certificate", "--rsa", "--bits", "2048", "--load-request", user_request, "--load-ca-certificate", ca_certificate, "--load-ca-privkey", ca_privkey, "--template", cfg.name, "--outfile", user_certificate_signed, ] rc = subprocess.run(argv, check=True) if rc.returncode != 0: return 1 # success return 0 if __name__ == "__main__": sys.exit(_build_certs()) libjcat-0.2.3/contrib/ci/000077500000000000000000000000001475014707200151665ustar00rootroot00000000000000libjcat-0.2.3/contrib/ci/Dockerfile-debian000066400000000000000000000005521475014707200204020ustar00rootroot00000000000000FROM debian:testing RUN echo fubar > /etc/machine-id RUN apt-get update -qq RUN apt-get install -yq --no-install-recommends \ gnutls-bin \ gnutls-dev \ gobject-introspection \ gtk-doc-tools \ libgirepository1.0-dev \ libglib2.0-dev \ libglib2.0-dev-bin \ libgpgme11-dev \ libjson-glib-dev \ meson \ ninja-build \ pkg-config \ valac WORKDIR /build libjcat-0.2.3/contrib/ci/Dockerfile-debian-s390x000066400000000000000000000010211475014707200212560ustar00rootroot00000000000000FROM debian:testing RUN echo fubar > /etc/machine-id RUN cat /etc/apt/sources.list | sed "s/deb/deb-src/" >> /etc/apt/sources.list RUN dpkg --add-architecture s390x RUN apt update -qq RUN apt install -yq --no-install-recommends \ build-essential \ binutils-multiarch \ dpkg-dev \ gcc \ gcc-multilib-s390x-linux-gnu \ gnutls-bin \ gnutls-dev:s390x \ libglib2.0-dev:s390x \ libglib2.0-dev-bin:s390x \ libgpgme11-dev:s390x \ libjson-glib-dev:s390x \ meson \ qemu-user \ -o APT::Immediate-Configure=0 WORKDIR /build libjcat-0.2.3/contrib/ci/Dockerfile-fedora000066400000000000000000000004751475014707200204240ustar00rootroot00000000000000FROM fedora:32 RUN dnf -y update RUN dnf -y install \ diffutils \ gcovr \ git-core \ glib2-devel \ gnutls-devel \ gnutls-utils \ gobject-introspection-devel \ gpgme-devel \ gtk-doc \ json-glib-devel \ meson \ redhat-rpm-config \ shared-mime-info \ vala RUN echo fubar > /etc/machine-id WORKDIR /build libjcat-0.2.3/contrib/ci/Dockerfile-fedora-w64000066400000000000000000000004731475014707200210400ustar00rootroot00000000000000FROM fedora:33 RUN dnf -y update RUN dnf -y install \ diffutils \ gcc \ glib2-devel \ gnutls-utils \ git-core \ meson \ mingw64-gcc \ mingw64-glib2 \ mingw64-gnutls \ mingw64-json-glib \ mingw64-pkg-config \ redhat-rpm-config \ shared-mime-info \ wine RUN echo fubar > /etc/machine-id WORKDIR /build libjcat-0.2.3/contrib/ci/build-debian-s390x.sh000077500000000000000000000007741475014707200207400ustar00rootroot00000000000000#!/bin/sh set -e #evaluate using Debian's build flags eval "$(dpkg-buildflags --export=sh)" #filter out -Bsymbolic-functions export LDFLAGS=$(dpkg-buildflags --get LDFLAGS | sed "s/-Wl,-Bsymbolic-functions\s//") export LC_ALL=C.UTF-8 mkdir -p build && cd build rm * -rf meson .. \ --cross-file=../contrib/s390x.cross \ -Dintrospection=false \ -Dman=false \ -Dgpg=false \ -Dgtkdoc=false \ -Dtests=true $@ ninja -v || bash ninja test -v DESTDIR=/tmp/install-ninja ninja install cd .. libjcat-0.2.3/contrib/ci/build-debian.sh000077500000000000000000000003141475014707200200420ustar00rootroot00000000000000#!/bin/sh set -e export LC_ALL=C.UTF-8 mkdir -p build && cd build rm -rf * meson .. \ -Dgtkdoc=true \ -Dtests=true $@ ninja -v || bash ninja test -v DESTDIR=/tmp/install-ninja ninja install cd .. libjcat-0.2.3/contrib/ci/build-fedora-w64.sh000077500000000000000000000006351475014707200205040ustar00rootroot00000000000000#!/bin/sh set -e export LC_ALL=C.UTF-8 mkdir -p build && cd build rm * -rf meson .. \ --cross-file=../contrib/mingw64.cross \ -Dintrospection=false \ -Dman=false \ -Dgpg=false \ -Dgtkdoc=false \ -Dtests=true $@ ninja -v || bash wine reg add "HKEY_CURRENT_USER\Environment" /v PATH /d /usr/x86_64-w64-mingw32/sys-root/mingw/bin ninja test -v DESTDIR=/tmp/install-ninja ninja install cd .. libjcat-0.2.3/contrib/ci/build-fedora.sh000077500000000000000000000004241475014707200200620ustar00rootroot00000000000000#!/bin/sh set -e export LC_ALL=C.UTF-8 mkdir -p build && cd build rm * -rf meson .. \ -Db_coverage=true \ -Dgtkdoc=true \ -Dtests=true $@ ninja -v || bash ninja test -v ninja coverage-text cat meson-logs/coverage.txt DESTDIR=/tmp/install-ninja ninja install cd .. libjcat-0.2.3/contrib/generate-version-script.py000077500000000000000000000107621475014707200217350ustar00rootroot00000000000000#!/usr/bin/python3 # pylint: disable=invalid-name,missing-docstring # # Copyright (C) 2017 Richard Hughes # # SPDX-License-Identifier: LGPL-2.1+ import sys import argparse import xml.etree.ElementTree as ET 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("."))) def usage(return_code): """print usage and exit with the supplied return code""" if return_code == 0: out = sys.stdout else: out = sys.stderr out.write("usage: %s \n" % sys.argv[0]) sys.exit(return_code) class LdVersionScript: """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"] introspectable = int(node.get("introspectable", 1)) version = node.get("version", None) if introspectable and not version: print("No version for", identifier) sys.exit(1) if not version: return None version = node.attrib["version"] if version not in self.releases: self.releases[version] = [] release = self.releases[version] if identifier not in release: release.append(identifier) return version def _add_cls(self, cls): # add all class functions for node in cls.findall(XMLNS + "function"): self._add_node(node) # choose the lowest version method for the _get_type symbol version_lowest = None # add all class methods 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 ): version_lowest = version_tmp # add the 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 ): 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 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"): self._add_node(node) for cls in ns.findall(XMLNS + "record"): self._add_cls(cls) for cls in ns.findall(XMLNS + "class"): self._add_cls(cls) def render(self): # get a sorted list of all the versions versions = [] for version in self.releases: versions.append(version) # output the version data to a file 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" for symbol in symbols: verout += " %s;\n" % symbol verout += " local: *;\n" if oldversion: verout += "} %s_%s;\n" % (self.library_name, oldversion) else: verout += "};\n" oldversion = version return verout 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=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()) libjcat-0.2.3/contrib/libjcat.spec.in000066400000000000000000000041111475014707200174610ustar00rootroot00000000000000%global glib2_version 2.45.8 %global json_glib_version 1.1.1 %define alphatag #ALPHATAG# Summary: Library for reading Jcat files Name: libjcat Version: #VERSION# Release: 0.#BUILD#%{?alphatag}%{?dist} 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 BuildRequires: gobject-introspection-devel BuildRequires: glib2-devel >= %{glib2_version} BuildRequires: json-glib-devel >= %{json_glib_version} BuildRequires: gnutls-devel BuildRequires: gnutls-utils BuildRequires: gpgme-devel BuildRequires: vala Requires: glib2%{?_isa} >= %{glib2_version} %description This library allows reading and writing gzip-compressed JSON catalog files, which can be used to store GPG, PKCS-7 and SHA-256 checksums for each file. This provides equivalent functionality to the catalog files supported in Microsoft Windows. %package devel Summary: Development package for %{name} Requires: %{name}%{?_isa} = %{version}-%{release} %description devel Files for development with %{name}. %package tests Summary: Files for installed tests %description tests Executable and data files for installed tests. %prep %autosetup -p0 %build %meson \ -Dgtkdoc=true \ -Dtests=true %meson_build %install %meson_install %check %meson_test %files %doc README.md %license LICENSE %{_bindir}/jcat-tool %{_datadir}/man/man1/*.1* %dir %{_libdir}/girepository-1.0 %{_libdir}/girepository-1.0/*.typelib %{_libdir}/libjcat.so.1* %files devel %dir %{_datadir}/gir-1.0 %{_datadir}/gir-1.0/*.gir %dir %{_datadir}/gtk-doc %dir %{_datadir}/gtk-doc/html %{_datadir}/gtk-doc/html/libjcat %{_includedir}/libjcat-1 %{_libdir}/libjcat.so %{_libdir}/pkgconfig/jcat.pc %dir %{_datadir}/vala %dir %{_datadir}/vala/vapi %{_datadir}/vala/vapi/jcat.deps %{_datadir}/vala/vapi/jcat.vapi %files tests %doc README.md %{_libexecdir}/installed-tests/libjcat/* %{_datadir}/installed-tests/libjcat/* %dir %{_datadir}/installed-tests/libjcat %changelog %autochangelog libjcat-0.2.3/contrib/mingw64.cross000066400000000000000000000005151475014707200171420ustar00rootroot00000000000000[binaries] c = '/usr/bin/x86_64-w64-mingw32-gcc' cpp = '/usr/bin/x86_64-w64-mingw32-g++' ar = '/usr/bin/x86_64-w64-mingw32-ar' strip = '/usr/bin/x86_64-w64-mingw32-strip' pkgconfig = '/usr/bin/x86_64-w64-mingw32-pkg-config' exe_wrapper = 'wine' [host_machine] system = 'windows' cpu_family = 'x86_64' cpu = 'i686' endian = 'little' libjcat-0.2.3/contrib/s390x.cross000066400000000000000000000004221475014707200165320ustar00rootroot00000000000000[binaries] c = 's390x-linux-gnu-gcc' cpp = 's390x-linux-gnu-cpp' ar = 's390x-linux-gnu-ar' strip = 's390x-linux-gnu-strip' pkgconfig = 's390x-linux-gnu-pkg-config' exe_wrapper = 'qemu-s390x' [host_machine] system = 'linux' cpu_family = 's390x' cpu = 's390x' endian = 'big' libjcat-0.2.3/data/000077500000000000000000000000001475014707200140445ustar00rootroot00000000000000libjcat-0.2.3/data/meson.build000066400000000000000000000000551475014707200162060ustar00rootroot00000000000000if get_option('tests') subdir('tests') endif libjcat-0.2.3/data/tests/000077500000000000000000000000001475014707200152065ustar00rootroot00000000000000libjcat-0.2.3/data/tests/colorhug/000077500000000000000000000000001475014707200170305ustar00rootroot00000000000000libjcat-0.2.3/data/tests/colorhug/firmware.bin000066400000000000000000000175001475014707200213410ustar00rootroot000000000000001 (~1 ðpÿ~ 1(üêëìíîï 0„0…'0™12!1 0„0…@0™12!1 0„0…@0™12!1@0„™0…Ø0†0‡0™1,!~ š1±*!Õ 02=Ž!4W(4†‡  9ÇÈGÊHËY(ÊËKJj(0ÇGº9™1H!!!3{(3†‡  9ÇÈGÌHÍ}(ÌÍML‡(0ÇG" Ø)!º 0ñ0òÿ0ó0ô@0õ0ö™1Û!1# !ÇGÔw)0¹(™1T!1¸(#!™1€!1£)™1'!1¸(#!™18!1£)™1p!1!ÇG"¢£)#!™1Ã!1£)0 >†0‡Þ0„…0!ÇÇ Ï(£)Þ0†‡0 >„0…0!ÇÇ ß(£)0 >†0‡Ø0„…0!ÇÇ ï(£)0!Ó0Ò0Ñ0Ð0 >†0‡Ð0„…0!ÇÇ )£)!^ Ô!_ ÕÖד1Ï#1 W!Ó V!Ò U!Ñ T!Ð0 >†0‡Ð0„…0!ÇÇ 5)£)$0!ÇG¹£)#!:N)!ÿ:N) 0!ÇGr)€0ñ?0ò@0ó0ô›1x#1!ÇGÕU£)€0ñ?0ò0ó0ô¡0ÇGõœ1¼$1!ÈHÕ£)ÕÕ £)T:¡(:¥(:µ(:¾(:Ä(:Ô(:ä( :ô(:«(:¯(/: )::) :@):Ÿ(t)!3´)3†‡  9ÇÈGÎH϶)ÎÏONØ)UÇG" !TÇG"¡ññ 0!ÇGò@0ÈHó0›11#1!ÉI³ñ 0!ÇGò@0ÈHó0›11#!ÉI´ B:*@98*!À A:ÿ)!®® *!® B@9:D9:D9Û0[!2 :À Ä,*D9«>†‡Û[àD9©>8*D9j>†‡Û[àD9§>†‡Û[ß_E*û0Û[àF*`ÄN*`ÛD9«>S*`ÛD9j>†‡[A:t*`†‡l*Äf*D9©>i*D9§>†‡„0Û`†‡[0Û[à`†‡˜*0Û`†‡[`†‡0Ó0Ô`Û[Õ0Ö0ל1$‘1* `†‡0 Û[àß©*`†‡Ì*į*D9©>²*D9§>†‡;0Û`†‡[0Ó0Ô`Û[Õ0Ö0ל1$‘1Ô*;0 Û`†‡[ D9˜>ÛÜ0Ü=[Ý\Þþ0Û]†^‡[!²ý*=Ž’0‘Ÿ0—{0’Žú*Žö*!²² 2:+=Ž+’0 çg!²=+û0 çg=™1–!’1=Ž!+ 3+•1Ÿ%’10 çg!²þ0 çg=?+™1¬!’1ï0 çg=V+P+s0 Ó0ÔÕ0Ö0ל1$’1¿0 çg=’\+™1ø!’1=’o+ÿ0 Ó0ÔÕ0Ö0ל1$’1=“0!2w+ =’Ì+ éÅ+ çgïo çç g 9èh!¸÷0 çg= o˜+!8©>›+!8§>†‡9 ç0çgþ9g!¸°+1Ï%’1À+r0 Ó0Ôo0çgÕ0Ö0ל1$’10 çgé0iÌ+=}+ 0 è0ç0æ0å ØXä0ã0â0á0àWcò+Vbò+Uaò+T`,d þ+ , ØXä0à0á=0â=0ã=ä+Wc,Vb,Ua,T` ,0×0Ö0Õ0Ô0ã0â0á0àWc6,Vb6,Ua6,T`b,d Y, U,0å0æ=0ç=0è=hgfeU,ÿ0×ÿ0Öÿ0Õÿ0Ô ØXä0à0á=0â=0ã=(,™1T!“1 ØXäd >„•0>…ØÙÚÛ[ôZóYòXñhøg÷föeõš1C"“1t ßsÞrÝqÜh_Ÿ,g^Ÿ,f]Ÿ,e\ª,ÿ0×ÿ0Öÿ0Õÿ0Ôh×gÖfÕeÔ ÈÉç,!À H!.:ó, Èó,!Àó,!À ÄÏ,D9«>Ò,D9j>†‡ñqòr†‡ó,r†‡ó,0ñqÈó,@9:·,:Ã,:Æ,ó,!ÀH0¾0¿@0ñqÁ›4•4W4•4q4•484f40464444444•4 44)44444À4–4 444444444 4!44444"4444444@4444444@44444ÿ4 44¡4444)4@444&4ÿ44u44•4@44444)4@4‘44À444H44u44g44h44s44k44i44 44L44t44d44.4444C44o44l44o44r44H44u44g44A44L44S4444444444?4'44444444444 44!=“˜0ô™0óõu½-s†t‡0ó0ô=0ñqõu­-0=‘Ÿ0—{0’ö0vâ-vñ0ñ5ÿ>Ì-q5 >†‡0À?0Á?Â?Ã?0ñqö0vÉ-=–=ô-÷0ñq!À¢£¤ç-0ñq!µ0ñq¶°±¯ö0v!.vj>†‡v«>†‡v§>†‡v©>†‡0ñqö0v.(0ñq ê0=˜@0ñ0òq ¢r£0¡„0ñq !ò n†‡A?övóôt!$L.s#R.#ósö0 >†‡vóôó ô ó ô @?s£A?t=¤õvu}.uH>†‡ó †‡s0ós 0ósõe.0 >†‡®.0ó| †‡sH0ó0ô|>†‡sÀ?tÁ? n†‡¦.È0ó|†‡sˆ0ó|†‡s0ó| †‡s@0ó0ô|>†‡sÀ?tÁ?„0ó|†‡s!¢ ì1p% @9:DA:ø./!C:/#0¾•0¿ 0ð.:0!¾•0¿0Á0ÂÀ0ñqÀ/ C!:ß.:ê.:// @‰ 9:@/Æ0!¾0¿0Á0ÂÁ0ñqÀ€0ñq!À Cñq!ÆÅ0!¾0¿0Á0Â0ñqÀ€0ñq!À Bñq!Å A:U/: /:&/ :U/:/:4/U/!À0 Ü™0ÛÝ]q/[†\‡0Û0Ü=0û{Ý]a/ 0ñ0ò0ó0ô 0õ0ö™1Û!—1= Þ0^š/^§>†‡^©>†‡0û{Þ0^ˆ/Ä0ñ0ò0ó0ô0õ0ö™1Û!—1=(0û{ ê 0û{înû{üBû{!ÃÃÅ/0û{²0 Ó0ÔÃ0û{Õ0Ö0ל1$ 0û{!² Òxã/ñRš1u"—1øë/ññ Rš1u" R˜>ùú0ú=yÐzÑP†Q‡xœ1†,œ1‰,œ1»,œ1‰,œ1»,œ1m,œ1v,œ1¤,œ1|,œ1y,œ1Œ,œ1˜,œ1», ‰ 9þþ -)4d€1‰ 3)4ñq ŽŽ444444ÿ4ÿ4K)Q)!9$:I)Q)!¹  ‰ 9ñ0ñ5‰ Z)9qó 0òû0ññ f)ò f)20ób) ‰ 9ñ0ñ5‰ v) òò r 9qòr9ññ qï9qŽr69ññ ñ qû9qŽ!¯t0 Ó0ÔÕ0Ö0ל1$=Žû0û{=ï0û{Ž!¯¯ u0 Ó0ÔÕ0Ö0ל1$òr9ññ ñ ñ q÷9qŽr69ñññ qß9qŒrøøq÷÷0õ0ö;ÿ0vë)ÿ0uw†x‡s0÷0ø=á)=* n†‡€:*j†‡„:*Œ0ñn†‡q=ß0ñq m: *–1<& í!­A*0÷| †‡w@0÷0ø|>†‡wÀ?xÁ?Œ0÷|†‡w­0 Ó0Ò0Ñ0ÐqW*u ÐvÑ=wÒ=xÓ=0õ5ö ÷ ø ‰ X*0ô6ó ò ñ ‰ _*tsrqL* SôRóQòPñö 0òr÷qò0ò5‰ }*vó0ó5ÿ>ƒ*s5rô0ô5ÿ>‹*t5õu÷w†‡ñŸ*wòv«>£*wòvj>†‡rw†‡w>†‡=Žº*ŽŽÿ0™1a!š10™18!š10™1€!š10™1Ã!š1•1Ÿ%š1!×0Wè*0™18!š1_0™1a!š10™18!š1_0™1a!š10!ÖV×0WÏ*d’1é"š11= š1è*!½ ì!°±­ í0âj†‡b0âbê0âj†‡b0âbê0â|†‡b!ÀÁ¢£¤œ1e$š10 Ó0ÔÕ0Ö0ל1$š1ž1«&öq8+vj>:+v«>†‡ôt÷÷E+0rô0õw>†‡tÀ?uÁ?sôw †‡t@0ôw†‡tˆ0ôw†‡tqn+0ôvj>r+0ôv«>†‡tw@0r0q+0?0qõ0rövu+0?0sõ0tövu™+00ò6ñ ‰ š+÷øtx¦+swÂ+#wqõxr=öu‘v’U0–ª0–• 0÷0ø= +#0!°± m:é+0ô| †‡tH0ô0õ|>†‡tÀ?uÁ?È0ô|†‡t!B Gó+!A Fþ+G! F!ÁÁž19&H0ô0õ j>†‡tÀ?uÁ?È0ôj†‡tJ,™1p!œ1 ØX!»0™1Ã!œ10™18!!;™1Ã!0 ØXø0—1Ú'œ1ñ 0 ØXò@0ÙYó0›11# ÚZ!´–1Ñ& T:P,d,S:+,:G,:d,w:d,:&,:,d, @‰ 9:°,!À0 áa!²Ÿ1#'—1V'Ã0!¾0¿@ÁÁ À”1³$‘1ê! DÄ>!¾0¿@ÁÁ À!À BáDÄ>†‡a0Ó0ÔÕ0Ö0ל1$ A„ 0˜15>‚@0r0qÅ,00ò6ñ ‰ Æ,øùtyÒ,sx-#•r’’q‘‘0ñ0ò=xuöv†0‡“x>ö0y=÷twø,sv-x>uöv†0‡”U0–ª0–•0ø0ù=Ì,#0 j†‡00ö0ôtê!2:3- B=–.-0/-0ôt!² m:j-H0ô0õj>†‡tÀ?uÁ?ž19&1!=:U-„0ô j†‡töa-È0ô j†‡tˆ0ô j†‡t!¢¢ í!µ0ñqµ¶0ñq¶ m:-j †‡È0ñj†‡qm:!­0ñ n †‡q@0ñ0òn>†‡qÀ?rÁ?„0ñn†‡q!­­ 0ñ| †‡q@0ñ0ò|>†‡qÀ?rÁ?€0ñ|†‡q oý9..o6?9ã0ã5ÿ>Ø-c5 >ädînãcü0ãcün†‡ ‰ 9 :+.æ0f.n>†‡@?ãA?äc†d‡åf@>†‡en>†‡0A1 0ãcæ0f÷-@0ã0än>†‡cÀ?dÁ?š1ð"š1"ý0 oãc:1%0ñqó0!B0AW.Añqó½O.½½ W.=:W.0ñq½0¾>†‡sñòñ ò ñ ò @?qÁA?r=Âsñ j †‡qH0ñq!¼@Ž.§.>„?…ñ<†‡q0¾0¿=0ñq¼0óóy.>„?…ñ<†‡q0¾0¿=0ñq¼0óó’.=!Àä.¢Á.0öv í!±½.›1Æ#!¶µ0ö| †‡v@0ö0÷|>†‡vÀ?wÁ?Œ0ö|†‡v„0ö j†‡v Àú.íí !°ð.›1Æ#ž1!¶µ ì1p%0öv í0ö| †‡v@0ö0÷|>†‡vÀ?wÁ?„0ö|†‡v!¶µ ì1p% @€:À0ñq!À/‰0!¾•0¿0Á0 Bñòñ5ò 0q„•0r=…?!¾?¿?ò>ñ0q„rr …ósÁ?ò>ñ0q„rr …ósÂ0 B‰/Bñòñ5ò 0q„•0r=…?!¾?¿>„?…ñòqÁrÂ!À!À C:./:8/:h/Œ/libjcat-0.2.3/data/tests/colorhug/firmware.bin.asc000066400000000000000000000007311475014707200221040ustar00rootroot00000000000000-----BEGIN PGP SIGNATURE----- Version: GnuPG v1 iQEcBAABAgAGBQJVLPMHAAoJEK2KUo/sRIgeAxAH/jPY7c2qrG4UEsZXgUFxMUQe QEufh3cK9cv8kA7SAzpSHy6M0rNanC2vCqcc/fTJI/yBRfBjPPZYEsQgwpB/8m9y wiTPRuQySwCKsH+ZXNh3j6x8Oaf3DTiO7bJI/M3sOb4fdvb0Csp910g67Nt+HtMw I5EUM0uvMquZTUygp9B6BBJv8xRKtCNgqvPhyoDZKxKrPzaFwvb7BY50Q03LymU6 hQUIkjHIvMcTljNocOZNvTBHvEGB2BiBb60QhAXYyNfDrS58pm2JHfw/pgOuQTzT 3Lw9qmedRXbWR95u/piUmyUsY5ey75lD08U/2aE9RLBZ9xR17u1mAgyLGoIMYEk= =ZdoH -----END PGP SIGNATURE----- libjcat-0.2.3/data/tests/colorhug/firmware.bin.ed25519000066400000000000000000000001001475014707200223220ustar00rootroot00000000000000ð. ¡]é)š‚Èg?j«ÃÉ é^îQ±ËqR¢ o°AÛˆØ*”·Ik¿ò¤”é™åžÍ%æyQZ†Ëlibjcat-0.2.3/data/tests/colorhug/firmware.bin.p7b000066400000000000000000000043251475014707200220310ustar00rootroot00000000000000-----BEGIN PKCS7----- MIIGYAYJKoZIhvcNAQcCoIIGUTCCBk0CAQExDTALBglghkgBZQMEAgEwCwYJKoZI hvcNAQcBoIIESDCCBEQwggKsoAMCAQICDFmdjlgcgXiV33RlVTANBgkqhkiG9w0B AQsFADA6MRAwDgYDVQQDEwdMVkZTIENBMSYwJAYDVQQKEx1MaW51eCBWZW5kb3Ig RmlybXdhcmUgUHJvamVjdDAeFw0xNzA4MDEwMDAwMDBaFw0xOTA4MDEwMDAwMDBa MBkxFzAVBgNVBAMTDlJpY2hhcmQgSHVnaGVzMIIBIjANBgkqhkiG9w0BAQEFAAOC AQ8AMIIBCgKCAQEA5XlsYGdD5isOAEim4tRR9usJa8C4Gs3TUPfe5EfXcIT44dJr plVcXpH2Wau/Pbcvc/2cY/bZmgcRMgw8O/4HoJyCCCKfjCfT6yN9BlLnxAgZVLSw QT2d2JW0m5bY/VgZNwdNZWb+fMnPDx7JMCjtdpUpwQ0R6hwrryRt+6zFyhDayCCL GOsxpmo7Fc9ix/nP5DEcPjU6Bofz0jFFMesod8babaQSWm2b/QN7aTgkrPjslC+p BkTLq7IrndgQzLKI9bXn++LFKE2Srm0nHZ6DapKCgsSE3UOqDGtKTUf86aT2IGnV 5JzTZ/HZk/sGqAyS2wb5m13rJfbzkKnf9c14qwIDAQABo4HqMIHnMAwGA1UdEwEB /wQCMAAwRQYDVR0RBD4wPIYlaHR0cHM6Ly9zZWN1cmUtbHZmcy5yaGNsb3VkLmNv bS9sdmZzL4ETcmljaGFyZEBodWdoc2llLmNvbTATBgNVHSUEDDAKBggrBgEFBQcD AzAPBgNVHQ8BAf8EBQMDB4AAMB0GA1UdDgQWBBSZpooSP4z6IVWsXkoUjpByAh5D fzAfBgNVHSMEGDAWgBSxjerkI6d+CY617jHgat2eNDdlrDAqBgNVHR8EIzAhMB+g HaAbhhlodHRwOi8vd3d3LmZ3dXBkLm9yZy9wa2kvMA0GCSqGSIb3DQEBCwUAA4IB gQBSXRGZB6YR8wTyuOdEelRcJj45Mz5tiuuCfei8ZOTyFzkGjnRno0Dl57tnmUmX ufN2Rb9yzXBGHmSTXT6j2uVg+U1xevPAVCWlIslhwxJcqncfpALxL7TwVL5PpJls /Ao7y/KkS5Bxd8u45A2/wIFkawxn/X0nRmwNh6jF9m3+NSwCv3QxYdgGcfhzD96p 6hG+DuXT97h0lJ3gJJDPbVkWTvuhoNo+iEz8fAfSmlk12HDQ+oQIGRgpFZYHREFr 2/A2HoBfAPFVdmRfYWNrxODrVg3tQEHmtxG7HIHocyRSVzqd31yJKgkwh4I9meUY rCOf0hhMjWmxiviPKJx4SEcNg7Ye8Ib2OtXxcQbZ71ax57dUyVZZXEcfR3KjBuFp vY6QnVF5D3NsyV5q3M1VV8XRh9ELRafruX+Ygx8NLkDPKqFGZh0xKDzr55gJF9q8 rfuHjQ/cd5tokRMI1qlGymbQ/bWgsLBO2MOWeZezITBO1ZVbz6QMJ4YnvHug8nsZ /SkxggHeMIIB2gIBATBKMDoxEDAOBgNVBAMTB0xWRlMgQ0ExJjAkBgNVBAoTHUxp bnV4IFZlbmRvciBGaXJtd2FyZSBQcm9qZWN0AgxZnY5YHIF4ld90ZVUwCwYJYIZI AWUDBAIBoGkwGAYJKoZIhvcNAQkDMQsGCSqGSIb3DQEHATAcBgkqhkiG9w0BCQUx DxcNMTcwODIzMTQzNTQ1WjAvBgkqhkiG9w0BCQQxIgQgoZZQTQmHHaT32DuHS1AP jubgYZq3mfB0gUsxbYj5b38wDQYJKoZIhvcNAQEBBQAEggEAWc7kxSri1v+c+N8h S8cerVmAPBm150DjB58F3gxSl91gs/z8d1uWOx88eX0DjOU4C7sQj7E9WiZSPcvb z2KvXqg7MJy+ev9wXPwDqqPtsVZdLKd665JqF7kfSXxpMFzutu/NxW7UUUrKot4v d93NlAEXmjjuQ8V6STtYapxzyuWGXThI/K89kXaMvzmqTYQ4S9+98sXG1PMX69zm z00PT+rL2QGMsZCSUcnE/u38s0q7uCEfBB9uoq5QIECYch65ezX3H2GqVcKPG4M3 6Ttko+W01+2IIPN02ZHPqXqEw8diTiMYS5HVRD7nVs5TTxNNB+rAIBR+mJJBkxin 7MLHjQ== -----END PKCS7----- libjcat-0.2.3/data/tests/colorhug/firmware.bin.sha256000066400000000000000000000001001475014707200223340ustar00rootroot00000000000000a196504d09871da4f7d83b874b500f8ee6e0619ab799f074814b316d88f96f7flibjcat-0.2.3/data/tests/colorhug/meson.build000066400000000000000000000025311475014707200211730ustar00rootroot00000000000000install_data([ 'firmware.bin', 'firmware.bin.asc', 'firmware.bin.ed25519', 'firmware.bin.p7b', ], install_dir: join_paths(installed_test_datadir, 'colorhug'), ) if get_option('pkcs7') # generate self-signed detached signature colorhug_pkcs7_signature = custom_target('firmware.bin.p7c', input: 'firmware.bin', output: 'firmware.bin.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'), ) # 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 libjcat-0.2.3/data/tests/libjcat.test.in000066400000000000000000000002301475014707200201170ustar00rootroot00000000000000[Test] Type=session Exec=sh -c "G_TEST_SRCDIR=@installed_test_datadir@ G_TEST_BUILDDIR=@installed_test_datadir@ @installed_test_bindir@/jcat-self-test" libjcat-0.2.3/data/tests/meson.build000066400000000000000000000012071475014707200173500ustar00rootroot00000000000000install_data([ 'secret.ed25519', 'test.btcheckpoint', 'test.btverifier', ], install_dir: installed_test_datadir, ) configure_file( input : 'libjcat.test.in', output : 'libjcat.test', configuration : conf, install: true, install_dir: installed_test_datadir, ) # generate private PKCS7 key certtool = find_program('gnutls-certtool', 'certtool') pkcs7_privkey = custom_target('test-privkey.pem', output: 'test-privkey.pem', command: [certtool, '--generate-privkey', '--outfile', '@OUTPUT@'], ) subdir('pki') subdir('colorhug') testdatadir_src = meson.current_source_dir() testdatadir_dst = meson.current_build_dir() libjcat-0.2.3/data/tests/pki/000077500000000000000000000000001475014707200157715ustar00rootroot00000000000000libjcat-0.2.3/data/tests/pki/GPG-KEY-Linux-Vendor-Firmware-Service000066400000000000000000000016771475014707200244720ustar00rootroot00000000000000-----BEGIN PGP PUBLIC KEY BLOCK----- Version: GnuPG v2 mQENBFWt/98BCADZ4+lUHSp4OMlzVf4HlJNLJ7Ks5QxGwL/hy2wChoNLuA/j4GNM 9mBZutKynYmphD0Mi4XjXn7JNXyuJa8Qutz98/Iyhsjq4LeiL9ayaKMXT+3pKlTm Gd/Fzo3QEOqTJ5s2RamrfwFIVuvwoj+rNmzj5fUCgoDOZeqVl6gxb7ZPzL8sWTOU iLeGMSzZBGE0ioJ82PZzsHelrrObDP1mMre1jQ6zxLlnYUlLvtJpydAfeBxU+6yL fgPeoFeuCE6JIszyWuyAgpBpYSGgj1bpt9Sxc2+MoZ0BjDzoijZqt4O48gYuEaLf iqYzQybe1JF0McO4C0dmjdKQz2qm0XrQyNhVABEBAAG0LkxpbnV4IFZlbmRvciBG aXJtd2FyZSBTZXJ2aWNlIDxzaWduQGZ3dXBkLm9yZz6JATcEEwEIACEFAlWt/98C GwMFCwkIBwIGFQgJCgsCBBYCAwECHgECF4AACgkQSKbYDkU4usJjjQgAzmTcA8qH s+1kieEZvsUzH4wun2Hlz7R5FRc/7BijgIQAA9TTrJnwbJmEBzEvHv7FKQLiBN3a 0lQIZgahmcUt1qm6VW94VAio+SDCdqTx73wUsgM3t9sAwKxkEdJQQoO8PqYHV3uK rq0t2YjXglIBHRDiJlOTAR3if37OCDKCcHOOODqYrsN7wNleez+ulkDyP7C7ZTbm /A7Xec73t2OQUnejU0uvRvc7VSnQDRFBHA9TPiBhbruMw+ZX+z/wfPd7x2RCqoOE vHh+QofE41Ya2QOkT96fAKfcJ+gvIbmwp3w7h+Hus1h3xDrykCG9cCxuH0HxooVI XL3IlFx/6OUpBA== =6Dz2 -----END PGP PUBLIC KEY BLOCK----- libjcat-0.2.3/data/tests/pki/LVFS-CA.pem000066400000000000000000000032171475014707200175320ustar00rootroot00000000000000-----BEGIN CERTIFICATE----- MIIEqjCCAxKgAwIBAgIBATANBgkqhkiG9w0BAQsFADA6MRAwDgYDVQQDEwdMVkZT IENBMSYwJAYDVQQKEx1MaW51eCBWZW5kb3IgRmlybXdhcmUgUHJvamVjdDAeFw0x NzA4MDEwMDAwMDBaFw00NzA4MDEwMDAwMDBaMDoxEDAOBgNVBAMTB0xWRlMgQ0Ex JjAkBgNVBAoTHUxpbnV4IFZlbmRvciBGaXJtd2FyZSBQcm9qZWN0MIIBojANBgkq hkiG9w0BAQEFAAOCAY8AMIIBigKCAYEAtfUXH3NwDJzWyhkPyPcFI899+tPZ/SMp OkDtRr9dJjgQkSO9jKCue4DVq8Bd9RcL76F7XnEKG0LiuKnr+D7+x86TtDAPCbkP WAS7fAaetLtiNFU96cokhjeALB3hyamkMQnCw+5Ov+sHJfGI9Bor9UaIIbIB4r8v oU1WpE7N6Ix2qsS5b88+Z6EIV6CX8RbciOC/TfyYVnpF1cd4l7LH7TtL+ERpsPwv rk0JgVoRzG3BT5yYfuxHIe4H4Axh95tW9i6urzyQkXRz14twwwcEDvl5ALrBLNJJ 8EDz9oR8HBPbxbd4i2dBfziY7TW4o/VgZKTGWA39JfwWNc5RxaYzBhBmg5nRcVFs E7PlovhyFH/0RNm/3E6vZQCeM+FNps0ovVq8Yqg8whL/yZ0iNlavCGTWhaxisVHG 7mQopV4jZlafxvrcBFzK8RPe8Gi04FFn4ugZtJnOuMel+AiADhgtWZCENiyWV+V7 WF1SFF4HaHuS8qqna/p9lrpVq6TBr0WRAgMBAAGjgbowgbcwEgYDVR0TAQH/BAgw BgEB/wIBATAwBgNVHREEKTAnhhVodHRwOi8vd3d3LmZ3dXBkLm9yZy+BDnNpZ25A Znd1cGQub3JnMBMGA1UdJQQMMAoGCCsGAQUFBwMDMA8GA1UdDwEB/wQFAwMHBgAw HQYDVR0OBBYEFLGN6uQjp34JjrXuMeBq3Z40N2WsMCoGA1UdHwQjMCEwH6AdoBuG GWh0dHA6Ly93d3cuZnd1cGQub3JnL3BraS8wDQYJKoZIhvcNAQELBQADggGBABNK mC4AcqsBCVRGpwJeUymh5G6uUpzkoEDw+y9TEoWzfldV0epU7ruqI2p8B8YshDK6 +D4CFmCnW8cc+Jb6jrJ2ZcjUqWE/c+uwZhwsUHNdk6ummPPKfMhRSbduk1ngdQe5 meIgWGkoCfJ48GUAVVD6MlrMTNFsot1GN9x3ALMqhSU49+X43yikcc9WY2F8JOY8 xYpGpgUQV1hBSPOGK4XhgztpFLqw0GxJiLrOfKjtJwSTkxGCpPi2dLS0huk/mreT NAQ5FnMLkoqfR1RGga3tiP5w13gqDBV7a6MYMdmMfAAZhfRtlDu6SiAmjEmlSkOK PNhdoCNVDQLQpGaKZUI5hjMfR90U8Cm/6e0ondwjV4J6f4CS4wkQ5zzITGWptagE 01tpgTXf7TLaFGtzR8cl8XgV+UO3T4DQjEQkXUaS7n72ZCGv/s4LraLunhBrVHSq glEXpU/V/JNptgArIiRFZOrto52cUnnlNEfgqIzAHv/LMFRIkMo8ZMGTgScFrA== -----END CERTIFICATE----- libjcat-0.2.3/data/tests/pki/meson.build000066400000000000000000000011541475014707200201340ustar00rootroot00000000000000install_data([ 'GPG-KEY-Linux-Vendor-Firmware-Service', 'LVFS-CA.pem', 'test.ed25519', ], install_dir: join_paths(installed_test_datadir, 'pki'), ) # generate certificate pkcs7_config = join_paths(meson.current_source_dir(), 'test.cfg') pkcs7_certificate = custom_target('test.pem', input: pkcs7_privkey, output: 'test.pem', command: [certtool, '--generate-self-signed', '--template', pkcs7_config, '--load-privkey', '@INPUT@', '--outfile', '@OUTPUT@'], install: true, install_dir: join_paths(installed_test_datadir, 'pki'), ) libjcat-0.2.3/data/tests/pki/test.cfg000066400000000000000000000001351475014707200174300ustar00rootroot00000000000000organization = "Hughski Limited" expiration_days = -1 email = "info@hughski.com" signing_key libjcat-0.2.3/data/tests/pki/test.ed25519000066400000000000000000000000401475014707200176620ustar00rootroot00000000000000„V¥6Ï¿“?ÖõTÎ3£œí¢g,Œ…«ŽÏpFåùlibjcat-0.2.3/data/tests/secret.ed25519000066400000000000000000000001001475014707200174020ustar00rootroot00000000000000M‹°O±8©+¸5ôƒÂ|“tÇ\B©\j Œº7‘ÏÆè§²ÓÌ×l ÞS‡NÖv™ƳØÈ-a²'c~²«Ñ libjcat-0.2.3/data/tests/test.btcheckpoint000066400000000000000000000002371475014707200205660ustar00rootroot00000000000000lvfsqa 4 KdF0yX/GfFEPKXB1+v0pnT0lxdvquyYRybl5/HhmgWk= — lvfsqa xGPwhE1lSYI9j14ckjbUMmPII56Niamhq+x3IpeeEPMiYU6x+iGDXUkvorVzvK9sOY6zeQYZhr91qAkqprEaJaZpWQw= libjcat-0.2.3/data/tests/test.btverifier000066400000000000000000000000741475014707200202510ustar00rootroot00000000000000lvfsqa+c463f084+AbAFMnhYEhBWzVlO0eGRA6KtPP3FCWpdg/FRRPMIlys6libjcat-0.2.3/docs/000077500000000000000000000000001475014707200140635ustar00rootroot00000000000000libjcat-0.2.3/docs/libjcat-docs.xml000066400000000000000000000026611475014707200171500ustar00rootroot00000000000000 ]> libjcat Reference Manual About libjcat libjcat is a library for reading Jcat files. libjcat Functionality exported by libjcat for client applications. API Index Index of deprecated API libjcat-0.2.3/docs/libjcat.types000066400000000000000000000001711475014707200165600ustar00rootroot00000000000000jcat_blob_get_type jcat_context_get_type jcat_engine_get_type jcat_file_get_type jcat_item_get_type jcat_result_get_type libjcat-0.2.3/docs/meson.build000066400000000000000000000002551475014707200162270ustar00rootroot00000000000000gnome.gtkdoc( 'libjcat', src_dir : [ 'libjcat', join_paths(meson.current_build_dir(), '..', 'libjcat'), ], main_xml : 'libjcat-docs.xml', install : true ) libjcat-0.2.3/libjcat/000077500000000000000000000000001475014707200145435ustar00rootroot00000000000000libjcat-0.2.3/libjcat/jcat-blob-private.h000066400000000000000000000007711475014707200202260ustar00rootroot00000000000000/* * Copyright (C) 2020 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #include "jcat-blob.h" #include "jcat-common.h" JcatBlob * jcat_blob_import(JsonObject *obj, JcatImportFlags flags, GError **error) G_GNUC_NON_NULL(1); void 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) G_GNUC_NON_NULL(1, 3); libjcat-0.2.3/libjcat/jcat-blob.c000066400000000000000000000312671475014707200165550ustar00rootroot00000000000000/* * Copyright (C) 2020 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include "jcat-blob-private.h" #include "jcat-common-private.h" typedef struct { JcatBlobKind kind; JcatBlobKind target; JcatBlobFlags flags; GBytes *data; gchar *appstream_id; gint64 timestamp; } JcatBlobPrivate; G_DEFINE_TYPE_WITH_PRIVATE(JcatBlob, jcat_blob, G_TYPE_OBJECT) #define GET_PRIVATE(o) (jcat_blob_get_instance_private(o)) static void jcat_blob_finalize(GObject *obj) { JcatBlob *self = JCAT_BLOB(obj); JcatBlobPrivate *priv = GET_PRIVATE(self); g_free(priv->appstream_id); g_bytes_unref(priv->data); G_OBJECT_CLASS(jcat_blob_parent_class)->finalize(obj); } static void jcat_blob_class_init(JcatBlobClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS(klass); object_class->finalize = jcat_blob_finalize; } static void jcat_blob_init(JcatBlob *self) { JcatBlobPrivate *priv = GET_PRIVATE(self); priv->timestamp = g_get_real_time() / G_USEC_PER_SEC; } /** * jcat_blob_kind_from_string: * @kind: A string * * Converts the string to an enumerated kind. * * Returns: a #JcatBlobKind, or %JCAT_BLOB_KIND_UNKNOWN if the kind was not found * * Since: 0.1.0 **/ JcatBlobKind jcat_blob_kind_from_string(const gchar *kind) { if (g_strcmp0(kind, "gpg") == 0) return JCAT_BLOB_KIND_GPG; if (g_strcmp0(kind, "pkcs7") == 0) return JCAT_BLOB_KIND_PKCS7; if (g_strcmp0(kind, "sha256") == 0) 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; } /** * jcat_blob_kind_to_string: * @kind: #JcatBlobKind * * Converts the enumerated kind to a string. * * Returns: a string, or %NULL if the kind was not found * * Since: 0.1.0 **/ const gchar * jcat_blob_kind_to_string(JcatBlobKind kind) { if (kind == JCAT_BLOB_KIND_GPG) return "gpg"; if (kind == JCAT_BLOB_KIND_PKCS7) return "pkcs7"; if (kind == JCAT_BLOB_KIND_SHA256) 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; } /** * jcat_blob_kind_to_filename_ext: * @kind: #JcatBlobKind * * Converts the enumerated kind to the normal file extension. * * Returns: a string, or %NULL if the kind was not found * * Since: 0.1.0 **/ const gchar * jcat_blob_kind_to_filename_ext(JcatBlobKind kind) { if (kind == JCAT_BLOB_KIND_GPG) return "asc"; if (kind == JCAT_BLOB_KIND_PKCS7) return "p7b"; if (kind == JCAT_BLOB_KIND_SHA256) 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; } /* private */ void jcat_blob_add_string(JcatBlob *self, guint idt, GString *str) { 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", priv->flags & JCAT_BLOB_FLAG_IS_UTF8 ? "is-utf8" : "none"); if (priv->appstream_id != NULL) jcat_string_append_kv(str, idt + 1, "AppstreamId", priv->appstream_id); if (priv->timestamp != 0) { g_autoptr(GDateTime) dt = g_date_time_new_from_unix_utc(priv->timestamp); #if GLIB_CHECK_VERSION(2, 62, 0) g_autofree gchar *tmp = g_date_time_format_iso8601(dt); #else g_autofree gchar *tmp = g_date_time_format(dt, "%FT%TZ"); #endif jcat_string_append_kv(str, idt + 1, "Timestamp", tmp); } if (priv->data != NULL) { g_autofree gchar *tmp = jcat_blob_get_data_as_string(self); g_autofree gchar *size = g_strdup_printf("0x%x", (guint)g_bytes_get_size(priv->data)); jcat_string_append_kv(str, idt + 1, "Size", size); jcat_string_append_kv(str, idt + 1, "Data", tmp); } } /** * jcat_blob_to_string: * @self: #JcatBlob * * Converts the #JcatBlob to a string. * * Returns: string * * Since: 0.1.0 **/ gchar * jcat_blob_to_string(JcatBlob *self) { GString *str = g_string_new(NULL); jcat_blob_add_string(self, 0, str); return g_string_free(str, FALSE); } /* private */ JcatBlob * jcat_blob_import(JsonObject *obj, JcatImportFlags flags, GError **error) { const gchar *data_str; const gchar *required[] = {"Kind", "Data", "Flags", NULL}; g_autoptr(JcatBlob) self = g_object_new(JCAT_TYPE_BLOB, NULL); JcatBlobPrivate *priv = GET_PRIVATE(self); /* sanity check */ for (guint i = 0; required[i] != NULL; i++) { if (!json_object_has_member(obj, required[i])) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "failed to find %s", required[i]); return NULL; } } /* get kind, which can be unknown to us for forward compat */ priv->kind = json_object_get_int_member(obj, "Kind"); priv->flags = json_object_get_int_member(obj, "Flags"); /* 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"); if ((priv->flags & JCAT_BLOB_FLAG_IS_UTF8) == 0) { gsize bufsz = 0; g_autofree guchar *buf = g_base64_decode(data_str, &bufsz); priv->data = g_bytes_new_take(g_steal_pointer(&buf), bufsz); } else { const gchar *tmp = json_object_get_string_member(obj, "Data"); priv->data = g_bytes_new(tmp, strlen(tmp)); } /* success */ return g_steal_pointer(&self); } void jcat_blob_export(JcatBlob *self, JcatExportFlags flags, JsonBuilder *builder) { JcatBlobPrivate *priv = GET_PRIVATE(self); g_autofree gchar *data_str = jcat_blob_get_data_as_string(self); /* 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) { json_builder_set_member_name(builder, "AppstreamId"); json_builder_add_string_value(builder, priv->appstream_id); } if (priv->timestamp > 0 && (flags & JCAT_EXPORT_FLAG_NO_TIMESTAMP) == 0) { json_builder_set_member_name(builder, "Timestamp"); json_builder_add_int_value(builder, priv->timestamp); } json_builder_set_member_name(builder, "Data"); json_builder_add_string_value(builder, data_str); } /** * jcat_blob_get_timestamp: * @self: #JcatBlob * * Gets the creation timestamp for the blob. * * Returns: UTC UNIX time, or 0 if unset * * Since: 0.1.0 **/ gint64 jcat_blob_get_timestamp(JcatBlob *self) { JcatBlobPrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(JCAT_IS_BLOB(self), 0); return priv->timestamp; } /** * jcat_blob_set_timestamp: * @self: #JcatBlob * @timestamp: UTC timestamp * * Sets the creation timestamp for the blob. * * Since: 0.1.0 **/ void jcat_blob_set_timestamp(JcatBlob *self, gint64 timestamp) { JcatBlobPrivate *priv = GET_PRIVATE(self); g_return_if_fail(JCAT_IS_BLOB(self)); priv->timestamp = timestamp; } /** * jcat_blob_get_appstream_id: * @self: #JcatBlob * * Gets the optional AppStream ID for the blob. * * Returns: a string, or %NULL if not set * * Since: 0.1.0 **/ const gchar * jcat_blob_get_appstream_id(JcatBlob *self) { JcatBlobPrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(JCAT_IS_BLOB(self), NULL); return priv->appstream_id; } /** * jcat_blob_set_appstream_id: * @self: #JcatBlob * @appstream_id: (nullable): string * * Sets an optional AppStream ID on the blob. * * Since: 0.1.0 **/ void jcat_blob_set_appstream_id(JcatBlob *self, const gchar *appstream_id) { JcatBlobPrivate *priv = GET_PRIVATE(self); g_return_if_fail(JCAT_IS_BLOB(self)); g_free(priv->appstream_id); priv->appstream_id = g_strdup(appstream_id); } /** * jcat_blob_get_data: * @self: #JcatBlob * * Gets the data stored in the blob, typically in binary (unprintable) form. * * Returns: (transfer none): a #GBytes, or %NULL if the filename was not found * * Since: 0.1.0 **/ GBytes * jcat_blob_get_data(JcatBlob *self) { JcatBlobPrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(JCAT_IS_BLOB(self), NULL); return priv->data; } /** * jcat_blob_get_data_as_string: * @self: #JcatBlob * * Gets the data stored in the blob, in human readable form. * * Returns: (transfer full): base64 encoded version of data * * Since: 0.1.0 **/ gchar * jcat_blob_get_data_as_string(JcatBlob *self) { JcatBlobPrivate *priv = GET_PRIVATE(self); gsize bufsz = 0; const guchar *buf = g_bytes_get_data(priv->data, &bufsz); g_return_val_if_fail(JCAT_IS_BLOB(self), NULL); /* may be binary data or not NULL terminated */ if ((priv->flags & JCAT_BLOB_FLAG_IS_UTF8) == 0) return g_base64_encode(buf, bufsz); return g_strndup((const gchar *)buf, bufsz); } /** * jcat_blob_get_kind: * @self: #JcatBlob * * gets the blob kind * * Returns: #JcatBlobKind, e.g. %JCAT_BLOB_KIND_SHA256 * * Since: 0.1.0 **/ JcatBlobKind jcat_blob_get_kind(JcatBlob *self) { JcatBlobPrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(JCAT_IS_BLOB(self), 0); return priv->kind; } /** * jcat_blob_new_full: * @kind: #JcatBlobKind, e.g. %JCAT_BLOB_KIND_SHA256 * @data: #GBytes * @flags: #JcatBlobFlags * * Creates a new blob. * * Returns: a #JcatBlob * * Since: 0.1.0 **/ JcatBlob * jcat_blob_new_full(JcatBlobKind kind, GBytes *data, JcatBlobFlags flags) { JcatBlob *self = g_object_new(JCAT_TYPE_BLOB, NULL); JcatBlobPrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(data != NULL, NULL); priv->kind = kind; priv->data = g_bytes_ref(data); priv->flags = flags; return self; } /** * 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 * * Creates a new blob. * * Returns: a #JcatBlob * * Since: 0.1.0 **/ JcatBlob * jcat_blob_new(JcatBlobKind kind, GBytes *data) { return jcat_blob_new_full(kind, data, JCAT_BLOB_FLAG_NONE); } /** * jcat_blob_new_utf8: * @kind: #JcatBlobKind, e.g. %JCAT_BLOB_KIND_SHA256 * @data: ASCII data * * Creates a new ASCII blob. * * Returns: a #JcatBlob * * Since: 0.1.0 **/ JcatBlob * jcat_blob_new_utf8(JcatBlobKind kind, const gchar *data) { JcatBlob *self = g_object_new(JCAT_TYPE_BLOB, NULL); JcatBlobPrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(data != NULL, NULL); priv->flags = JCAT_BLOB_FLAG_IS_UTF8; priv->kind = kind; priv->data = g_bytes_new(data, strlen(data)); return self; } libjcat-0.2.3/libjcat/jcat-blob.h000066400000000000000000000065731475014707200165640ustar00rootroot00000000000000/* * Copyright (C) 2020 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include "jcat-compile.h" #define JCAT_TYPE_BLOB jcat_blob_get_type() G_DECLARE_DERIVABLE_TYPE(JcatBlob, jcat_blob, JCAT, BLOB, GObject) /** * JcatBlobKind: * @JCAT_BLOB_KIND_UNKNOWN: No known blob kind * @JCAT_BLOB_KIND_SHA256: SHA-256 checksum * @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. **/ typedef enum { JCAT_BLOB_KIND_UNKNOWN, JCAT_BLOB_KIND_SHA256, 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; /** * JcatBlobMethod: * @JCAT_BLOB_METHOD_UNKNOWN: Unknown * @JCAT_BLOB_METHOD_CHECKSUM: Checksum * @JCAT_BLOB_METHOD_SIGNATURE: Signature * * The blob verification method. **/ typedef enum { JCAT_BLOB_METHOD_UNKNOWN, JCAT_BLOB_METHOD_CHECKSUM, JCAT_BLOB_METHOD_SIGNATURE, /*< private >*/ JCAT_BLOB_METHOD_LAST } JcatBlobMethod; /** * JcatBlobFlags: * @JCAT_BLOB_FLAG_NONE: Generic binary data * @JCAT_BLOB_FLAG_IS_UTF8: ASCII text * * Flags used when creating the blob. **/ typedef enum { JCAT_BLOB_FLAG_NONE = 0, JCAT_BLOB_FLAG_IS_UTF8 = 1 << 0, /*< private >*/ JCAT_BLOB_FLAG_LAST } JcatBlobFlags; struct _JcatBlobClass { GObjectClass parent_class; gpointer padding[15]; }; JcatBlobKind jcat_blob_kind_from_string(const gchar *kind); const gchar * jcat_blob_kind_to_string(JcatBlobKind kind); const gchar * jcat_blob_kind_to_filename_ext(JcatBlobKind kind); JcatBlob * jcat_blob_new(JcatBlobKind kind, GBytes *data) G_GNUC_NON_NULL(2); JcatBlob * 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) G_GNUC_NON_NULL(2); gchar * jcat_blob_to_string(JcatBlob *self) G_GNUC_NON_NULL(1); GBytes * jcat_blob_get_data(JcatBlob *self) G_GNUC_NON_NULL(1); gchar * jcat_blob_get_data_as_string(JcatBlob *self) G_GNUC_NON_NULL(1); JcatBlobKind 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) G_GNUC_NON_NULL(1); void jcat_blob_set_timestamp(JcatBlob *self, gint64 timestamp) G_GNUC_NON_NULL(1); const gchar * jcat_blob_get_appstream_id(JcatBlob *self) G_GNUC_NON_NULL(1); void jcat_blob_set_appstream_id(JcatBlob *self, const gchar *appstream_id) G_GNUC_NON_NULL(1); libjcat-0.2.3/libjcat/jcat-bt-checkpoint-private.h000066400000000000000000000004121475014707200220320ustar00rootroot00000000000000/* * 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); libjcat-0.2.3/libjcat/jcat-bt-checkpoint.c000066400000000000000000000167201475014707200203660ustar00rootroot00000000000000/* * 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) { } libjcat-0.2.3/libjcat/jcat-bt-checkpoint.h000066400000000000000000000020411475014707200203620ustar00rootroot00000000000000/* * 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); libjcat-0.2.3/libjcat/jcat-bt-verifier-private.h000066400000000000000000000004001475014707200215130ustar00rootroot00000000000000/* * 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); libjcat-0.2.3/libjcat/jcat-bt-verifier.c000066400000000000000000000103311475014707200200420ustar00rootroot00000000000000/* * 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) { } libjcat-0.2.3/libjcat/jcat-bt-verifier.h000066400000000000000000000013621475014707200200530ustar00rootroot00000000000000/* * 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); libjcat-0.2.3/libjcat/jcat-common-private.h000066400000000000000000000016011475014707200205710ustar00rootroot00000000000000/* * Copyright (C) 2020 Richard Hughes * Copyright (C) 2022 Joe Qian * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #include "jcat-common.h" #include "jcat-compile.h" gboolean jcat_mkdir_parent(const gchar *filename, GError **error) G_GNUC_NON_NULL(1); gboolean 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) G_GNUC_NON_NULL(1); void 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); libjcat-0.2.3/libjcat/jcat-common.c000066400000000000000000000102261475014707200171170ustar00rootroot00000000000000/* * Copyright (C) 2017 Richard Hughes * Copyright (C) 2022 Joe Qian * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include #include "jcat-common-private.h" /* private */ gboolean jcat_mkdir_parent(const gchar *filename, GError **error) { g_autoptr(GFile) file = g_file_new_for_path(filename); g_autoptr(GFile) file_parent = g_file_get_parent(file); if (g_file_query_exists(file_parent, NULL)) return TRUE; return g_file_make_directory_with_parents(file_parent, NULL, error); } /* private */ gboolean jcat_set_contents_bytes(const gchar *filename, GBytes *bytes, gint mode, GError **error) { const gchar *data; gsize size; g_autoptr(GFile) file = NULL; g_autoptr(GFile) file_parent = NULL; file = g_file_new_for_path(filename); file_parent = g_file_get_parent(file); if (!g_file_query_exists(file_parent, NULL)) { if (!g_file_make_directory_with_parents(file_parent, NULL, error)) return FALSE; } 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 */ GBytes * jcat_get_contents_bytes(const gchar *filename, GError **error) { gchar *data = NULL; gsize len = 0; if (!g_file_get_contents(filename, &data, &len, error)) return NULL; g_debug("reading %s with %" G_GSIZE_FORMAT " bytes", filename, len); return g_bytes_new_take(data, len); } static gsize jcat_strwidth(const gchar *text) { const gchar *p = text; gsize width = 0; while (*p) { gunichar c = g_utf8_get_char(p); if (g_unichar_iswide(c)) width += 2; else if (!g_unichar_iszerowidth(c)) width += 1; p = g_utf8_next_char(p); } return width; } /* private */ void jcat_string_append_kv(GString *str, guint idt, const gchar *key, const gchar *value) { const guint align = 25; gsize keysz; g_return_if_fail(idt * 2 < align); /* ignore */ if (key == NULL) return; for (gsize i = 0; i < idt; i++) g_string_append(str, " "); if (key[0] != '\0') { g_string_append_printf(str, "%s:", key); keysz = (idt * 2) + jcat_strwidth(key) + 1; } else { keysz = idt * 2; } if (value != NULL) { g_auto(GStrv) split = NULL; split = g_strsplit(value, "\n", -1); for (guint i = 0; split[i] != NULL; i++) { if (i == 0) { for (gsize j = keysz; j < align; j++) g_string_append(str, " "); } else { for (gsize j = 0; j < align; j++) g_string_append(str, " "); } g_string_append(str, split[i]); g_string_append(str, "\n"); } } else { 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 } libjcat-0.2.3/libjcat/jcat-common.h000066400000000000000000000031511475014707200171230ustar00rootroot00000000000000/* * Copyright (C) 2020 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once /** * JcatImportFlags: * @JCAT_IMPORT_FLAG_NONE: No flags set * * Flags used for importing. **/ typedef enum { JCAT_IMPORT_FLAG_NONE = 0, /*< private >*/ JCAT_IMPORT_FLAG_LAST } JcatImportFlags; /** * JcatExportFlags: * @JCAT_EXPORT_FLAG_NONE: No flags set * @JCAT_EXPORT_FLAG_NO_TIMESTAMP: Do not export timestamps * * Flags used for exporting. **/ typedef enum { JCAT_EXPORT_FLAG_NONE = 0, JCAT_EXPORT_FLAG_NO_TIMESTAMP = 1 << 1, /*< private >*/ JCAT_EXPORT_FLAG_LAST } JcatExportFlags; /** * JcatVerifyFlags: * @JCAT_VERIFY_FLAG_NONE: No flags set * @JCAT_VERIFY_FLAG_DISABLE_TIME_CHECKS: Disable checking of validity periods * @JCAT_VERIFY_FLAG_REQUIRE_CHECKSUM: Require the item contains at least one checksum * @JCAT_VERIFY_FLAG_REQUIRE_SIGNATURE: Require the item contains at least one signature * * The flags to use when interacting with a keyring **/ typedef enum { JCAT_VERIFY_FLAG_NONE = 0, JCAT_VERIFY_FLAG_DISABLE_TIME_CHECKS = 1 << 2, JCAT_VERIFY_FLAG_REQUIRE_CHECKSUM = 1 << 3, JCAT_VERIFY_FLAG_REQUIRE_SIGNATURE = 1 << 4, /*< private >*/ JCAT_VERIFY_FLAG_LAST } JcatVerifyFlags; /** * JcatSignFlags: * @JCAT_SIGN_FLAG_NONE: No flags set * @JCAT_SIGN_FLAG_ADD_TIMESTAMP: Add a timestamp * @JCAT_SIGN_FLAG_ADD_CERT: Add a certificate * * The flags to when signing a binary **/ typedef enum { JCAT_SIGN_FLAG_NONE = 0, JCAT_SIGN_FLAG_ADD_TIMESTAMP = 1 << 0, JCAT_SIGN_FLAG_ADD_CERT = 1 << 1, /*< private >*/ JCAT_SIGN_FLAG_LAST } JcatSignFlags; libjcat-0.2.3/libjcat/jcat-compile.h000066400000000000000000000006601475014707200172650ustar00rootroot00000000000000/* * 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 libjcat-0.2.3/libjcat/jcat-context-private.h000066400000000000000000000003451475014707200207710ustar00rootroot00000000000000/* * Copyright (C) 2020 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include "jcat-context.h" GPtrArray * jcat_context_get_public_keys(JcatContext *self) G_GNUC_NON_NULL(1); libjcat-0.2.3/libjcat/jcat-context.c000066400000000000000000000371551475014707200173250ustar00rootroot00000000000000/* * Copyright (C) 2020 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "jcat-blob.h" #include "jcat-context-private.h" #include "jcat-engine-private.h" #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" #endif #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) #define GET_PRIVATE(o) (jcat_context_get_instance_private(o)) static void jcat_context_finalize(GObject *obj) { JcatContext *self = JCAT_CONTEXT(obj); JcatContextPrivate *priv = GET_PRIVATE(self); g_free(priv->keyring_path); g_ptr_array_unref(priv->engines); g_ptr_array_unref(priv->public_keys); G_OBJECT_CLASS(jcat_context_parent_class)->finalize(obj); } static void jcat_context_class_init(JcatContextClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS(klass); object_class->finalize = jcat_context_finalize; } static void 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 } /** * jcat_context_add_public_key: * @self: #JcatContext * @filename: A filename * * Adds a single public key. * * Since: 0.1.0 **/ void jcat_context_add_public_key(JcatContext *self, const gchar *filename) { JcatContextPrivate *priv = GET_PRIVATE(self); g_return_if_fail(JCAT_IS_CONTEXT(self)); g_return_if_fail(filename != NULL); g_ptr_array_add(priv->public_keys, g_strdup(filename)); } /** * jcat_context_add_public_keys: * @self: #JcatContext * @path: A directory of files * * Adds a public key directory. * * Since: 0.1.0 **/ void jcat_context_add_public_keys(JcatContext *self, const gchar *path) { JcatContextPrivate *priv = GET_PRIVATE(self); const gchar *fn_tmp; g_autoptr(GDir) dir = NULL; g_return_if_fail(JCAT_IS_CONTEXT(self)); g_return_if_fail(path != NULL); /* search all the public key files */ dir = g_dir_open(path, 0, NULL); if (dir == NULL) return; while ((fn_tmp = g_dir_read_name(dir)) != NULL) { g_ptr_array_add(priv->public_keys, g_build_filename(path, fn_tmp, NULL)); } } /* private */ GPtrArray * jcat_context_get_public_keys(JcatContext *self) { JcatContextPrivate *priv = GET_PRIVATE(self); return priv->public_keys; } /** * jcat_context_set_keyring_path: * @self: #JcatContext * @path: A directory * * Sets the local state directory for the engines to use. * * Since: 0.1.0 **/ void jcat_context_set_keyring_path(JcatContext *self, const gchar *path) { JcatContextPrivate *priv = GET_PRIVATE(self); g_return_if_fail(JCAT_IS_CONTEXT(self)); g_return_if_fail(path != NULL); g_free(priv->keyring_path); priv->keyring_path = g_strdup(path); } /** * jcat_context_get_keyring_path: * @self: #JcatContext * * Gets the local state directory the engines are using. * * Returns: (nullable): path * * Since: 0.1.0 **/ const gchar * jcat_context_get_keyring_path(JcatContext *self) { JcatContextPrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(JCAT_IS_CONTEXT(self), NULL); 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 * @kind: #JcatBlobKind, e.g. %JCAT_BLOB_KIND_GPG * @error: #GError, or %NULL * * Gets the engine for a specific engine kind, setting up the context * automatically if required. * * Returns: (transfer full): #JcatEngine, or %NULL for unavailable * * Since: 0.1.0 **/ JcatEngine * jcat_context_get_engine(JcatContext *self, JcatBlobKind kind, GError **error) { JcatContextPrivate *priv = GET_PRIVATE(self); 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) return g_object_ref(engine); } g_set_error(error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND, "Jcat engine kind '%s' not supported", jcat_blob_kind_to_string(kind)); return NULL; } /** * jcat_context_verify_blob: * @self: #JcatContext * @data: #GBytes * @blob: #JcatBlob * @flags: #JcatVerifyFlags, e.g. %JCAT_VERIFY_FLAG_DISABLE_TIME_CHECKS * @error: #GError, or %NULL * * Verifies a #JcatBlob using the public keys added to the context. * * Returns: (transfer full): #JcatResult, or %NULL for failed * * Since: 0.1.0 **/ JcatResult * jcat_context_verify_blob(JcatContext *self, GBytes *data, JcatBlob *blob, JcatVerifyFlags flags, GError **error) { GBytes *blob_signature; g_autoptr(JcatEngine) engine = NULL; g_return_val_if_fail(JCAT_IS_CONTEXT(self), NULL); g_return_val_if_fail(data != NULL, NULL); g_return_val_if_fail(JCAT_IS_BLOB(blob), NULL); /* get correct engine */ engine = jcat_context_get_engine(self, jcat_blob_get_kind(blob), error); if (engine == NULL) return NULL; blob_signature = jcat_blob_get_data(blob); if (jcat_engine_get_method(engine) == JCAT_BLOB_METHOD_CHECKSUM) return jcat_engine_self_verify(engine, data, blob_signature, flags, error); return jcat_engine_pubkey_verify(engine, data, blob_signature, flags, error); } /** * 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 * @item: #JcatItem * @flags: #JcatVerifyFlags, e.g. %JCAT_VERIFY_FLAG_REQUIRE_SIGNATURE * @error: #GError, or %NULL * * Verifies a #JcatItem using the public keys added to the context. All * `verify=CHECKSUM` engines (e.g. SHA256) must verify correctly, * but only one non-checksum signature has to verify. * * Returns: (transfer container) (element-type JcatResult): array of #JcatResult, or %NULL for *failed * * Since: 0.1.0 **/ GPtrArray * jcat_context_verify_item(JcatContext *self, GBytes *data, 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(data != NULL, 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; /* 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; result = jcat_engine_self_verify(engine, data, jcat_blob_get_data(blob), flags, error); if (result == NULL) { g_prefix_error(error, "checksum failure: "); return NULL; } g_ptr_array_add(results, g_steal_pointer(&result)); } 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(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; result = jcat_engine_pubkey_verify(engine, data, 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; } 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_new: * * Creates a new context. * * Returns: a #JcatContext * * Since: 0.1.0 **/ JcatContext * jcat_context_new(void) { return g_object_new(JCAT_TYPE_CONTEXT, NULL); } libjcat-0.2.3/libjcat/jcat-context.h000066400000000000000000000031701475014707200173200ustar00rootroot00000000000000/* * Copyright (C) 2020 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #include "jcat-blob.h" #include "jcat-engine.h" #include "jcat-item.h" #define JCAT_TYPE_CONTEXT jcat_context_get_type() G_DECLARE_DERIVABLE_TYPE(JcatContext, jcat_context, JCAT, CONTEXT, GObject) struct _JcatContextClass { GObjectClass parent_class; gpointer padding[15]; }; JcatContext * jcat_context_new(void); void 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) G_GNUC_NON_NULL(1, 2); JcatEngine * 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) G_GNUC_NON_NULL(1); const gchar * 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) G_GNUC_NON_NULL(1, 2, 3); GPtrArray * jcat_context_verify_item(JcatContext *self, GBytes *data, JcatItem *item, JcatVerifyFlags flags, 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); libjcat-0.2.3/libjcat/jcat-ed25519-engine.c000066400000000000000000000273631475014707200201020ustar00rootroot00000000000000/* * 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)); } libjcat-0.2.3/libjcat/jcat-ed25519-engine.h000066400000000000000000000006401475014707200200740ustar00rootroot00000000000000/* * 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); libjcat-0.2.3/libjcat/jcat-engine-private.h000066400000000000000000000006071475014707200205530ustar00rootroot00000000000000/* * Copyright (C) 2017 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include "jcat-engine.h" const gchar * jcat_engine_get_keyring_path(JcatEngine *self) G_GNUC_NON_NULL(1); void jcat_engine_add_string(JcatEngine *self, guint idt, GString *str) G_GNUC_NON_NULL(1, 3); gchar * jcat_engine_to_string(JcatEngine *self) G_GNUC_NON_NULL(1); libjcat-0.2.3/libjcat/jcat-engine.c000066400000000000000000000235371475014707200171050ustar00rootroot00000000000000/* * Copyright (C) 2017 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "jcat-common-private.h" #include "jcat-context-private.h" #include "jcat-engine-private.h" typedef struct { JcatContext *context; /* weak */ JcatBlobKind kind; JcatBlobMethod method; gboolean done_setup; } JcatEnginePrivate; G_DEFINE_TYPE_WITH_PRIVATE(JcatEngine, jcat_engine, G_TYPE_OBJECT) #define GET_PRIVATE(o) (jcat_engine_get_instance_private(o)) enum { PROP_0, PROP_CONTEXT, PROP_KIND, PROP_METHOD, PROP_VERIFY_KIND, PROP_LAST }; static const gchar * jcat_engine_method_to_string(JcatBlobMethod method) { if (method == JCAT_BLOB_METHOD_CHECKSUM) return "checksum"; if (method == JCAT_BLOB_METHOD_SIGNATURE) return "signature"; return NULL; } /* private */ void jcat_engine_add_string(JcatEngine *self, guint idt, GString *str) { JcatEnginePrivate *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)); jcat_string_append_kv(str, idt + 1, "VerifyKind", jcat_engine_method_to_string(priv->method)); } /* private */ gchar * jcat_engine_to_string(JcatEngine *self) { GString *str = g_string_new(NULL); jcat_engine_add_string(self, 0, str); return g_string_free(str, FALSE); } static gboolean jcat_engine_setup(JcatEngine *self, GError **error) { JcatEngineClass *klass = JCAT_ENGINE_GET_CLASS(self); JcatEnginePrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(JCAT_IS_ENGINE(self), FALSE); /* already done */ if (priv->done_setup) return TRUE; /* optional */ if (klass->setup != NULL) { if (!klass->setup(self, error)) return FALSE; } if (klass->add_public_key != NULL) { GPtrArray *fns = jcat_context_get_public_keys(priv->context); for (guint i = 0; i < fns->len; i++) { const gchar *fn = g_ptr_array_index(fns, i); if (!klass->add_public_key(self, fn, error)) return FALSE; } } /* success */ priv->done_setup = TRUE; return TRUE; } /** * jcat_engine_pubkey_verify: * @self: #JcatEngine * @blob: #GBytes * @blob_signature: #GBytes * @flags: #JcatVerifyFlags, e.g. %JCAT_VERIFY_FLAG_DISABLE_TIME_CHECKS * @error: #GError, or %NULL * * Verifies a chunk of data. * * Returns: (transfer full): #JcatResult, or %NULL for failed * * Since: 0.1.0 **/ JcatResult * jcat_engine_pubkey_verify(JcatEngine *self, GBytes *blob, GBytes *blob_signature, JcatVerifyFlags flags, GError **error) { JcatEngineClass *klass = JCAT_ENGINE_GET_CLASS(self); g_return_val_if_fail(JCAT_IS_ENGINE(self), NULL); g_return_val_if_fail(blob != NULL, NULL); g_return_val_if_fail(blob_signature != NULL, NULL); if (klass->pubkey_verify == NULL) { g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "verifying data is not supported"); return NULL; } if (!jcat_engine_setup(self, error)) return NULL; return klass->pubkey_verify(self, blob, blob_signature, flags, error); } /** * jcat_engine_pubkey_sign: * @self: #JcatEngine * @blob: #GBytes * @cert: #GBytes * @privkey: #GBytes * @flags: #JcatSignFlags, e.g. %JCAT_SIGN_FLAG_ADD_TIMESTAMP * @error: #GError, or %NULL * * Signs a chunk of data. * * Returns: (transfer full): #JcatBlob, or %NULL for failed * * Since: 0.1.0 **/ JcatBlob * jcat_engine_pubkey_sign(JcatEngine *self, GBytes *blob, GBytes *cert, GBytes *privkey, JcatSignFlags flags, GError **error) { JcatEngineClass *klass = JCAT_ENGINE_GET_CLASS(self); g_return_val_if_fail(JCAT_IS_ENGINE(self), NULL); g_return_val_if_fail(blob != NULL, NULL); g_return_val_if_fail(cert != NULL, NULL); g_return_val_if_fail(privkey != NULL, NULL); if (klass->pubkey_sign == NULL) { g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "signing data is not supported"); return NULL; } if (!jcat_engine_setup(self, error)) return NULL; return klass->pubkey_sign(self, blob, cert, privkey, flags, error); } /** * jcat_engine_self_verify: * @self: #JcatEngine * @blob: #GBytes * @blob_signature: #GBytes * @flags: #JcatVerifyFlags, e.g. %JCAT_VERIFY_FLAG_DISABLE_TIME_CHECKS * @error: #GError, or %NULL * * Verifies a chunk of data. * * Returns: (transfer full): #JcatResult, or %NULL for failed * * Since: 0.1.0 **/ JcatResult * jcat_engine_self_verify(JcatEngine *self, GBytes *blob, GBytes *blob_signature, JcatVerifyFlags flags, GError **error) { JcatEngineClass *klass = JCAT_ENGINE_GET_CLASS(self); g_return_val_if_fail(JCAT_IS_ENGINE(self), NULL); g_return_val_if_fail(blob != NULL, NULL); g_return_val_if_fail(blob_signature != NULL, NULL); if (klass->self_verify == NULL) { g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "verifying data is not supported"); return NULL; } if (!jcat_engine_setup(self, error)) return NULL; return klass->self_verify(self, blob, blob_signature, flags, error); } /** * jcat_engine_self_sign: * @self: #JcatEngine * @blob: #GBytes * @flags: #JcatSignFlags, e.g. %JCAT_SIGN_FLAG_ADD_TIMESTAMP * @error: #GError, or %NULL * * Signs a chunk of data. * * Returns: (transfer full): #JcatBlob, or %NULL for failed * * Since: 0.1.0 **/ JcatBlob * jcat_engine_self_sign(JcatEngine *self, GBytes *blob, JcatSignFlags flags, GError **error) { JcatEngineClass *klass = JCAT_ENGINE_GET_CLASS(self); g_return_val_if_fail(JCAT_IS_ENGINE(self), NULL); g_return_val_if_fail(blob != NULL, NULL); if (klass->self_sign == NULL) { g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "signing data is not supported"); return NULL; } if (!jcat_engine_setup(self, error)) return NULL; return klass->self_sign(self, blob, flags, error); } /** * 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 * * Gets the blob kind. * * Returns: #JcatBlobKind, e.g. %JCAT_BLOB_KIND_SHA256 * * Since: 0.1.3 **/ JcatBlobKind jcat_engine_get_kind(JcatEngine *self) { JcatEnginePrivate *priv = GET_PRIVATE(self); return priv->kind; } /** * jcat_engine_get_method: * @self: #JcatEngine * * Gets the verification method. * * Returns: #JcatBlobMethod, e.g. %JCAT_BLOB_METHOD_SIGNATURE * * Since: 0.1.3 **/ JcatBlobMethod jcat_engine_get_method(JcatEngine *self) { JcatEnginePrivate *priv = GET_PRIVATE(self); return priv->method; } const gchar * jcat_engine_get_keyring_path(JcatEngine *self) { JcatEnginePrivate *priv = GET_PRIVATE(self); if (priv->context == NULL) return NULL; return jcat_context_get_keyring_path(priv->context); } static void jcat_engine_finalize(GObject *object) { G_OBJECT_CLASS(jcat_engine_parent_class)->finalize(object); } static void jcat_engine_get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) { JcatEngine *self = JCAT_ENGINE(object); JcatEnginePrivate *priv = GET_PRIVATE(self); switch (prop_id) { case PROP_CONTEXT: g_value_set_object(value, priv->context); break; case PROP_KIND: g_value_set_uint(value, priv->kind); break; case PROP_METHOD: case PROP_VERIFY_KIND: g_value_set_uint(value, priv->method); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); break; } } static void jcat_engine_set_property(GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) { JcatEngine *self = JCAT_ENGINE(object); JcatEnginePrivate *priv = GET_PRIVATE(self); switch (prop_id) { case PROP_CONTEXT: /* weak */ priv->context = g_value_get_object(value); break; case PROP_KIND: priv->kind = g_value_get_uint(value); break; case PROP_METHOD: case PROP_VERIFY_KIND: priv->method = g_value_get_uint(value); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); break; } } static void jcat_engine_class_init(JcatEngineClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS(klass); GParamSpec *pspec; object_class->get_property = jcat_engine_get_property; object_class->set_property = jcat_engine_set_property; pspec = g_param_spec_object("context", NULL, NULL, JCAT_TYPE_CONTEXT, G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_NAME); g_object_class_install_property(object_class, PROP_CONTEXT, pspec); pspec = g_param_spec_uint("kind", NULL, NULL, 0, G_MAXUINT, 0, G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_NAME); g_object_class_install_property(object_class, PROP_KIND, pspec); pspec = g_param_spec_uint("method", NULL, NULL, JCAT_BLOB_METHOD_UNKNOWN, JCAT_BLOB_METHOD_LAST, JCAT_BLOB_METHOD_UNKNOWN, G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_NAME); g_object_class_install_property(object_class, PROP_METHOD, pspec); /* this is the old name to preserve GObject ABI */ pspec = g_param_spec_uint("verify-kind", NULL, NULL, JCAT_BLOB_METHOD_UNKNOWN, JCAT_BLOB_METHOD_LAST, JCAT_BLOB_METHOD_UNKNOWN, G_PARAM_READABLE | G_PARAM_DEPRECATED | G_PARAM_STATIC_NAME); g_object_class_install_property(object_class, PROP_VERIFY_KIND, pspec); object_class->finalize = jcat_engine_finalize; } static void jcat_engine_init(JcatEngine *self) { } libjcat-0.2.3/libjcat/jcat-engine.h000066400000000000000000000041451475014707200171040ustar00rootroot00000000000000/* * Copyright (C) 2017 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #include #include "jcat-blob.h" #include "jcat-common.h" #include "jcat-result.h" #define JCAT_TYPE_ENGINE (jcat_engine_get_type()) G_DECLARE_DERIVABLE_TYPE(JcatEngine, jcat_engine, JCAT, ENGINE, GObject) struct _JcatEngineClass { GObjectClass parent_class; gboolean (*setup)(JcatEngine *self, GError **error); gboolean (*add_public_key)(JcatEngine *self, const gchar *filename, GError **error); JcatResult *(*pubkey_verify)(JcatEngine *self, GBytes *blob, GBytes *blob_signature, JcatVerifyFlags flags, GError **error); JcatBlob *(*pubkey_sign)(JcatEngine *self, GBytes *blob, GBytes *cert, GBytes *privkey, JcatSignFlags flags, GError **error); JcatResult *(*self_verify)(JcatEngine *self, GBytes *blob, GBytes *blob_signature, JcatVerifyFlags flags, GError **error); JcatBlob *(*self_sign)(JcatEngine *self, GBytes *blob, JcatSignFlags flags, GError **error); gboolean (*add_public_key_raw)(JcatEngine *self, GBytes *blob, GError **error); gpointer padding[8]; }; JcatBlobKind jcat_engine_get_kind(JcatEngine *self) G_GNUC_NON_NULL(1); JcatBlobMethod 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) G_GNUC_NON_NULL(1, 2, 3); JcatBlob * jcat_engine_pubkey_sign(JcatEngine *self, GBytes *blob, GBytes *cert, GBytes *privkey, JcatSignFlags flags, 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) G_GNUC_NON_NULL(1, 2, 3); JcatBlob * 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); libjcat-0.2.3/libjcat/jcat-file-private.h000066400000000000000000000003541475014707200202240ustar00rootroot00000000000000/* * Copyright (C) 2020 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include "jcat-file.h" void jcat_file_add_string(JcatFile *self, guint idt, GString *str) G_GNUC_NON_NULL(1, 3); libjcat-0.2.3/libjcat/jcat-file.c000066400000000000000000000332311475014707200165470ustar00rootroot00000000000000/* * Copyright (C) 2020 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include "jcat-common-private.h" #include "jcat-file-private.h" #include "jcat-item-private.h" typedef struct { GPtrArray *items; guint32 version_major; guint32 version_minor; } JcatFilePrivate; G_DEFINE_TYPE_WITH_PRIVATE(JcatFile, jcat_file, G_TYPE_OBJECT) #define GET_PRIVATE(o) (jcat_file_get_instance_private(o)) static void jcat_file_finalize(GObject *obj) { JcatFile *self = JCAT_FILE(obj); JcatFilePrivate *priv = GET_PRIVATE(self); g_ptr_array_unref(priv->items); G_OBJECT_CLASS(jcat_file_parent_class)->finalize(obj); } static void jcat_file_class_init(JcatFileClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS(klass); object_class->finalize = jcat_file_finalize; } static void jcat_file_init(JcatFile *self) { JcatFilePrivate *priv = GET_PRIVATE(self); priv->items = g_ptr_array_new_with_free_func((GDestroyNotify)g_object_unref); } /* private */ void jcat_file_add_string(JcatFile *self, guint idt, GString *str) { JcatFilePrivate *priv = GET_PRIVATE(self); jcat_string_append_kv(str, idt, G_OBJECT_TYPE_NAME(self), NULL); if (priv->version_major > 0 || priv->version_minor > 0) { g_autofree gchar *version = NULL; version = g_strdup_printf("%u.%u", priv->version_major, priv->version_minor); jcat_string_append_kv(str, idt + 1, "Version", version); } for (guint i = 0; i < priv->items->len; i++) { JcatItem *item = g_ptr_array_index(priv->items, i); jcat_item_add_string(item, idt + 1, str); } } /** * jcat_file_to_string: * @self: #JcatFile * * Converts the #JcatFile to a string. * * Returns: string * * Since: 0.1.0 **/ gchar * jcat_file_to_string(JcatFile *self) { GString *str = g_string_new(NULL); jcat_file_add_string(self, 0, str); return g_string_free(str, FALSE); } static gboolean jcat_file_import_parser(JcatFile *self, JsonParser *parser, JcatImportFlags flags, GError **error) { JcatFilePrivate *priv = GET_PRIVATE(self); JsonObject *obj; const gchar *required[] = {"JcatVersionMajor", "JcatVersionMinor", "Items", NULL}; g_autoptr(GList) elements = NULL; /* sanity check */ obj = json_node_get_object(json_parser_get_root(parser)); for (guint i = 0; required[i] != NULL; i++) { if (!json_object_has_member(obj, required[i])) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "failed to find %s", required[i]); return FALSE; } } /* get version */ priv->version_major = json_object_get_int_member(obj, "JcatVersionMajor"); priv->version_minor = json_object_get_int_member(obj, "JcatVersionMinor"); /* get items */ elements = json_array_get_elements(json_object_get_array_member(obj, "Items")); for (GList *l = elements; l != NULL; l = l->next) { g_autoptr(JcatItem) item = NULL; JsonNode *node = l->data; if (!JSON_NODE_HOLDS_OBJECT(node)) { g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "failed to read object"); return FALSE; } item = jcat_item_import(json_node_get_object(node), flags, error); if (item == NULL) return FALSE; jcat_file_add_item(self, item); } return TRUE; } static void jcat_file_export_builder(JcatFile *self, JsonBuilder *builder, JcatExportFlags flags) { JcatFilePrivate *priv = GET_PRIVATE(self); json_builder_begin_object(builder); /* add metadata */ json_builder_set_member_name(builder, "JcatVersionMajor"); json_builder_add_int_value(builder, priv->version_major); json_builder_set_member_name(builder, "JcatVersionMinor"); json_builder_add_int_value(builder, priv->version_minor); /* add items */ if (priv->items->len > 0) { json_builder_set_member_name(builder, "Items"); json_builder_begin_array(builder); for (guint i = 0; i < priv->items->len; i++) { JcatItem *item = g_ptr_array_index(priv->items, i); json_builder_begin_object(builder); jcat_item_export(item, flags, builder); json_builder_end_object(builder); } json_builder_end_array(builder); } json_builder_end_object(builder); } /** * jcat_file_import_json: * @self: #JcatFile * @json: JSON data * @flags: #JcatImportFlags, typically %JCAT_IMPORT_FLAG_NONE * @error: #GError, or %NULL * * Imports a Jcat file from raw JSON. * * Returns: %TRUE for success * * Since: 0.1.0 **/ gboolean jcat_file_import_json(JcatFile *self, const gchar *json, JcatImportFlags flags, GError **error) { g_autoptr(JsonParser) parser = json_parser_new(); g_return_val_if_fail(JCAT_IS_FILE(self), FALSE); g_return_val_if_fail(json != NULL, FALSE); g_return_val_if_fail(error == NULL || *error == NULL, FALSE); if (!json_parser_load_from_data(parser, json, -1, error)) return FALSE; return jcat_file_import_parser(self, parser, flags, error); } /** * jcat_file_import_stream: * @self: #JcatFile * @istream: #GInputStream * @flags: #JcatImportFlags, typically %JCAT_IMPORT_FLAG_NONE * @cancellable: #GCancellable, or %NULL * @error: #GError, or %NULL * * Imports a compressed Jcat file from a file. * * Returns: %TRUE for success * * Since: 0.1.0 **/ gboolean jcat_file_import_stream(JcatFile *self, GInputStream *istream, JcatImportFlags flags, GCancellable *cancellable, GError **error) { g_autoptr(GConverter) conv = NULL; g_autoptr(GInputStream) istream_uncompressed = NULL; g_autoptr(JsonParser) parser = json_parser_new(); g_return_val_if_fail(JCAT_IS_FILE(self), FALSE); g_return_val_if_fail(G_IS_INPUT_STREAM(istream), FALSE); g_return_val_if_fail(error == NULL || *error == NULL, FALSE); 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); } /** * jcat_file_import_file: * @self: #JcatFile * @gfile: #gfile * @flags: #JcatImportFlags, typically %JCAT_IMPORT_FLAG_NONE * @cancellable: #GCancellable, or %NULL * @error: #GError, or %NULL * * Imports a compressed Jcat file from an input stream. * * Returns: %TRUE for success * * Since: 0.1.0 **/ gboolean jcat_file_import_file(JcatFile *self, GFile *gfile, JcatImportFlags flags, GCancellable *cancellable, GError **error) { g_autoptr(GInputStream) istream = NULL; g_return_val_if_fail(JCAT_IS_FILE(self), FALSE); g_return_val_if_fail(G_IS_FILE(gfile), FALSE); g_return_val_if_fail(error == NULL || *error == NULL, FALSE); istream = G_INPUT_STREAM(g_file_read(gfile, cancellable, error)); if (istream == NULL) return FALSE; return jcat_file_import_stream(self, istream, flags, cancellable, error); } /** * jcat_file_export_json: * @self: #JcatFile * @flags: a #JcatExportFlags, typically %JCAT_EXPORT_FLAG_NONE * @error: #GError, or %NULL * * Exports a Jcat file to raw JSON. * * Returns: (transfer full): JSON output, or %NULL for error * * Since: 0.1.0 **/ gchar * jcat_file_export_json(JcatFile *self, JcatExportFlags flags, GError **error) { g_autoptr(JsonBuilder) builder = json_builder_new(); g_autoptr(JsonGenerator) generator = json_generator_new(); g_autoptr(JsonNode) root = NULL; g_return_val_if_fail(JCAT_IS_FILE(self), NULL); g_return_val_if_fail(error == NULL || *error == NULL, NULL); /* export all */ jcat_file_export_builder(self, builder, flags); root = json_builder_get_root(builder); json_generator_set_root(generator, root); json_generator_set_pretty(generator, TRUE); return json_generator_to_data(generator, NULL); } /** * jcat_file_export_stream: * @self: #JcatFile * @ostream: #GOutputStream * @flags: a #JcatExportFlags, typically %JCAT_EXPORT_FLAG_NONE * @cancellable: #GCancellable, or %NULL * @error: #GError, or %NULL * * Exports a Jcat file to a compressed stream. * * Returns: %TRUE for success * * Since: 0.1.0 **/ gboolean jcat_file_export_stream(JcatFile *self, GOutputStream *ostream, JcatExportFlags flags, GCancellable *cancellable, GError **error) { g_autoptr(GConverter) conv = NULL; g_autoptr(GOutputStream) ostream_compressed = NULL; g_autoptr(JsonBuilder) builder = json_builder_new(); g_autoptr(JsonGenerator) generator = json_generator_new(); g_autoptr(JsonNode) root = NULL; g_return_val_if_fail(JCAT_IS_FILE(self), FALSE); g_return_val_if_fail(G_IS_OUTPUT_STREAM(ostream), FALSE); g_return_val_if_fail(error == NULL || *error == NULL, FALSE); /* export all */ jcat_file_export_builder(self, builder, flags); root = json_builder_get_root(builder); json_generator_set_root(generator, root); json_generator_set_pretty(generator, FALSE); /* compress file */ conv = G_CONVERTER(g_zlib_compressor_new(G_ZLIB_COMPRESSOR_FORMAT_GZIP, -1)); ostream_compressed = g_converter_output_stream_new(ostream, conv); return json_generator_to_stream(generator, ostream_compressed, cancellable, error); } /** * jcat_file_export_file: * @self: #JcatFile * @gfile: #gfile * @flags: a #JcatExportFlags, typically %JCAT_EXPORT_FLAG_NONE * @cancellable: #GCancellable, or %NULL * @error: #GError, or %NULL * * Exports a Jcat file to a compressed file. * * Returns: %TRUE for success * * Since: 0.1.0 **/ gboolean jcat_file_export_file(JcatFile *self, GFile *gfile, JcatExportFlags flags, GCancellable *cancellable, GError **error) { g_autoptr(GOutputStream) ostream = NULL; g_return_val_if_fail(JCAT_IS_FILE(self), FALSE); g_return_val_if_fail(G_IS_FILE(gfile), FALSE); g_return_val_if_fail(error == NULL || *error == NULL, FALSE); ostream = G_OUTPUT_STREAM( g_file_replace(gfile, NULL, FALSE, G_FILE_CREATE_NONE, cancellable, error)); if (ostream == NULL) return FALSE; return jcat_file_export_stream(self, ostream, flags, cancellable, error); } /** * jcat_file_get_items: * @self: #JcatFile * * Returns all the items in the file. * * Returns: (transfer container) (element-type JcatItem): all the items in the file * * Since: 0.1.0 **/ GPtrArray * jcat_file_get_items(JcatFile *self) { JcatFilePrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(JCAT_IS_FILE(self), NULL); return g_ptr_array_ref(priv->items); } /** * jcat_file_get_item_by_id: * @self: #JcatFile * @id: An ID, typically a filename basename * @error: #GError, or %NULL * * Finds the item with the specified ID, falling back to the ID alias if set. * * Returns: (transfer full): a #JcatItem, or %NULL if the filename was not found * * Since: 0.1.0 **/ JcatItem * jcat_file_get_item_by_id(JcatFile *self, const gchar *id, GError **error) { JcatFilePrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(JCAT_IS_FILE(self), NULL); g_return_val_if_fail(id != NULL, NULL); g_return_val_if_fail(error == NULL || *error == NULL, NULL); /* exact ID match */ for (guint i = 0; i < priv->items->len; i++) { JcatItem *item = g_ptr_array_index(priv->items, i); if (g_strcmp0(jcat_item_get_id(item), id) == 0) return g_object_ref(item); } /* try aliases this time */ for (guint i = 0; i < priv->items->len; i++) { JcatItem *item = g_ptr_array_index(priv->items, i); g_autoptr(GPtrArray) alias_ids = jcat_item_get_alias_ids(item); for (guint j = 0; j < alias_ids->len; j++) { const gchar *id_tmp = g_ptr_array_index(alias_ids, j); if (g_strcmp0(id_tmp, id) == 0) return g_object_ref(item); } } /* failed */ g_set_error(error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND, "failed to find %s", id); return NULL; } /** * jcat_file_get_item_default: * @self: #JcatFile * @error: #GError, or %NULL * * Finds the default item. If more than one #JcatItem exists this function will * return with an error. * * Returns: (transfer full): a #JcatItem, or %NULL if no default exists * * Since: 0.1.0 **/ JcatItem * jcat_file_get_item_default(JcatFile *self, GError **error) { JcatFilePrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(JCAT_IS_FILE(self), NULL); g_return_val_if_fail(error == NULL || *error == NULL, NULL); /* sanity check */ if (priv->items->len == 0) { g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND, "no items found"); return NULL; } if (priv->items->len > 1) { g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_FAILED, "multiple items found, no default possible"); return NULL; } /* only one possible */ return g_object_ref(g_ptr_array_index(priv->items, 0)); } /** * jcat_file_add_item: * @self: #JcatFile * @item: #JcatItem * * Adds an item to a file. * * Since: 0.1.0 **/ void jcat_file_add_item(JcatFile *self, JcatItem *item) { JcatFilePrivate *priv = GET_PRIVATE(self); g_return_if_fail(JCAT_IS_FILE(self)); g_return_if_fail(JCAT_IS_ITEM(item)); g_ptr_array_add(priv->items, g_object_ref(item)); } /** * jcat_file_get_version_major: * @self: #JcatFile * * Returns the major version number of the Jcat specification * * Returns: integer * * Since: 0.1.0 **/ guint32 jcat_file_get_version_major(JcatFile *self) { JcatFilePrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(JCAT_IS_FILE(self), 0); return priv->version_major; } /** * jcat_file_get_version_minor: * @self: #JcatFile * * Returns the minor version number of the Jcat specification * * Returns: integer * * Since: 0.1.0 **/ guint32 jcat_file_get_version_minor(JcatFile *self) { JcatFilePrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(JCAT_IS_FILE(self), 0); return priv->version_minor; } /** * jcat_file_new: * * Creates a new file. * * Returns: a #JcatFile * * Since: 0.1.0 **/ JcatFile * jcat_file_new(void) { JcatFile *self = g_object_new(JCAT_TYPE_FILE, NULL); JcatFilePrivate *priv = GET_PRIVATE(self); priv->version_major = 0; priv->version_minor = 1; return self; } libjcat-0.2.3/libjcat/jcat-file.h000066400000000000000000000035751475014707200165640ustar00rootroot00000000000000/* * Copyright (C) 2020 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #include "jcat-common.h" #include "jcat-item.h" #define JCAT_TYPE_FILE (jcat_file_get_type()) G_DECLARE_DERIVABLE_TYPE(JcatFile, jcat_file, JCAT, FILE, GObject) struct _JcatFileClass { GObjectClass parent_class; gpointer padding[15]; }; JcatFile * jcat_file_new(void); gchar * 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) G_GNUC_NON_NULL(1, 2); gboolean jcat_file_import_file(JcatFile *self, GFile *gfile, JcatImportFlags flags, GCancellable *cancellable, GError **error) G_GNUC_NON_NULL(1, 2); gboolean 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) G_GNUC_NON_NULL(1, 2); gboolean jcat_file_export_file(JcatFile *self, GFile *gfile, JcatExportFlags flags, GCancellable *cancellable, GError **error) G_GNUC_NON_NULL(1, 2); gchar * jcat_file_export_json(JcatFile *self, JcatExportFlags flags, GError **error) G_GNUC_NON_NULL(1); GPtrArray * 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) G_GNUC_NON_NULL(1, 2); JcatItem * jcat_file_get_item_default(JcatFile *self, GError **error) G_GNUC_NON_NULL(1); void jcat_file_add_item(JcatFile *self, JcatItem *item) G_GNUC_NON_NULL(1); guint32 jcat_file_get_version_major(JcatFile *self) G_GNUC_NON_NULL(1); guint32 jcat_file_get_version_minor(JcatFile *self) G_GNUC_NON_NULL(1); libjcat-0.2.3/libjcat/jcat-gpg-engine.c000066400000000000000000000177401475014707200176570ustar00rootroot00000000000000/* * Copyright (C) 2017-2020 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include "jcat-engine-private.h" #include "jcat-gpg-engine.h" struct _JcatGpgEngine { JcatEngine parent_instance; gpgme_ctx_t ctx; }; G_DEFINE_TYPE(JcatGpgEngine, jcat_gpg_engine, JCAT_TYPE_ENGINE) G_DEFINE_AUTO_CLEANUP_FREE_FUNC(gpgme_data_t, gpgme_data_release, NULL) static gboolean jcat_gpg_engine_add_public_key(JcatEngine *engine, const gchar *filename, GError **error) { JcatGpgEngine *self = JCAT_GPG_ENGINE(engine); gpgme_error_t rc; gpgme_import_result_t result; gpgme_import_status_t s; g_auto(gpgme_data_t) data = NULL; g_autofree gchar *basename = g_path_get_basename(filename); /* not us */ if (!g_str_has_prefix(basename, "GPG-KEY-")) { g_debug("ignoring %s as not GPG public key", basename); return TRUE; } /* import public key */ g_debug("Adding GnuPG public key %s", filename); rc = gpgme_data_new_from_file(&data, filename, 1); if (rc != GPG_ERR_NO_ERROR) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_FAILED, "failed to load %s: %s", filename, gpgme_strerror(rc)); return FALSE; } rc = gpgme_op_import(self->ctx, data); if (rc != GPG_ERR_NO_ERROR) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_FAILED, "failed to import %s: %s", filename, gpgme_strerror(rc)); return FALSE; } /* print what keys were imported */ result = gpgme_op_import_result(self->ctx); for (s = result->imports; s != NULL; s = s->next) { g_debug("importing key %s [%u] %s", s->fpr, s->status, gpgme_strerror(s->result)); } /* make sure keys were really imported */ if (result->imported == 0 && result->unchanged == 0) { g_debug("imported: %d, unchanged: %d, not_imported: %d", result->imported, result->unchanged, result->not_imported); g_set_error(error, G_IO_ERROR, G_IO_ERROR_FAILED, "key import failed %s", filename); return FALSE; } return TRUE; } static gboolean jcat_gpg_engine_setup(JcatEngine *engine, GError **error) { JcatGpgEngine *self = JCAT_GPG_ENGINE(engine); gpgme_error_t rc; g_autofree gchar *gpg_home = NULL; if (self->ctx != NULL) return TRUE; /* startup gpgme */ rc = gpg_err_init(); if (rc != GPG_ERR_NO_ERROR) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_FAILED, "failed to init: %s", gpgme_strerror(rc)); return FALSE; } /* create a new GPG context */ g_debug("using gpgme v%s", gpgme_check_version(NULL)); rc = gpgme_new(&self->ctx); if (rc != GPG_ERR_NO_ERROR) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_FAILED, "failed to create context: %s", gpgme_strerror(rc)); return FALSE; } /* set the protocol */ rc = gpgme_set_protocol(self->ctx, GPGME_PROTOCOL_OpenPGP); if (rc != GPG_ERR_NO_ERROR) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_FAILED, "failed to set protocol: %s", gpgme_strerror(rc)); return FALSE; } /* set a custom home directory */ gpg_home = g_build_filename(jcat_engine_get_keyring_path(engine), "gnupg", NULL); if (g_mkdir_with_parents(gpg_home, 0700) < 0) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_FAILED, "failed to create %s", gpg_home); return FALSE; } g_debug("Using engine at %s", gpg_home); rc = gpgme_ctx_set_engine_info(self->ctx, GPGME_PROTOCOL_OpenPGP, NULL, gpg_home); if (rc != GPG_ERR_NO_ERROR) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_FAILED, "failed to set protocol: %s", gpgme_strerror(rc)); return FALSE; } /* enable armor mode */ gpgme_set_armor(self->ctx, TRUE); return TRUE; } static gboolean jcat_gpg_engine_check_signature(gpgme_signature_t signature, GError **error) { gboolean ret = FALSE; /* look at the signature status */ switch (gpgme_err_code(signature->status)) { case GPG_ERR_NO_ERROR: ret = TRUE; break; case GPG_ERR_SIG_EXPIRED: case GPG_ERR_KEY_EXPIRED: g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "valid signature '%s' has expired", signature->fpr); break; case GPG_ERR_CERT_REVOKED: g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "valid signature '%s' has been revoked", signature->fpr); break; case GPG_ERR_BAD_SIGNATURE: g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "'%s' is not a valid signature", signature->fpr); break; case GPG_ERR_NO_PUBKEY: g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "Could not check signature '%s' as no public key", signature->fpr); break; default: g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "gpgme failed to verify signature '%s'", signature->fpr); break; } return ret; } static JcatResult * jcat_gpg_engine_pubkey_verify(JcatEngine *engine, GBytes *blob, GBytes *blob_signature, JcatVerifyFlags flags, GError **error) { JcatGpgEngine *self = JCAT_GPG_ENGINE(engine); gpgme_error_t rc; gpgme_signature_t s; gpgme_verify_result_t result; gint64 timestamp_newest = 0; g_auto(gpgme_data_t) data = NULL; g_auto(gpgme_data_t) sig = NULL; g_autoptr(GString) authority_newest = g_string_new(NULL); /* load file data */ rc = gpgme_data_new_from_mem(&data, g_bytes_get_data(blob, NULL), g_bytes_get_size(blob), 0); if (rc != GPG_ERR_NO_ERROR) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_FAILED, "failed to load data: %s", gpgme_strerror(rc)); return NULL; } rc = gpgme_data_new_from_mem(&sig, g_bytes_get_data(blob_signature, NULL), g_bytes_get_size(blob_signature), 0); if (rc != GPG_ERR_NO_ERROR) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_FAILED, "failed to load signature: %s", gpgme_strerror(rc)); return NULL; } /* verify */ rc = gpgme_op_verify(self->ctx, sig, data, NULL); if (rc != GPG_ERR_NO_ERROR) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_FAILED, "failed to verify data: %s", gpgme_strerror(rc)); return NULL; } /* verify the result */ result = gpgme_op_verify_result(self->ctx); if (result == NULL) { g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_FAILED, "no result record from libgpgme"); return NULL; } if (result->signatures == NULL) { g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_FAILED, "no signatures from libgpgme"); return NULL; } /* look at each signature */ for (s = result->signatures; s != NULL; s = s->next) { g_debug("returned signature fingerprint %s", s->fpr); if (!jcat_gpg_engine_check_signature(s, error)) return NULL; /* save details about the key for the result */ if ((gint64)s->timestamp > timestamp_newest) { timestamp_newest = (gint64)s->timestamp; g_string_assign(authority_newest, s->fpr); } } return JCAT_RESULT(g_object_new(JCAT_TYPE_RESULT, "engine", engine, "timestamp", timestamp_newest, "authority", authority_newest->str, NULL)); } static void jcat_gpg_engine_finalize(GObject *object) { JcatGpgEngine *self = JCAT_GPG_ENGINE(object); if (self->ctx != NULL) gpgme_release(self->ctx); G_OBJECT_CLASS(jcat_gpg_engine_parent_class)->finalize(object); } static void jcat_gpg_engine_class_init(JcatGpgEngineClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS(klass); JcatEngineClass *klass_app = JCAT_ENGINE_CLASS(klass); klass_app->setup = jcat_gpg_engine_setup; klass_app->add_public_key = jcat_gpg_engine_add_public_key; klass_app->pubkey_verify = jcat_gpg_engine_pubkey_verify; object_class->finalize = jcat_gpg_engine_finalize; } static void jcat_gpg_engine_init(JcatGpgEngine *self) { } JcatEngine * jcat_gpg_engine_new(JcatContext *context) { g_return_val_if_fail(JCAT_IS_CONTEXT(context), NULL); return JCAT_ENGINE(g_object_new(JCAT_TYPE_GPG_ENGINE, "context", context, "kind", JCAT_BLOB_KIND_GPG, "method", JCAT_BLOB_METHOD_SIGNATURE, NULL)); } libjcat-0.2.3/libjcat/jcat-gpg-engine.h000066400000000000000000000006151475014707200176550ustar00rootroot00000000000000/* * Copyright (C) 2017-2020 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include "jcat-context.h" #include "jcat-engine.h" #define JCAT_TYPE_GPG_ENGINE (jcat_gpg_engine_get_type()) G_DECLARE_FINAL_TYPE(JcatGpgEngine, jcat_gpg_engine, JCAT, GPG_ENGINE, JcatEngine) JcatEngine * jcat_gpg_engine_new(JcatContext *context) G_GNUC_NON_NULL(1); libjcat-0.2.3/libjcat/jcat-item-private.h000066400000000000000000000007711475014707200202460ustar00rootroot00000000000000/* * Copyright (C) 2020 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #include "jcat-common.h" #include "jcat-item.h" JcatItem * jcat_item_import(JsonObject *obj, JcatImportFlags flags, GError **error) G_GNUC_NON_NULL(1); void 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) G_GNUC_NON_NULL(1, 3); libjcat-0.2.3/libjcat/jcat-item.c000066400000000000000000000250741475014707200165740ustar00rootroot00000000000000/* * Copyright (C) 2020 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "jcat-blob-private.h" #include "jcat-common-private.h" #include "jcat-item-private.h" typedef struct { gchar *id; GPtrArray *blobs; GPtrArray *alias_ids; } JcatItemPrivate; G_DEFINE_TYPE_WITH_PRIVATE(JcatItem, jcat_item, G_TYPE_OBJECT) #define GET_PRIVATE(o) (jcat_item_get_instance_private(o)) static void jcat_item_finalize(GObject *obj) { JcatItem *self = JCAT_ITEM(obj); JcatItemPrivate *priv = GET_PRIVATE(self); g_free(priv->id); g_ptr_array_unref(priv->blobs); g_ptr_array_unref(priv->alias_ids); G_OBJECT_CLASS(jcat_item_parent_class)->finalize(obj); } static void jcat_item_class_init(JcatItemClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS(klass); object_class->finalize = jcat_item_finalize; } static void jcat_item_init(JcatItem *self) { JcatItemPrivate *priv = GET_PRIVATE(self); priv->blobs = g_ptr_array_new_with_free_func((GDestroyNotify)g_object_unref); priv->alias_ids = g_ptr_array_new_with_free_func(g_free); } /* private */ void jcat_item_add_string(JcatItem *self, guint idt, GString *str) { JcatItemPrivate *priv = GET_PRIVATE(self); jcat_string_append_kv(str, idt, G_OBJECT_TYPE_NAME(self), NULL); jcat_string_append_kv(str, idt + 1, "ID", priv->id); for (guint i = 0; i < priv->alias_ids->len; i++) { const gchar *alias_id = g_ptr_array_index(priv->alias_ids, i); jcat_string_append_kv(str, idt + 1, "AliasId", alias_id); } for (guint i = 0; i < priv->blobs->len; i++) { JcatBlob *blob = g_ptr_array_index(priv->blobs, i); jcat_blob_add_string(blob, idt + 1, str); } } /** * jcat_item_to_string: * @self: #JcatItem * * Converts the #JcatItem to a string. * * Returns: string * * Since: 0.1.0 **/ gchar * jcat_item_to_string(JcatItem *self) { GString *str = g_string_new(NULL); jcat_item_add_string(self, 0, str); return g_string_free(str, FALSE); } /* private */ JcatItem * jcat_item_import(JsonObject *obj, JcatImportFlags flags, GError **error) { const gchar *required[] = {"Id", "Blobs", NULL}; g_autoptr(GList) elements = NULL; g_autoptr(JcatItem) self = g_object_new(JCAT_TYPE_ITEM, NULL); JcatItemPrivate *priv = GET_PRIVATE(self); /* sanity check */ for (guint i = 0; required[i] != NULL; i++) { if (!json_object_has_member(obj, required[i])) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "failed to find %s", required[i]); return NULL; } } /* get ID */ priv->id = g_strdup(json_object_get_string_member(obj, "Id")); /* get blobs */ elements = json_array_get_elements(json_object_get_array_member(obj, "Blobs")); for (GList *l = elements; l != NULL; l = l->next) { g_autoptr(JcatBlob) blob = NULL; JsonNode *node = l->data; if (!JSON_NODE_HOLDS_OBJECT(node)) { g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "failed to read object"); return NULL; } blob = jcat_blob_import(json_node_get_object(node), flags, error); if (blob == NULL) return NULL; jcat_item_add_blob(self, blob); } /* get alias_ids */ if (json_object_has_member(obj, "AliasIds")) { JsonArray *array; g_autoptr(GList) alias_ids = NULL; array = json_object_get_array_member(obj, "AliasIds"); if (array == NULL) { g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "failed to read AliasIds array"); return NULL; } alias_ids = json_array_get_elements(array); for (GList *l = alias_ids; l != NULL; l = l->next) { JsonNode *node = l->data; if (!JSON_NODE_HOLDS_VALUE(node)) { g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "failed to read AliasIds value"); return NULL; } jcat_item_add_alias_id(self, json_node_get_string(node)); } } /* success */ return g_steal_pointer(&self); } void jcat_item_export(JcatItem *self, JcatExportFlags flags, JsonBuilder *builder) { JcatItemPrivate *priv = GET_PRIVATE(self); /* add metadata */ json_builder_set_member_name(builder, "Id"); json_builder_add_string_value(builder, priv->id); /* add alias_ids */ 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++) { const gchar *id_tmp = g_ptr_array_index(priv->alias_ids, i); json_builder_add_string_value(builder, id_tmp); } json_builder_end_array(builder); } /* add items */ if (priv->blobs->len > 0) { json_builder_set_member_name(builder, "Blobs"); json_builder_begin_array(builder); for (guint i = 0; i < priv->blobs->len; i++) { JcatBlob *blob = g_ptr_array_index(priv->blobs, i); json_builder_begin_object(builder); jcat_blob_export(blob, flags, builder); json_builder_end_object(builder); } json_builder_end_array(builder); } } /** * jcat_item_get_blobs: * @self: #JcatItem * * Gets all the blobs for this item. * * Returns: (transfer container) (element-type JcatBlob): blobs * * Since: 0.1.0 **/ GPtrArray * jcat_item_get_blobs(JcatItem *self) { JcatItemPrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(JCAT_IS_ITEM(self), NULL); return g_ptr_array_ref(priv->blobs); } /** * jcat_item_get_blobs_by_kind: * @self: #JcatItem * @kind: #JcatBlobKind, e.g. %JCAT_BLOB_KIND_SHA256 * * Gets the item blobs by a specific kind. * * Returns: (transfer container) (element-type JcatBlob): blobs * * Since: 0.1.0 **/ GPtrArray * jcat_item_get_blobs_by_kind(JcatItem *self, JcatBlobKind kind) { JcatItemPrivate *priv = GET_PRIVATE(self); GPtrArray *blobs = g_ptr_array_new_with_free_func((GDestroyNotify)g_object_unref); g_return_val_if_fail(JCAT_IS_ITEM(self), NULL); g_return_val_if_fail(kind != JCAT_BLOB_KIND_UNKNOWN, NULL); for (guint i = 0; i < priv->blobs->len; i++) { JcatBlob *blob = g_ptr_array_index(priv->blobs, i); if (jcat_blob_get_kind(blob) == kind) g_ptr_array_add(blobs, g_object_ref(blob)); } return blobs; } /** * 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 * * Adds a new blob to the item. * * Since: 0.1.0 **/ void jcat_item_add_blob(JcatItem *self, JcatBlob *blob) { JcatItemPrivate *priv = GET_PRIVATE(self); g_return_if_fail(JCAT_IS_ITEM(self)); g_return_if_fail(JCAT_IS_BLOB(blob)); /* remove existing blob with this AppStream ID and kind */ 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); break; } } /* add */ g_ptr_array_add(priv->blobs, g_object_ref(blob)); } /** * jcat_item_get_id: * @self: #JcatItem * * Returns the item ID. * * Returns: (transfer none): string * * Since: 0.1.0 **/ const gchar * jcat_item_get_id(JcatItem *self) { JcatItemPrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(JCAT_IS_ITEM(self), NULL); return priv->id; } /** * jcat_item_add_alias_id: * @self: #JcatItem * @id: An item ID alias, typically a file basename * * Adds an item alias ID. Alias IDs are matched when using functions such as * jcat_file_get_item_by_id(). * * Since: 0.1.1 **/ void jcat_item_add_alias_id(JcatItem *self, const gchar *id) { JcatItemPrivate *priv = GET_PRIVATE(self); g_return_if_fail(JCAT_IS_ITEM(self)); for (guint i = 0; i < priv->alias_ids->len; i++) { const gchar *id_tmp = g_ptr_array_index(priv->alias_ids, i); if (g_strcmp0(id, id_tmp) == 0) return; } g_ptr_array_add(priv->alias_ids, g_strdup(id)); } /** * jcat_item_remove_alias_id: * @self: #JcatItem * @id: An item ID alias, typically a file basename * * Removes an item alias ID. * * Since: 0.1.1 **/ void jcat_item_remove_alias_id(JcatItem *self, const gchar *id) { JcatItemPrivate *priv = GET_PRIVATE(self); g_return_if_fail(JCAT_IS_ITEM(self)); for (guint i = 0; i < priv->alias_ids->len; i++) { const gchar *id_tmp = g_ptr_array_index(priv->alias_ids, i); if (g_strcmp0(id, id_tmp) == 0) { g_ptr_array_remove(priv->alias_ids, (gpointer)id_tmp); return; } } } /** * jcat_item_get_alias_ids: * @self: #JcatItem * * Gets the list of alias IDs. * * Returns: (transfer container) (element-type utf8): array * * Since: 0.1.1 **/ GPtrArray * jcat_item_get_alias_ids(JcatItem *self) { JcatItemPrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(JCAT_IS_ITEM(self), NULL); return g_ptr_array_ref(priv->alias_ids); } /** * 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 * * Creates a new item. * * Returns: a #JcatItem * * Since: 0.1.0 **/ JcatItem * jcat_item_new(const gchar *id) { JcatItem *self = g_object_new(JCAT_TYPE_ITEM, NULL); JcatItemPrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(id != NULL, NULL); priv->id = g_strdup(id); return self; } libjcat-0.2.3/libjcat/jcat-item.h000066400000000000000000000022601475014707200165710ustar00rootroot00000000000000/* * Copyright (C) 2020 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #include "jcat-blob.h" #define JCAT_TYPE_ITEM (jcat_item_get_type()) G_DECLARE_DERIVABLE_TYPE(JcatItem, jcat_item, JCAT, ITEM, GObject) struct _JcatItemClass { GObjectClass parent_class; gpointer padding[15]; }; JcatItem * jcat_item_new(const gchar *id); gchar * jcat_item_to_string(JcatItem *self) G_GNUC_NON_NULL(1); GPtrArray * jcat_item_get_blobs(JcatItem *self) G_GNUC_NON_NULL(1); GPtrArray * 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) G_GNUC_NON_NULL(1, 2); const gchar * jcat_item_get_id(JcatItem *self) G_GNUC_NON_NULL(1); void 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) G_GNUC_NON_NULL(1, 2); GPtrArray * jcat_item_get_alias_ids(JcatItem *self) G_GNUC_NON_NULL(1); gboolean jcat_item_has_target(JcatItem *self) G_GNUC_NON_NULL(1); libjcat-0.2.3/libjcat/jcat-pkcs7-common.c000066400000000000000000000211431475014707200201440ustar00rootroot00000000000000/* * Copyright (C) 2017 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "jcat-pkcs7-common.h" gnutls_x509_crt_t jcat_pkcs7_load_crt_from_blob(GBytes *blob, gnutls_x509_crt_fmt_t format, GError **error) { gnutls_datum_t d = {0}; int rc; g_auto(gnutls_x509_crt_t) crt = NULL; /* create certificate */ rc = gnutls_x509_crt_init(&crt); if (rc < 0) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "crt_init: %s [%i]", gnutls_strerror(rc), rc); return NULL; } /* import the certificate */ d.size = g_bytes_get_size(blob); d.data = (unsigned char *)g_bytes_get_data(blob, NULL); rc = gnutls_x509_crt_import(crt, &d, format); if (rc < 0) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "crt_import: %s [%i]", gnutls_strerror(rc), rc); return NULL; } return g_steal_pointer(&crt); } gnutls_privkey_t jcat_pkcs7_load_privkey_from_blob(GBytes *blob, GError **error) { int rc; gnutls_datum_t d = {0}; g_auto(gnutls_privkey_t) key = NULL; /* load the private key */ rc = gnutls_privkey_init(&key); if (rc < 0) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "privkey_init: %s [%i]", gnutls_strerror(rc), rc); return NULL; } d.size = g_bytes_get_size(blob); d.data = (unsigned char *)g_bytes_get_data(blob, NULL); rc = gnutls_privkey_import_x509_raw(key, &d, GNUTLS_X509_FMT_PEM, NULL, 0); if (rc < 0) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "privkey_import_x509_raw: %s [%i]", gnutls_strerror(rc), rc); return NULL; } return g_steal_pointer(&key); } gnutls_pubkey_t jcat_pkcs7_load_pubkey_from_privkey(gnutls_privkey_t privkey, GError **error) { g_auto(gnutls_pubkey_t) pubkey = NULL; int rc; /* get the public key part of the private key */ rc = gnutls_pubkey_init(&pubkey); if (rc < 0) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "pubkey_init: %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_INVALID_DATA, "pubkey_import_privkey: %s [%i]", gnutls_strerror(rc), rc); return NULL; } /* success */ return g_steal_pointer(&pubkey); } gchar * jcat_pkcs7_datum_to_dn_str(const gnutls_datum_t *raw) { g_auto(gnutls_x509_dn_t) dn = NULL; g_autoptr(gnutls_datum_t) str = NULL; int rc; rc = gnutls_x509_dn_init(&dn); if (rc < 0) return NULL; rc = gnutls_x509_dn_import(dn, raw); if (rc < 0) return NULL; str = (gnutls_datum_t *)gnutls_malloc(sizeof(gnutls_datum_t)); str->data = NULL; rc = gnutls_x509_dn_get_str2(dn, str, 0); if (rc < 0) return NULL; return g_strndup((const gchar *)str->data, str->size); } /* generates a private key just like `certtool --generate-privkey` */ GBytes * jcat_pkcs7_create_private_key(GError **error) { gnutls_datum_t d = {0}; int bits; int key_type = GNUTLS_PK_RSA; int rc; g_auto(gnutls_x509_privkey_t) key = NULL; g_auto(gnutls_x509_spki_t) spki = NULL; g_autoptr(gnutls_data_t) d_payload = NULL; /* initialize key and SPKI */ rc = gnutls_x509_privkey_init(&key); if (rc < 0) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "privkey_init: %s [%i]", gnutls_strerror(rc), rc); return NULL; } rc = gnutls_x509_spki_init(&spki); if (rc < 0) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "spki_init: %s [%i]", gnutls_strerror(rc), rc); return NULL; } /* generate key */ bits = gnutls_sec_param_to_pk_bits(key_type, GNUTLS_SEC_PARAM_HIGH); g_debug("generating a %d bit %s private key...", bits, gnutls_pk_algorithm_get_name(key_type)); rc = gnutls_x509_privkey_generate2(key, key_type, bits, 0, NULL, 0); if (rc < 0) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "privkey_generate2: %s [%i]", gnutls_strerror(rc), rc); return NULL; } rc = gnutls_x509_privkey_verify_params(key); if (rc < 0) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "privkey_verify_params: %s [%i]", gnutls_strerror(rc), rc); return NULL; } /* save to file */ rc = gnutls_x509_privkey_export2(key, GNUTLS_X509_FMT_PEM, &d); if (rc < 0) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "privkey_export2: %s [%i]", gnutls_strerror(rc), rc); return NULL; } d_payload = d.data; return g_bytes_new(d_payload, d.size); } /* generates a self signed certificate just like: * `certtool --generate-self-signed --load-privkey priv.pem` */ GBytes * jcat_pkcs7_create_client_certificate(gnutls_privkey_t privkey, GError **error) { int rc; gnutls_datum_t d = {0}; guchar sha1buf[20]; gsize sha1bufsz = sizeof(sha1buf); g_auto(gnutls_pubkey_t) pubkey = NULL; g_auto(gnutls_x509_crt_t) crt = NULL; g_autoptr(gnutls_data_t) d_payload = NULL; /* load the public key from the private key */ pubkey = jcat_pkcs7_load_pubkey_from_privkey(privkey, error); if (pubkey == NULL) return NULL; /* create certificate */ rc = gnutls_x509_crt_init(&crt); if (rc < 0) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "crt_init: %s [%i]", gnutls_strerror(rc), rc); return NULL; } /* set public key */ rc = gnutls_x509_crt_set_pubkey(crt, pubkey); if (rc < 0) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "crt_set_pubkey: %s [%i]", gnutls_strerror(rc), rc); return NULL; } /* set positive random serial number */ rc = gnutls_rnd(GNUTLS_RND_NONCE, sha1buf, sizeof(sha1buf)); if (rc < 0) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "gnutls_rnd: %s [%i]", gnutls_strerror(rc), rc); return NULL; } sha1buf[0] &= 0x7f; rc = gnutls_x509_crt_set_serial(crt, sha1buf, sizeof(sha1buf)); if (rc < 0) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "crt_set_serial: %s [%i]", gnutls_strerror(rc), rc); return NULL; } /* set activation */ rc = gnutls_x509_crt_set_activation_time(crt, time(NULL)); if (rc < 0) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "set_activation_time: %s [%i]", gnutls_strerror(rc), rc); return NULL; } /* set expiration */ rc = gnutls_x509_crt_set_expiration_time(crt, (time_t)-1); if (rc < 0) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "set_expiration_time: %s [%i]", gnutls_strerror(rc), rc); return NULL; } /* set basic constraints */ rc = gnutls_x509_crt_set_basic_constraints(crt, 0, -1); if (rc < 0) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "set_basic_constraints: %s [%i]", gnutls_strerror(rc), rc); return NULL; } /* set usage */ rc = gnutls_x509_crt_set_key_usage(crt, GNUTLS_KEY_DIGITAL_SIGNATURE); if (rc < 0) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "set_key_usage: %s [%i]", gnutls_strerror(rc), rc); return NULL; } /* set subject key ID */ rc = gnutls_x509_crt_get_key_id(crt, GNUTLS_KEYID_USE_SHA1, sha1buf, &sha1bufsz); if (rc < 0) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "get_key_id: %s [%i]", gnutls_strerror(rc), rc); return NULL; } rc = gnutls_x509_crt_set_subject_key_id(crt, sha1buf, sha1bufsz); if (rc < 0) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "set_subject_key_id: %s [%i]", gnutls_strerror(rc), rc); return NULL; } /* set version */ rc = gnutls_x509_crt_set_version(crt, 3); if (rc < 0) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "error setting certificate version: %s [%i]", gnutls_strerror(rc), rc); return NULL; } /* self-sign certificate */ rc = gnutls_x509_crt_privkey_sign(crt, crt, privkey, GNUTLS_DIG_SHA256, 0); if (rc < 0) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "crt_privkey_sign: %s [%i]", gnutls_strerror(rc), rc); return NULL; } /* export to file */ rc = gnutls_x509_crt_export2(crt, GNUTLS_X509_FMT_PEM, &d); if (rc < 0) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "crt_export2: %s [%i]", gnutls_strerror(rc), rc); return NULL; } d_payload = d.data; return g_bytes_new(d_payload, d.size); } libjcat-0.2.3/libjcat/jcat-pkcs7-common.h000066400000000000000000000034761475014707200201620ustar00rootroot00000000000000/* * Copyright (C) 2020 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #include #include #include "jcat-compile.h" typedef guchar gnutls_data_t; static void _gnutls_datum_deinit(gnutls_datum_t *d) { gnutls_free(d->data); gnutls_free(d); } #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wunused-function" G_DEFINE_AUTO_CLEANUP_FREE_FUNC(gnutls_pkcs7_t, gnutls_pkcs7_deinit, NULL) G_DEFINE_AUTO_CLEANUP_FREE_FUNC(gnutls_privkey_t, gnutls_privkey_deinit, NULL) G_DEFINE_AUTO_CLEANUP_FREE_FUNC(gnutls_pubkey_t, gnutls_pubkey_deinit, NULL) G_DEFINE_AUTO_CLEANUP_FREE_FUNC(gnutls_x509_crt_t, gnutls_x509_crt_deinit, NULL) G_DEFINE_AUTO_CLEANUP_FREE_FUNC(gnutls_x509_dn_t, gnutls_x509_dn_deinit, NULL) G_DEFINE_AUTO_CLEANUP_FREE_FUNC(gnutls_x509_privkey_t, gnutls_x509_privkey_deinit, NULL) G_DEFINE_AUTO_CLEANUP_FREE_FUNC(gnutls_x509_spki_t, gnutls_x509_spki_deinit, NULL) G_DEFINE_AUTOPTR_CLEANUP_FUNC(gnutls_data_t, gnutls_free) G_DEFINE_AUTOPTR_CLEANUP_FUNC(gnutls_pkcs7_signature_info_st, gnutls_pkcs7_signature_info_deinit) G_DEFINE_AUTOPTR_CLEANUP_FUNC(gnutls_datum_t, _gnutls_datum_deinit) #pragma clang diagnostic pop gchar * 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) G_GNUC_NON_NULL(1); gnutls_privkey_t 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) 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) G_GNUC_NON_NULL(1); libjcat-0.2.3/libjcat/jcat-pkcs7-engine.c000066400000000000000000000327251475014707200201310ustar00rootroot00000000000000/* * Copyright (C) 2017-2020 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "jcat-common-private.h" #include "jcat-engine-private.h" #include "jcat-pkcs7-common.h" #include "jcat-pkcs7-engine.h" struct _JcatPkcs7Engine { JcatEngine parent_instance; gnutls_x509_trust_list_t tl; }; G_DEFINE_TYPE(JcatPkcs7Engine, jcat_pkcs7_engine, JCAT_TYPE_ENGINE) static gboolean 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; /* load file and add to the trust list */ crt = jcat_pkcs7_load_crt_from_blob(blob, format, error); if (crt == NULL) return FALSE; rc = gnutls_x509_crt_get_key_usage(crt, &key_usage, NULL); if (rc < 0) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "failed to get key usage: %s [%i]", gnutls_strerror(rc), rc); return FALSE; } if ((key_usage & GNUTLS_KEY_DIGITAL_SIGNATURE) == 0 && (key_usage & GNUTLS_KEY_KEY_CERT_SIGN) == 0) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "certificate not suitable for use [0x%x]", key_usage); return FALSE; } rc = gnutls_x509_trust_list_add_cas(self->tl, &crt, 1, 0); if (rc < 0) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "failed to add to trust list: %s [%i]", gnutls_strerror(rc), rc); return FALSE; } g_debug("loaded %i certificates", rc); /* confusingly the trust list does not copy the certificate */ crt = NULL; return TRUE; } 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")) { 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")) { 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); g_debug("ignoring %s as not PKCS-7 certificate", basename); } return TRUE; } static gboolean jcat_pkcs7_engine_setup(JcatEngine *engine, GError **error) { JcatPkcs7Engine *self = JCAT_PKCS7_ENGINE(engine); int rc; if (self->tl != NULL) return TRUE; /* create trust list, a bit like a engine */ rc = gnutls_x509_trust_list_init(&self->tl, 0); if (rc != GNUTLS_E_SUCCESS) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "failed to create trust list: %s [%i]", gnutls_strerror(rc), rc); return FALSE; } return TRUE; } /* verifies a detached signature just like: * `certtool --p7-verify --load-certificate client.pem --infile=test.p7b` */ static JcatResult * jcat_pkcs7_engine_verify(JcatEngine *engine, GBytes *blob, GBytes *blob_signature, gnutls_x509_crt_t crt, JcatVerifyFlags flags, GError **error) { JcatPkcs7Engine *self = JCAT_PKCS7_ENGINE(engine); gnutls_datum_t datum = {0}; gint64 timestamp_newest = 0; gnutls_pkcs7_signature_info_st info_tmp = {0x0}; int count; int rc; g_auto(gnutls_pkcs7_t) pkcs7 = NULL; g_autoptr(GString) authority_newest = g_string_new(NULL); /* startup */ rc = gnutls_pkcs7_init(&pkcs7); if (rc != GNUTLS_E_SUCCESS) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "failed to init pkcs7: %s [%i]", gnutls_strerror(rc), rc); return NULL; } /* import the signature */ datum.data = (guchar *)g_bytes_get_data(blob_signature, NULL); datum.size = g_bytes_get_size(blob_signature); rc = gnutls_pkcs7_import(pkcs7, &datum, GNUTLS_X509_FMT_PEM); if (rc != GNUTLS_E_SUCCESS) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "failed to import the PKCS7 signature: %s [%i]", gnutls_strerror(rc), rc); return NULL; } /* verify the blob */ datum.data = (guchar *)g_bytes_get_data(blob, NULL); datum.size = g_bytes_get_size(blob); count = gnutls_pkcs7_get_signature_count(pkcs7); g_debug("got %i PKCS7 signatures", count); if (count == 0) { g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "no PKCS7 signatures found"); return NULL; } for (gint i = 0; i < count; i++) { g_autoptr(gnutls_pkcs7_signature_info_st) info = &info_tmp; gint64 signing_time = 0; gnutls_certificate_verify_flags verify_flags = 0; g_autofree gchar *dn = NULL; /* use with care */ if (flags & JCAT_VERIFY_FLAG_DISABLE_TIME_CHECKS) { g_debug("WARNING: disabling time checks"); verify_flags |= GNUTLS_VERIFY_DISABLE_TIME_CHECKS; verify_flags |= GNUTLS_VERIFY_DISABLE_TRUSTED_TIME_CHECKS; } /* always get issuer */ rc = gnutls_pkcs7_get_signature_info(pkcs7, i, &info_tmp); if (rc < 0) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "failed to get signature info: %s [%i]", gnutls_strerror(rc), rc); return NULL; } /* verify the data against the detached signature */ if (crt != NULL) { rc = gnutls_pkcs7_verify_direct(pkcs7, crt, i, &datum, 0); } else { rc = gnutls_pkcs7_verify(pkcs7, self->tl, NULL, /* vdata */ 0, /* vdata_size */ i, /* index */ &datum, /* data */ verify_flags); } if (rc < 0) { dn = jcat_pkcs7_datum_to_dn_str(&info->issuer_dn); g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "failed to verify data for %s: %s [%i]", dn, gnutls_strerror(rc), rc); return NULL; } /* save details about the key for the result */ signing_time = info->signing_time > 0 ? (gint64)info->signing_time : 1; if (signing_time > timestamp_newest) { timestamp_newest = signing_time; dn = jcat_pkcs7_datum_to_dn_str(&info->issuer_dn); if (dn != NULL) g_string_assign(authority_newest, dn); } } /* success */ return JCAT_RESULT(g_object_new(JCAT_TYPE_RESULT, "engine", engine, "timestamp", timestamp_newest, "authority", authority_newest->str, NULL)); } /* verifies a detached signature just like: * `certtool --p7-verify --load-certificate client.pem --infile=test.p7b` */ static JcatResult * jcat_pkcs7_engine_self_verify(JcatEngine *engine, GBytes *blob, GBytes *blob_signature, JcatVerifyFlags flags, GError **error) { g_autofree gchar *filename = NULL; g_auto(gnutls_x509_crt_t) crt = NULL; g_autoptr(GBytes) cert_blob = NULL; filename = g_build_filename(jcat_engine_get_keyring_path(engine), "pki", "client.pem", NULL); cert_blob = jcat_get_contents_bytes(filename, error); if (cert_blob == NULL) return NULL; crt = jcat_pkcs7_load_crt_from_blob(cert_blob, GNUTLS_X509_FMT_PEM, error); if (crt == NULL) return NULL; return jcat_pkcs7_engine_verify(engine, blob, blob_signature, crt, flags, error); } /* verifies a detached signature just like: * `certtool --p7-verify --load-certificate client.pem --infile=test.p7b` */ static JcatResult * jcat_pkcs7_engine_pubkey_verify(JcatEngine *engine, GBytes *blob, GBytes *blob_signature, JcatVerifyFlags flags, GError **error) { return jcat_pkcs7_engine_verify(engine, blob, blob_signature, NULL, flags, error); } static JcatBlob * jcat_pkcs7_engine_pubkey_sign(JcatEngine *engine, GBytes *blob, GBytes *cert, GBytes *privkey, JcatSignFlags flags, GError **error) { gnutls_datum_t d = {0}; gnutls_digest_algorithm_t dig = GNUTLS_DIG_NULL; guint gnutls_flags = 0; int rc; g_autofree gchar *str = NULL; g_auto(gnutls_pkcs7_t) pkcs7 = NULL; g_auto(gnutls_privkey_t) key = NULL; g_auto(gnutls_pubkey_t) pubkey = NULL; g_auto(gnutls_x509_crt_t) crt = NULL; g_autoptr(gnutls_data_t) d_payload = NULL; /* 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 keys */ key = jcat_pkcs7_load_privkey_from_blob(privkey, error); if (key == NULL) return NULL; crt = jcat_pkcs7_load_crt_from_blob(cert, GNUTLS_X509_FMT_PEM, error); if (crt == NULL) return NULL; /* get the digest algorithm from the publix key */ pubkey = jcat_pkcs7_load_pubkey_from_privkey(key, error); if (pubkey == NULL) return NULL; rc = gnutls_pubkey_get_preferred_hash_algorithm(pubkey, &dig, NULL); if (rc < 0) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "preferred_hash_algorithm: %s [%i]", gnutls_strerror(rc), rc); return NULL; } /* create container */ rc = gnutls_pkcs7_init(&pkcs7); if (rc < 0) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "pkcs7_init: %s [%i]", gnutls_strerror(rc), rc); return NULL; } /* sign data */ d.data = (unsigned char *)g_bytes_get_data(blob, NULL); d.size = g_bytes_get_size(blob); if (flags & JCAT_SIGN_FLAG_ADD_TIMESTAMP) gnutls_flags |= GNUTLS_PKCS7_INCLUDE_TIME; if (flags & JCAT_SIGN_FLAG_ADD_CERT) gnutls_flags |= GNUTLS_PKCS7_INCLUDE_CERT; rc = gnutls_pkcs7_sign(pkcs7, crt, key, &d, NULL, NULL, dig, gnutls_flags); if (rc < 0) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "pkcs7_sign: %s [%i]", gnutls_strerror(rc), rc); return NULL; } /* set certificate */ if (flags & JCAT_SIGN_FLAG_ADD_CERT) { rc = gnutls_pkcs7_set_crt(pkcs7, crt); if (rc < 0) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "pkcs7_set_cr: %s", gnutls_strerror(rc)); return NULL; } } /* export */ rc = gnutls_pkcs7_export2(pkcs7, GNUTLS_X509_FMT_PEM, &d); if (rc < 0) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "pkcs7_export: %s", gnutls_strerror(rc)); return NULL; } d_payload = d.data; str = g_strndup((const gchar *)d_payload, d.size); return jcat_blob_new_utf8(JCAT_BLOB_KIND_PKCS7, str); } /* creates a detached signature just like: * `certtool --p7-detached-sign --load-certificate client.pem \ * --load-privkey secret.pem --outfile=test.p7b` */ static JcatBlob * jcat_pkcs7_engine_self_sign(JcatEngine *engine, GBytes *blob, JcatSignFlags flags, GError **error) { g_autofree gchar *fn_cert = NULL; g_autofree gchar *fn_privkey = NULL; g_autoptr(GBytes) cert = NULL; g_autoptr(GBytes) privkey = NULL; /* check private key exists, otherwise generate and save */ fn_privkey = g_build_filename(jcat_engine_get_keyring_path(engine), "pki", "secret.key", NULL); if (g_file_test(fn_privkey, G_FILE_TEST_EXISTS)) { privkey = jcat_get_contents_bytes(fn_privkey, error); if (privkey == NULL) return NULL; } else { privkey = jcat_pkcs7_create_private_key(error); if (privkey == NULL) return NULL; if (!jcat_mkdir_parent(fn_privkey, error)) return NULL; if (!jcat_set_contents_bytes(fn_privkey, privkey, 0600, error)) return NULL; } /* check client certificate exists, otherwise generate and save */ fn_cert = g_build_filename(jcat_engine_get_keyring_path(engine), "pki", "client.pem", NULL); if (g_file_test(fn_cert, G_FILE_TEST_EXISTS)) { cert = jcat_get_contents_bytes(fn_cert, error); if (cert == NULL) return NULL; } else { g_auto(gnutls_privkey_t) key = NULL; key = jcat_pkcs7_load_privkey_from_blob(privkey, error); if (key == NULL) return NULL; cert = jcat_pkcs7_create_client_certificate(key, error); if (cert == NULL) return NULL; if (!jcat_mkdir_parent(fn_cert, error)) return NULL; if (!jcat_set_contents_bytes(fn_cert, cert, 0666, error)) return NULL; } /* sign */ return jcat_pkcs7_engine_pubkey_sign(engine, blob, cert, privkey, flags, error); } static void jcat_pkcs7_engine_finalize(GObject *object) { JcatPkcs7Engine *self = JCAT_PKCS7_ENGINE(object); gnutls_x509_trust_list_deinit(self->tl, 1); G_OBJECT_CLASS(jcat_pkcs7_engine_parent_class)->finalize(object); } static void jcat_pkcs7_engine_class_init(JcatPkcs7EngineClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS(klass); 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; klass_app->self_sign = jcat_pkcs7_engine_self_sign; object_class->finalize = jcat_pkcs7_engine_finalize; } static void jcat_pkcs7_engine_init(JcatPkcs7Engine *self) { } JcatEngine * jcat_pkcs7_engine_new(JcatContext *context) { g_return_val_if_fail(JCAT_IS_CONTEXT(context), NULL); return JCAT_ENGINE(g_object_new(JCAT_TYPE_PKCS7_ENGINE, "context", context, "kind", JCAT_BLOB_KIND_PKCS7, "method", JCAT_BLOB_METHOD_SIGNATURE, NULL)); } libjcat-0.2.3/libjcat/jcat-pkcs7-engine.h000066400000000000000000000006311475014707200201250ustar00rootroot00000000000000/* * Copyright (C) 2017-2020 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include "jcat-context.h" #include "jcat-engine.h" #define JCAT_TYPE_PKCS7_ENGINE (jcat_pkcs7_engine_get_type()) G_DECLARE_FINAL_TYPE(JcatPkcs7Engine, jcat_pkcs7_engine, JCAT, PKCS7_ENGINE, JcatEngine) JcatEngine * jcat_pkcs7_engine_new(JcatContext *context) G_GNUC_NON_NULL(1); libjcat-0.2.3/libjcat/jcat-result-private.h000066400000000000000000000005321475014707200206210ustar00rootroot00000000000000/* * Copyright (C) 2017-2020 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include "jcat-engine.h" #include "jcat-result.h" void jcat_result_add_string(JcatResult *self, guint idt, GString *str) G_GNUC_NON_NULL(1, 3); JcatEngine * jcat_result_get_engine(JcatResult *self) G_GNUC_NON_NULL(1); libjcat-0.2.3/libjcat/jcat-result.c000066400000000000000000000123071475014707200171470ustar00rootroot00000000000000/* * Copyright (C) 2017-2020 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "jcat-common-private.h" #include "jcat-engine-private.h" #include "jcat-result-private.h" struct _JcatResult { GObject parent_instance; gint64 timestamp; gchar *authority; JcatEngine *engine; }; G_DEFINE_TYPE(JcatResult, jcat_result, G_TYPE_OBJECT) enum { PROP_0, PROP_ENGINE, PROP_TIMESTAMP, PROP_AUTHORITY, PROP_LAST }; /** * jcat_result_get_engine: * @self: #JcatResult * * Gets the engine that created this result. * * Returns: (transfer full): #JcatEngine, or %NULL * * Since: 0.1.0 **/ JcatEngine * jcat_result_get_engine(JcatResult *self) { g_return_val_if_fail(JCAT_IS_RESULT(self), NULL); if (self->engine == NULL) return NULL; return g_object_ref(self->engine); } /** * jcat_result_get_timestamp: * @self: #JcatResult * * Gets the signing timestamp, if set. * * Returns: UNIX timestamp, or 0 if unset * * Since: 0.1.0 **/ gint64 jcat_result_get_timestamp(JcatResult *self) { g_return_val_if_fail(JCAT_IS_RESULT(self), 0); return self->timestamp; } /** * jcat_result_get_authority: * @self: #JcatResult * * Gets the signing authority, if set. * * Returns: string, or %NULL * * Since: 0.1.0 **/ const gchar * jcat_result_get_authority(JcatResult *self) { g_return_val_if_fail(JCAT_IS_RESULT(self), NULL); return self->authority; } /** * jcat_result_get_kind: * @self: #JcatResult * * Gets the blob kind. * * Returns: #JcatBlobKind, e.g. %JCAT_BLOB_KIND_SHA256 * * Since: 0.1.3 **/ JcatBlobKind jcat_result_get_kind(JcatResult *self) { if (self->engine == NULL) return JCAT_BLOB_KIND_UNKNOWN; return jcat_engine_get_kind(self->engine); } /** * jcat_result_get_method: * @self: #JcatResult * * Gets the verification kind. * * Returns: #JcatBlobMethod, e.g. %JCAT_BLOB_METHOD_SIGNATURE * * Since: 0.1.3 **/ JcatBlobMethod jcat_result_get_method(JcatResult *self) { if (self->engine == NULL) return JCAT_BLOB_METHOD_UNKNOWN; return jcat_engine_get_method(self->engine); } /* private */ void jcat_result_add_string(JcatResult *self, guint idt, GString *str) { jcat_string_append_kv(str, idt, G_OBJECT_TYPE_NAME(self), NULL); if (self->timestamp != 0) { g_autoptr(GDateTime) dt = g_date_time_new_from_unix_utc(self->timestamp); #if GLIB_CHECK_VERSION(2, 62, 0) g_autofree gchar *tmp = g_date_time_format_iso8601(dt); #else g_autofree gchar *tmp = g_date_time_format(dt, "%FT%TZ"); #endif jcat_string_append_kv(str, idt + 1, "Timestamp", tmp); } if (self->authority != NULL && self->authority[0] != '\0') jcat_string_append_kv(str, idt + 1, "Authority", self->authority); if (self->engine != NULL) jcat_engine_add_string(self->engine, idt + 1, str); } /** * jcat_result_to_string: * @self: #JcatResult * * Converts the #JcatResult to a string. * * Returns: string * * Since: 0.1.0 **/ gchar * jcat_result_to_string(JcatResult *self) { GString *str = g_string_new(NULL); jcat_result_add_string(self, 0, str); return g_string_free(str, FALSE); } static void jcat_result_finalize(GObject *object) { JcatResult *self = JCAT_RESULT(object); if (self->engine != NULL) g_object_unref(self->engine); g_free(self->authority); G_OBJECT_CLASS(jcat_result_parent_class)->finalize(object); } static void jcat_result_get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) { JcatResult *self = JCAT_RESULT(object); switch (prop_id) { case PROP_ENGINE: g_value_set_object(value, self->engine); break; case PROP_TIMESTAMP: g_value_set_int64(value, self->timestamp); break; case PROP_AUTHORITY: g_value_set_string(value, self->authority); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); break; } } static void jcat_result_set_property(GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) { JcatResult *self = JCAT_RESULT(object); switch (prop_id) { case PROP_ENGINE: g_set_object(&self->engine, g_value_get_object(value)); break; case PROP_TIMESTAMP: self->timestamp = g_value_get_int64(value); break; case PROP_AUTHORITY: self->authority = g_value_dup_string(value); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); break; } } static void jcat_result_class_init(JcatResultClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS(klass); GParamSpec *pspec; object_class->get_property = jcat_result_get_property; object_class->set_property = jcat_result_set_property; object_class->finalize = jcat_result_finalize; pspec = g_param_spec_object("engine", NULL, NULL, JCAT_TYPE_ENGINE, G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_NAME); g_object_class_install_property(object_class, PROP_ENGINE, pspec); pspec = g_param_spec_int64("timestamp", NULL, NULL, 0, G_MAXINT64, 0, G_PARAM_READWRITE | G_PARAM_STATIC_NAME); g_object_class_install_property(object_class, PROP_TIMESTAMP, pspec); pspec = g_param_spec_string("authority", NULL, NULL, NULL, G_PARAM_READWRITE | G_PARAM_STATIC_NAME); g_object_class_install_property(object_class, PROP_AUTHORITY, pspec); } static void jcat_result_init(JcatResult *self) { } libjcat-0.2.3/libjcat/jcat-result.h000066400000000000000000000011751475014707200171550ustar00rootroot00000000000000/* * Copyright (C) 2017-2020 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #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) G_GNUC_NON_NULL(1); gint64 jcat_result_get_timestamp(JcatResult *self) G_GNUC_NON_NULL(1); const gchar * jcat_result_get_authority(JcatResult *self) G_GNUC_NON_NULL(1); JcatBlobKind jcat_result_get_kind(JcatResult *self) G_GNUC_NON_NULL(1); JcatBlobMethod jcat_result_get_method(JcatResult *self) G_GNUC_NON_NULL(1); libjcat-0.2.3/libjcat/jcat-self-test.c000066400000000000000000001242051475014707200175400ustar00rootroot00000000000000/* * Copyright (C) 2020 Richard Hughes * Copyright (C) 2022 Joe Qian * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #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" #include "jcat-file.h" #include "jcat-item-private.h" #include "jcat-result-private.h" static void jcat_blob_func(void) { g_autofree gchar *str = NULL; g_autoptr(JcatBlob) blob = NULL; const gchar *str_perfect = "JcatBlob:\n" " Kind: gpg\n" " Flags: is-utf8\n" " AppstreamId: org.fwupd\n" " Timestamp: 1970-01-01T03:25:45Z\n" " Size: 0x5\n" " Data: BEGIN\n"; /* enums */ for (guint i = JCAT_BLOB_KIND_UNKNOWN + 1; i < JCAT_BLOB_KIND_LAST; i++) { const gchar *tmp = jcat_blob_kind_to_string(i); g_assert_nonnull(tmp); g_assert_cmpint(jcat_blob_kind_from_string(tmp), ==, i); } for (guint i = JCAT_BLOB_KIND_UNKNOWN + 1; i < JCAT_BLOB_KIND_LAST; i++) { const gchar *tmp = jcat_blob_kind_to_filename_ext(i); g_assert_nonnull(tmp); } /* sanity check */ blob = jcat_blob_new_utf8(JCAT_BLOB_KIND_GPG, "BEGIN"); g_assert_cmpint(jcat_blob_get_kind(blob), ==, JCAT_BLOB_KIND_GPG); g_assert_nonnull(jcat_blob_get_data(blob)); jcat_blob_set_appstream_id(blob, "org.fwupd"); g_assert_cmpstr(jcat_blob_get_appstream_id(blob), ==, "org.fwupd"); jcat_blob_set_timestamp(blob, 12345); g_assert_cmpint(jcat_blob_get_timestamp(blob), ==, 12345); /* to string */ str = jcat_blob_to_string(blob); g_print("%s", str); g_assert_cmpstr(str, ==, str_perfect); } static void jcat_item_func(void) { g_autofree gchar *str = NULL; g_autoptr(JcatItem) item = NULL; const gchar *str_perfect = "JcatItem:\n" " ID: filename.bin\n" " AliasId: foo.bin\n"; /* sanity check */ item = jcat_item_new("filename.bin"); jcat_item_add_alias_id(item, "foo.bin"); jcat_item_add_alias_id(item, "bar.bin"); jcat_item_remove_alias_id(item, "bar.bin"); g_assert_cmpstr(jcat_item_get_id(item), ==, "filename.bin"); /* to string */ str = jcat_item_to_string(item); g_print("%s", str); g_assert_cmpstr(str, ==, str_perfect); } static void jcat_file_func(void) { gboolean ret; g_autofree gchar *json1 = NULL; g_autofree gchar *json2 = NULL; g_autoptr(GBytes) data = g_bytes_new("hello world", 12); g_autoptr(GError) error = NULL; g_autoptr(GFile) gfile = g_file_new_for_path("/tmp/firmware.jcat"); g_autoptr(GPtrArray) blobs0 = NULL; g_autoptr(GPtrArray) blobs1 = NULL; g_autoptr(GPtrArray) blobs2 = NULL; g_autoptr(GPtrArray) blobs3 = NULL; g_autoptr(GPtrArray) items0 = NULL; g_autoptr(GPtrArray) items1 = NULL; g_autoptr(JcatBlob) blob1 = jcat_blob_new_utf8(JCAT_BLOB_KIND_GPG, "BEGIN"); g_autoptr(JcatBlob) blob2 = jcat_blob_new(JCAT_BLOB_KIND_SHA256, data); g_autoptr(JcatFile) file2 = jcat_file_new(); g_autoptr(JcatFile) file = jcat_file_new(); g_autoptr(JcatItem) item1 = NULL; g_autoptr(JcatItem) item2 = NULL; g_autoptr(JcatItem) item3 = NULL; g_autoptr(JcatItem) item = jcat_item_new("firmware.bin"); const gchar *json_perfect = "{\n" " \"JcatVersionMajor\" : 0,\n" " \"JcatVersionMinor\" : 1,\n" " \"Items\" : [\n" " {\n" " \"Id\" : \"firmware.bin\",\n" " \"AliasIds\" : [\n" " \"foo.bin\"\n" " ],\n" " \"Blobs\" : [\n" " {\n" " \"Kind\" : 2,\n" " \"Flags\" : 1,\n" " \"AppstreamId\" : \"org.fwupd\",\n" " \"Data\" : \"BEGIN\"\n" " },\n" " {\n" " \"Kind\" : 1,\n" " \"Flags\" : 0,\n" " \"Data\" : \"aGVsbG8gd29ybGQA\"\n" " }\n" " ]\n" " }\n" " ]\n" "}"; /* check blob */ g_assert(jcat_blob_get_data(blob2) == data); jcat_blob_set_appstream_id(blob1, "org.fwupd"); g_assert_cmpstr(jcat_blob_get_appstream_id(blob1), ==, "org.fwupd"); jcat_blob_set_timestamp(blob1, 0); g_assert_cmpint(jcat_blob_get_timestamp(blob1), ==, 0); jcat_blob_set_timestamp(blob2, 0); g_assert_cmpint(jcat_blob_get_timestamp(blob2), ==, 0); /* get default item */ item1 = jcat_file_get_item_default(file, &error); g_assert_error(error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND); g_assert_null(item1); g_clear_error(&error); /* check item */ g_assert_cmpstr(jcat_item_get_id(item), ==, "firmware.bin"); blobs0 = jcat_item_get_blobs(item); g_assert_cmpint(blobs0->len, ==, 0); jcat_item_add_blob(item, blob1); jcat_item_add_blob(item, blob2); jcat_item_add_blob(item, blob2); jcat_item_add_alias_id(item, "foo.bin"); blobs1 = jcat_item_get_blobs(item); g_assert_cmpint(blobs1->len, ==, 2); blobs2 = jcat_item_get_blobs_by_kind(item, JCAT_BLOB_KIND_GPG); g_assert_cmpint(blobs2->len, ==, 1); blobs3 = jcat_item_get_blobs_by_kind(item, JCAT_BLOB_KIND_PKCS7); g_assert_cmpint(blobs3->len, ==, 0); /* check file */ g_assert_cmpint(jcat_file_get_version_major(file), ==, 0); g_assert_cmpint(jcat_file_get_version_minor(file), ==, 1); items0 = jcat_file_get_items(file); g_assert_cmpint(items0->len, ==, 0); jcat_file_add_item(file, item); items1 = jcat_file_get_items(file); g_assert_cmpint(items1->len, ==, 1); item1 = jcat_file_get_item_by_id(file, "firmware.bin", &error); g_assert_no_error(error); g_assert_nonnull(item1); g_assert(item == item1); item2 = jcat_file_get_item_by_id(file, "dave.bin", &error); g_assert_error(error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND); g_assert_null(item2); g_clear_error(&error); /* get default item */ item3 = jcat_file_get_item_default(file, &error); g_assert_no_error(error); g_assert_nonnull(item3); /* export as string */ json1 = jcat_file_export_json(file, JCAT_EXPORT_FLAG_NONE, &error); g_print("%s\n", json1); g_assert_cmpstr(json1, ==, json_perfect); /* export as compressed file */ ret = jcat_file_export_file(file, gfile, JCAT_EXPORT_FLAG_NONE, NULL, &error); g_assert_no_error(error); g_assert_true(ret); /* load compressed file */ ret = jcat_file_import_file(file2, gfile, JCAT_IMPORT_FLAG_NONE, NULL, &error); g_assert_no_error(error); g_assert_true(ret); json2 = jcat_file_export_json(file2, JCAT_EXPORT_FLAG_NO_TIMESTAMP, &error); g_print("%s\n", json2); g_assert_cmpstr(json2, ==, json1); } static void jcat_sha1_engine_func(void) { g_autofree gchar *fn_fail = NULL; g_autofree gchar *fn_pass = NULL; g_autofree gchar *sig = NULL; g_autoptr(GBytes) blob_sig1 = NULL; g_autoptr(GBytes) data_fail = NULL; g_autoptr(GBytes) data_fwbin = NULL; g_autoptr(GError) error = NULL; 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"; /* get engine */ engine = jcat_context_get_engine(context, JCAT_BLOB_KIND_SHA1, &error); g_assert_no_error(error); g_assert_nonnull(engine); g_assert_cmpint(jcat_engine_get_kind(engine), ==, JCAT_BLOB_KIND_SHA1); g_assert_cmpint(jcat_engine_get_method(engine), ==, JCAT_BLOB_METHOD_CHECKSUM); /* verify checksum */ 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_sig1 = g_bytes_new_static(sig_actual, strlen(sig_actual)); result_pass = jcat_engine_self_verify(engine, data_fwbin, blob_sig1, JCAT_VERIFY_FLAG_NONE, &error); g_assert_no_error(error); g_assert_nonnull(result_pass); g_assert_cmpint(jcat_result_get_timestamp(result_pass), ==, 0); g_assert_cmpstr(jcat_result_get_authority(result_pass), ==, NULL); /* verify will fail */ 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_self_verify(engine, data_fail, blob_sig1, 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); /* verify signing */ blob_sig2 = jcat_engine_self_sign(engine, data_fwbin, JCAT_SIGN_FLAG_NONE, &error); g_assert_no_error(error); 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 jcat_sha256_engine_func(void) { g_autofree gchar *fn_fail = NULL; g_autofree gchar *fn_pass = NULL; g_autofree gchar *sig = NULL; g_autoptr(GBytes) blob_sig1 = NULL; g_autoptr(GBytes) data_fail = NULL; g_autoptr(GBytes) data_fwbin = NULL; g_autoptr(GError) error = NULL; g_autoptr(JcatBlob) blob_sig2 = 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; const gchar *sig_actual = "a196504d09871da4f7d83b874b500f8ee6e0619ab799f074814b316d88f96f7f"; /* get engine */ engine = jcat_context_get_engine(context, JCAT_BLOB_KIND_SHA256, &error); g_assert_no_error(error); g_assert_nonnull(engine); g_assert_cmpint(jcat_engine_get_kind(engine), ==, JCAT_BLOB_KIND_SHA256); g_assert_cmpint(jcat_engine_get_method(engine), ==, JCAT_BLOB_METHOD_CHECKSUM); /* verify checksum */ 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_sig1 = g_bytes_new_static(sig_actual, strlen(sig_actual)); result_pass = jcat_engine_self_verify(engine, data_fwbin, blob_sig1, JCAT_VERIFY_FLAG_NONE, &error); g_assert_no_error(error); g_assert_nonnull(result_pass); g_assert_cmpint(jcat_result_get_timestamp(result_pass), ==, 0); g_assert_cmpstr(jcat_result_get_authority(result_pass), ==, NULL); /* verify will fail */ 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_self_verify(engine, data_fail, blob_sig1, 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); /* verify signing */ blob_sig2 = jcat_engine_self_sign(engine, data_fwbin, JCAT_SIGN_FLAG_NONE, &error); g_assert_no_error(error); g_assert_nonnull(blob_sig2); sig = jcat_blob_get_data_as_string(blob_sig2); g_assert_cmpstr(sig, ==, sig_actual); } static void jcat_gpg_engine_func(void) { #ifdef ENABLE_GPG g_autofree gchar *fn_fail = NULL; g_autofree gchar *fn_pass = NULL; g_autofree gchar *pki_dir = NULL; g_autofree gchar *str = 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; const gchar *str_perfect = "JcatGpgEngine:\n" " Kind: gpg\n" " VerifyKind: signature\n"; const gchar *sig_actual = "-----BEGIN PGP SIGNATURE-----\n" "Version: GnuPG v1\n\n" "iQEcBAABCAAGBQJVt0B4AAoJEEim2A5FOLrCFb8IAK+QTLY34Wu8xZ8nl6p3JdMu" "HOaifXAmX7291UrsFRwdabU2m65pqxQLwcoFrqGv738KuaKtu4oIwo9LIrmmTbEh" "IID8uszxBt0bMdcIHrvwd+ADx+MqL4hR3guXEE3YOBTLvv2RF1UBcJPInNf/7Ui1" "3lW1c3trL8RAJyx1B5RdKqAMlyfwiuvKM5oT4SN4uRSbQf+9mt78ZSWfJVZZH/RR" "H9q7PzR5GdmbsRPM0DgC27Trvqjo3MzoVtoLjIyEb/aWqyulUbnJUNKPYTnZgkzM" "v2yVofWKIM3e3wX5+MOtf6EV58mWa2cHJQ4MCYmpKxbIvAIZagZ4c9A8BA6tQWg=" "=fkit\n" "-----END PGP SIGNATURE-----\n"; /* 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_GPG, &error); g_assert_no_error(error); g_assert_nonnull(engine); g_assert_cmpint(jcat_engine_get_kind(engine), ==, JCAT_BLOB_KIND_GPG); g_assert_cmpint(jcat_engine_get_method(engine), ==, JCAT_BLOB_METHOD_SIGNATURE); /* to string */ str = jcat_engine_to_string(engine); g_print("%s", str); g_assert_cmpstr(str, ==, str_perfect); /* verify with GnuPG */ 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); data_sig = g_bytes_new_static(sig_actual, strlen(sig_actual)); 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); g_assert_cmpint(jcat_result_get_timestamp(result_pass), ==, 1438072952); g_assert_cmpstr(jcat_result_get_authority(result_pass), ==, "3FC6B804410ED0840D8F2F9748A6D80E4538BAC2"); /* verify will fail with GnuPG */ 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 GnuPG support enabled"); #endif } static void jcat_gpg_engine_msg_func(void) { #ifdef ENABLE_GPG g_autofree gchar *fn = NULL; g_autofree gchar *pki_dir = NULL; g_autoptr(GBytes) data = 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 = NULL; const gchar *sig = "-----BEGIN PGP MESSAGE-----\n" "owGbwMvMwMEovmZX76/pfOKMp0WSGOLOX3/ikZqTk6+jUJ5flJOiyNXJaMzCwMjB\n" "ICumyCJmt5VRUil28/1+z1cwbaxMID0MXJwCMJG4RxwMLUYXDkUad34I3vrT8+X2\n" "m+ZyHyMWnTiQYaQb/eLJGqbiAJc5Jr4a/PPqHNi7auwzGsKsljebabjtnJRzpDr0\n" "YvwrnmmWLJUnTzjM3MH5Kn+RzqXkywsYdk9yD2OUdLy736CiemFMdcuF02lOZvPU\n" "HaTKl76wW62QH8Lr8yGMQ1Xgc6nC2ZwUhvctky7NOZtc1T477uBTL81p31ZmaIUJ\n" "paS8uWZl8UzX5sFsqQi37G1TbDc8Cm+oU/yRkFj2pLBzw367ncsa4n7EqEWu1yrN\n" "yD39LUeErePdqfKCG+xhL6WkWt5ZJ/6//XnjouXhl5Z4tWspT49MtNp5d3aDQ43c\n" "mnbresn6A7KMZgdOiwIA\n" "=a9ui\n" "-----END PGP MESSAGE-----\n"; /* 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_GPG, &error); g_assert_no_error(error); g_assert_nonnull(engine); g_assert_cmpint(jcat_engine_get_kind(engine), ==, JCAT_BLOB_KIND_GPG); g_assert_cmpint(jcat_engine_get_method(engine), ==, JCAT_BLOB_METHOD_SIGNATURE); /* verify with GnuPG, which should fail as the signature is not a * detached signature at all, but gnupg stabs us in the back by returning * success from gpgme_op_verify() with an empty list of signatures */ fn = g_test_build_filename(G_TEST_DIST, "colorhug", "firmware.bin", NULL); data = jcat_get_contents_bytes(fn, &error); g_assert_no_error(error); g_assert_nonnull(data); data_sig = g_bytes_new_static(sig, strlen(sig)); result = jcat_engine_pubkey_verify(engine, data, data_sig, JCAT_VERIFY_FLAG_NONE, &error); g_assert_error(error, G_IO_ERROR, G_IO_ERROR_FAILED); g_assert_null(result); #else g_test_skip("no GnuPG support enabled"); #endif } static void jcat_pkcs7_engine_func(void) { #ifdef ENABLE_PKCS7 g_autofree gchar *fn_fail = NULL; g_autofree gchar *fn_pass = NULL; g_autofree gchar *fn_sig = 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; 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_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); g_assert_no_error(error); g_assert_nonnull(engine); g_assert_cmpint(jcat_engine_get_kind(engine), ==, JCAT_BLOB_KIND_PKCS7); g_assert_cmpint(jcat_engine_get_method(engine), ==, JCAT_BLOB_METHOD_SIGNATURE); /* verify with a signature from the old LVFS */ 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.p7b", 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_DISABLE_TIME_CHECKS, &error); g_assert_no_error(error); g_assert_nonnull(result_pass); g_assert_cmpint(jcat_result_get_timestamp(result_pass), >=, 1502871248); g_assert_cmpstr(jcat_result_get_authority(result_pass), ==, "O=Linux Vendor Firmware Project,CN=LVFS CA"); /* verify will fail with a self-signed signature */ sig_fn2 = g_test_build_filename(G_TEST_BUILT, "colorhug", "firmware.bin.p7c", NULL); blob_sig2 = jcat_get_contents_bytes(sig_fn2, &error); g_assert_no_error(error); g_assert_nonnull(blob_sig2); result_fail = jcat_engine_pubkey_verify(engine, data_fwbin, blob_sig2, 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); /* 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_pkcs7_engine_self_signed_func(void) { #ifdef ENABLE_PKCS7 static const char payload_str[] = "Hello, world!"; g_autofree gchar *str = NULL; g_autoptr(JcatBlob) signature = NULL; g_autoptr(JcatContext) context = jcat_context_new(); g_autoptr(JcatEngine) engine = NULL; g_autoptr(JcatEngine) engine2 = NULL; g_autoptr(JcatResult) result = NULL; g_autoptr(GBytes) payload = NULL; g_autoptr(GError) error = NULL; const gchar *str_perfect = "JcatResult:\n" " Timestamp: 1970-01-01T03:25:45Z\n" " JcatPkcs7Engine:\n" " Kind: pkcs7\n" " VerifyKind: signature\n"; /* set up context */ jcat_context_set_keyring_path(context, "/tmp"); /* get engine */ engine = jcat_context_get_engine(context, JCAT_BLOB_KIND_PKCS7, &error); g_assert_no_error(error); g_assert_nonnull(engine); payload = g_bytes_new_static(payload_str, sizeof(payload_str)); g_assert_nonnull(payload); 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_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 g_autofree gchar *fn_pass = NULL; g_autofree gchar *fn_sig = NULL; g_autofree gchar *pki_dir = NULL; g_autoptr(GBytes) data_fwbin = NULL; g_autoptr(GBytes) data_sig = NULL; g_autoptr(GError) error = NULL; g_autoptr(JcatBlob) blob = NULL; g_autoptr(JcatContext) context = jcat_context_new(); g_autoptr(JcatEngine) engine1 = NULL; #ifdef ENABLE_GPG g_autoptr(JcatEngine) engine2 = NULL; #endif 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"); pki_dir = g_test_build_filename(G_TEST_DIST, "pki", NULL); jcat_context_add_public_keys(context, pki_dir); /* get all engines */ engine1 = jcat_context_get_engine(context, JCAT_BLOB_KIND_SHA256, &error); g_assert_no_error(error); g_assert_nonnull(engine1); #ifdef ENABLE_GPG engine2 = jcat_context_get_engine(context, JCAT_BLOB_KIND_GPG, &error); g_assert_no_error(error); g_assert_nonnull(engine2); #endif engine3 = jcat_context_get_engine(context, JCAT_BLOB_KIND_PKCS7, &error); g_assert_no_error(error); g_assert_nonnull(engine3); engine4 = jcat_context_get_engine(context, JCAT_BLOB_KIND_LAST, &error); g_assert_error(error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND); g_assert_null(engine4); g_clear_error(&error); /* verify 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); fn_sig = g_test_build_filename(G_TEST_DIST, "colorhug", "firmware.bin.p7b", NULL); data_sig = jcat_get_contents_bytes(fn_sig, &error); g_assert_no_error(error); g_assert_nonnull(data_sig); blob = jcat_blob_new(JCAT_BLOB_KIND_PKCS7, data_sig); result = jcat_context_verify_blob(context, data_fwbin, blob, JCAT_VERIFY_FLAG_DISABLE_TIME_CHECKS, &error); g_assert_no_error(error); g_assert_nonnull(result); g_assert_cmpint(jcat_result_get_timestamp(result), >=, 1502871248); 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 } static void jcat_context_verify_item_sign_func(void) { #ifdef ENABLE_PKCS7 JcatResult *result; g_autofree gchar *fn_pass = NULL; g_autofree gchar *fn_sig = NULL; g_autofree gchar *pki_dir = NULL; g_autoptr(GBytes) data_fwbin = NULL; g_autoptr(GBytes) data_sig = NULL; g_autoptr(GError) error = NULL; g_autoptr(JcatBlob) blob = NULL; g_autoptr(JcatItem) item = jcat_item_new("filename.bin"); g_autoptr(JcatContext) context = jcat_context_new(); g_autoptr(JcatEngine) engine1 = NULL; #ifdef ENABLE_GPG g_autoptr(JcatEngine) engine2 = NULL; #endif g_autoptr(JcatEngine) engine3 = NULL; g_autoptr(JcatEngine) engine4 = NULL; g_autoptr(GPtrArray) results_fail = NULL; g_autoptr(GPtrArray) results_pass = NULL; /* set up context */ jcat_context_set_keyring_path(context, "/tmp"); pki_dir = g_test_build_filename(G_TEST_DIST, "pki", NULL); jcat_context_add_public_keys(context, pki_dir); /* get all engines */ engine1 = jcat_context_get_engine(context, JCAT_BLOB_KIND_SHA256, &error); g_assert_no_error(error); g_assert_nonnull(engine1); #ifdef ENABLE_GPG engine2 = jcat_context_get_engine(context, JCAT_BLOB_KIND_GPG, &error); g_assert_no_error(error); g_assert_nonnull(engine2); #endif engine3 = jcat_context_get_engine(context, JCAT_BLOB_KIND_PKCS7, &error); g_assert_no_error(error); g_assert_nonnull(engine3); engine4 = jcat_context_get_engine(context, JCAT_BLOB_KIND_LAST, &error); g_assert_error(error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND); g_assert_null(engine4); g_clear_error(&error); /* verify 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); fn_sig = g_test_build_filename(G_TEST_DIST, "colorhug", "firmware.bin.p7b", NULL); data_sig = jcat_get_contents_bytes(fn_sig, &error); g_assert_no_error(error); g_assert_nonnull(data_sig); blob = jcat_blob_new(JCAT_BLOB_KIND_PKCS7, data_sig); jcat_item_add_blob(item, blob); results_pass = jcat_context_verify_item(context, data_fwbin, item, JCAT_VERIFY_FLAG_DISABLE_TIME_CHECKS | JCAT_VERIFY_FLAG_REQUIRE_SIGNATURE, &error); g_assert_no_error(error); g_assert_nonnull(results_pass); g_assert_cmpint(results_pass->len, ==, 1); result = g_ptr_array_index(results_pass, 0); g_assert_cmpint(jcat_result_get_timestamp(result), >=, 1502871248); g_assert_cmpstr(jcat_result_get_authority(result), ==, "O=Linux Vendor Firmware Project,CN=LVFS CA"); /* enforce a checksum */ results_fail = jcat_context_verify_item(context, data_fwbin, item, JCAT_VERIFY_FLAG_DISABLE_TIME_CHECKS | JCAT_VERIFY_FLAG_REQUIRE_CHECKSUM, &error); g_assert_error(error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED); g_assert_null(results_fail); g_clear_error(&error); #else g_test_skip("no GnuTLS support enabled"); #endif } 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 JcatResult *result; g_autofree gchar *fn_pass = NULL; g_autofree gchar *pki_dir = NULL; g_autoptr(GBytes) data_fwbin = NULL; g_autoptr(GError) error = NULL; g_autoptr(JcatBlob) blob = NULL; g_autoptr(JcatItem) item = jcat_item_new("filename.bin"); g_autoptr(JcatContext) context = jcat_context_new(); g_autoptr(JcatEngine) engine1 = NULL; #ifdef ENABLE_GPG g_autoptr(JcatEngine) engine2 = NULL; #endif g_autoptr(JcatEngine) engine3 = NULL; g_autoptr(JcatEngine) engine4 = NULL; g_autoptr(GPtrArray) results_fail = NULL; g_autoptr(GPtrArray) results_pass = NULL; const gchar *sig_actual = "a196504d09871da4f7d83b874b500f8ee6e0619ab799f074814b316d88f96f7f"; /* set up context */ jcat_context_set_keyring_path(context, "/tmp"); pki_dir = g_test_build_filename(G_TEST_DIST, "pki", NULL); jcat_context_add_public_keys(context, pki_dir); /* get all engines */ engine1 = jcat_context_get_engine(context, JCAT_BLOB_KIND_SHA256, &error); g_assert_no_error(error); g_assert_nonnull(engine1); #ifdef ENABLE_GPG engine2 = jcat_context_get_engine(context, JCAT_BLOB_KIND_GPG, &error); g_assert_no_error(error); g_assert_nonnull(engine2); #endif engine3 = jcat_context_get_engine(context, JCAT_BLOB_KIND_PKCS7, &error); g_assert_no_error(error); g_assert_nonnull(engine3); engine4 = jcat_context_get_engine(context, JCAT_BLOB_KIND_LAST, &error); g_assert_error(error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND); g_assert_null(engine4); g_clear_error(&error); /* verify 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 = jcat_blob_new_utf8(JCAT_BLOB_KIND_SHA256, sig_actual); jcat_item_add_blob(item, blob); results_pass = jcat_context_verify_item(context, data_fwbin, item, JCAT_VERIFY_FLAG_DISABLE_TIME_CHECKS | JCAT_VERIFY_FLAG_REQUIRE_CHECKSUM, &error); g_assert_no_error(error); g_assert_nonnull(results_pass); g_assert_cmpint(results_pass->len, ==, 1); result = g_ptr_array_index(results_pass, 0); g_assert_cmpint(jcat_result_get_timestamp(result), ==, 0); g_assert_cmpstr(jcat_result_get_authority(result), ==, NULL); /* enforce a signature */ results_fail = jcat_context_verify_item(context, data_fwbin, item, JCAT_VERIFY_FLAG_DISABLE_TIME_CHECKS | JCAT_VERIFY_FLAG_REQUIRE_SIGNATURE, &error); g_assert_error(error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED); g_assert_null(results_fail); g_clear_error(&error); #else g_test_skip("no GnuTLS support enabled"); #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(); } libjcat-0.2.3/libjcat/jcat-sha1-engine.c000066400000000000000000000042431475014707200177300ustar00rootroot00000000000000/* * Copyright (C) 2020 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include "jcat-engine-private.h" #include "jcat-sha1-engine.h" struct _JcatSha1Engine { JcatEngine parent_instance; }; G_DEFINE_TYPE(JcatSha1Engine, jcat_sha1_engine, JCAT_TYPE_ENGINE) static JcatBlob * jcat_sha1_engine_self_sign(JcatEngine *engine, GBytes *data, JcatSignFlags flags, GError **error) { g_autofree gchar *tmp = NULL; tmp = g_compute_checksum_for_bytes(G_CHECKSUM_SHA1, data); return jcat_blob_new_utf8(JCAT_BLOB_KIND_SHA1, tmp); } static JcatResult * jcat_sha1_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_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 and got %s", tmp, sig); return NULL; } return JCAT_RESULT(g_object_new(JCAT_TYPE_RESULT, "engine", engine, NULL)); } static void jcat_sha1_engine_finalize(GObject *object) { G_OBJECT_CLASS(jcat_sha1_engine_parent_class)->finalize(object); } static void jcat_sha1_engine_class_init(JcatSha1EngineClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS(klass); JcatEngineClass *klass_app = JCAT_ENGINE_CLASS(klass); klass_app->self_sign = jcat_sha1_engine_self_sign; klass_app->self_verify = jcat_sha1_engine_self_verify; object_class->finalize = jcat_sha1_engine_finalize; } static void jcat_sha1_engine_init(JcatSha1Engine *self) { } JcatEngine * jcat_sha1_engine_new(JcatContext *context) { g_return_val_if_fail(JCAT_IS_CONTEXT(context), NULL); return JCAT_ENGINE(g_object_new(JCAT_TYPE_SHA1_ENGINE, "context", context, "kind", JCAT_BLOB_KIND_SHA1, "method", JCAT_BLOB_METHOD_CHECKSUM, NULL)); } libjcat-0.2.3/libjcat/jcat-sha1-engine.h000066400000000000000000000006161475014707200177350ustar00rootroot00000000000000/* * Copyright (C) 2020 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include "jcat-context.h" #include "jcat-engine.h" #define JCAT_TYPE_SHA1_ENGINE (jcat_sha1_engine_get_type()) G_DECLARE_FINAL_TYPE(JcatSha1Engine, jcat_sha1_engine, JCAT, SHA1_ENGINE, JcatEngine) JcatEngine * jcat_sha1_engine_new(JcatContext *context) G_GNUC_NON_NULL(1); libjcat-0.2.3/libjcat/jcat-sha256-engine.c000066400000000000000000000043251475014707200201050ustar00rootroot00000000000000/* * Copyright (C) 2020 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include "jcat-engine-private.h" #include "jcat-sha256-engine.h" struct _JcatSha256Engine { JcatEngine parent_instance; }; G_DEFINE_TYPE(JcatSha256Engine, jcat_sha256_engine, JCAT_TYPE_ENGINE) static JcatBlob * jcat_sha256_engine_self_sign(JcatEngine *engine, GBytes *data, JcatSignFlags flags, GError **error) { g_autofree gchar *tmp = NULL; tmp = g_compute_checksum_for_bytes(G_CHECKSUM_SHA256, data); return jcat_blob_new_utf8(JCAT_BLOB_KIND_SHA256, tmp); } static JcatResult * jcat_sha256_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_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 and got %s", tmp, sig); return NULL; } return JCAT_RESULT(g_object_new(JCAT_TYPE_RESULT, "engine", engine, NULL)); } static void jcat_sha256_engine_finalize(GObject *object) { G_OBJECT_CLASS(jcat_sha256_engine_parent_class)->finalize(object); } static void jcat_sha256_engine_class_init(JcatSha256EngineClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS(klass); JcatEngineClass *klass_app = JCAT_ENGINE_CLASS(klass); klass_app->self_sign = jcat_sha256_engine_self_sign; klass_app->self_verify = jcat_sha256_engine_self_verify; object_class->finalize = jcat_sha256_engine_finalize; } static void jcat_sha256_engine_init(JcatSha256Engine *self) { } JcatEngine * jcat_sha256_engine_new(JcatContext *context) { g_return_val_if_fail(JCAT_IS_CONTEXT(context), NULL); return JCAT_ENGINE(g_object_new(JCAT_TYPE_SHA256_ENGINE, "context", context, "kind", JCAT_BLOB_KIND_SHA256, "method", JCAT_BLOB_METHOD_CHECKSUM, NULL)); } libjcat-0.2.3/libjcat/jcat-sha256-engine.h000066400000000000000000000006321475014707200201070ustar00rootroot00000000000000/* * Copyright (C) 2020 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include "jcat-context.h" #include "jcat-engine.h" #define JCAT_TYPE_SHA256_ENGINE (jcat_sha256_engine_get_type()) G_DECLARE_FINAL_TYPE(JcatSha256Engine, jcat_sha256_engine, JCAT, SHA256_ENGINE, JcatEngine) JcatEngine * jcat_sha256_engine_new(JcatContext *context) G_GNUC_NON_NULL(1); libjcat-0.2.3/libjcat/jcat-sha512-engine.c000066400000000000000000000043251475014707200201000ustar00rootroot00000000000000/* * 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)); } libjcat-0.2.3/libjcat/jcat-sha512-engine.h000066400000000000000000000006321475014707200201020ustar00rootroot00000000000000/* * 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); libjcat-0.2.3/libjcat/jcat-tool.1000066400000000000000000000011301475014707200165140ustar00rootroot00000000000000.\" Report problems in https://github.com/hughsie/libjcat .TH man 8 "11 April 2021" @PACKAGE_VERSION@ "jcat-tool man page" .SH NAME jcat-tool \- standalone detached signature utility .SH SYNOPSIS jcat-tool [CMD] .SH DESCRIPTION .PP This manual page documents briefly the \fBjcat-tool\fR command. .PP \fBjcat-tool\fR allows a user to read, create and modify \fB.jcat\fR files. .SH OPTIONS The jcat-tool command takes various options depending on the action. Run \fBjcat-tool --help\fR for the full list. .SH SEE ALSO certtool(1) .SH BUGS No known bugs. .SH AUTHOR Richard Hughes (richard@hughsie.com) libjcat-0.2.3/libjcat/jcat-tool.c000066400000000000000000000672211475014707200166130ustar00rootroot00000000000000/* * Copyright (C) 2020 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #define G_LOG_DOMAIN "Jcat" #include "config.h" #include #include #include #include #include #ifdef HAVE_GIO_UNIX #include #endif #include "jcat-common-private.h" #include "jcat-context.h" #include "jcat-file.h" typedef struct { GCancellable *cancellable; GPtrArray *cmd_array; JcatContext *context; gboolean basename; gboolean disable_time_checks; gchar *prefix; gchar *appstream_id; JcatBlobKind kind; JcatBlobKind target; } JcatToolPrivate; static void jcat_tool_private_free(JcatToolPrivate *priv) { if (priv == NULL) return; g_object_unref(priv->cancellable); if (priv->context != NULL) g_object_unref(priv->context); if (priv->cmd_array != NULL) g_ptr_array_unref(priv->cmd_array); g_free(priv->appstream_id); g_free(priv->prefix); g_free(priv); } #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wunused-function" G_DEFINE_AUTOPTR_CLEANUP_FUNC(JcatToolPrivate, jcat_tool_private_free) #pragma clang diagnostic pop typedef gboolean (*FuUtilPrivateCb)(JcatToolPrivate *util, gchar **values, GError **error); typedef struct { gchar *name; gchar *arguments; gchar *description; FuUtilPrivateCb callback; } FuUtilItem; static void jcat_tool_item_free(FuUtilItem *item) { g_free(item->name); g_free(item->arguments); g_free(item->description); g_free(item); } static gint jcat_tool_sort_command_name_cb(FuUtilItem **item1, FuUtilItem **item2) { return g_strcmp0((*item1)->name, (*item2)->name); } static void jcat_tool_add(GPtrArray *array, const gchar *name, const gchar *arguments, const gchar *description, FuUtilPrivateCb callback) { g_auto(GStrv) names = NULL; g_return_if_fail(name != NULL); g_return_if_fail(description != NULL); g_return_if_fail(callback != NULL); /* add each one */ names = g_strsplit(name, ",", -1); for (guint i = 0; names[i] != NULL; i++) { FuUtilItem *item = g_new0(FuUtilItem, 1); item->name = g_strdup(names[i]); if (i == 0) { item->description = g_strdup(description); } else { /* TRANSLATORS: this is a command alias, e.g. 'get-devices' */ item->description = g_strdup_printf(_("Alias to %s"), names[0]); } item->arguments = g_strdup(arguments); item->callback = callback; g_ptr_array_add(array, item); } } static gchar * jcat_tool_get_descriptions(GPtrArray *array) { gsize len; const gsize max_len = 31; FuUtilItem *item; GString *string; /* print each command */ string = g_string_new(""); for (guint i = 0; i < array->len; i++) { item = g_ptr_array_index(array, i); g_string_append(string, " "); g_string_append(string, item->name); len = strlen(item->name) + 2; if (item->arguments != NULL) { g_string_append(string, " "); g_string_append(string, item->arguments); len += strlen(item->arguments) + 1; } if (len < max_len) { for (guint j = len; j < max_len + 1; j++) g_string_append_c(string, ' '); g_string_append(string, item->description); g_string_append_c(string, '\n'); } else { g_string_append_c(string, '\n'); for (guint j = 0; j < max_len + 1; j++) g_string_append_c(string, ' '); g_string_append(string, item->description); g_string_append_c(string, '\n'); } } /* remove trailing newline */ if (string->len > 0) g_string_set_size(string, string->len - 1); return g_string_free(string, FALSE); } static gboolean jcat_tool_run(JcatToolPrivate *priv, const gchar *command, gchar **values, GError **error) { /* find command */ for (guint i = 0; i < priv->cmd_array->len; i++) { FuUtilItem *item = g_ptr_array_index(priv->cmd_array, i); if (g_strcmp0(item->name, command) == 0) return item->callback(priv, values, error); } /* not found */ g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_FAILED, /* TRANSLATORS: error message */ _("Command not found")); return FALSE; } static gboolean jcat_tool_info(JcatToolPrivate *priv, gchar **values, GError **error) { g_autoptr(GFile) gfile = NULL; g_autoptr(JcatFile) file = jcat_file_new(); g_autofree gchar *str = NULL; /* check args */ if (g_strv_length(values) != 1) { g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_FAILED, "Invalid arguments, expected FILENAME"); return FALSE; } /* import file */ gfile = g_file_new_for_path(values[0]); if (!jcat_file_import_file(file, gfile, JCAT_IMPORT_FLAG_NONE, priv->cancellable, error)) return FALSE; /* output to console */ str = jcat_file_to_string(file); g_print("%s", str); /* success */ return TRUE; } static gboolean jcat_tool_add_alias(JcatToolPrivate *priv, gchar **values, GError **error) { g_autoptr(GFile) gfile = NULL; g_autoptr(JcatFile) file = jcat_file_new(); g_autoptr(JcatItem) item = NULL; /* check args */ if (g_strv_length(values) != 3) { g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_FAILED, "Invalid arguments, expected FILENAME ID ALIAS_ID"); return FALSE; } /* import file */ gfile = g_file_new_for_path(values[0]); if (!jcat_file_import_file(file, gfile, JCAT_IMPORT_FLAG_NONE, priv->cancellable, error)) return FALSE; /* add alias */ item = jcat_file_get_item_by_id(file, values[1], error); if (item == NULL) return FALSE; jcat_item_add_alias_id(item, values[2]); /* export new file */ return jcat_file_export_file(file, gfile, JCAT_EXPORT_FLAG_NONE, priv->cancellable, error); } static gboolean jcat_tool_remove_alias(JcatToolPrivate *priv, gchar **values, GError **error) { g_autoptr(GFile) gfile = NULL; g_autoptr(JcatFile) file = jcat_file_new(); g_autoptr(JcatItem) item = NULL; /* check args */ if (g_strv_length(values) != 3) { g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_FAILED, "Invalid arguments, expected FILENAME ID ALIAS_ID"); return FALSE; } /* import file */ gfile = g_file_new_for_path(values[0]); if (!jcat_file_import_file(file, gfile, JCAT_IMPORT_FLAG_NONE, priv->cancellable, error)) return FALSE; /* remove alias */ item = jcat_file_get_item_by_id(file, values[1], error); if (item == NULL) return FALSE; jcat_item_remove_alias_id(item, values[2]); /* export new file */ return jcat_file_export_file(file, gfile, JCAT_EXPORT_FLAG_NONE, priv->cancellable, error); } static gchar * jcat_tool_import_convert_id_safe(JcatToolPrivate *priv, const gchar *filename) { if (priv->basename) return g_path_get_basename(filename); return g_strdup(filename); } static gboolean jcat_tool_import(JcatToolPrivate *priv, gchar **values, GError **error) { g_autoptr(GBytes) data_sig = NULL; g_autoptr(GFile) gfile = NULL; g_autoptr(JcatBlob) blob = NULL; g_autoptr(JcatFile) file = jcat_file_new(); g_autoptr(JcatItem) item = NULL; g_autofree gchar *id_safe = NULL; /* check args */ if (g_strv_length(values) != 3) { g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_FAILED, "Invalid arguments, expected FILENAME DATA DETACHED_KEY"); return FALSE; } /* import existing file */ gfile = g_file_new_for_path(values[0]); if (g_file_query_exists(gfile, priv->cancellable)) { if (!jcat_file_import_file(file, gfile, JCAT_IMPORT_FLAG_NONE, priv->cancellable, error)) return FALSE; } /* load source */ data_sig = jcat_get_contents_bytes(values[2], error); if (data_sig == NULL) return FALSE; /* 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++) g_string_append_printf(tmp, "%s,", jcat_blob_kind_to_filename_ext(i)); if (tmp->len > 0) g_string_truncate(tmp, tmp->len - 1); g_set_error(error, G_IO_ERROR, G_IO_ERROR_FAILED, "Cannot detect blob kind from extension, expected %s", tmp->str); return FALSE; } /* sign the file using the engine */ 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) { item = jcat_item_new(id_safe); jcat_file_add_item(file, item); } /* just import existing key */ if (priv->appstream_id != NULL) jcat_blob_set_appstream_id(blob, priv->appstream_id); jcat_item_add_blob(item, blob); /* export new file */ return jcat_file_export_file(file, gfile, JCAT_EXPORT_FLAG_NONE, priv->cancellable, error); } static gboolean jcat_tool_self_sign(JcatToolPrivate *priv, gchar **values, GError **error) { g_autoptr(GBytes) source = NULL; g_autoptr(GFile) gfile = NULL; g_autoptr(JcatBlob) blob = NULL; g_autoptr(JcatEngine) engine = NULL; g_autoptr(JcatFile) file = jcat_file_new(); g_autoptr(JcatItem) item = NULL; g_autofree gchar *id_safe = NULL; /* check args */ if (g_strv_length(values) != 2) { g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_FAILED, "Invalid arguments, expected FILENAME SOURCE"); return FALSE; } /* import existing file */ gfile = g_file_new_for_path(values[0]); if (g_file_query_exists(gfile, priv->cancellable)) { if (!jcat_file_import_file(file, gfile, JCAT_IMPORT_FLAG_NONE, priv->cancellable, error)) return FALSE; } /* 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) { item = jcat_item_new(id_safe); 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; engine = jcat_context_get_engine(priv->context, priv->kind, error); if (engine == NULL) return FALSE; blob = jcat_engine_self_sign(engine, source, JCAT_SIGN_FLAG_NONE, error); if (blob == NULL) 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 */ return jcat_file_export_file(file, gfile, JCAT_EXPORT_FLAG_NONE, priv->cancellable, error); } static gboolean jcat_tool_sign(JcatToolPrivate *priv, gchar **values, GError **error) { g_autoptr(GBytes) cert = NULL; g_autoptr(GBytes) privkey = NULL; g_autoptr(GBytes) source = NULL; g_autoptr(GFile) gfile = NULL; g_autoptr(JcatBlob) blob = NULL; g_autoptr(JcatFile) file = jcat_file_new(); g_autoptr(JcatItem) item = NULL; g_autoptr(JcatEngine) engine = NULL; g_autofree gchar *id_safe = NULL; /* check args */ if (g_strv_length(values) != 4) { g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_FAILED, "Invalid arguments, expected FILENAME " "SOURCE CERT PRIVKEY"); return FALSE; } /* import existing file */ gfile = g_file_new_for_path(values[0]); if (g_file_query_exists(gfile, priv->cancellable)) { if (!jcat_file_import_file(file, gfile, JCAT_IMPORT_FLAG_NONE, priv->cancellable, error)) return FALSE; } /* 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) { item = jcat_item_new(id_safe); 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; engine = jcat_context_get_engine(priv->context, priv->kind, error); if (engine == NULL) return FALSE; blob = jcat_engine_pubkey_sign(engine, source, cert, privkey, JCAT_SIGN_FLAG_ADD_TIMESTAMP | JCAT_SIGN_FLAG_ADD_CERT, error); if (blob == NULL) 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 */ return jcat_file_export_file(file, gfile, JCAT_EXPORT_FLAG_NONE, priv->cancellable, error); } static gboolean jcat_tool_verify_item(JcatToolPrivate *priv, JcatItem *item, GError **error) { gboolean ret = TRUE; g_autoptr(GBytes) source = NULL; g_autoptr(GPtrArray) alias_ids = jcat_item_get_alias_ids(item); g_autoptr(GPtrArray) blobs = jcat_item_get_blobs(item); g_autoptr(GPtrArray) fns_possible = g_ptr_array_new_with_free_func(g_free); g_autofree gchar *fn_safe = NULL; /* load source */ g_print("%s:\n", jcat_item_get_id(item)); /* find the source */ g_ptr_array_add(fns_possible, g_build_filename(priv->prefix, jcat_item_get_id(item), NULL)); for (guint i = 0; i < alias_ids->len; i++) { const gchar *alias_id = g_ptr_array_index(alias_ids, i); g_ptr_array_add(fns_possible, g_build_filename(priv->prefix, alias_id, NULL)); } for (guint i = 0; i < fns_possible->len; i++) { const gchar *fn = g_ptr_array_index(fns_possible, i); if (g_file_test(fn, G_FILE_TEST_EXISTS)) { fn_safe = g_strdup(fn); break; } } if (fn_safe == NULL) { g_autofree gchar *str = NULL; g_autofree gchar **strv = g_new0(gchar *, fns_possible->len); for (guint i = 0; i < fns_possible->len; i++) strv[i] = g_ptr_array_index(fns_possible, i); str = g_strjoinv(" or ", strv); 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; /* 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, 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", kind_str->str, error_verify->message); continue; } 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", kind_str->str, authority != NULL ? authority : "OK"); } if (!ret) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "Validation failed"); return FALSE; } /* success */ return TRUE; } static gboolean jcat_tool_export(JcatToolPrivate *priv, gchar **values, GError **error) { g_autoptr(GFile) gfile = NULL; g_autoptr(JcatFile) file = jcat_file_new(); g_autoptr(GPtrArray) items = NULL; /* check args */ if (g_strv_length(values) != 1) { g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_FAILED, "Invalid arguments, expected FILENAME"); return FALSE; } /* import existing file */ gfile = g_file_new_for_path(values[0]); if (!jcat_file_import_file(file, gfile, JCAT_IMPORT_FLAG_NONE, priv->cancellable, error)) return FALSE; /* verify each file */ items = jcat_file_get_items(file); for (guint i = 0; i < items->len; i++) { JcatItem *item = g_ptr_array_index(items, i); g_autoptr(GPtrArray) blobs = jcat_item_get_blobs(item); for (guint j = 0; j < blobs->len; j++) { JcatBlob *blob = g_ptr_array_index(blobs, j); g_autofree gchar *fn = NULL; g_autoptr(GString) str = NULL; /* skip */ if (priv->kind != JCAT_BLOB_KIND_UNKNOWN && priv->kind != jcat_blob_get_kind(blob)) continue; /* export */ str = g_string_new(jcat_item_get_id(item)); if (jcat_blob_get_appstream_id(blob) != NULL) g_string_append_printf(str, "-%s", jcat_blob_get_appstream_id(blob)); g_string_append_printf( str, ".%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), 0666, error)) return FALSE; g_print("Wrote %s\n", fn); } } /* success */ return TRUE; } static gboolean jcat_tool_verify(JcatToolPrivate *priv, gchar **values, GError **error) { gboolean ret = TRUE; g_autoptr(GFile) gfile = NULL; g_autoptr(JcatFile) file = jcat_file_new(); /* check args */ if (g_strv_length(values) < 1) { g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_FAILED, "Invalid arguments, expected FILENAME [SOURCE]"); return FALSE; } /* import existing file */ gfile = g_file_new_for_path(values[0]); if (!jcat_file_import_file(file, gfile, JCAT_IMPORT_FLAG_NONE, priv->cancellable, error)) return FALSE; /* verify each file */ if (g_strv_length(values) > 1) { g_autoptr(JcatItem) item = NULL; g_autofree gchar *id_safe = jcat_tool_import_convert_id_safe(priv, values[1]); item = jcat_file_get_item_by_id(file, id_safe, error); if (item == NULL) return FALSE; if (!jcat_tool_verify_item(priv, item, error)) return FALSE; } else { g_autoptr(GPtrArray) items = jcat_file_get_items(file); for (guint i = 0; i < items->len; i++) { JcatItem *item = g_ptr_array_index(items, i); g_autoptr(GError) error_local = NULL; if (!jcat_tool_verify_item(priv, item, &error_local)) { g_print(" FAILED: %s\n", error_local->message); ret = FALSE; continue; } } } if (!ret) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "Validation failed"); return FALSE; } /* success */ return TRUE; } #ifdef HAVE_GIO_UNIX static gboolean jcat_tool_sigint_cb(gpointer user_data) { JcatToolPrivate *priv = (JcatToolPrivate *)user_data; g_debug("Handling SIGINT"); g_cancellable_cancel(priv->cancellable); return FALSE; } #endif int main(int argc, char *argv[]) { gboolean basename = FALSE; gboolean ret; gboolean verbose = FALSE; gboolean version = FALSE; g_autofree gchar *appstream_id = NULL; 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; g_autoptr(JcatToolPrivate) priv = g_new0(JcatToolPrivate, 1); g_autoptr(GError) error = NULL; g_autoptr(GOptionContext) context = NULL; const GOptionEntry options[] = { {"version", '\0', 0, G_OPTION_ARG_NONE, &version, _("Print the version number"), NULL}, {"verbose", 'v', 0, G_OPTION_ARG_NONE, &verbose, _("Print verbose debug statements"), NULL}, {"basename", 'v', 0, G_OPTION_ARG_NONE, &basename, _("Only consider the basename for the ID"), NULL}, {"disable-time-checks", 'v', 0, G_OPTION_ARG_NONE, &priv->disable_time_checks, _("Disable time checks when verifying"), NULL}, {"public-key", '\0', 0, G_OPTION_ARG_STRING, &public_key, _("Location of public key used for verification"), NULL}, {"public-keys", '\0', 0, G_OPTION_ARG_STRING, &public_keys, _("Location of public key directories used for verification"), NULL}, {"prefix", '\0', 0, G_OPTION_ARG_STRING, &prefix, _("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, G_OPTION_ARG_STRING, &appstream_id, _("Appstream ID for blob, e.g. `com.bbc`"), NULL}, {"keyring", '\0', 0, G_OPTION_ARG_STRING, &keyring_path, _("Keyring location, e.g. `/var/lib/fwupd`"), NULL}, {NULL}}; #ifdef _WIN32 /* workaround Windows setting the codepage to 1252 */ g_setenv("LANG", "C.UTF-8", FALSE); #endif setlocale(LC_ALL, ""); bindtextdomain(GETTEXT_PACKAGE, JCAT_LOCALEDIR); bind_textdomain_codeset(GETTEXT_PACKAGE, "UTF-8"); textdomain(GETTEXT_PACKAGE); /* add commands */ priv->cmd_array = g_ptr_array_new_with_free_func((GDestroyNotify)jcat_tool_item_free); jcat_tool_add(priv->cmd_array, "info", "FILENAME", /* TRANSLATORS: command description */ _("Show information about a file"), jcat_tool_info); jcat_tool_add(priv->cmd_array, "self-sign", "FILENAME SOURCE", /* TRANSLATORS: command description */ _("Add a self-signed signature to a file"), jcat_tool_self_sign); jcat_tool_add(priv->cmd_array, "sign", "FILENAME SOURCE CERT PRIVKEY", /* TRANSLATORS: command description */ _("Add a signature to a file"), jcat_tool_sign); jcat_tool_add(priv->cmd_array, "import", "FILENAME DATA DETACHED_KEY", /* TRANSLATORS: command description */ _("Import an existing signature to a file"), jcat_tool_import); jcat_tool_add(priv->cmd_array, "export", "FILENAME", /* TRANSLATORS: command description */ _("Exports all embedded signatures to files"), jcat_tool_export); jcat_tool_add(priv->cmd_array, "verify", "FILENAME [SOURCE]", /* TRANSLATORS: command description */ _("Verify a signature from a file"), jcat_tool_verify); jcat_tool_add(priv->cmd_array, "add-alias", "FILENAME ID ALIAS_ID", /* TRANSLATORS: command description */ _("Add an alias for a specific item"), jcat_tool_add_alias); jcat_tool_add(priv->cmd_array, "remove-alias", "FILENAME ID ALIAS_ID", /* TRANSLATORS: command description */ _("Remove an alias for a specific item"), jcat_tool_remove_alias); /* do stuff on ctrl+c */ priv->cancellable = g_cancellable_new(); #ifdef HAVE_GIO_UNIX g_unix_signal_add_full(G_PRIORITY_DEFAULT, SIGINT, jcat_tool_sigint_cb, priv, NULL); #endif /* sort by command name */ g_ptr_array_sort(priv->cmd_array, (GCompareFunc)jcat_tool_sort_command_name_cb); /* get a list of the commands */ context = g_option_context_new(NULL); cmd_descriptions = jcat_tool_get_descriptions(priv->cmd_array); g_option_context_set_summary(context, cmd_descriptions); /* TRANSLATORS: JCAT stands for device firmware update */ g_set_application_name(_("JSON Catalog Utility")); g_option_context_add_main_entries(context, options, NULL); ret = g_option_context_parse(context, &argc, &argv, &error); if (!ret) { /* TRANSLATORS: the user didn't read the man page */ g_print("%s: %s\n", _("Failed to parse arguments"), error->message); return EXIT_FAILURE; } /* create context */ priv->basename = basename; priv->appstream_id = g_strdup(appstream_id); priv->prefix = g_strdup(prefix != NULL ? prefix : "."); priv->context = jcat_context_new(); if (public_key != NULL) jcat_context_add_public_key(priv->context, public_key); if (public_keys != NULL) jcat_context_add_public_keys(priv->context, public_keys); if (keyring_path != NULL) jcat_context_set_keyring_path(priv->context, keyring_path); if (kind != NULL) { priv->kind = jcat_blob_kind_from_string(kind); if (priv->kind == JCAT_BLOB_KIND_UNKNOWN) { g_autoptr(GString) tmp = g_string_new(NULL); for (guint i = 1; i < JCAT_BLOB_KIND_LAST; i++) g_string_append_printf(tmp, "%s,", jcat_blob_kind_to_string(i)); if (tmp->len > 0) g_string_truncate(tmp, tmp->len - 1); g_printerr("Failed to parse '%s', expected %s", kind, tmp->str); 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) g_setenv("G_MESSAGES_DEBUG", "all", FALSE); /* version */ if (version) { g_print("%s %s\n", PACKAGE_NAME, PACKAGE_VERSION); return EXIT_SUCCESS; } /* run the specified command */ ret = jcat_tool_run(priv, argv[1], (gchar **)&argv[2], &error); if (!ret) { if (g_error_matches(error, G_IO_ERROR, G_IO_ERROR_FAILED)) { g_autofree gchar *tmp = NULL; tmp = g_option_context_get_help(context, TRUE, NULL); g_print("%s\n\n%s", error->message, tmp); } else { g_print("%s\n", error->message); } return EXIT_FAILURE; } /* success/ */ return EXIT_SUCCESS; } libjcat-0.2.3/libjcat/jcat-version.c000066400000000000000000000007221475014707200173140ustar00rootroot00000000000000/* * 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); } libjcat-0.2.3/libjcat/jcat-version.h.in000066400000000000000000000026741475014707200177360ustar00rootroot00000000000000/* * Copyright (C) 2015 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #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@) #endif /** * JCAT_MINOR_VERSION: * * The compile-time minor version * * Since: 0.1.0 */ #ifndef JCAT_MINOR_VERSION #define JCAT_MINOR_VERSION (@JCAT_MINOR_VERSION@) #endif /** * JCAT_MICRO_VERSION: * * The compile-time micro version * * Since: 0.1.0 */ #ifndef JCAT_MICRO_VERSION #define JCAT_MICRO_VERSION (@JCAT_MICRO_VERSION@) #endif /** * 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 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); libjcat-0.2.3/libjcat/jcat.h000066400000000000000000000007331475014707200156400ustar00rootroot00000000000000/* * Copyright (C) 2018 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #define __LIBJCAT_H_INSIDE__ #include #include #include #include #include #include #include #include #include #undef __LIBJCAT_H_INSIDE__ libjcat-0.2.3/libjcat/jcat.map000066400000000000000000000056461475014707200161760ustar00rootroot00000000000000# generated automatically, do not edit! LIBJCAT_0.1.0 { global: jcat_blob_get_appstream_id; jcat_blob_get_data; jcat_blob_get_data_as_string; jcat_blob_get_kind; jcat_blob_get_timestamp; jcat_blob_get_type; jcat_blob_kind_from_string; jcat_blob_kind_to_filename_ext; jcat_blob_kind_to_string; jcat_blob_new; jcat_blob_new_full; jcat_blob_new_utf8; jcat_blob_set_appstream_id; jcat_blob_set_timestamp; jcat_blob_to_string; jcat_context_add_public_key; jcat_context_add_public_keys; jcat_context_get_engine; jcat_context_get_keyring_path; jcat_context_get_type; jcat_context_new; jcat_context_set_keyring_path; jcat_context_verify_blob; jcat_context_verify_item; jcat_engine_get_type; jcat_engine_pubkey_sign; jcat_engine_pubkey_verify; jcat_engine_self_sign; jcat_engine_self_verify; jcat_file_add_item; jcat_file_export_file; jcat_file_export_json; jcat_file_export_stream; jcat_file_get_item_by_id; jcat_file_get_item_default; jcat_file_get_items; jcat_file_get_type; jcat_file_get_version_major; jcat_file_get_version_minor; jcat_file_import_file; jcat_file_import_json; jcat_file_import_stream; jcat_file_new; jcat_file_to_string; jcat_item_add_blob; jcat_item_get_blobs; jcat_item_get_blobs_by_kind; jcat_item_get_id; jcat_item_get_type; jcat_item_new; jcat_item_to_string; jcat_result_get_authority; jcat_result_get_timestamp; jcat_result_get_type; jcat_result_to_string; local: *; }; LIBJCAT_0.1.1 { global: jcat_item_add_alias_id; jcat_item_get_alias_ids; jcat_item_remove_alias_id; local: *; } LIBJCAT_0.1.0; LIBJCAT_0.1.3 { global: jcat_engine_get_kind; jcat_engine_get_method; jcat_result_get_kind; 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; libjcat-0.2.3/libjcat/meson.build000066400000000000000000000135471475014707200167170ustar00rootroot00000000000000 libjcat_version_h = configure_file( input : 'jcat-version.h.in', output : 'jcat-version.h', configuration : conf ) install_headers( 'jcat.h', subdir : 'libjcat-1', ) jcat_headers = files( '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( jcat_headers, subdir : 'libjcat-1/libjcat', ) jcat_src = [] if get_option('gpg') jcat_src += 'jcat-gpg-engine.c' endif if get_option('pkcs7') 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([ '-Wl,--version-script,@0@/@1@'.format(meson.current_source_dir(), jcat_mapfile) ]) libjcat = library( 'jcat', sources : [ 'jcat-blob.c', 'jcat-context.c', 'jcat-common.c', '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, version : lt_version, include_directories : configinc, dependencies : libjcat_deps, link_args : libjcat_ldflags, link_depends : jcat_mapfile, install : true ) pkgg_variables = [] if get_option('gpg') pkgg_variables += 'supported_gpg=1' endif 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, requires : [ 'gio-2.0', 'json-glib-1.0', ], subdirs : 'libjcat-1', version : meson.project_version(), name : 'libjcat', filebase : 'jcat', description : 'libjcat is a library to read Jcat files', variables : pkgg_variables, ) libjcat_dep = declare_dependency( link_with : libjcat, include_directories : [ include_directories('.'), configinc, ], variables : pkgg_variables, dependencies : libjcat_deps ) if get_option('introspection') if libjsonglib.type_name() == 'internal' json_glib_girtarget = subproject('json-glib').get_variable('json_glib_gir')[0] else json_glib_girtarget = 'Json-1.0' endif jcat_gir = gnome.generate_gir(libjcat, sources : [ 'jcat-blob.c', 'jcat-blob.h', 'jcat-common.c', 'jcat-common.h', 'jcat-file.c', 'jcat-file.h', 'jcat-item.c', 'jcat-item.h', 'jcat-context.c', 'jcat-context.h', 'jcat-engine.c', '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', symbol_prefix : 'jcat', identifier_prefix : 'Jcat', export_packages : 'jcat', header : 'jcat.h', dependencies : [ libjcat_deps, ], includes : [ 'Gio-2.0', 'GObject-2.0', json_glib_girtarget, ], link_with : [ libjcat, ], install : true ) if get_option('vapi') gnome.generate_vapi('jcat', sources : jcat_gir[0], packages : [ 'gio-2.0', 'json-glib-1.0' ], install : true, ) endif python_interpreter = find_program('python3') # Verify the map file is correct -- note we can't actually use the generated # file for two reasons: # # 1. We don't hard depend on GObject Introspection # 2. The map file is required to build the lib that the GIR is built from # # To avoid the circular dep, and to ensure we don't change exported API # accidentally actually check in a version of the version script to git. mapfile_target = custom_target('jcat_mapfile', input: jcat_gir[0], output: 'jcat.map', command: [ python_interpreter, join_paths(meson.project_source_root(), 'contrib', 'generate-version-script.py'), 'LIBJCAT', '@INPUT@', '@OUTPUT@', ], ) test('jcat-exported-api', diffcmd, args : [ '-urNp', join_paths(meson.current_source_dir(), 'jcat.map'), mapfile_target, ], ) endif if get_option('cli') jcat_tool = executable( 'jcat-tool', sources : [ 'jcat-common.c', 'jcat-tool.c', ], include_directories : [ configinc, ], dependencies : [ giounix, libjsonglib, ], link_with : [ libjcat, ], install : true, install_dir : bindir, ) if get_option('man') configure_file( input : 'jcat-tool.1', output : 'jcat-tool.1', configuration : conf, install: true, install_dir: join_paths(mandir, 'man1'), ) endif endif if get_option('tests') 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) testdatadirs.set('G_TEST_BUILDDIR', testdatadir_dst) e = executable( 'jcat-self-test', test_deps, sources : [ 'jcat-self-test.c', 'jcat-blob.c', 'jcat-common.c', 'jcat-context.c', 'jcat-engine.c', '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, ], install : true, install_dir : installed_test_bindir ) test('jcat-self-test', e, env : testdatadirs) endif jcat_incdir = include_directories('.') libjcat-0.2.3/meson.build000066400000000000000000000115521475014707200153010ustar00rootroot00000000000000project('libjcat', 'c', version : '0.2.3', license : 'LGPL-2.1+', meson_version : '>=0.56.0', default_options : ['warning_level=2', 'c_std=c99'], ) libjcat_version = meson.project_version() varr = libjcat_version.split('.') libjcat_major_version = varr[0] libjcat_minor_version = varr[1] libjcat_micro_version = varr[2] conf = configuration_data() conf.set('JCAT_MAJOR_VERSION', libjcat_major_version) conf.set('JCAT_MINOR_VERSION', libjcat_minor_version) conf.set('JCAT_MICRO_VERSION', libjcat_micro_version) conf.set_quoted('PACKAGE_VERSION', libjcat_version) # libtool versioning - this applies to libjcat lt_current = '1' lt_revision = '0' lt_age = '0' lt_version = '@0@.@1@.@2@'.format(lt_current, lt_age, lt_revision) configinc = include_directories('.') # get supported warning flags warning_flags = [ '-Wfatal-errors', '-Wno-nonnull-compare', '-Waggregate-return', '-Wunused', '-Warray-bounds', '-Wcast-align', '-Wclobbered', '-Wdeclaration-after-statement', '-Wduplicated-branches', '-Wduplicated-cond', '-Wempty-body', '-Wformat=2', '-Wformat-nonliteral', '-Wformat-security', '-Wformat-signedness', '-Wignored-qualifiers', '-Wimplicit-function-declaration', '-Wincompatible-pointer-types-discards-qualifiers', '-Winit-self', '-Wlogical-op', '-Wmaybe-uninitialized', '-Wmissing-declarations', '-Wmissing-format-attribute', '-Wmissing-include-dirs', '-Wmissing-noreturn', '-Wmissing-parameter-type', '-Wmissing-prototypes', '-Wnested-externs', '-Wno-cast-function-type', '-Wno-error=cpp', '-Wno-unknown-pragmas', '-Wno-discarded-qualifiers', '-Wno-missing-field-initializers', '-Wno-strict-aliasing', '-Wno-suggest-attribute=format', '-Wno-unused-parameter', '-Wnull-dereference', '-Wold-style-definition', '-Woverride-init', '-Wpointer-arith', '-Wredundant-decls', '-Wreturn-type', '-Wshadow', '-Wsign-compare', '-Wstrict-aliasing', '-Wstrict-prototypes', '-Wswitch-default', '-Wtype-limits', '-Wundef', '-Wuninitialized', '-Wunused-but-set-variable', '-Wunused-variable', '-Wwrite-strings' ] cc = meson.get_compiler('c') add_project_arguments(cc.get_supported_arguments(warning_flags), language : 'c') if not meson.is_cross_build() add_project_arguments('-fstack-protector-strong', language : 'c') endif # enable full RELRO where possible # FIXME: until https://github.com/mesonbuild/meson/issues/1140 is fixed global_link_args = [] test_link_args = [ '-Wl,-z,relro', '-Wl,-z,now', ] foreach link_arg: test_link_args if cc.has_link_argument(link_arg) global_link_args += link_arg endif endforeach add_project_link_arguments( global_link_args, language: 'c' ) prefix = get_option('prefix') if host_machine.system() == 'windows' bindir = get_option('bindir') libexecdir = get_option('libexecdir') localstatedir = get_option('localstatedir') installed_test_bindir = get_option('libexecdir') installed_test_datadir = get_option('datadir') else bindir = join_paths(prefix, get_option('bindir')) datadir = join_paths(prefix, get_option('datadir')) localstatedir = join_paths(prefix, get_option('localstatedir')) libexecdir = join_paths(prefix, get_option('libexecdir')) installed_test_bindir = join_paths(libexecdir, 'installed-tests', meson.project_name()) installed_test_datadir = join_paths(datadir, 'installed-tests', meson.project_name()) endif mandir = join_paths(prefix, get_option('mandir')) localedir = join_paths(prefix, get_option('localedir')) diffcmd = find_program('diff') gio = dependency('gio-2.0', version : '>= 2.45.8') giounix = dependency('gio-unix-2.0', version : '>= 2.45.8', required: false) if giounix.found() conf.set('HAVE_GIO_UNIX', '1') endif libjsonglib = dependency('json-glib-1.0', version : '>= 1.1.1', fallback : ['json-glib', 'json_glib_dep']) libjcat_deps = [ gio, libjsonglib, ] if get_option('pkcs7') gnutls = dependency('gnutls', version : '>= 3.6.0') conf.set('ENABLE_PKCS7', '1') 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') conf.set('ENABLE_GPG', '1') libjcat_deps += gpgme libjcat_deps += gpgerror endif 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) conf.set_quoted('JCAT_LOCALEDIR', localedir) conf.set_quoted('G_LOG_DOMAIN', 'Jcat') conf.set_quoted('VERSION', meson.project_version()) configure_file( output : 'config.h', configuration : conf ) subdir('data') subdir('libjcat') if get_option('gtkdoc') gtkdocscan = find_program('gtkdoc-scan', required : true) subdir('docs') endif libjcat-0.2.3/meson_options.txt000066400000000000000000000015211475014707200165670ustar00rootroot00000000000000option('gtkdoc', type : 'boolean', value : false, description : 'enable developer documentation') option('introspection', type : 'boolean', value : true, description : 'generate GObject Introspection data') option('vapi', type : 'boolean', value : true, description : 'enable VAPI') 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') libjcat-0.2.3/subprojects/000077500000000000000000000000001475014707200154765ustar00rootroot00000000000000libjcat-0.2.3/subprojects/json-glib.wrap000066400000000000000000000003631475014707200202570ustar00rootroot00000000000000[wrap-git] directory=json-glib url=https://gitlab.gnome.org/GNOME/json-glib.git # tactically chosen to use a super old GLib version, # but also new enough to support being used as a subproject revision=c30c988ac3b0d9e7c332232e3c3969818749b239