rpm-sequoia-1.7.0/.cargo/config.toml000064400000000000000000000002561046102023000154110ustar 00000000000000[target.'cfg(all())'] # Note: if the RUSTFLAGS environment variable is set, this will be # ignored. rustflags = [ "-Aunused-parens", "-Aunused-macros", ] rpm-sequoia-1.7.0/.cargo_vcs_info.json0000644000000001360000000000100133030ustar { "git": { "sha1": "0667e04ae7fb8cf0490919978d69883d16400e41" }, "path_in_vcs": "" }rpm-sequoia-1.7.0/.ci/all_commits.sh000075500000000000000000000022711046102023000154100ustar 00000000000000#!/usr/bin/env bash # Test all commits on this branch but the last one. # # Used in the all_commits ci job to ensure all commits build # and tests pass at least for the sequoia-openpgp crate. # NOTE: under gitlab's Settings, "CI/CD", General Pipelines ensure # that the "git shallow clone" setting is set to 0. Otherwise other # branch are not fetched. set -e set -x # Use dummy identity to make git rebase happy. git config user.name "C.I. McTestface" git config user.email "ci.mctestface@example.com" # Make sure the gitlab project is configured. if ! git describe --all origin/main then echo "origin/main is not present. Configure the gitlab project (see .ci/all_commits.sh)." exit 1 fi # If the previous commit already is on main we're done. git merge-base --is-ancestor HEAD~ origin/main && echo "All commits tested already" && exit 0 # Leave out the last commit - it has already been checked. git checkout HEAD~ git status git rebase origin/main \ --exec 'echo ===; echo ===; echo ===; git log -n 1;' \ --exec 'cargo test --all' && echo "All commits passed tests" && exit 0 # The rebase failed - probably because a test failed. git rebase --abort; exit 1 rpm-sequoia-1.7.0/.codespellrc000064400000000000000000000004051046102023000143720ustar 00000000000000[codespell] skip = *.bin,*.gpg,*.pgp,./.git,data,highlight.js,*/target,Makefile,*.html,*/cargo,*.xml,*.xmlv2,Cargo.lock, ignore-words-list = crate,ede,iff,mut,nd,te,uint,KeyServer,keyserver,Keyserver,keyservers,Keyservers,keypair,keypairs,KeyPair,fpr,dedup,ba, rpm-sequoia-1.7.0/.github/workflows/authenticate-commits.yml000064400000000000000000000005141046102023000223420ustar 00000000000000name: authenticate-commits on: pull_request: types: [opened, reopened, synchronize] jobs: authenticate-commits: runs-on: ubuntu-latest permissions: contents: read pull-requests: write issues: write steps: - name: Authenticating commits uses: sequoia-pgp/authenticate-commits@v1 rpm-sequoia-1.7.0/.github/workflows/ci.yml000064400000000000000000000122361046102023000166120ustar 00000000000000name: ci on: push: env: CARGO_TERM_COLOR: always jobs: codespell: name: Codespell runs-on: ubuntu-22.04 steps: - name: Setup | Checkout rpm-sequoia uses: actions/checkout@v2 - name: Setup | Dependencies run: sudo apt update && sudo apt install codespell - name: Codespell run: codespell --version && codespell --config .codespellrc --summary compile: name: Compile runs-on: ubuntu-latest steps: - name: Setup | Checkout rpm-sequoia uses: actions/checkout@v2 with: fetch-depth: 0 - name: Setup | Build Cache rpm-sequoia uses: actions/cache@v3 with: path: | ~/.cargo/bin/ ~/.cargo/registry/index/ ~/.cargo/registry/cache/ ~/.cargo/git/db/ target/ key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }} - name: Setup | Dependencies rpm-sequoia run: sudo apt update && sudo apt install cargo clang git nettle-dev pkg-config libssl-dev - name: Build | Compile rpm-sequoia run: cargo build - name: Build | Test rpm-sequoia run: cargo test - name: Build | Doc rpm-sequoia run: cargo doc --no-deps all_commits: name: All Commits runs-on: ubuntu-latest needs: ["Compile"] steps: - name: Setup | Checkout rpm-sequoia uses: actions/checkout@v2 with: fetch-depth: 0 - name: Setup | Build Cache rpm-sequoia uses: actions/cache@v3 with: path: | ~/.cargo/bin/ ~/.cargo/registry/index/ ~/.cargo/registry/cache/ ~/.cargo/git/db/ target/ key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }} - name: Setup | Dependencies rpm-sequoia run: sudo apt update && sudo apt install cargo clang git nettle-dev pkg-config libssl-dev - name: Build | Compile rpm-sequoia run: cargo build - name: Build | Test other commits run: .ci/all_commits.sh rpm: name: RPM runs-on: ubuntu-latest needs: ["Compile"] steps: - name: Setup | Checkout rpm-sequoia uses: actions/checkout@v2 with: fetch-depth: 0 - name: Setup | Build Cache rpm-sequoia uses: actions/cache@v3 with: path: | ~/.cargo/bin/ ~/.cargo/registry/index/ ~/.cargo/registry/cache/ ~/.cargo/git/db/ target/ key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }} - name: Setup | Dependencies rpm-sequoia run: sudo apt update && sudo apt install cargo clang git nettle-dev pkg-config libssl-dev - name: Build | Compile rpm-sequoia run: cargo build - name: Setup | rpm Dependencies run: sudo apt install automake autoconf autopoint gettext libtool tar zlib1g-dev libpopt-dev libsqlite0-dev liblua5.4-dev fakechroot libarchive-dev - name: Setup | Checkout rpm uses: actions/checkout@v2 with: repository: rpm-software-management/rpm.git ref: rpm-4.18.x fetch-depth: 1 path: rpm-pristine - name: Setup | Build Cache rpm uses: actions/cache@v3 with: path: | rpm/ rpm-build/ key: ${{ runner.os }}-rpm-${{ hashFiles('rpm-pristine/.git/HEAD', 'rpm-pristine/.git/refs/heads/master') }} - name: Test | rpm run: | export PKG_CONFIG_PATH=$(pwd)/target/debug if ! test -e $PKG_CONFIG_PATH/rpm-sequoia-uninstalled.pc then echo "$PKG_CONFIG_PATH/rpm-sequoia-uninstalled.pc is missing. Did you build librpm-sequoia?" exit 1 fi export LD_LIBRARY_PATH=$PKG_CONFIG_PATH if ! test -e $LD_LIBRARY_PATH/librpm_sequoia.so then echo "$LD_LIBRARY_PATH/librpm_sequoia.so is missing. Did you build librpm-sequoia?" exit 1 fi echo "::group::configure" # If rpm doesn't exist, then we don't have a cache of an # rpm build. if ! test -e rpm then cp -a rpm-pristine rpm cd rpm autoreconf -is cd .. mkdir -p rpm-build cd rpm-build ../rpm/configure --prefix=/ --with-crypto=sequoia else cd rpm-build fi echo "::endgroup::" echo "::group::make" make echo "::endgroup::" echo "::group::make check" cd tests if ! make check TESTSUITEFLAGS="-k OpenPGP -k signature -k rpmkeys -k digest" then echo "::endgroup::" for log in rpmtests.dir/*/rpmtests.log do echo "::group::$log" cat $log || true echo "::endgroup::" done exit 1 else echo "::endgroup::" fi rpm-sequoia-1.7.0/.github/workflows/fast-forward.yml000064400000000000000000000007761046102023000206240ustar 00000000000000name: fast-forward on: issue_comment: types: [created, edited] jobs: fast-forward: # Only run if the comment contains the /fast-forward command. if: ${{ contains(github.event.comment.body, '/fast-forward') && github.event.issue.pull_request }} runs-on: ubuntu-latest permissions: contents: write pull-requests: write issues: write steps: - name: Fast forwarding uses: sequoia-pgp/fast-forward@main with: merge: true rpm-sequoia-1.7.0/.github/workflows/pull-request.yml000064400000000000000000000007201046102023000206540ustar 00000000000000name: pull-request on: pull_request: types: [opened, reopened, synchronize] jobs: check-fast-forward: runs-on: ubuntu-latest permissions: contents: read # We appear to need write permission for both pull-requests and # issues in order to post a comment to a pull request. pull-requests: write issues: write steps: - name: Checking if fast forwarding is possible uses: sequoia-pgp/fast-forward@main rpm-sequoia-1.7.0/Cargo.toml0000644000000037150000000000100113070ustar # THIS FILE IS AUTOMATICALLY GENERATED BY CARGO # # When uploading crates to the registry Cargo will automatically # "normalize" Cargo.toml files for maximal compatibility # with all versions of Cargo and also rewrite `path` dependencies # to registry (e.g., crates.io) dependencies. # # If you are reading this file be aware that the original Cargo.toml # will likely look very different (and much more reasonable). # See Cargo.toml.orig for the original contents. [package] edition = "2021" rust-version = "1.73" name = "rpm-sequoia" version = "1.7.0" authors = ["Neal H. Walfield "] build = "build.rs" description = "An implementation of the RPM PGP interface using Sequoia." homepage = "https://sequoia-pgp.org/" readme = "README.md" keywords = [ "cryptography", "openpgp", "pgp", "signing", ] categories = [ "cryptography", "authentication", ] license = "LGPL-2.0-or-later" repository = "https://github.com/rpm-software-management/rpm-sequoia" [lib] crate-type = ["cdylib"] [dependencies.anyhow] version = "1" [dependencies.buffered-reader] version = "1" default-features = false [dependencies.chrono] version = "0.4" features = ["std"] default-features = false [dependencies.lazy_static] version = "1" [dependencies.libc] version = "0.2" [dependencies.sequoia-openpgp] version = "1.21.1" default-features = false [dependencies.sequoia-policy-config] version = "0.6" [dependencies.thiserror] version = "1" [dev-dependencies.assert_cmd] version = "2.0" [build-dependencies.anyhow] version = "1" [build-dependencies.cdylib-link-lines] version = "0.1.4" [features] crypto-botan = ["sequoia-openpgp/crypto-botan"] crypto-botan2 = ["sequoia-openpgp/crypto-botan2"] crypto-cng = ["sequoia-openpgp/crypto-cng"] crypto-nettle = ["sequoia-openpgp/crypto-nettle"] crypto-openssl = ["sequoia-openpgp/crypto-openssl"] crypto-rust = ["sequoia-openpgp/crypto-rust"] default = ["crypto-nettle"] [badges.maintenance] status = "actively-developed" rpm-sequoia-1.7.0/Cargo.toml.orig000064400000000000000000000030611046102023000147620ustar 00000000000000[package] name = "rpm-sequoia" description = "An implementation of the RPM PGP interface using Sequoia." version = "1.7.0" authors = ["Neal H. Walfield "] homepage = "https://sequoia-pgp.org/" repository = "https://github.com/rpm-software-management/rpm-sequoia" readme = "README.md" keywords = ["cryptography", "openpgp", "pgp", "signing"] categories = ["cryptography", "authentication"] license = "LGPL-2.0-or-later" edition = "2021" rust-version = "1.73" build = "build.rs" [badges] maintenance = { status = "actively-developed" } [dependencies] anyhow = "1" buffered-reader = { version = "1", default-features = false } chrono = { version = "0.4", default-features = false, features = [ "std" ] } lazy_static = "1" libc = "0.2" sequoia-openpgp = { version = "1.21.1", default-features = false } sequoia-policy-config = "0.6" thiserror = "1" [build-dependencies] anyhow = "1" cdylib-link-lines = "0.1.4" [dev-dependencies] assert_cmd = "2.0" [lib] crate-type = ["cdylib"] [features] # To use a different cryptographic backend, e.g., OpenSSL, do: # # cargo build --release --no-default-features --features crypto-openssl # We explicitly do not want to enable Sequoia's decompression support. # Hence we only select a crypto backend. default = ["crypto-nettle"] crypto-nettle = ["sequoia-openpgp/crypto-nettle"] crypto-rust = ["sequoia-openpgp/crypto-rust"] crypto-cng = ["sequoia-openpgp/crypto-cng"] crypto-openssl = ["sequoia-openpgp/crypto-openssl"] crypto-botan = ["sequoia-openpgp/crypto-botan"] crypto-botan2 = ["sequoia-openpgp/crypto-botan2"] rpm-sequoia-1.7.0/LICENSE.txt000064400000000000000000000627341046102023000137320ustar 00000000000000rpm-sequoia is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. rpm-sequoia 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 General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. --- GNU LIBRARY GENERAL PUBLIC LICENSE Version 2, June 1991 Copyright (C) 1991 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 library GPL. It is numbered 2 because it goes with version 2 of the ordinary GPL.] 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 Library General Public License, applies to some specially designated Free Software Foundation software, and to any other libraries whose authors decide to use it. You can use it for your libraries, too. When we speak of free software, we are referring to freedom, 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 or use pieces of it in new free programs; and that you know you can do these things. To protect your rights, we need to make restrictions that forbid anyone to deny you these rights or to ask you to surrender the 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 a program 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. Our method of protecting your rights has two steps: (1) copyright the library, and (2) offer you this license which gives you legal permission to copy, distribute and/or modify the library. Also, for each distributor's protection, we want to make certain that everyone understands that there is no warranty for this free library. If the library is modified by someone else and passed on, we want its recipients to know that what they have is not the original version, so that any problems introduced by others will not reflect on the original authors' reputations. Finally, any free program is threatened constantly by software patents. We wish to avoid the danger that companies distributing free software will individually obtain patent licenses, thus in effect transforming the program into proprietary software. To prevent this, we have made it clear that any patent must be licensed for everyone's free use or not licensed at all. Most GNU software, including some libraries, is covered by the ordinary GNU General Public License, which was designed for utility programs. This license, the GNU Library General Public License, applies to certain designated libraries. This license is quite different from the ordinary one; be sure to read it in full, and don't assume that anything in it is the same as in the ordinary license. The reason we have a separate public license for some libraries is that they blur the distinction we usually make between modifying or adding to a program and simply using it. Linking a program with a library, without changing the library, is in some sense simply using the library, and is analogous to running a utility program or application program. However, in a textual and legal sense, the linked executable is a combined work, a derivative of the original library, and the ordinary General Public License treats it as such. Because of this blurred distinction, using the ordinary General Public License for libraries did not effectively promote software sharing, because most developers did not use the libraries. We concluded that weaker conditions might promote sharing better. However, unrestricted linking of non-free programs would deprive the users of those programs of all benefit from the free status of the libraries themselves. This Library General Public License is intended to permit developers of non-free programs to use free libraries, while preserving your freedom as a user of such programs to change the free libraries that are incorporated in them. (We have not seen how to achieve this as regards changes in header files, but we have achieved it as regards changes in the actual functions of the Library.) The hope is that this will lead to faster development of free libraries. 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, while the latter only works together with the library. Note that it is possible for a library to be covered by the ordinary General Public License rather than by this special one. GNU LIBRARY GENERAL PUBLIC LICENSE TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 0. This License Agreement applies to any software library which contains a notice placed by the copyright holder or other authorized party saying it may be distributed under the terms of this Library 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 compile 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) 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. c) 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. d) 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 source code 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 to 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 Library 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 Library General Public License as published by the Free Software Foundation; either version 2 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 Library General Public License for more details. You should have received a copy of the GNU Library 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! rpm-sequoia-1.7.0/README.md000064400000000000000000000117341046102023000133600ustar 00000000000000This library provides an implementation of the [rpm]'s [pgp interface] using [Sequoia]. [rpm]: https://github.com/rpm-software-management/rpm [pgp interface]: https://github.com/rpm-software-management/rpm/blob/master/include/rpm/rpmpgp.h [Sequoia]: https://sequoia-pgp.org # Configuration This library's [crypto policy] can be customized. It finds the configuration file by checking the following in turn: - the `RPM_SEQUOIA_CRYPTO_POLICY` environment variable, - `/etc/crypto-policies/back-ends/rpm-sequoia.config`, - the `SEQUOIA_CRYPTO_POLICY` environment variable, and finally, - `/etc/crypto-policies/back-ends/sequoia.config`. Only the first configuration file that is present is used. If an environment is set to the empty string, then an empty configuration file is used. That is, the default policy is used. Thus, if `RPM_SEQUOIA_CRYPTO_POLICY` is not set, and `/etc/crypto-policies/back-ends/rpm-sequoia.config`, the latter will be used. In this case, `SEQUOIA_CRYPTO_POLICY` and `/etc/crypto-policies/back-ends/sequoia.config` will be completely ignored. Refer to the [Fedora Crypto Policy] project for information about the crypto policy. [crypto policy]: https://docs.rs/sequoia-policy-config/latest/sequoia_policy_config/ [Sequoia's default policy]: https://docs.sequoia-pgp.org/sequoia_openpgp/policy/struct.StandardPolicy.html [Fedora Crypto Policy]: https://gitlab.com/redhat-crypto/fedora-crypto-policies/ # Building To build, you need [rustc] (version 1.73 or later), cargo, and [nettle-devel], which is the cryptographic library that Sequoia uses by default. [rustc]: https://packages.fedoraproject.org/pkgs/rust/rust/ [nettle-devel]: https://packages.fedoraproject.org/pkgs/nettle/nettle-devel Here's how to build rpm-sequoia and a version of rpm that uses it: ``` $ sudo dnf install cargo rustc clang pkg-config nettle-devel $ mkdir /tmp/rpm $ cd /tmp/rpm $ git clone git@github.com:rpm-software-management/rpm-sequoia.git Cloning into 'rpm-sequoia'... done. $ cd rpm-sequoia $ PREFIX=/usr LIBDIR="\${prefix}/lib64" \ cargo build --release && cargo test --release Updating crates.io index ... test result: ok. ... $ cd /tmp/rpm $ git clone git@github.com:rpm-software-management/rpm.git Cloning into 'rpm'... done. $ cd rpm $ git checkout rpm-4.18.1-release Switched to a new branch 'rpm-4.18.1-release' $ sudo dnf install automake autoconf gettext-devel libtool tar zlib-devel file-devel libarchive-devel popt-devel sqlite-devel lua-devel fakechroot $ autoreconf -fis ... $ mkdir b $ cd b $ export PKG_CONFIG_PATH=/tmp/rpm/rpm-sequoia/target/release $ export LD_LIBRARY_PATH=/tmp/rpm/rpm-sequoia/target/release $ ../configure --prefix=/ --with-crypto=sequoia $ make $ make check ``` Note: this builds version 4.18 of `rpm`, which is the current stable release of `rpm`. The current development branch of `rpm` has switched to using `cmake` instead of `autoconf`. Please refer to [rpm's `INSTALL`] file for how to build `master`. [rpm's `INSTALL`]: https://github.com/rpm-software-management/rpm/blob/master/INSTALL To use a different cryptographic backend, you need to disable the default backend, and select your preferred backend. For instance, to use Sequoia's OpenSSL backend, you would compile `rpm-sequoia` as follows: ``` $ cargo build --release --no-default-features --features crypto-openssl ``` See [`sequoia-openpgp`'s README] for the list of currently supported cryptographic backends. [`sequoia-openpgp`'s README]: https://gitlab.com/sequoia-pgp/sequoia#features The rpm-sequoia artifacts (the .a, .so, and the .pc files) are placed in the build directory, which, in this case, is `/tmp/rpm/rpm-sequoia/target/release`. We also set two environment variables when calling `cargo build`: * `PREFIX` is the prefix that will be used in the generated `rpm-sequoia.pc` file. It defaults to `/usr/local`. * `LIBDIR` is the installed library path listed in the generated metadata. It can be an absolute path or one based on `${prefix}`, and defaults to `${prefix}/lib`. To run just one or two tests, do something like the following: Note: when building or running the test suite, it is essential to make sure `PKG_CONFIG_PATH` and `LD_LIBRARY_PATH` are set appropriately (as in the above transcript). ``` $ cd /tmp/rpm/rpm/b/tests $ export PKG_CONFIG_PATH=/tmp/rpm/rpm-sequoia/target/release $ export LD_LIBRARY_PATH=/tmp/rpm/rpm-sequoia/target/release $ make populate_testing $ T="266 273"; for t in $T; do if ! ../../tests/rpmtests $t; then cat rpmtests.dir/$t/rpmtests.log; fi; done ``` To get tracing output, set RPM_TRACE to 1: ``` $ cd /tmp/rpm/rpm/b/tests $ export PKG_CONFIG_PATH=/tmp/rpm/rpm-sequoia/target/release $ export LD_LIBRARY_PATH=/tmp/rpm/rpm-sequoia/target/release $ make populate_testing $ export RPM_TRACE=1 $ ../../tests/rpmtests 273 $ cat rpmtests.dir/273/rpmtests.log ... +pgpDigParamsFree: -> success +rpmFreeCrypto: entered +rpmFreeCrypto: -> success 273. rpmsigdig.at:495: 273. rpmsign --addsign (rpmsigdig.at:495): FAILED (rpmsigdig.at:503) ... ``` rpm-sequoia-1.7.0/build.rs000064400000000000000000000151131046102023000135410ustar 00000000000000use std::env; use std::fs::File; use std::io::Write; use std::path::{Path, PathBuf}; use std::collections::HashMap; use anyhow::Result; struct PkgConfigTemplate { cargo_toml: HashMap, pc_in: String, } impl PkgConfigTemplate { /// Read the pkg-config template file. fn new(src: P, pc_in: S) -> Result where P: AsRef, S: AsRef { let src = src.as_ref(); let mut pc_in_ = PathBuf::from(src); pc_in_.push(pc_in.as_ref()); let pc_in = pc_in_; let pc_in = std::fs::read_to_string(pc_in)?; let cargo_toml = HashMap::from([ ("NAME".to_string(), env!("CARGO_PKG_NAME").to_string()), ("DESCRIPTION".to_string(), env!("CARGO_PKG_DESCRIPTION").to_string()), ("VERSION".to_string(), env!("CARGO_PKG_VERSION").to_string()), ("HOMEPAGE".to_string(), env!("CARGO_PKG_HOMEPAGE").to_string()), ("REQUIRES".to_string(), if cfg!(feature = "crypto-botan") { "botan-3" } else if cfg!(feature = "crypto-botan2") { "botan-2" } else if cfg!(feature = "crypto-nettle") { "nettle" } else if cfg!(feature = "crypto-openssl") { "libssl" } else if cfg!(feature = "crypto-cng") { "" } else if cfg!(feature = "crypto-rust") { "" } else { panic!("No cryptographic backend selected. Try: \ \"cargo build --no-default-features \ --features crypto-openssl\"") }.to_string()), ]); Ok(PkgConfigTemplate { cargo_toml, pc_in, }) } /// Perform substitutions on the pkg-config file based on what was /// read from the Cargo.toml file and the provided substitution /// map. /// /// The mappings in the substitution map are preferred to those in /// the Cargo.toml file. /// /// Substitutions take the form of keys and values where the /// string @KEY@ is substituted with the value of KEY. So, /// @VERSION@ is substituted with the value of VERSION. fn substitute(&self, map: HashMap) -> Result { let mut pc: String = self.pc_in.clone(); for (key, value) in map.iter().chain(self.cargo_toml.iter()) { pc = pc.replace(&format!("@{}@", key), value); } Ok(pc) } } fn main() -> Result<(), anyhow::Error> { // Generate // ${CARGO_TARGET_DIR}/${PROFILE}/rpm-sequoia{-uninstalled}.pc // from ${SRC}/rpm-sequoia.pc.in. let src = env::current_dir()?; // Location of the build directory (e.g., // `/tmp/rpm-sequoia/debug`). let mut build_dir = PathBuf::from(&src); if let Some(target_dir) = env::var_os("CARGO_TARGET_DIR") { // Note: if CARGO_TARGET_DIR is absolute, this will first // clear build_dir, which is what we want. build_dir.push(target_dir); } else { build_dir.push("target"); } let profile = env::var_os("PROFILE").expect("PROFILE not set"); build_dir.push(&profile); let pc_in = PkgConfigTemplate::new(&src, "rpm-sequoia.pc.in")?; // Generate rpm-sequoia.pc. let mut pc = build_dir.clone(); pc.push("rpm-sequoia.pc"); let prefix = env::var_os("PREFIX"); let prefix: &str = match prefix.as_ref().map(|s| s.to_str()) { Some(Some(s)) => s, Some(None) => Err(anyhow::anyhow!("PREFIX contains invalid UTF-8"))?, None => "/usr/local", }; let libdir = env::var_os("LIBDIR"); let libdir: &str = match libdir.as_ref().map(|s| s.to_str()) { Some(Some(s)) => s, Some(None) => Err(anyhow::anyhow!("LIBDIR contains invalid UTF-8"))?, None => "${prefix}/lib", }; let content = pc_in.substitute(HashMap::from([ ("PREFIX".to_string(), prefix.into()), ("LIBDIR".to_string(), libdir.into()), ]))?; let mut pc = File::create(&pc).expect( &format!("Creating {:?} (CARGO_TARGET_DIR: {:?})", pc, env::var_os("CARGO_TARGET_DIR"))); pc.write_all(content.as_bytes())?; // Generate rpm-sequoia-uninstalled.pc. let mut pc = build_dir.clone(); pc.push("rpm-sequoia-uninstalled.pc"); let content = pc_in.substitute(HashMap::from([ ("PREFIX".to_string(), build_dir.to_str() .expect("build directory is not valid UTF-8").to_string()), ("LIBDIR".to_string(), "${prefix}".into()), ]))?; let mut pc = File::create(&pc).expect( &format!("Creating {:?} (CARGO_TARGET_DIR: {:?})", pc, env::var_os("CARGO_TARGET_DIR"))); pc.write_all(content.as_bytes())?; // Rerun if... println!("cargo:rerun-if-changed=build.rs"); println!("cargo:rerun-if-changed=Cargo.toml"); println!("cargo:rerun-if-changed=rpm-sequoia.pc.in"); println!("cargo:rerun-if-env-changed=PREFIX"); println!("cargo:rerun-if-env-changed=LIBDIR"); println!("cargo:rerun-if-env-changed=PROFILE"); println!("cargo:rerun-if-env-changed=CARGO_TARGET_DIR"); // Set the soname. let arch = env::var("CARGO_CFG_TARGET_ARCH").unwrap(); let os = env::var("CARGO_CFG_TARGET_OS").unwrap(); let env = env::var("CARGO_CFG_TARGET_ENV").unwrap(); // We do not care about `_pre` and such. let major = env::var("CARGO_PKG_VERSION_MAJOR").unwrap(); let minor = env::var("CARGO_PKG_VERSION_MINOR").unwrap(); let patch = env::var("CARGO_PKG_VERSION_PATCH").unwrap(); // libdir might contain "${prefix}". Replace it with // the actual prefix value if found. let libdir_resolved = libdir.replace("${prefix}", prefix); let linker_lines = cdylib_link_lines::shared_object_link_args( "rpm_sequoia", &major, &minor, &patch, &arch, &os, &env, PathBuf::from(libdir_resolved), build_dir.clone(), ); for line in linker_lines { println!("cargo:rustc-cdylib-link-arg={}", line); } #[cfg(unix)] { // Create a symlink. let mut create = true; let mut link = build_dir.clone(); link.push(format!("librpm_sequoia.so.{}", major)); if let Ok(current) = std::fs::read_link(&link) { if current.to_str() == Some("librpm_sequoia.so") { // Do nothing. create = false; } else { // Invalid. std::fs::remove_file(&link)?; } } if create { std::os::unix::fs::symlink("librpm_sequoia.so", link)?; } } Ok(()) } rpm-sequoia-1.7.0/deny.toml000064400000000000000000000221011046102023000137230ustar 00000000000000# This template contains all of the possible sections and their default values # Note that all fields that take a lint level have these possible values: # * deny - An error will be produced and the check will fail # * warn - A warning will be produced, but the check will not fail # * allow - No warning or error will be produced, though in some cases a note # will be # The values provided in this template are the default values that will be used # when any section or field is not specified in your own configuration # If 1 or more target triples (and optionally, target_features) are specified, # only the specified targets will be checked when running `cargo deny check`. # This means, if a particular package is only ever used as a target specific # dependency, such as, for example, the `nix` crate only being used via the # `target_family = "unix"` configuration, that only having windows targets in # this list would mean the nix crate, as well as any of its exclusive # dependencies not shared by any other crates, would be ignored, as the target # list here is effectively saying which targets you are building for. targets = [ # The triple can be any string, but only the target triples built in to # rustc (as of 1.40) can be checked against actual config expressions #{ triple = "x86_64-unknown-linux-musl" }, # You can also specify which target_features you promise are enabled for a # particular target. target_features are currently not validated against # the actual valid features supported by the target architecture. #{ triple = "wasm32-unknown-unknown", features = ["atomics"] }, ] # This section is considered when running `cargo deny check advisories` # More documentation for the advisories section can be found here: # https://embarkstudios.github.io/cargo-deny/checks/advisories/cfg.html [advisories] # The path where the advisory database is cloned/fetched into db-path = "~/.cargo/advisory-db" # The url(s) of the advisory databases to use db-urls = ["https://github.com/rustsec/advisory-db"] # The lint level for security vulnerabilities vulnerability = "deny" # The lint level for unmaintained crates unmaintained = "warn" # The lint level for crates that have been yanked from their source registry yanked = "warn" # The lint level for crates with security notices. Note that as of # 2019-12-17 there are no security notice advisories in # https://github.com/rustsec/advisory-db notice = "warn" # A list of advisory IDs to ignore. Note that ignored advisories will still # output a note when they are encountered. ignore = [ "RUSTSEC-2020-0159", # We do not use local timezones (only UTC). So we are not # impacted by this. "RUSTSEC-2020-0071", ] # Threshold for security vulnerabilities, any vulnerability with a CVSS score # lower than the range specified will be ignored. Note that ignored advisories # will still output a note when they are encountered. # * None - CVSS Score 0.0 # * Low - CVSS Score 0.1 - 3.9 # * Medium - CVSS Score 4.0 - 6.9 # * High - CVSS Score 7.0 - 8.9 # * Critical - CVSS Score 9.0 - 10.0 #severity-threshold = # This section is considered when running `cargo deny check licenses` # More documentation for the licenses section can be found here: # https://embarkstudios.github.io/cargo-deny/checks/licenses/cfg.html [licenses] # The lint level for crates which do not have a detectable license unlicensed = "deny" # List of explicitly allowed licenses # See https://spdx.org/licenses/ for list of possible licenses # [possible values: any SPDX 3.7 short identifier (+ optional exception)]. allow = [ #"MIT", #"Apache-2.0", #"Apache-2.0 WITH LLVM-exception", ] # List of explicitly disallowed licenses # See https://spdx.org/licenses/ for list of possible licenses # [possible values: any SPDX 3.7 short identifier (+ optional exception)]. deny = [ #"Nokia", ] # Lint level for licenses considered copyleft copyleft = "warn" # Blanket approval or denial for OSI-approved or FSF Free/Libre licenses # * both - The license will be approved if it is both OSI-approved *AND* FSF # * either - The license will be approved if it is either OSI-approved *OR* FSF # * osi-only - The license will be approved if is OSI-approved *AND NOT* FSF # * fsf-only - The license will be approved if is FSF *AND NOT* OSI-approved # * neither - This predicate is ignored and the default lint level is used allow-osi-fsf-free = "either" # Lint level used when no other predicates are matched # 1. License isn't in the allow or deny lists # 2. License isn't copyleft # 3. License isn't OSI/FSF, or allow-osi-fsf-free = "neither" default = "deny" # The confidence threshold for detecting a license from license text. # The higher the value, the more closely the license text must be to the # canonical license text of a valid SPDX license file. # [possible values: any between 0.0 and 1.0]. confidence-threshold = 0.95 # Allow 1 or more licenses on a per-crate basis, so that particular licenses # aren't accepted for every possible crate as with the normal allow list exceptions = [ # Each entry is the crate and version constraint, and its specific allow # list #{ allow = ["Zlib"], name = "adler32", version = "*" }, ] # Some crates don't have (easily) machine readable licensing information, # adding a clarification entry for it allows you to manually specify the # licensing information #[[licenses.clarify]] # The name of the crate the clarification applies to #name = "ring" # The optional version constraint for the crate #version = "*" # The SPDX expression for the license requirements of the crate #expression = "MIT AND ISC AND OpenSSL" # One or more files in the crate's source used as the "source of truth" for # the license expression. If the contents match, the clarification will be used # when running the license check, otherwise the clarification will be ignored # and the crate will be checked normally, which may produce warnings or errors # depending on the rest of your configuration #license-files = [ # Each entry is a crate relative path, and the (opaque) hash of its contents #{ path = "LICENSE", hash = 0xbd0eed23 } #] [licenses.private] # If true, ignores workspace crates that aren't published, or are only # published to private registries ignore = true # One or more private registries that you might publish crates to, if a crate # is only published to private registries, and ignore is true, the crate will # not have its license(s) checked registries = [ #"https://sekretz.com/registry ] # This section is considered when running `cargo deny check bans`. # More documentation about the 'bans' section can be found here: # https://embarkstudios.github.io/cargo-deny/checks/bans/cfg.html [bans] # Lint level for when multiple versions of the same crate are detected multiple-versions = "warn" # Lint level for when a crate version requirement is `*` wildcards = "deny" # The graph highlighting used when creating dotgraphs for crates # with multiple versions # * lowest-version - The path to the lowest versioned duplicate is highlighted # * simplest-path - The path to the version with the fewest edges is highlighted # * all - Both lowest-version and simplest-path are used highlight = "all" # List of crates that are allowed. Use with care! allow = [ #{ name = "ansi_term", version = "=0.11.0" }, ] # List of crates to deny deny = [ # Each entry the name of a crate and a version range. If version is # not specified, all versions will be matched. #{ name = "ansi_term", version = "=0.11.0" }, # # Wrapper crates can optionally be specified to allow the crate when it # is a direct dependency of the otherwise banned crate #{ name = "ansi_term", version = "=0.11.0", wrappers = [] }, ] # Certain crates/versions that will be skipped when doing duplicate detection. skip = [ #{ name = "ansi_term", version = "=0.11.0" }, ] # Similarly to `skip` allows you to skip certain crates during duplicate # detection. Unlike skip, it also includes the entire tree of transitive # dependencies starting at the specified crate, up to a certain depth, which is # by default infinite skip-tree = [ #{ name = "ansi_term", version = "=0.11.0", depth = 20 }, ] # This section is considered when running `cargo deny check sources`. # More documentation about the 'sources' section can be found here: # https://embarkstudios.github.io/cargo-deny/checks/sources/cfg.html [sources] # Lint level for what to happen when a crate from a crate registry that is not # in the allow list is encountered unknown-registry = "warn" # Lint level for what to happen when a crate from a git repository that is not # in the allow list is encountered unknown-git = "warn" # List of URLs for allowed crate registries. Defaults to the crates.io index # if not specified. If it is specified but empty, no registries are allowed. allow-registry = ["https://github.com/rust-lang/crates.io-index"] # List of URLs for allowed Git repositories allow-git = [] [sources.allow-org] # 1 or more github.com organizations to allow git sources for #github = [""] # 1 or more gitlab.com organizations to allow git sources for #gitlab = [""] # 1 or more bitbucket.org organizations to allow git sources for #bitbucket = [""] rpm-sequoia-1.7.0/doc/release-checklist.md000064400000000000000000000031061046102023000165510ustar 00000000000000This is a checklist for doing releases. 1. Start from `origin/main`, create a branch `staging`. 1. Switch to the branch. 1. Bump the version in `Cargo.toml` to `XXX`. 1. Bump the version in `README.md` to `XXX`. 1. Run `cargo check` (this implicitly updates `Cargo.lock`). 1. Update dependencies and run tests. - Use the exact Rust toolchain version of the current Sequoia MSRV (refer to `Cargo.toml`): `rustup default 1.xx` - Run `cargo update` to update the dependencies. If some dependency is updated and breaks due to our MSRV, find a good version of that dependency and select it using e.g. `cargo update -p backtrace --precise 3.46`. - Run `cargo build && cargo check` 1. Commit changes to `Cargo.toml` and `Cargo.lock`. 1. Make a commit with the message `Release XXX.`. - Push to github, and create a merge request. Don't auto merge!!! 1. Make sure `cargo publish` works: - `mkdir -p /tmp/sequoia-staging` - `cd /tmp/sequoia-staging` - `git clone git@github.com:rpm-software-management/rpm-sequoia.git` - `cd rpm-sequoia` - `git checkout origin/staging` - `cargo publish --features crypto-nettle --dry-run` 1. Wait until CI and `cargo publish ... --dry-run` are successful. In case of errors, correct them, and restart. 1. Merge the merge request. 1. Run `cargo publish --features crypto-nettle`. 1. Make a tag `vXXX` with the message `Release XXX.` signed with an offline-key, which has been certified by our `openpgp-ca@sequoia-pgp.org` key. 1. Push the signed tag `vXXX`. rpm-sequoia-1.7.0/openpgp-policy.toml000064400000000000000000001220751046102023000157440ustar 00000000000000version = 0 commit_goodlist = [] [authorization."Neal H. Walfield "] sign_commit = true sign_tag = true sign_archive = true add_user = true retire_user = true audit = true keyring = """ -----BEGIN PGP PUBLIC KEY BLOCK----- Comment: F717 3B3C 7C68 5CD9 ECC4 191B 74E4 45BA 0E15 C957 Comment: Neal H. Walfield (Code Signing Key) Comment: Neal H. Walfield Comment: Neal H. Walfield Comment: Neal H. Walfield Comment: Neal H. Walfield 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 HCL+AFMw7YtSwqT2TxbLYPc/7bU+pWAe1YT6pk0OLZhuRzyG3UuZaMVFHcp26sLP rGROgdo4R79MUUDMLYkuYKRcTrdi4bceZlmIpJeUt+SY8zcAP3m4epeEOu3vrJER 1xglFmeNDEcjD32iTJHlkGcyHw+NWhl6l8LvFov4ZVyW4pXiYjnjbOHq8ukbPIDP eafMkishG18ekELGoGuLpDqnPYG9AYXiy03D6ZwduLI770bMkG5zhKzDksIGQ8x0 JUPvtroSlgNQwe1PFlDS7c2bIs3JqwCpuJClEEqKkvg7samKXWNCO23vl9ucmOwk xMm2w4v09WooFlG28hSsH+FL+ROEU2Px1MJbUahReTbsho828NstqZ46UWmcWe9q 9YDwnHBj2sKEy47f2mZ3ZoR1wchRIxze7dcPLUTPMoEyjAFKzsoozpE+Ah/hUwIj PSEK0kfPaQx4jR9QKCwTnMrHNs16nVUM9zhXLNlwb2rdG6Y0dAyBllLvE4UHAF8x 3Gv/6O3xC/PU6LrAUitH0mViV+GHHn3jWV7oO3NmV27x+Vw1DqZO9XZMPqmgRoRz MEQXzSROZWFsIEguIFdhbGZpZWxkIDxuZWFsQHdhbGZpZWxkLm9yZz7CwWgEEwEK AD4CGwMICwkIBw0MCwoFFQoJCAsCHgECF4ACGQEWIQSPF3dxGKM92pukjmKqyzJD YwBS2QUCZCarHgUJEORDtQAKCRCqyzJDYwBS2YLaDp0fMZrwfLGqC8LJiRfI+HwV +29E7EWaJmOF6S24sewr1t//2Vz4mc41E6bDprHsHte4JEmzyCnXnXlPaobyONWU IbYcnM8723myJml0Du4EvvvJPUieMiwFWzRdmFxc/eQ+6aeTTUknNJKVhY18/qsA pvYTIkvmlePgLQVGvpnYqQ1ElgMf768KJ4+lVMmIT2+fHlLe5RyJRlZcjVryV5H9 fOy+TQqhLLoTFhxAiJBACYIUCglGgjTQejivXXRXjhvMpvlBzSxwI9iZGZUL4FE1 kjK99epoictN9iv1O5XTZYTqkdBf1PD79GicHAbG6Io8F07xR/CijZl27l6rLb55 9/33Iswi6Ma8mdnmeTJ1d+io9XbtAGhPZqKxM2XtKbVO8uJli6YCqeEpVP+/Wy2c g4RSj42b5yrplbqVvnbddx8ZYCm5SXD6Pb/ZrrUmKlayuM2UnMs9+snIGG/SMRUv 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 =MESu -----END PGP PUBLIC KEY BLOCK----- """ rpm-sequoia-1.7.0/rpm-sequoia.pc.in000064400000000000000000000002611046102023000152650ustar 00000000000000prefix=@PREFIX@ libdir=@LIBDIR@ Name: @NAME@ Description: @DESCRIPTION@ URL: @HOMEPAGE@ Version: @VERSION@ Requires.private: @REQUIRES@ Cflags: Libs: -L${libdir} -lrpm_sequoia rpm-sequoia-1.7.0/src/digest.rs000064400000000000000000000065311046102023000145140ustar 00000000000000// This is a reimplementation of rpm/rpmio/digest_openssl.c / // rpm/rpmio/digest_libgcrypt.c using Sequoia. use libc::{ c_int, size_t, }; use sequoia_openpgp as openpgp; use openpgp::types::HashAlgorithm; use openpgp::crypto::hash::Digest; use crate::Error; use crate::Result; #[derive(Clone)] pub struct DigestContext { pub(crate) ctx: Box, } impl DigestContext { pub(crate) fn digest_size(&self) -> usize { self.ctx.digest_size() } pub(crate) fn update>(&mut self, data: T) { self.ctx.update(data.as_ref()); } pub(crate) fn digest(&mut self, digest: &mut [u8]) -> Result<()> { Ok(self.ctx.digest(digest)?) } pub(crate) fn into_digest(self) -> Result> { Ok(self.ctx.into_digest()?) } } ffi!( /// DIGEST_CTX rpmDigestInit(int hashalgo, rpmDigestFlags flags) /// /// rpmDigestFlags currently does not define any flags. fn _rpmDigestInit(hashalgo: c_int, flags: c_int) -> *mut DigestContext { if hashalgo < 0 || hashalgo > u8::MAX as c_int { return Err(Error::Fail("Out of range".into())); } let hashalgo = HashAlgorithm::from(hashalgo as u8); if flags != 0 { return Err(Error::Fail(format!("Unsupported flags: {}", flags))); } let ctx = DigestContext { ctx: hashalgo.context()?, }; Ok(move_to_c!(ctx)) }); ffi!( /// DIGEST_CTX rpmDigestDup(DIGEST_CTX octx) fn _rpmDigestDup(ctx: *const DigestContext) -> *mut DigestContext { let ctx = check_ptr!(ctx); Ok(Box::into_raw(Box::new(ctx.clone()))) }); ffi!( /// size_t rpmDigestLength(int hashalgo) fn _rpmDigestLength(hashalgo: c_int) -> size_t[0] { if hashalgo < 0 || hashalgo > u8::MAX as c_int { return Ok(0); } let hashalgo = HashAlgorithm::from(hashalgo as u8); use HashAlgorithm::*; let len = match hashalgo { MD5 => 16, SHA1 => 20, RipeMD => 20, SHA256 => 32, SHA384 => 48, SHA512 => 64, SHA224 => 28, _ => 0, }; Ok(len) }); ffi!( /// int rpmDigestUpdate(DIGEST_CTX ctx, const void * data, size_t len) fn _rpmDigestUpdate(ctx: *mut DigestContext, data: *const u8, len: size_t) -> ErrorCode { let ctx = check_mut!(ctx); let data = check_slice!(data, len); ctx.update(data); Ok(()) }); ffi!( /// int rpmDigestFinal(DIGEST_CTX ctx, void ** datap, size_t *lenp, int asAscii) fn _rpmDigestFinal(ctx: *mut DigestContext, datap: *mut *mut u8, lenp: *mut size_t, as_ascii: c_int) -> Binary { let ctx = claim_from_c!(ctx); let datap = check_optional_mut!(datap); let lenp = check_optional_mut!(lenp); let mut digest = ctx.into_digest()?; if as_ascii != 0 { digest = digest .iter() .map(|x| { let x = format!("{:02x}", x); let x = x.as_bytes(); std::iter::once(x[0]).chain(std::iter::once(x[1])) }) .flatten() // Add a NUL. .chain(std::iter::once(0)) .collect(); } digest.shrink_to_fit(); if let Some(lenp) = lenp { *lenp = digest.len() as size_t; } if let Some(datap) = datap { *datap = digest.as_mut_ptr(); // Pass ownership to the caller. std::mem::forget(digest); } Ok(()) }); rpm-sequoia-1.7.0/src/ffi.rs000064400000000000000000000254171046102023000140050ustar 00000000000000pub(crate) fn idempotent(x: T) -> T { x } pub(crate) fn zero(_: T) -> libc::c_int { 0 } pub(crate) fn minus_one(_: T) -> libc::c_int { -1 } pub(crate) fn unit(_: T) -> () { () } // Wraps an ffi function, which returns an arbitrary type. // // The inner function returns `Result<$rt>`. This wrapper maps // `Ok($rt)` to `$Crt` using `$rt_to_crt` and `Err(err)` to // `$err_to_crt`. macro_rules! ffi { // Wraps an ffi function, which returns 0 on success and -1 on error. // // fn func(...) -> Binary ($(#[$outer:meta])* fn $f:ident($($v:ident: $t:ty),*) -> Binary $body:block) => { ffi!($(#[$outer])* fn $f($($v: $t),*) -> Result<(), crate::Error> -> (crate::ErrorCode; $crate::ffi::zero; $crate::ffi::minus_one) { $body }); }; // Wraps an ffi function, which returns an RC. // // fn func(...) -> ErrorCode ($(#[$outer:meta])* fn $f:ident($($v:ident: $t:ty),*) -> ErrorCode $body:block) => { ffi!($(#[$outer])* fn $f($($v: $t),*) -> Result<(), crate::Error> -> (crate::ErrorCode; $crate::ffi::zero; |err| crate::ErrorCode::from(err)) { $body }); }; // Wraps an ffi function, which returns a PgpArmorError. // // fn func(...) -> PgpArmor ($(#[$outer:meta])* fn $f:ident($($v:ident: $t:ty),*) -> PgpArmor $body:block) => { ffi!($(#[$outer])* fn $f($($v: $t),*) -> Result -> (crate::ErrorCode; c_int::from; c_int::from) { $body }); }; // Wraps an ffi function, which returns an object whose type is // *const T. Returns NULL on error. // // fn func(...) -> *const u8 ($(#[$outer:meta])* fn $f:ident($($v:ident: $t:ty),*) -> *const $value:ty $body:block) => { ffi!($(#[$outer])* fn $f($($v: $t),*) -> Result<*const $value, crate::Error> -> (*const $value; $crate::ffi::idempotent; |_| std::ptr::null()) { $body }); }; // Wraps an ffi function, which returns an object whose type is // *mut T. Returns NULL on error. // // fn func(...) -> *mut u8 ($(#[$outer:meta])* fn $f:ident($($v:ident: $t:ty),*) -> *mut $value:ty $body:block) => { ffi!($(#[$outer])* fn $f($($v: $t),*) -> Result<*mut $value, crate::Error> -> (*mut $value; $crate::ffi::idempotent; |_| std::ptr::null_mut()) { $body }); }; // Wraps an ffi function, which returns a value. The value is passed // through as is and errors are mapped to `$err`. // // Example: A function that returns an int. If the function // returns Err, that is mapped to 1: // // fn func(...) -> c_int[1] ($(#[$outer:meta])* fn $f:ident($($v:ident: $t:ty),*) -> $value:ty[$err:expr] $body:block) => { ffi!($(#[$outer])* fn $f($($v: $t),*) -> Result<$value, crate::Error> -> ($value; $crate::ffi::idempotent; |_| $err) { $body }); }; // Wraps an ffi function, which returns void. // // The inner function returns `Result<()>` and this is mapped to `()`. // // Note: inner body returns Ok(()) by default. // // Example: // // fn func(...) // // Note: there is no default type in the declaration. ($(#[$outer:meta])* fn $f:ident($($v:ident: $t:ty),*) $body:block) => { ffi!( $(#[$outer])* fn $f($($v: $t),*) -> Result<(), crate::Error> -> ((); $crate::ffi::unit; $crate::ffi::unit) { let () = $body; Ok(()) }); }; // $Crt is the C function's return type. It must be possible to // convert an Error value v of type $rt to a value of type $Crt by doing: // $Crt::from($rt). // // $ok is the value (of type $rt) to map Ok to. ($(#[$outer:meta])* fn $f:ident($($v:ident: $t:ty),*) -> Result<$rt:ty, $et:ty> -> ($Crt:ty; $rt_to_crt:expr; $err_to_crt: expr) $body:block ) => { // The wrapper. It calls $f and turns the result into an // error code. $(#[$outer])* #[allow(unused)] #[no_mangle] pub extern "C" fn $f($($v: $t),*) -> $Crt { tracer!(*crate::TRACE, stringify!($f)); // The actual function. fn inner($($v: $t),*) -> std::result::Result<$rt, $et> { $body } t!("entered"); // We use AssertUnwindSafe. This is safe, because if we // catch a panic, we abort. If we turn the panic into an // error, then we need to reexamine this assumption. let r = std::panic::catch_unwind(::std::panic::AssertUnwindSafe(|| { match inner($($v,)*) { Ok(v) => { t!("-> success"); let rt: $Crt = $rt_to_crt(v); rt } Err(err) => { t!("-> error: {}{}", err, { use std::error::Error; let mut causes = String::new(); let mut cause = err.source(); while let Some(e) = cause { causes.push_str("\n because: "); causes.push_str(&e.to_string()); cause = e.source(); } causes }); let rt: $Crt = $err_to_crt(err); rt } } })); match r { Ok(code) => code, Err(_) => { t!("-> panic!"); unsafe { ::libc::abort() }; } } } } } // Creates a stub for a ffi, which returns an error. #[allow(unused_macros)] macro_rules! stub { ($f:ident) => { #[no_mangle] pub extern "C" fn $f() -> crate::ErrorCode { tracer!(*crate::TRACE, stringify!($f)); t!("{} is a stub", stringify!($f)); crate::Error::Fail( format!("Unimplemented: {}", stringify!($f))).into() } }; } // Checks if a `*const T` pointer is NULL if so, returns an error. // Otherwise, returns `&T`. macro_rules! check_ptr { ($p:ident) => {{ let p: *const _ = $p; if p.is_null() { return Err(Error::Fail( format!("{} must not be NULL", stringify!($p))).into()); } else { t!("{}: & <- {:?}", stringify!($p), $p); unsafe { &*p } } }} } // Returns an Option<&T> from a *const T. macro_rules! check_optional_ptr { ($p:ident) => {{ let p: *const _ = $p; if p.is_null() { None } else { t!("{}: Option<&> <- {:?}", stringify!($p), $p); Some(unsafe { &*p }) } }} } // Checks if a `*mut T` pointer is NULL if so, returns an error. // Otherwise, returns `&mut T`. macro_rules! check_mut { ($p:ident) => {{ let p: *mut _ = $p; if p.is_null() { return Err(Error::Fail( format!("{} must not be NULL", stringify!($p))).into()); } else { t!("{}: &mut <- {:?}", stringify!($p), $p); unsafe { &mut *p } } }} } // Returns an Option<&mut T> from a *mut T. macro_rules! check_optional_mut { ($p:ident) => {{ let p: *mut _ = $p; if p.is_null() { None } else { t!("{}: Option<&mut> <- {:?}", stringify!($p), $p); Some(unsafe { &mut *p }) } }} } // Checks if a `*const T` pointer is NULL if so, returns an error. // Otherwise, returns a slice `&[T]` with `l` elements. macro_rules! check_slice { ($p:ident, $l:expr) => { if $p.is_null() { return Err(Error::Fail( format!("{} must not be NULL", stringify!($p)))); } else { t!("{}: &[] <- {:?}", stringify!($p), $p); unsafe { std::slice::from_raw_parts($p as *const u8, $l) } } } } // Checks if a `*mut T` pointer is NULL if so, returns an error. // Otherwise, returns a slice `&mut [T]` with `l` elements. macro_rules! check_mut_slice { ($p:ident, $l:expr) => {{ let p: *mut _ = $p; if p.is_null() { return Err(Error::Fail( format!("{} must not be NULL", stringify!($p)))); } else { t!("{}: &[] <- {:?}", stringify!($p), p); unsafe { std::slice::from_raw_parts_mut($p as *mut u8, $l) } } }} } // Checks if a `*const c_char` pointer is NULL if so, returns an // error. Otherwise, returns a CStr. macro_rules! check_cstr { ($s:ident) => {{ let _: *const libc::c_char = $s; let s = check_ptr!($s); unsafe { std::ffi::CStr::from_ptr(s) } }} } // Moves ownership of a parameter of type T to C. // // Given a T, returns a *mut T. macro_rules! move_to_c { ($expr:expr) => {{ let p = Box::into_raw(Box::new($expr)); t!("{}: returning {:?}", stringify!($expr), p); p }} } // Moves ownership of an object owned by C. // // This is the opposite of move_to_c. macro_rules! claim_from_c { ($p:ident) => {{ if $p.is_null() { return Err(Error::Fail( format!("{} must not be NULL", stringify!($p)))); } unsafe { t!("{}: owned <- {:?}", stringify!($p), $p); Box::from_raw($p) } }}; } // Moves ownership of a parameter of type Option to C. macro_rules! move_option_to_c { ($expr:expr) => { $expr.map(|x| box_raw!(x)).unwrap_or(::std::ptr::null_mut()) } } /// Transfers ownership from C to Rust, then frees the object. /// /// NOP if called with NULL. macro_rules! free { ($p:ident) => {{ if let Some(ptr) = $p { let ptr = unsafe { Box::from_raw(ptr) }; drop(ptr); } }}; } rpm-sequoia-1.7.0/src/lib.rs000064400000000000000000002344031046102023000140040ustar 00000000000000//! An implementation of RPM's OpenPGP interface. //! //! This library provides an implementation of [RPM's OpenPGP //! interface](https://github.com/rpm-software-management/rpm/blob/master/include/rpm/rpmpgp.h). //! //! **You should not link to this library directly**. //! //! If you are looking for an OpenPGP interface, consider using //! [Sequoia], which this library is based on. If you want to use //! RPM's OpenPGP interface, which you should only do if you are //! interacting with RPM, then you should link against [RPM], which //! reexports this interface. //! //! [Sequoia]: https://gitlab.com/sequoia-pgp/sequoia //! [RPM]: http://rpm.org //! //! If you are investigating a bug in this library, set the //! `RPM_TRACE` environment variable to 1 to get a verbose trace of //! the library's execution: //! //! ```sh //! $ LD_LIBRARY_PATH=/tmp/rpm-sequoia/release RPM_TRACE=1 ./rpmkeys \ //! --import ../tests/data/keys/CVE-2021-3521-badbind.asc //! _rpmInitCrypto: entered //! _rpmInitCrypto: -> success //! _pgpParsePkts: entered //! ... //! ``` //! //! # Policy //! //! When Sequoia evaluates the validity of an object (e.g., a //! cryptographic signature) it consults a policy. The policy is user //! defined. This library uses [Sequoia's standard policy]. //! //! [Sequoia's standard policy]: https://docs.sequoia-pgp.org/sequoia_openpgp/policy/struct.StandardPolicy.html //! //! Sequoia's standard policy allows self-signatures (i.e., the //! signatures that bind a User ID or subkey to a certificate) made //! with SHA-1 until February 2023. It completely disallows data //! signatures made with SHA-1. The reason for this is that SHA-1 //! collision resistance is broken, but its second pre-image //! resistance is still okay. //! //! As an added protection, Sequoia uses [SHA-1 collision detection], //! which is a variant of SHA-1, which mitigates known attacks against //! SHA-1. SHA-1 CD has a very low [false positive rate] (2^-90) so //! it can be treated as a drop-in, fully compatible replacement for //! SHA-1. //! //! [SHA-1 collision detection]: https://github.com/cr-marcstevens/sha1collisiondetection //! [false positive rate]: https://github.com/cr-marcstevens/sha1collisiondetection#about //! //! # Configuration File //! //! This library reads the [crypto policy configuration] in //! `/etc/crypto-policies/back-ends/sequoia.config`. If that file //! doesn't exist, it tries //! `/usr/share/crypto-policies/back-ends/rpm-sequoia.config`. This //! can be overridden using the `SEQUOIA_CRYPTO_POLICY` environment //! variable. If set to the empty string, then no crypto policy will //! be read and instead [Sequoia's default policy] will be used. //! //! Refer to the [Fedora Crypto Policy] project for information about //! the crypto policy. //! //! [crypto policy configuration]: https://docs.rs/sequoia-policy-config/latest/sequoia_policy_config/ //! [Sequoia's default policy]: https://docs.sequoia-pgp.org/sequoia_openpgp/policy/struct.StandardPolicy.html //! [Fedora Crypto Policy]: https://gitlab.com/redhat-crypto/fedora-crypto-policies/ use std::env; use std::ffi::{ CString, }; use std::fmt::Debug; use std::io::Read; use std::io::Write; use std::path::PathBuf; use std::sync::RwLock; use std::time::{ Duration, SystemTime, UNIX_EPOCH, }; #[allow(unused_imports)] use anyhow::Context; use libc::{ c_char, c_int, c_uint, c_void, size_t, }; use chrono::{ DateTime, Utc, }; use sequoia_openpgp as openpgp; use openpgp::armor; use openpgp::Cert; use openpgp::cert::prelude::*; use openpgp::Fingerprint; use openpgp::packet::key::{ PublicParts, }; use openpgp::packet::{ Packet, Signature, Tag, }; use openpgp::parse::{ PacketParser, PacketParserResult, PacketParserBuilder, Dearmor, }; use openpgp::parse::Parse; use openpgp::policy::{ NullPolicy, StandardPolicy, Policy, }; use openpgp::serialize::SerializeInto; use openpgp::types::RevocationStatus; #[macro_use] mod log; #[macro_use] mod ffi; #[macro_use] pub mod rpm; use rpm::{ Error, ErrorCode, PgpArmor, PgpArmorError, Result, }; pub mod digest; lazy_static::lazy_static! { static ref P: RwLock> = RwLock::new(StandardPolicy::new()); } const NP: &NullPolicy = &NullPolicy::new(); // Set according to the RPM_TRACE environment variable (enabled if // non-zero), or if we are built in debug mode. lazy_static::lazy_static! { static ref TRACE: bool = { if let Ok(v) = env::var("RPM_TRACE") { let v: isize = v.parse().unwrap_or(1); v != 0 } else { false } }; } /// Prints the error and causes, if any. pub fn print_error_chain(err: &anyhow::Error) { eprintln!(" {}", err); err.chain().skip(1).for_each(|cause| eprintln!(" because: {}", cause)); } // Sometimes the same error cascades, e.g.: // // ``` // $ sq-wot --time 20230110T0406 --keyring sha1.pgp path B5FA089BA76FE3E17DC11660960E53286738F94C 231BC4AB9D8CAB86D1622CE02C0CE554998EECDB FABA8485B2D4D5BF1582AA963A8115E774FA9852 "" // [ ] FABA8485B2D4D5BF1582AA963A8115E774FA9852 : not authenticated (0%) // ◯ B5FA089BA76FE3E17DC11660960E53286738F94C ("") // │ No adequate certification found. // │ No binding signature at time 2023-01-10T04:06:00Z // │ No binding signature at time 2023-01-10T04:06:00Z // │ No binding signature at time 2023-01-10T04:06:00Z // ... // ``` // // Although technically correct, it's just noise. Compress them. fn error_chain(err: &anyhow::Error) -> Vec { let mut errs = std::iter::once(err.to_string()) .chain(err.chain().map(|source| source.to_string())) .collect::>(); errs.dedup(); errs } // Generate macros for working with lints. // // Note: $dollar is a hack, which we use because nested macros with // repetitions don't currently work. See: // https://github.com/rust-lang/rust/pull/95860 macro_rules! linter { ($dollar:tt, $lints:ident) => { // A helper macro to add a lint. // // If `$err` is `None`, `$msg` is turned into an `anyhow::Error` and // appended to `lints`. // // If `$err` is `Some`, `$msg` is added as context to `$err` and is // then appended to `lints`. macro_rules! add_lint { ($err:expr, $msg:expr $dollar(, $args:expr)*) => {{ let err: Option = $err; let msg = format!("{}", format_args!($msg $dollar(, $args)*)); let err = if let Some(err) = err { err.context(msg) } else { anyhow::anyhow!(msg) }; $lints.push(err); }}; } // A helper to return an error. // // This adds a lint using `lint!` and then returns // `Error::Fail($msg)`. macro_rules! return_err { ($err:expr, $msg:expr $dollar(, $args:expr)*) => {{ add_lint!($err, $msg $dollar(, $args)*); return Err(Error::Fail( format!("{}", format_args!($msg $dollar(, $args)*)))); }}; } } } // By default we prefer this environment variable and this file, but // if that is not present, we fallback to the default configuration. const RPM_SEQUOIA_CONFIG_ENV: &'static str = "RPM_SEQUOIA_CRYPTO_POLICY"; const RPM_SEQUOIA_CONFIG: &[&str] = &[ "/etc/crypto-policies/back-ends/rpm-sequoia.config", "/usr/share/crypto-policies/back-ends/rpm-sequoia.config", ]; ffi!( /// int rpmInitCrypto(void) fn _rpmInitCrypto() -> Binary { // XXX: Remove this once v4 signatures are ubiquitous. // // Unfortunately, much of the rpm ecosystem is still (2022) // generating v3 signatures. As they aren't completely broken, // accept them by default, but still let them be overridden by the // system policy. // // See https://bugzilla.redhat.com/show_bug.cgi?id=2141686 let mut p = openpgp::policy::StandardPolicy::new(); p.accept_packet_tag_version(openpgp::packet::Tag::Signature, 3); let mut p = sequoia_policy_config::ConfiguredStandardPolicy ::from_policy(p); // We can only specify a single file to // `ConfiguredStandardPolicy::parse_config_file`. We work around // it (for now) by taking the first file that exists. let rpm_sequoia_config = RPM_SEQUOIA_CONFIG .iter() .find(|path| { PathBuf::from(path).exists() }) .unwrap_or(&RPM_SEQUOIA_CONFIG[0]); match p.parse_config(RPM_SEQUOIA_CONFIG_ENV, rpm_sequoia_config) { Ok(false) => { // Fallback to the default configuration. if let Err(err) = p.parse_default_config() { print_error_chain(&err); return Err(err.into()); } } Ok(true) => (), Err(err) => { print_error_chain(&err); return Err(err.into()); } } *crate::P.write().unwrap() = p.build(); Ok(()) }); ffi!( /// int rpmFreeCrypto(void) fn _rpmFreeCrypto() -> Binary { Ok(()) }); // These are still implemented in C due to internationalization, and // to avoid translating the string tables, which is a fair amount of // error prone work, and doesn't improve safety. // // stub!(pgpValString); // stub!(pgpIdentItem); // This is implemented in C: it is just a wrapper around pgpParsePkts, // which uses some internal rpm functions. // // stub!(pgpReadPkts); /// An OpenPGP object. /// /// This data structure can hold either a signature, a certificate, or /// a subkey. enum PgpDigParamsObj { Cert(Cert), Subkey(Cert, Fingerprint), Signature(Signature), } pub struct PgpDigParams { obj: PgpDigParamsObj, signid: [u8; 8], userid: Option, } impl PgpDigParams { fn cert(&self) -> Option<&Cert> { match &self.obj { PgpDigParamsObj::Cert(cert) => Some(cert), PgpDigParamsObj::Subkey(cert, _) => Some(cert), PgpDigParamsObj::Signature(_) => None, } } fn key(&self) -> Option> { match &self.obj { PgpDigParamsObj::Cert(cert) => { Some(cert.primary_key().into()) } PgpDigParamsObj::Subkey(cert, fpr) => { Some(cert.keys().subkeys() .key_handle(fpr) .next() .expect("subkey missing") .into()) } PgpDigParamsObj::Signature(_) => None, } } fn signature(&self) -> Option<&Signature> { match &self.obj { PgpDigParamsObj::Cert(_) => None, PgpDigParamsObj::Subkey(_, _) => None, PgpDigParamsObj::Signature(sig) => Some(sig), } } } ffi!( /// Returns the signature's type. /// /// If `dig` is NULL or does not contain a signature, then this /// function returns -1. fn _pgpSignatureType(dig: *const PgpDigParams) -> c_int[-1] { let dig = check_ptr!(dig); dig.signature() .ok_or_else(|| Error::Fail("Not a signature".into())) .map(|sig| { u8::from(sig.typ()).into() }) }); ffi!( /// Frees the parameters. fn _pgpDigParamsFree(dig: Option<&mut PgpDigParams>) { free!(dig); }); ffi!( /// "Compares" the two parameters and returns 1 if they differ and 0 if /// they match. /// /// Two signatures are considered the same if they have the same /// parameters (version, signature type, public key and hash /// algorithms, and the first issuer packet). Note: this function /// explicitly does not check that the MPIs are the same, nor that the /// signature creation time is the same! This is intended. The only /// use of this function in the rpm code base is to check whether a key /// has already made a signature (cf. sign/rpmgensig.c:haveSignature). /// /// Two certificates are considered the same if they have the same /// fingerprint. (rpm does not currently use this functionality.) /// /// Two subkeys are considered the same if they have the same /// fingerprint. (rpm does not currently use this functionality.) fn _pgpDigParamsCmp(p1: *const PgpDigParams, p2: *const PgpDigParams) -> c_int[1] { let p1 = check_ptr!(p1); let p2 = check_ptr!(p2); let r = match (&p1.obj, &p2.obj) { (PgpDigParamsObj::Cert(c1), PgpDigParamsObj::Cert(c2)) => { c1.fingerprint() == c2.fingerprint() } (PgpDigParamsObj::Subkey(_, f1), PgpDigParamsObj::Subkey(_, f2)) => { f1 == f2 } (PgpDigParamsObj::Signature(s1), PgpDigParamsObj::Signature(s2)) => { t!("s1: {:?}", s1); t!("s2: {:?}", s2); s1.hash_algo() == s2.hash_algo() && s1.pk_algo() == s2.pk_algo() && s1.version() == s2.version() && s1.typ() == s2.typ() && p1.signid == p2.signid } _ => { false } }; Ok(if r { 0 } else { 1 }) }); const PGPVAL_PUBKEYALGO: c_uint = 6; const PGPVAL_HASHALGO: c_uint = 9; ffi!( /// Returns the object's public key or algorithm algorithm. /// /// `algotype` is either `PGPVAL_PUBKEYALGO` or `PGPVAL_HASHALGO`. /// Other algo types are not support and cause this function to return /// 0. fn _pgpDigParamsAlgo(dig: *const PgpDigParams, algotype: c_uint) -> c_uint[0] { let dig = check_ptr!(dig); match (algotype, &dig.obj) { // pubkey algo. (PGPVAL_PUBKEYALGO, PgpDigParamsObj::Cert(cert)) => { Ok(u8::from(cert.primary_key().pk_algo()).into()) } (PGPVAL_PUBKEYALGO, PgpDigParamsObj::Subkey(_, _)) => { Ok(u8::from(dig.key().expect("valid").pk_algo()).into()) } (PGPVAL_PUBKEYALGO, PgpDigParamsObj::Signature(sig)) => { Ok(u8::from(sig.pk_algo()).into()) } // hash algo. (PGPVAL_HASHALGO, PgpDigParamsObj::Cert(cert)) => { match cert.with_policy(&*P.read().unwrap(), None) { Ok(vc) => { let algo = vc.primary_key().binding_signature().hash_algo(); Ok(u8::from(algo).into()) } Err(err) => { Err(Error::Fail( format!("Using {}: {}", cert.fingerprint(), err))) } } } (PGPVAL_HASHALGO, PgpDigParamsObj::Subkey(_, fpr)) => { let ka = dig.key().expect("valid"); match ka.with_policy(&*P.read().unwrap(), None) { Ok(ka) => { let algo = ka.binding_signature().hash_algo(); Ok(u8::from(algo).into()) } Err(err) => { Err(Error::Fail( format!("Using {}: {}", fpr, err))) } } } (PGPVAL_HASHALGO, PgpDigParamsObj::Signature(sig)) => { Ok(u8::from(sig.hash_algo()).into()) } // Unknown algo. (t, PgpDigParamsObj::Cert(_)) | (t, PgpDigParamsObj::Subkey(_, _)) | (t, PgpDigParamsObj::Signature(_)) => { Err(Error::Fail(format!("Invalid algorithm type: {}", t))) } } }); ffi!( /// Returns the issuer or the Key ID. /// /// If `dig` is a signature, then this returns the Key ID stored in the /// first Issuer or Issuer Fingerprint subpacket as a hex string. /// (This is not authenticated!) /// /// If `dig` is a certificate or a subkey, then this returns the key's /// Key ID. /// /// The caller must *not* free the returned buffer. fn _pgpDigParamsSignID(dig: *const PgpDigParams) -> *const u8 { let dig = check_ptr!(dig); t!("SignID: {}", dig.signid.iter().map(|v| format!("{:02X}", v)).collect::()); Ok(dig.signid.as_ptr()) }); ffi!( /// Returns the primary User ID, if any. /// /// If `dig` is a signature, then this returns `NULL`. /// /// If `dig` is a certificate or a subkey, then this returns the /// certificate's primary User ID, if any. /// /// This interface does not provide a way for the caller to recognize /// any embedded `NUL` characters. /// /// The caller must *not* free the returned buffer. fn _pgpDigParamsUserID(dig: *const PgpDigParams) -> *const c_char { let dig = check_ptr!(dig); if let Some(ref userid) = dig.userid { Ok(userid.as_ptr()) } else { Ok(std::ptr::null()) } }); ffi!( /// Returns the object's version. /// /// If `dig` is a signature, then this returns the version of the /// signature packet. /// /// If `dig` is a certificate, then this returns the version of the /// primary key packet. /// /// If `dig` is a subkey, then this returns the version of the subkey's /// key packet. fn _pgpDigParamsVersion(dig: *const PgpDigParams) -> c_int[0] { let dig = check_ptr!(dig); let version = match &dig.obj { PgpDigParamsObj::Cert(cert) => { cert.primary_key().version() } PgpDigParamsObj::Subkey(_, _) => { dig.key().unwrap().version() } PgpDigParamsObj::Signature(sig) => { sig.version() } }; Ok(version as c_int) }); ffi!( /// Returns the object's time. /// /// If `dig` is a signature, then this returns the signature's creation /// time. /// /// If `dig` is a certificate, then this returns the primary key's key /// creation time. /// /// If `dig` is a subkey, then this returns the subkey's key creation /// time. fn _pgpDigParamsCreationTime(dig: *const PgpDigParams) -> u32[0] { let dig = check_ptr!(dig); let t = match &dig.obj { PgpDigParamsObj::Cert(cert) => { cert.primary_key().creation_time() } PgpDigParamsObj::Subkey(cert, fpr) => { cert.keys().subkeys() .key_handle(fpr) .next() .expect("subkey missing") .creation_time() } PgpDigParamsObj::Signature(sig) => { sig.signature_creation_time().unwrap_or(UNIX_EPOCH) } }; Ok(t.duration_since(UNIX_EPOCH) .map_err(|_| Error::Fail("time".into()))? .as_secs() as u32) }); ffi!( /// Verifies the signature. /// /// If `key` is `NULL`, then this computes the hash and checks it /// against the hash prefix. /// /// If `key` is not `NULL`, then this checks that the signature is /// correct. /// /// This function does not modify `ctx`. Instead, it first duplicates /// `ctx` and then hashes the the meta-data into that context. /// /// This function fails if the signature is not valid, or a supplied /// key is not valid. /// /// A signature is valid if: /// /// - The signature is alive now (not created in the future, and not /// yet expired) /// /// - It is accepted by the [policy]. /// /// A key is valid if as of the *signature's* creation time if: /// /// - The certificate is valid according to the [policy]. /// /// - The certificate is alive /// /// - The certificate is not revoke /// /// - The key is alive /// /// - The key is not revoke /// /// - The key has the signing capability set. /// /// [policy]: index.html#policy fn _pgpVerifySignature(key: *const PgpDigParams, sig: *const PgpDigParams, ctx: *const digest::DigestContext) -> ErrorCode { match _pgpVerifySignature2(key, sig, ctx, std::ptr::null_mut()) { 0 => Ok(()), ec => Err(Error::from(ec)), } }); ffi!( /// Like _pgpVerifySignature, but returns error messages and lints in /// `lint_str`. fn _pgpVerifySignature2(key: *const PgpDigParams, sig: *const PgpDigParams, ctx: *const digest::DigestContext, lint_str: *mut *mut c_char) -> ErrorCode { let key: Option<&PgpDigParams> = check_optional_ptr!(key); let sig: &PgpDigParams = check_ptr!(sig); // This function MUST NOT free or even change ctx. let mut ctx = check_ptr!(ctx).clone(); let mut lint_str: Option<&mut _> = check_optional_mut!(lint_str); if let Some(lint_str) = lint_str.as_mut() { **lint_str = std::ptr::null_mut(); } let mut lints = Vec::new(); let r = pgp_verify_signature(key, sig, ctx, &mut lints); // Return any lint / error messages. if lints.len() > 0 { let mut s: String = if let Some(key) = key { format!( "Verifying a signature using certificate {} ({}):", key.cert() .map(|cert| cert.fingerprint().to_string()) .unwrap_or_else(|| "".to_string()), key.cert() .and_then(|cert| { cert.userids().next() .map(|userid| { String::from_utf8_lossy(userid.value()).into_owned() }) }) .unwrap_or_else(|| { "".into() })) } else { format!( "Verifying a signature, but no certificate was \ provided:") }; // Indent the lints. let sep = "\n "; let lints_count = lints.len(); for (err_i, err) in lints.into_iter().enumerate() { for (cause_i, cause) in error_chain(&err).into_iter().enumerate() { if cause_i == 0 { s.push_str(sep); if lints_count > 1 { s.push_str(&format!("{}. ", err_i + 1)); } } else { s.push_str(sep); s.push_str(" because: "); } s.push_str(&cause); } } t!("Lints: {}", s); if let Some(lint_str) = lint_str.as_mut() { // Add a trailing NUL. s.push('\0'); **lint_str = s.as_mut_ptr() as *mut c_char; // Pass ownership to the caller. std::mem::forget(s); } } r }); // Verifies the signature. // // Lints are appended to `lints`. Note: multiple lints may be added. fn pgp_verify_signature(key: Option<&PgpDigParams>, sig: &PgpDigParams, mut ctx: digest::DigestContext, lints: &mut Vec) -> Result<()> { tracer!(*crate::TRACE, "pgp_verify_signature"); linter!($, lints); // Whether the verification relies on legacy cryptography. let mut legacy = false; let sig = sig.signature().ok_or_else(|| { Error::Fail("sig parameter does not designate a signature".into()) })?; let sig_id = || { let digest_prefix = sig.digest_prefix(); format!("{:02x}{:02x} created at {}", digest_prefix[0], digest_prefix[1], if let Some(t) = sig.signature_creation_time() { DateTime::::from(t) .format("%c").to_string() } else { "".to_string() }) }; let sig_time = if let Some(t) = sig.signature_creation_time() { t } else { return_err!( None, "Signature {} invalid: signature missing a creation time", sig_id()); }; // Allow some clock skew. if let Err(err) = sig.signature_alive(None, Duration::new(5 * 60, 0)) { return_err!( Some(err), "Signature {} invalid: signature is not alive", sig_id()); } { let policy = P.read().unwrap(); if let Err(err) = policy.signature(sig, Default::default()) { if NP.signature(sig, Default::default()).is_ok() { legacy = true; add_lint!( Some(err), "Signature {} invalid: signature relies on legacy cryptography", sig_id()); } else { return_err!( Some(err), "Signature {} invalid: policy violation", sig_id()); } } // XXX: As of sequoia-openpgp v1.11.0, this check is not done // by `policy.signature` (see issue #953). We do it manually, // but once rpm-sequoia depends on a newer version of // sequoia-openpgp that does this, remove this code. if let Err(err) = policy.packet(&Packet::from(sig.clone())) { if NP.packet(&Packet::from(sig.clone())).is_ok() { legacy = true; add_lint!( Some(err), "Signature {} invalid: signature relies on legacy cryptography", sig_id()); } else { return_err!( Some(err), "Signature {} invalid: policy violation", sig_id()); } } } // XXX: rpm only cares about the first issuer // subpacket. let issuer = match sig.get_issuers().into_iter().next() { Some(issuer) => issuer, None => return_err!( None, "Signature {} invalid: signature has no issuer subpacket", sig_id()), }; if let Some(key) = key { // Actually verify the signature. let cert = key.cert().ok_or_else(|| { Error::Fail("key parameter is not a cert".into()) })?; let subkey = key.key().expect("is a certificate").fingerprint(); t!("Checking signature {} using {} with {} / {}", sig_id(), sig.hash_algo(), cert.fingerprint(), subkey); // We evaluate the certificate as of the signature creation // time. let p = &*P.read().unwrap(); let vc = cert.with_policy(p, sig_time) .or_else(|err| { // Try again, but use the current time as a reference // time. It is quite common for old self-signatures // to be stripped. match cert.with_policy(p, None) { Ok(vc) => { // We'd really like to emit the following // lint, but for most users it is not // actionable. When the ecosystem changes so // that certificates include older self // signatures, enable it again. // add_lint!( // None, // "Certificate has no valid binding signature \ // as of the signature's creation time, but \ // is valid now. The certificate has probably \ // been stripped or minimized."); Ok(vc) } Err(err2) => { add_lint!( Some(err), "Certificate {} invalid: policy violation", cert.keyid()); Err(err2) } } }) .or_else(|err| { legacy = true; add_lint!( Some(err), "Certificate {} invalid: policy violation", cert.keyid()); cert.with_policy(NP, sig_time) })?; if let Err(err) = vc.alive() { legacy = true; add_lint!( Some(err), "Certificate {} invalid: certificate is not alive", vc.keyid()); } if let RevocationStatus::Revoked(_) = vc.revocation_status() { legacy = true; add_lint!( None, "Certificate {} invalid: certificate is revoked", vc.keyid()); } // Find the key. match vc.keys().key_handle(issuer.clone()).next() { Some(ka) => { if ka.fingerprint() != subkey { return_err!(None, "Key {} invalid: wrong subkey ({})", ka.keyid(), subkey); } if ! ka.for_signing() { return_err!(None, "Key {} invalid: not signing capable", ka.keyid()); } if let Err(err) = ka.alive() { legacy = true; add_lint!(Some(err), "Key {} invalid: key is not alive", ka.keyid()); } if let RevocationStatus::Revoked(_) = ka.revocation_status() { legacy = true; add_lint!(None, "Key {} is invalid: key is revoked", ka.keyid()); } // Finally we can verify the signature. sig.clone().verify_hash(&ka, ctx.ctx.clone())?; if legacy { return Err(Error::NotTrusted( "Verification relies on legacy crypto".into()) .into()); } else { return Ok(()); } } None => { return_err!(None, "Certificate {} does not contain key {} \ or it is not valid", vc.keyid(), issuer); } } } else { // We don't have a key, but we still check that the prefix is // correct. // These traits should be imported only where needed to avoid // bugs. use openpgp::serialize::Marshal; use openpgp::serialize::MarshalInto; // See https://datatracker.ietf.org/doc/html/rfc4880#section-5.2.4 let mut sig_data = Vec::with_capacity(128); // Hash the signature into the context. match sig.version() { 4 => { sig_data.push(sig.version()); sig_data.push(sig.typ().into()); sig_data.push(sig.pk_algo().into()); sig_data.push(sig.hash_algo().into()); let l = sig.hashed_area().serialized_len() as u16; sig_data.push((l >> 8) as u8); sig_data.push((l >> 0) as u8); sig.hashed_area().serialize(&mut sig_data).expect("vec"); let sig_len = sig_data.len(); // Trailer. sig_data.push(sig.version()); sig_data.push(0xFF); sig_data.push((sig_len >> 24) as u8); sig_data.push((sig_len >> 16) as u8); sig_data.push((sig_len >> 8) as u8); sig_data.push((sig_len >> 0) as u8); } 3 => { sig_data.push(sig.typ().into()); let ct = sig.signature_creation_time().unwrap_or(UNIX_EPOCH); let ct = ct.duration_since(UNIX_EPOCH) .map_err(|_| Error::Fail("time".into()))? .as_secs() as u32; sig_data.push((ct >> 24) as u8); sig_data.push((ct >> 16) as u8); sig_data.push((ct >> 8) as u8); sig_data.push((ct >> 0) as u8); } v => { return Err(Error::Fail( format!("Unsupported signature version: {}", v))); } } ctx.update(&sig_data); let digest_size = ctx.digest_size(); let mut digest: Vec = Vec::with_capacity(digest_size); for _ in 0..digest_size { digest.push(0); } ctx.digest(&mut digest[..])?; let p = sig.digest_prefix(); if p[0] != digest[0] || p[1] != digest[1] { return Err(Error::Fail("digest prefix mismatch".into())); } else { t!("digest prefix matches"); } return Err(Error::NoKey( format!("Not provided (issuer: {})", issuer).into())); } } ffi!( /// Returns the Key ID of the public key or the secret key stored in /// `pkt`. /// /// Returns -1 if `pkt` is not a public key or secret key. /// /// Note: this function does not handle public subkeys or secret /// subkeys! /// /// `keyid` must be allocated by the caller and points to at least 8 /// bytes of memory. /// /// Returns 0 on success and -1 on failure. fn _pgpPubkeyKeyID(pkt: *const u8, pktlen: size_t, keyid: *mut u8) -> Binary { let pkt = check_slice!(pkt, pktlen); let ppr = PacketParser::from_bytes(pkt)?; let k = if let PacketParserResult::Some(ref pp) = ppr { match &pp.packet { Packet::PublicKey(key) => Some(key.keyid()), Packet::SecretKey(key) => Some(key.keyid()), _ => None, } } else { None }; t!("Key ID: {}", k.as_ref() .map(|k| k.to_string()) .unwrap_or_else(|| String::from("none"))); if let Some(k) = k { let buffer = check_mut_slice!(keyid, 8); buffer.copy_from_slice(k.as_bytes()); Ok(()) } else { Err(Error::Fail("Not a key".into())) } }); ffi!( /// Calculate OpenPGP public key fingerprint. /// /// Returns -1 if `pkt` is not a public key or secret key. /// /// Note: this function does not handle public subkeys or secret /// subkeys! /// /// `*fprout` is allocated using `malloc` and must be allocated by the /// caller. /// /// Returns 0 on success and -1 on failure. fn _pgpPubkeyFingerprint(pkt: *const u8, pktlen: size_t, fprout: *mut *mut u8, fprlen: *mut size_t) -> Binary { let pkt = check_slice!(pkt, pktlen); let ppr = PacketParserBuilder::from_bytes(pkt)? .dearmor(Dearmor::Disabled) // Disable dearmoring. .build()?; let fpr = if let PacketParserResult::Some(ref pp) = ppr { match &pp.packet { Packet::PublicKey(key) => Some(key.fingerprint()), Packet::SecretKey(key) => Some(key.fingerprint()), _ => None, } } else { None }; t!("Fingerprint: {}", fpr.as_ref() .map(|fpr| fpr.to_string()) .unwrap_or_else(|| String::from("none"))); if let Some(fpr) = fpr { let fpr = fpr.as_bytes(); unsafe { let buffer = libc::malloc(fpr.len()); libc::memcpy(buffer, fpr.as_ptr() as *const c_void, fpr.len()); *fprout = buffer as *mut u8; *fprlen = fpr.len(); } Ok(()) } else { Err(Error::Fail("Not a key".into())) } }); ffi!( /// Wraps the data in ASCII armor. /// /// `atype` is the armor type. /// /// The caller must free the returned buffer. /// /// Returns `NULL` on failure. fn _pgpArmorWrap(atype: c_int, s: *const c_char, ns: size_t) -> *mut c_char { let atype = armor::Kind::try_from(PgpArmor::from(atype))?; let s = check_slice!(s, ns); let mut writer = armor::Writer::new(Vec::new(), atype) .map_err(|err| Error::Fail(format!("creating armor writer: {}", err)))?; writer.write(s) .map_err(|err| Error::Fail(format!("writing armor body: {}", err)))?; let mut buffer = writer.finalize() .map_err(|err| Error::Fail(format!("finalizing armor: {}", err)))?; // Add a trailing NUL. buffer.push(0); let ptr = buffer.as_mut_ptr() as *mut c_char; std::mem::forget(buffer); Ok(ptr) }); ffi!( /// Returns the length of the certificate in bytes. /// /// `pkts` points to a buffer. Fails if `pkts` does not point to /// exactly one valid OpenPGP certificate. /// /// Returns 0 on failure. fn _pgpPubKeyCertLen(pkts: *const u8, pktslen: size_t, certlen: *mut size_t) -> Binary { use openpgp::packet::Header; use openpgp::packet::header::PacketLengthType; use openpgp::packet::header::BodyLength; use openpgp::packet::header::CTB; use buffered_reader::BufferedReader; let pkts = check_slice!(pkts, pktslen); let certlen = check_mut!(certlen); // XXX: These functions are more or less copied from // sequoia/openpgp/src/parse.rs. When sequoia-openpgp makes them // public, we drop this copy. fn body_length_parse_new_format(bio: &mut T) -> openpgp::Result where T: BufferedReader, C: Debug + Send + Sync { let octet1 : u8 = bio.data_consume_hard(1)?[0]; match octet1 { 0..=191 => // One octet. Ok(BodyLength::Full(octet1 as u32)), 192..=223 => { // Two octets length. let octet2 = bio.data_consume_hard(1)?[0]; Ok(BodyLength::Full(((octet1 as u32 - 192) << 8) + octet2 as u32 + 192)) }, 224..=254 => // Partial body length. Ok(BodyLength::Partial(1 << (octet1 & 0x1F))), 255 => // Five octets. Ok(BodyLength::Full(bio.read_be_u32()?)), } } /// Decodes an old format body length as described in [Section /// 4.2.1 of RFC 4880]. /// /// [Section 4.2.1 of RFC 4880]: https://tools.ietf.org/html/rfc4880#section-4.2.1 fn body_length_parse_old_format(bio: &mut T, length_type: PacketLengthType) -> openpgp::Result where T: BufferedReader, C: Debug + Send + Sync { match length_type { PacketLengthType::OneOctet => Ok(BodyLength::Full(bio.data_consume_hard(1)?[0] as u32)), PacketLengthType::TwoOctets => Ok(BodyLength::Full(bio.read_be_u16()? as u32)), PacketLengthType::FourOctets => Ok(BodyLength::Full(bio.read_be_u32()? as u32)), PacketLengthType::Indeterminate => Ok(BodyLength::Indeterminate), } } fn parse_header(bio: &mut T) -> openpgp::Result
where T: BufferedReader, C: Debug + Send + Sync { let ctb = CTB::try_from(bio.data_consume_hard(1)?[0])?; let length = match ctb { CTB::New(_) => body_length_parse_new_format(bio)?, CTB::Old(ref ctb) => body_length_parse_old_format(bio, ctb.length_type())?, }; return Ok(Header::new(ctb, length)); } let mut br = buffered_reader::Memory::new(pkts); let mut found_cert = false; let len: Option = loop { // The start of this packet as a byte offset into buffer. let start_of_packet = br.total_out(); if start_of_packet == pkts.len() { // We're done. break Some(start_of_packet); } let header = match parse_header(&mut br) { Ok(header) => header, Err(err) => { t!("Error reading certificate at offset {}: {}", start_of_packet, err); break None; } }; use Tag::*; let t = header.ctb().tag(); t!("Found a {:?} at offset {}, length: {:?}", t, start_of_packet, header.length()); match t { // Start of a new certificate. PublicKey | SecretKey => { if found_cert { break Some(start_of_packet); } else { found_cert = true; } } // The body of a certificate. PublicSubkey | SecretSubkey | UserID | UserAttribute | Signature | Marker | Trust | Unknown(_) | Private(_) => { if start_of_packet == 0 { t!("Encountered a ({:?}) at offset {}, \ which is not a valid start of a certificate", t, start_of_packet); break None; } } Reserved | PKESK | SKESK | OnePassSig | CompressedData | SED | Literal | SEIP | MDC | AED => { t!("Encountered a ({:?}) at offset {}, \ which does not belong in a certificate", t, start_of_packet); break None; } } // Advance to the next packet. match header.length() { BodyLength::Full(l) => { let l = *l as usize; if let Err(err) = br.data_consume_hard(l) { t!("Error while reading packet: {}", err); break None; } } BodyLength::Partial(_) => { t!("Packet {} has partial body length, \ which is unsupported by keyring splitter", t); break None; } BodyLength::Indeterminate => { t!("Packet {} has intedeterminite length, \ which is unsupported by keyring splitter", t); break None; } } }; if let Some(len) = len { *certlen = len; Ok(()) } else { Err(Error::Fail("No certificate found".into())) } }); ffi!( /// Parses OpenPGP data. /// /// If `pkts` contains a signature and `pkttype` is 0 or /// `Tag::Signature`, this returns a `PgpDigParams` containing a /// signature. /// /// If `pkts` contains a certificate and `pkttype` is 0, /// `Tag::PublicKey`, or `Tag::SecretKey`, this returns a /// `PgpDigParams` containing a certificate. The certificate is /// checked for validity in the sense that it only contains packets /// that belong to a certificate; this function does **not** check the /// binding signatures, etc. That check is done when the key is used /// in [_pgpVerifySignature]. /// /// Returns 0 on success, -1 on failure. fn _pgpPrtParams(pkts: *const u8, pktlen: size_t, pkttype: c_uint, paramsp: *mut *mut PgpDigParams) -> Binary { match _pgpPrtParams2(pkts, pktlen, pkttype, paramsp, std::ptr::null_mut()) { 0 => Ok(()), ec => Err(Error::from(ec)), } }); ffi!( /// Like _pgpPrtParams, but returns error messages and lints in /// `lint_str`. fn _pgpPrtParams2(pkts: *const u8, pktlen: size_t, pkttype: c_uint, paramsp: *mut *mut PgpDigParams, lint_str: *mut *mut c_char) -> Binary { let mut lint_str: Option<&mut _> = check_optional_mut!(lint_str); if let Some(lint_str) = lint_str.as_mut() { **lint_str = std::ptr::null_mut(); } let mut lints = Vec::new(); let r = pgp_prt_params(pkts, pktlen, pkttype, paramsp, &mut lints); // Return any lint / error messages. if lints.len() > 0 { let mut s: String = format!("Parsing an OpenPGP packet:"); // Indent the lints. let sep = "\n "; let lints_count = lints.len(); for (err_i, err) in lints.into_iter().enumerate() { for (cause_i, cause) in error_chain(&err).into_iter().enumerate() { if cause_i == 0 { s.push_str(sep); if lints_count > 1 { s.push_str(&format!("{}. ", err_i + 1)); } } else { s.push_str(sep); s.push_str(" because: "); } s.push_str(&cause); } } t!("Lints: {}", s); if let Some(lint_str) = lint_str.as_mut() { // Add a trailing NUL. s.push('\0'); **lint_str = s.as_mut_ptr() as *mut c_char; // Pass ownership to the caller. std::mem::forget(s); } } r }); fn pgp_prt_params(pkts: *const u8, pktlen: size_t, pkttype: c_uint, paramsp: *mut *mut PgpDigParams, lints: &mut Vec) -> Result<()> { tracer!(*crate::TRACE, "pgp_prt_params"); linter!($, lints); let pkttype: Option = if pkttype == 0 { None } else { Some(Tag::from(pkttype as u8)) }; let pkts = check_slice!(pkts, pktlen); let paramsp = check_mut!(paramsp); *paramsp = std::ptr::null_mut(); let ppr = PacketParser::from_bytes(pkts)?; let (obj, issuer, userid) = if let PacketParserResult::Some(pp) = ppr { // Process the packet. match pp.packet { Packet::Signature(_) if pkttype.is_none() || pkttype == Some(Tag::Signature) => { let (packet, next_ppr) = pp.next()?; if let PacketParserResult::Some(p) = next_ppr { return_err!(None, "Expected a bare OpenPGP signature, \ but it's followed by a {}", p.packet.tag()); } let sig = if let Packet::Signature(sig) = packet { sig } else { panic!("it's a sig"); }; (PgpDigParamsObj::Signature(sig.clone()), // XXX: Although there is normally only one issuer // subpacket, there may be multiple such subpackets. // Unfortunately, the API only allows us to return // one. sig.get_issuers().into_iter().next() .map(|i| i.as_bytes().to_vec()), None) } Packet::PublicKey(_) | Packet::SecretKey(_) if pkttype.is_none() || pkttype == Some(Tag::PublicKey) || pkttype == Some(Tag::SecretKey) => { let cert = match CertParser::from(PacketParserResult::Some(pp)).next() { Some(Ok(cert)) => cert, Some(Err(err)) => return_err!( Some(err), "Failed to read an OpenPGP certificate"), None => return_err!( None, "Failed to read an OpenPGP certificate"), }; let keyid = cert.keyid().as_bytes().to_vec(); let userid = if let Ok(vc) = cert.with_policy(&*P.read().unwrap(), None) { vc.primary_userid() .ok() .and_then(|u| { CString::new(u.value()).ok() }) } else { None }; (PgpDigParamsObj::Cert(cert), Some(keyid), userid) } Packet::Unknown(mut u) => { let mut err = u.set_error(anyhow::anyhow!("Error")); if let Some(openpgp::Error::MalformedMPI(_)) = err.downcast_ref::() { err = err.context("\ Signature appears to be created by a \ non-conformant OpenPGP implementation, see \ ."); } return_err!(Some(err), "Failed to parse {}", u.tag()); } ref p => { return_err!( None, "Unexpected OpenPGP packet in this context {}", p.tag()); } } } else { return_err!( None, "Expected an OpenPGP packet, encountered the end of the file"); }; let mut buffer: [u8; 8] = [0; 8]; if let Some(issuer) = issuer { let issuer = if issuer.len() > buffer.len() { // We've got a fingerprint. For v4 keys, the last 16 // bytes is the key id. &issuer[issuer.len() - buffer.len()..] } else { &issuer[..] }; for (i, c) in issuer.into_iter().enumerate() { buffer[i] = *c as u8; } } *paramsp = move_to_c!(PgpDigParams { obj, signid: buffer, userid: userid, }); Ok(()) } ffi!( /// Returns a `PgpDigParams` data structure for each subkey. /// /// This does not return a `PgpDigParams` for the primary (just use /// this one). The subkeys are **not** checked for validity. That /// check is done when the key is used in [_pgpVerifySignature]. fn _pgpPrtParamsSubkeys(pkts: *const u8, pktlen: size_t, _mainkey: *const PgpDigParams, subkeys: *mut *mut PgpDigParams, subkeys_count: *mut c_int) -> Binary { let pkts = check_slice!(pkts, pktlen); let subkeys = check_mut!(subkeys); *subkeys = std::ptr::null_mut(); let subkeys_count = check_mut!(subkeys_count); let ppr = PacketParser::from_bytes(pkts)?; let cert = match ppr { PacketParserResult::Some(ref pp) => { match pp.packet { Packet::PublicKey(_) | Packet::SecretKey(_) => { let cert = CertParser::from(ppr) .next() .ok_or(Error::Fail("Not an OpenPGP certificate".into()))??; cert } ref p => { return Err(Error::Fail(format!("{}", p.tag()))); } } } _ => return Err(Error::Fail("Not an OpenPGP message".into())), }; let userid = if let Ok(vc) = cert.with_policy(&*P.read().unwrap(), None) { vc.primary_userid() .ok() .and_then(|u| { CString::new(u.value()).ok() }) } else { None }; // We return all subkeys here. Subkeys are checked for validity // on demand. let mut keys: Vec<*mut PgpDigParams> = cert .keys().subkeys() .map(|ka| { t!("Subkey: {}", ka.keyid()); let zeros = [0; 8]; let mut dig = PgpDigParams { obj: PgpDigParamsObj::Subkey(cert.clone(), ka.fingerprint()), signid: zeros, userid: userid.clone(), }; dig.signid.copy_from_slice(ka.keyid().as_bytes()); move_to_c!(dig) }) .collect(); t!("Got {} subkeys", keys.len()); *subkeys_count = keys.len() as c_int; if keys.len() == 0 { *subkeys = std::ptr::null_mut(); } else { *subkeys = keys.as_mut_ptr() as *mut PgpDigParams; // Pass ownership to the caller. std::mem::forget(keys); } Ok(()) }); ffi!( /// Strips the ASCII armor and returns the decoded data in `pkt`. /// /// Despite its name, this function does not actually parse any OpenPGP /// packets; it just strips the ASCII armor encoding. /// /// Returns the type of armor on success (>0) or an error code /// indicating the type of failure (<0). fn _pgpParsePkts(armor: *const c_char, pkt: *mut *mut c_char, pktlen: *mut size_t) -> PgpArmor { let armor = check_cstr!(armor); let pkt = check_mut!(pkt); *pkt = std::ptr::null_mut(); let pktlen = check_mut!(pktlen); let mut reader = armor::Reader::from_reader( std::io::BufReader::new( armor.to_str().map_err(|_| PgpArmorError::BodyDecode)?.as_bytes()), armor::ReaderMode::Tolerant(None)); let mut buf = Vec::new(); reader.read_to_end(&mut buf).map_err(|_| PgpArmorError::BodyDecode)?; let kind = reader.kind(); *pktlen = buf.len() as size_t; *pkt = buf.as_mut_ptr() as *mut c_char; // Pass ownership to the caller. std::mem::forget(buf); Ok(kind.into()) }); ffi!( /// Lints the first certificate in pkts. /// /// This function links the certificate according to the current /// [policy]. It warns about things like unusable subkeys, because they /// do not have a valid binding signature. It will also generate a /// warning if there are no valid, signing-capable keys. /// /// There are four cases: /// /// - The packets do not describe a certificate: returns an error and /// sets `*explanation` to `NULL`. /// /// - The packets describe a certificate and the certificate is /// completely unusable: returns an error and sets `*explanation` to /// a human readable explanation. /// /// - The packets describe a certificate and some components are not /// usable: returns success, and sets `*explanation` to a human /// readable explanation. /// /// - The packets describe a certificate and there are no lints: /// returns success, and sets `*explanation` to `NULL`. /// /// [policy]: index.html#policy fn _pgpPubKeyLint(pkts: *const c_char, pktslen: size_t, explanation: *mut *mut c_char) -> ErrorCode { let pkts = check_slice!(pkts, pktslen); let explanation = check_mut!(explanation); // Make sure we always set explanation to something. *explanation = std::ptr::null_mut(); let cert = CertParser::from_bytes(pkts)?.next() .ok_or(Error::Fail("Not an OpenPGP certificate".into()))??; let mut lints: Vec = Vec::new(); let mut lint = |l: &str| { lints.push(l.into()); }; let usable = 'done : loop { match cert.with_policy(&*P.read().unwrap(), None) { Err(err) => { lint(&format!("Policy rejects {}: {}", cert.keyid(), err)); break 'done false; } Ok(vc) => { if let RevocationStatus::Revoked(revs) = vc.revocation_status() { for rev in revs { if let Some((reason, msg)) = rev.reason_for_revocation() { let mut l = format!( "The certificate was revoked: {}", reason); if ! msg.is_empty() { l.push_str(&format!( ", {}", String::from_utf8_lossy(msg))); } lint(&l); } else { lint("The certificate was revoked: \ unspecified reason"); } } } if let Err(err) = vc.alive() { if let Some(e) = vc.primary_key().key_expiration_time() { if e <= SystemTime::now() { lint(&format!("The certificate is expired: {}", err)); } else { lint(&format!("The certificate is not live: {}", err)); } } } } }; let mut have_signing = false; for ka in cert.keys() { let keyid = ka.keyid(); match ka.with_policy(&*P.read().unwrap(), None) { Err(err) => { lint(&format!("Policy rejects subkey {}: {}", keyid, err)); continue; } Ok(ka) => { if ! ka.for_signing() { // Silently ignore non-signing capable // subkeys. We don't care about them. continue; } if let RevocationStatus::Revoked(revs) = ka.revocation_status() { for rev in revs { if let Some((reason, msg)) = rev.reason_for_revocation() { let mut l = format!( "Subkey {} was revoked: {}", keyid, reason); if ! msg.is_empty() { l.push_str(&format!( ", {}", String::from_utf8_lossy(msg))); } lint(&l); } else { lint(&format!( "Subkey {} was revoked: \ unspecified reason", keyid)); } } continue; } if let Err(err) = ka.alive() { if let Some(e) = ka.key_expiration_time() { if e <= SystemTime::now() { lint(&format!("Subkey {} is expired: {}", keyid, err)); } else { lint(&format!("Subkey {} is not live: {}", keyid, err)); } } continue; } if ! ka.pk_algo().is_supported() { lint(&format!("Subkey {} is not supported \ (no support for {})", keyid, ka.pk_algo())); continue; } have_signing = true; } } } if ! have_signing { lint("Certificate does not have any usable signing keys"); } break true; }; if ! lints.is_empty() { // Indent the lints. let sep = "\n "; let mut s: String = format!("Certificate {}:{}", cert.keyid(), sep); s.push_str(&lints.join(sep)); s.push('\0'); *explanation = s.as_mut_ptr() as *mut c_char; // Pass ownership to the caller. std::mem::forget(s); } if usable { Ok(()) } else { Err(Error::Fail(format!("Certificate {} is unusable", cert.keyid()))) } }); /// An optional OpenPGP certificate *and* an optional signature. /// /// This data structure is deprecated and is scheduled for removal in /// rpm 4.19. pub struct PgpDig { cert: Option>, sig: Option>, } /// Dump the packets to stderr. /// /// This is used by _pgpPrtPkts, which is deprecated and is scheduled /// for removal in rpm 4.19. It is intended to be bug compatible with /// rpm's internal implementation. fn dump_packets(pkts: &[u8]) -> Result<()> { use openpgp::types::CompressionAlgorithm; use openpgp::types::KeyServerPreferences; use openpgp::types::PublicKeyAlgorithm; use openpgp::types::SignatureType; use openpgp::types::SymmetricAlgorithm; use openpgp::packet::signature::subpacket::Subpacket; use openpgp::packet::signature::subpacket::SubpacketTag; use openpgp::packet::signature::subpacket::SubpacketValue; let mut ppr = PacketParser::from_bytes(pkts)?; fn pk_algo(a: PublicKeyAlgorithm) -> &'static str { use PublicKeyAlgorithm::*; #[allow(deprecated)] match a { RSAEncryptSign => "RSA", RSAEncrypt => "RSA(Encrypt-Only)", RSASign => "RSA(Sign-Only)", ElGamalEncrypt => "Elgamal(Encrypt-Only)", DSA => "DSA", ECDH => "Elliptic Curve", ECDSA => "ECDSA", ElGamalEncryptSign => "Elgamal", EdDSA => "EdDSA", _ => "Unknown public key algorithm", } } fn sigtype(t: SignatureType) -> &'static str { use SignatureType::*; match t { Binary => "Binary document signature", Text => "Text document signature", Standalone => "Standalone signature", GenericCertification => "Generic certification of a User ID and Public Key", PersonaCertification => "Persona certification of a User ID and Public Key", CasualCertification => "Casual certification of a User ID and Public Key", PositiveCertification => "Positive certification of a User ID and Public Key", SubkeyBinding => "Subkey Binding Signature", PrimaryKeyBinding => "Primary Key Binding Signature", DirectKey => "Signature directly on a key", KeyRevocation => "Key revocation signature", SubkeyRevocation => "Subkey revocation signature", CertificationRevocation => "Certification revocation signature", Timestamp => "Timestamp signature", _ => "Unknown signature type", } } fn symalgo(a: SymmetricAlgorithm) -> &'static str { use SymmetricAlgorithm::*; match a { Unencrypted => "Plaintext", IDEA => "IDEA", TripleDES => "3DES", CAST5 => "CAST5", Blowfish => "BLOWFISH", AES128 => "AES(128-bit key)", AES192 => "AES(192-bit key)", AES256 => "AES(256-bit key)", Twofish => "TWOFISH(256-bit key)", _ => "Unknown symmetric key algorithm", } } fn compalgo(a: CompressionAlgorithm) -> &'static str { use CompressionAlgorithm::*; match a { Uncompressed => "Uncompressed", Zip => "ZIP", Zlib => "ZLIB", BZip2 => "BZIP2", _ => "Unknown compression algorithm", } } fn ksprefs(prefs: KeyServerPreferences) -> &'static str { // This is wrong, but this is what the internal implementation // does. if prefs.no_modify() { "No-modify(128)" } else if KeyServerPreferences::empty().normalized_eq(&prefs) { "" } else { "Unknown key server preference" } } fn subpacket(sp: &Subpacket) -> String { let mut output: Vec = Vec::new(); let tag = sp.tag(); let s = { use SubpacketTag::*; match tag { SignatureCreationTime => "signature creation time", SignatureExpirationTime => "signature expiration time", ExportableCertification => "exportable certification", TrustSignature => "trust signature", RegularExpression => "regular expression", Revocable => "revocable", KeyExpirationTime => "key expiration time", PlaceholderForBackwardCompatibility => "additional recipient request", PreferredSymmetricAlgorithms => "preferred symmetric algorithms", RevocationKey => "revocation key", Issuer => "issuer key ID", NotationData => "notation data", PreferredHashAlgorithms => "preferred hash algorithms", PreferredCompressionAlgorithms => "preferred compression algorithms", KeyServerPreferences => "key server preferences", PreferredKeyServer => "preferred key server", PrimaryUserID => "primary user id", PolicyURI => "policy URL", KeyFlags => "key flags", SignersUserID => "signer's user id", ReasonForRevocation => "reason for revocation", Features => "features", EmbeddedSignature => "embedded signature", _ => "Unknown signature subkey type", } }; output.push(s.into()); output.push(format!("({})", Into::::into(tag))); if sp.critical() { output.push(" *CRITICAL*".into()); } { use SubpacketValue::*; match sp.value() { PreferredSymmetricAlgorithms(algos) => { output.push(" ".into()); output.push( algos.iter() .map(|a| { format!("{}({})", symalgo(*a), Into::::into(*a)) }) .collect::>() .join(" ")) } PreferredHashAlgorithms(algos) => { output.push(" ".into()); output.push( algos.iter() .map(|a| { format!("{}({})", a.to_string(), Into::::into(*a)) }) .collect::>() .join(" ")) } PreferredCompressionAlgorithms(algos) => { output.push(" ".into()); output.push( algos.iter() .map(|a| { format!("{}({})", compalgo(*a), Into::::into(*a)) }) .collect::>() .join(" ")) } KeyServerPreferences(prefs) => { output.push(format!(" {}", ksprefs(prefs.clone()))) } SignatureExpirationTime(d) | KeyExpirationTime(d) => { // expiration time is an offset from the creation // time, but rpm's internal OpenPGP implementation // treats it as an absolute time. As we're going // for bug-for-bug compatibility here, we do the // same. let t = DateTime::from_timestamp( d.as_secs() as i64, 0) // This is just compatibility, debugging // output. Fallback to the unix epoch. .unwrap_or_default(); output.push(format!(" {}(0x{:08x})", t.format("%c"), d.as_secs())); } SignatureCreationTime(_) | Issuer(_) | KeyFlags(_) => (), _ => { use sequoia_openpgp::serialize::MarshalInto; output.push(" ".into()); output.extend( sp.value() .to_vec() .unwrap_or(Vec::new()) .into_iter() .map(|b| format!("{:02x}", b))) } } } output.join("") } while let PacketParserResult::Some(pp) = ppr { let (packet, next_ppr) = pp.recurse()?; ppr = next_ppr; // We only dump what rpm's internal OpenPGP implementation // dumps. Other packets we silently ignore. match packet { Packet::Signature(sig) => { // V4 Signature(2) DSA(17) SHA512(10) Generic certification of a User ID and Public Key(16) // signature creation time(2) // issuer key ID(16) // signhash16 1418 eprintln!("V{} Signature(2) {}({}) {}({}) {}({})", sig.version(), pk_algo(sig.pk_algo()), Into::::into(sig.pk_algo()), sig.hash_algo().to_string(), Into::::into(sig.hash_algo()), sigtype(sig.typ()), Into::::into(sig.typ())); sig.hashed_area().iter().for_each(|sb| { eprintln!(" {}", subpacket(sb)); }); sig.unhashed_area().iter().for_each(|sb| { eprintln!(" {}", subpacket(sb)); }); eprintln!(" signhash16 {:02x}{:02x}", sig.digest_prefix()[0], sig.digest_prefix()[1]); }, Packet::PublicKey(key) => { // V4 Public Key(6) RSA(1) Tue Apr 7 08:52:57 2015(0x55239ae9) let secs = key.creation_time() .duration_since(SystemTime::UNIX_EPOCH) .map(|d| d.as_secs()) .unwrap_or(0); let t: DateTime:: = key.creation_time().into(); eprintln!("V{} Public Key(6) {}({}) {}(0x{:08x})", key.version(), pk_algo(key.pk_algo()), Into::::into(key.pk_algo()), t.format("%c"), secs); } Packet::PublicSubkey(key) => { // Public Subkey(14) 045523a696010... use sequoia_openpgp::serialize::MarshalInto; let secs = key.creation_time() .duration_since(SystemTime::UNIX_EPOCH) .map(|d| d.as_secs()) .unwrap_or(0); eprintln!("Public Subkey(14) {:02}{:08x}{:02x}{}", key.version(), secs, Into::::into(key.pk_algo()), key.mpis().to_vec() .unwrap_or_else(|_| Vec::new()) .into_iter() .map(|b| format!("{:02x}", b)) .collect::()); } Packet::UserID(userid) => { // User ID(13) "Neal H. Walfield " eprintln!("User ID(13) {:?}", String::from_utf8_lossy(userid.value())); } Packet::Unknown(_pkt) => (), Packet::OnePassSig(_ops) => (), Packet::SecretKey(_key) => (), Packet::SecretSubkey(_key) => (), Packet::Marker(_marker) => (), Packet::Trust(_trust) => (), Packet::UserAttribute(_ua) => (), Packet::Literal(_lit) => (), Packet::CompressedData(_cd) => (), Packet::PKESK(_pkesk) => (), Packet::SKESK(_skesk) => (), Packet::SEIP(_seip) => (), Packet::MDC(_mdc) => (), Packet::AED(_aed) => (), _ => (), } } Ok(()) } ffi!( /// Parses and optionally prints to stdout a OpenPGP packet(s). /// /// This function is deprecated and is scheduled for removal in rpm /// 4.19. /// /// @param pkts OpenPGP packet(s) /// @param pktlen OpenPGP packet(s) length (no. of bytes) /// @param(out) dig parsed output of signature/pubkey packet parameters /// @param printing should packets be printed? /// /// Returns 0 on success, -1 on failure. fn _pgpPrtPkts(pkts: *const u8, pktslen: size_t, dig: *mut PgpDig, printing: c_int) -> Binary { let dig = check_mut!(dig); let mut params: *mut PgpDigParams = std::ptr::null_mut(); if printing != 0 { // We ignore any error here as this printing should not change // the functions semantics. let _ = dump_packets(check_slice!(pkts, pktslen)); } let result = _pgpPrtParams(pkts, pktslen, 0, &mut params); if result == -1 { return Err(Error::Fail("Parse error".into())); } let params = claim_from_c!(params); match params.obj { PgpDigParamsObj::Cert(_) => dig.cert = Some(params), PgpDigParamsObj::Subkey(_, _) => dig.cert = Some(params), PgpDigParamsObj::Signature(_) => dig.sig = Some(params), } Ok(()) }); ffi!( /// Create a container for parsed OpenPGP packet(s). /// /// This function is deprecated and is scheduled for removal in rpm /// 4.19. /// /// @return container fn _pgpNewDig() -> *mut PgpDig { Ok(move_to_c!(PgpDig { cert: None, sig: None, })) }); ffi!( /// Release (malloc'd) data from container. /// /// This function is deprecated and is scheduled for removal in rpm /// 4.19. /// /// @param dig container fn _pgpCleanDig(dig: *mut PgpDig) { let dig = check_mut!(dig); dig.cert = None; dig.sig = None; }); ffi!( /// Destroy a container for parsed OpenPGP packet(s). /// /// This function is deprecated and is scheduled for removal in rpm /// 4.19. /// /// @param dig container /// @return NULL always fn _pgpFreeDig(dig: Option<&mut PgpDig>) -> *mut PgpDig { free!(dig); Ok(std::ptr::null_mut()) }); ffi!( /// Retrieve parameters for parsed OpenPGP packet(s). /// /// This function is deprecated and is scheduled for removal in rpm /// 4.19. /// /// @param dig container /// @param pkttype type of params to retrieve (signature / pubkey) /// @return pointer to OpenPGP parameters, NULL on error/not found fn _pgpDigGetParams(dig: *const PgpDig, pkttype: c_uint) -> *const PgpDigParams { let dig = check_ptr!(dig); let ptr = match Tag::from(pkttype as u8) { Tag::PublicKey => { if let Some(ref cert) = dig.cert { cert.as_ref() } else { std::ptr::null() } } Tag::Signature => { if let Some(ref sig) = dig.sig { sig.as_ref() } else { std::ptr::null() } } _ => { std::ptr::null() } }; Ok(ptr) }); ffi!( /// Verify a PGP signature. /// /// This function is deprecated and is scheduled for removal in rpm /// 4.19. /// /// @param dig container /// @param hashctx digest context /// @return RPMRC_OK on success fn _pgpVerifySig(dig: *const PgpDig, ctx: *const digest::DigestContext) -> ErrorCode { Err( _pgpVerifySignature( _pgpDigGetParams(dig, u8::from(Tag::PublicKey) as u32), _pgpDigGetParams(dig, u8::from(Tag::Signature) as u32), ctx).into()) }); ffi!( /// Merge the PGP packets of two certificates /// /// The certificates must describe the same public key. The call should merge /// important pgp packets (self-signatures, new subkeys, ...) and remove duplicates. /// /// - `pkts1` - OpenPGP pointer to a buffer with the first certificate /// - `pkts1len` - length of the buffer with the first certificate /// - `pkts2` - OpenPGP pointer to a buffer with the second certificate /// - `pkts2len` - length of the buffer with the second certificate /// - `pktsm` - merged certificate (malloced) /// - `pktsmlen` - length of merged certificate /// - `flags` - merge flags (currently not used, must be zero) /// /// Returns `RPMRC_OK` on success. fn _pgpPubkeyMerge( pkts1: *const u8, pkts1len: size_t, pkts2: *const u8, pkts2len: size_t, pktsm: *mut *mut u8, pktsmlen: *mut size_t, flags: c_int) -> ErrorCode { let pkts1 = check_slice!(pkts1, pkts1len); let pkts2 = check_slice!(pkts2, pkts2len); let cert1 = Cert::from_bytes(pkts1)?; let cert2 = Cert::from_bytes(pkts2)?; if cert1.fingerprint() != cert2.fingerprint() { return Err(Error::Fail("Can't merge different certificates".into())); } let (merged, updated) = cert1.clone().insert_packets2(cert2.into_packets2())?; let merged_bytes_; let (result, result_len) = if ! updated { // The certificate is unchanged. Nevertheless, // Cert::from_bytes may have changed the bit representation, // e.g., by canonicalizing it. To avoid making rpm think that // the certificate has changed when it hasn't (it does a // memcmp), we return pkts1. (pkts1.as_ptr(), pkts1len) } else { merged_bytes_ = merged.to_vec()?; (merged_bytes_.as_ptr(), merged_bytes_.len()) }; unsafe { let buffer = libc::malloc(result_len); libc::memcpy(buffer, result as *const c_void, result_len); *pktsmlen = result_len as size_t; *pktsm = buffer as *mut u8; } Ok(()) }); #[cfg(test)] mod tests { use super::*; use openpgp::cert::CertBuilder; use openpgp::serialize::SerializeInto; use openpgp::types::KeyFlags; // Check that we can successfully merge two certificates. #[test] fn merge_certs() { let p = openpgp::policy::StandardPolicy::new(); let (cert, _rev) = CertBuilder::new() .add_userid("Alice") .generate() .unwrap(); let vc = cert.with_policy(&p, None).unwrap(); // We should only have a primary, which is certification capable. assert_eq!(vc.keys().for_signing().count(), 0); assert_eq!(vc.keys().for_transport_encryption().count(), 0); assert_eq!(vc.keys().for_storage_encryption().count(), 0); // Add a signing subkey. let cert2 = KeyBuilder::new( KeyFlags::empty().set_signing()) .subkey(vc.clone()).unwrap() .attach_cert().unwrap(); // Add an encryption subkey. let cert3 = KeyBuilder::new( KeyFlags::empty().set_transport_encryption()) .subkey(vc.clone()).unwrap() .attach_cert().unwrap(); let cert2_bytes = cert2.to_vec().unwrap(); let cert3_bytes = cert3.to_vec().unwrap(); let mut result: *mut u8 = std::ptr::null_mut(); let mut result_len: size_t = 0; eprintln!("About to run pgpPubkeyMerge"); let ec = _pgpPubkeyMerge( cert2_bytes.as_ptr(), cert2_bytes.len(), cert3_bytes.as_ptr(), cert3_bytes.len(), &mut result, &mut result_len, 0); assert_eq!(ec, 0); assert!(! result.is_null()); let result = unsafe { std::slice::from_raw_parts(result as *const u8, result_len) }; let result = Cert::from_bytes(result).expect("valid cert"); assert_eq!(cert.fingerprint(), result.fingerprint()); assert!(result != cert); assert!(result != cert2); assert!(result != cert3); let expected = cert2.clone().merge_public(cert3.clone()).unwrap(); assert_eq!(result, expected); let result_vc = result.with_policy(&p, None).unwrap(); assert_eq!(result_vc.keys().for_signing().count(), 1); assert_eq!(result_vc.keys().for_transport_encryption().count(), 1); assert_eq!(result_vc.keys().for_storage_encryption().count(), 0); } // Check that when we attempt to merge two different certificates, // we return an error. #[test] fn merge_certs_mismatch() { let (cert, _rev) = CertBuilder::new() .add_userid("Alice") .generate() .unwrap(); let (cert2, _rev) = CertBuilder::new() .add_userid("Bob") .generate() .unwrap(); let cert_bytes = cert.to_vec().unwrap(); let cert2_bytes = cert2.to_vec().unwrap(); let mut result: *mut u8 = std::ptr::null_mut(); let mut result_len: size_t = 0; eprintln!("About to run pgpPubkeyMerge"); let ec = _pgpPubkeyMerge( cert_bytes.as_ptr(), cert_bytes.len(), cert2_bytes.as_ptr(), cert2_bytes.len(), &mut result, &mut result_len, 0); assert_ne!(ec, 0); } } rpm-sequoia-1.7.0/src/log.rs000064400000000000000000000073371046102023000140230ustar 00000000000000use std::cell::RefCell; // Like eprintln! macro_rules! log { ($dst:expr $(,)?) => ( eprintln!("{}", $dst) ); ($dst:expr, $($arg:tt)*) => ( eprintln!("{}", std::format!($dst, $($arg)*)) ); } // The indent level. It is increased with each call to tracer and // decremented when the tracker goes out of scope. thread_local! { pub static INDENT_LEVEL: RefCell = RefCell::new(0); } // Like eprintln!, but the first argument is a boolean, which // indicates if the string should actually be printed. macro_rules! trace { ( $TRACE:expr, $fmt:expr, $($pargs:expr),* ) => { if $TRACE { let indent_level = crate::log::INDENT_LEVEL.with(|i| { *i.borrow() }); let ws = " "; log!("{}{}", &ws[0..std::cmp::min(ws.len(), std::cmp::max(1, indent_level) - 1)], format!($fmt, $($pargs),*)); } }; ( $TRACE:expr, $fmt:expr ) => { trace!($TRACE, $fmt, ); }; } macro_rules! tracer { ( $TRACE:expr, $func:expr ) => { // Currently, Rust doesn't support $( ... ) in a nested // macro's definition. See: // https://users.rust-lang.org/t/nested-macros-issue/8348/2 macro_rules! t { ( $fmt:expr ) => { trace!($TRACE, "{}: {}", $func, $fmt) }; ( $fmt:expr, $a:expr ) => { trace!($TRACE, "{}: {}", $func, format!($fmt, $a)) }; ( $fmt:expr, $a:expr, $b:expr ) => { trace!($TRACE, "{}: {}", $func, format!($fmt, $a, $b)) }; ( $fmt:expr, $a:expr, $b:expr, $c:expr ) => { trace!($TRACE, "{}: {}", $func, format!($fmt, $a, $b, $c)) }; ( $fmt:expr, $a:expr, $b:expr, $c:expr, $d:expr ) => { trace!($TRACE, "{}: {}", $func, format!($fmt, $a, $b, $c, $d)) }; ( $fmt:expr, $a:expr, $b:expr, $c:expr, $d:expr, $e:expr ) => { trace!($TRACE, "{}: {}", $func, format!($fmt, $a, $b, $c, $d, $e)) }; ( $fmt:expr, $a:expr, $b:expr, $c:expr, $d:expr, $e:expr, $f:expr ) => { trace!($TRACE, "{}: {}", $func, format!($fmt, $a, $b, $c, $d, $e, $f)) }; ( $fmt:expr, $a:expr, $b:expr, $c:expr, $d:expr, $e:expr, $f:expr, $g:expr ) => { trace!($TRACE, "{}: {}", $func, format!($fmt, $a, $b, $c, $d, $e, $f, $g)) }; ( $fmt:expr, $a:expr, $b:expr, $c:expr, $d:expr, $e:expr, $f:expr, $g:expr, $h:expr ) => { trace!($TRACE, "{}: {}", $func, format!($fmt, $a, $b, $c, $d, $e, $f, $g, $h)) }; ( $fmt:expr, $a:expr, $b:expr, $c:expr, $d:expr, $e:expr, $f:expr, $g:expr, $h:expr, $i:expr ) => { trace!($TRACE, "{}: {}", $func, format!($fmt, $a, $b, $c, $d, $e, $f, $g, $h, $i)) }; ( $fmt:expr, $a:expr, $b:expr, $c:expr, $d:expr, $e:expr, $f:expr, $g:expr, $h:expr, $i:expr, $j:expr ) => { trace!($TRACE, "{}: {}", $func, format!($fmt, $a, $b, $c, $d, $e, $f, $g, $h, $i, $j)) }; ( $fmt:expr, $a:expr, $b:expr, $c:expr, $d:expr, $e:expr, $f:expr, $g:expr, $h:expr, $i:expr, $j:expr, $k:expr ) => { trace!($TRACE, "{}: {}", $func, format!($fmt, $a, $b, $c, $d, $e, $f, $g, $h, $i, $j, $k)) }; } struct Indent {} impl Indent { fn init() -> Self { crate::log::INDENT_LEVEL.with(|i| { i.replace_with(|i| *i + 1); }); Indent {} } } impl Drop for Indent { fn drop(&mut self) { crate::log::INDENT_LEVEL.with(|i| { i.replace_with(|i| *i - 1); }); } } let _indent = Indent::init(); } } rpm-sequoia-1.7.0/src/rpm.rs000064400000000000000000000116601046102023000140320ustar 00000000000000use libc::c_int; use sequoia_openpgp as openpgp; use openpgp::armor; // We use Error rather than anyhow's error so that we force the // function to convert the error into a form that we can easily pass // back to the engine. pub type Result = std::result::Result; pub type ErrorCode = c_int; #[non_exhaustive] #[derive(thiserror::Error, Debug, Clone)] #[allow(unused)] pub enum Error { #[error("Success")] Ok, #[error("Not found: {0}")] NotFound(String), #[error("Failure: {0}")] Fail(String), #[error("Signature is OK, but key is not trusted: {0}")] NotTrusted(String), #[error("Public key is unavailable: {0}")] NoKey(String), } impl From for ErrorCode { fn from(err: Error) -> ErrorCode { match err { Error::Ok => 0, Error::NotFound(_) => 1, Error::Fail(_) => 2, Error::NotTrusted(_) => 3, Error::NoKey(_) => 4, } } } impl From for Error { fn from(err: ErrorCode) -> Error { match err { 0 => Error::Ok, 1 => Error::NotFound("".into()), 2 => Error::Fail("".into()), 3 => Error::NotTrusted("".into()), 4 => Error::NoKey("".into()), _ => Error::Fail("".into()), } } } impl From for Error { fn from(err: anyhow::Error) -> Error { Error::Fail(format!("{}", err)) } } #[non_exhaustive] #[derive(thiserror::Error, Debug, Clone)] #[allow(unused)] pub enum PgpArmorError { #[error("Success")] Ok, #[error("unknown error")] UnknownError, #[error("armor crc check")] CrcCheck, #[error("armor body decode")] BodyDecode, #[error("armor crc decode")] CrcDecode, #[error("armor no end pgp")] NoEndPgp, #[error("armor unknown preamble tag")] UnknownPreambleTag, #[error("armor unknown armor type")] UnknownArmorType, #[error("armor no begin pgp")] NoBeginPgp, } impl From for ErrorCode { fn from(err: PgpArmorError) -> ErrorCode { match err { PgpArmorError::Ok => 0, PgpArmorError::UnknownError => -1, PgpArmorError::CrcCheck => -7, PgpArmorError::BodyDecode => -6, PgpArmorError::CrcDecode => -5, PgpArmorError::NoEndPgp => -4, PgpArmorError::UnknownPreambleTag => -3, PgpArmorError::UnknownArmorType => -2, PgpArmorError::NoBeginPgp => -1, } } } impl From for PgpArmorError { fn from(_err: Error) -> PgpArmorError { PgpArmorError::UnknownError } } impl From for PgpArmorError { fn from(_err: anyhow::Error) -> PgpArmorError { PgpArmorError::UnknownError } } #[non_exhaustive] #[allow(unused)] #[derive(Debug, Clone)] pub enum PgpArmor { None, Message, Pubkey, Signature, SignedMessage, File, Privkey, Seckey, } impl From for c_int { fn from(a: PgpArmor) -> c_int { match a { PgpArmor::None => 0, PgpArmor::Message => 1, PgpArmor::Pubkey => 2, PgpArmor::Signature => 3, PgpArmor::SignedMessage => 4, PgpArmor::File => 5, PgpArmor::Privkey => 6, PgpArmor::Seckey => 7, } } } impl From for PgpArmor { fn from(a: c_int) -> PgpArmor { match a { 0 => PgpArmor::None, 1 => PgpArmor::Message, 2 => PgpArmor::Pubkey, 3 => PgpArmor::Signature, 4 => PgpArmor::SignedMessage, 5 => PgpArmor::File, 6 => PgpArmor::Privkey, 7 => PgpArmor::Seckey, _ => PgpArmor::None, } } } impl TryFrom for armor::Kind { type Error = Error; fn try_from(a: PgpArmor) -> Result { let err = || Err(Error::Fail(format!("Unsupported armor type: {:?}", a))); match a { PgpArmor::None => err(), PgpArmor::Message => Ok(armor::Kind::Message), PgpArmor::Pubkey => Ok(armor::Kind::PublicKey), PgpArmor::Signature => Ok(armor::Kind::Signature), PgpArmor::SignedMessage => err(), PgpArmor::File => Ok(armor::Kind::File), PgpArmor::Privkey => err(), PgpArmor::Seckey => Ok(armor::Kind::SecretKey), } } } impl From> for PgpArmor { fn from(k: Option) -> PgpArmor { use armor::Kind::*; match k { None => PgpArmor::None, Some(Message) => PgpArmor::Message, Some(PublicKey) => PgpArmor::Pubkey, Some(SecretKey) => PgpArmor::Seckey, // XXX: PgpArmor::Privkey Some(Signature) => PgpArmor::Signature, // XXX: PgpArmor::SignedMessage Some(File) => PgpArmor::File, } } } rpm-sequoia-1.7.0/src/symbols.txt000064400000000000000000000017741046102023000151240ustar 00000000000000# #include # Implemented by rpmpgp.c. # pgpValString _pgpPubkeyFingerprint _pgpPubkeyKeyID _pgpPrtParams _pgpPrtParams2 _pgpPrtParamsSubkeys _pgpPrtPkts # Implemented by rpmpgp.c. # pgpReadPkts _pgpParsePkts _pgpPubKeyCertLen _pgpPubKeyLint _pgpArmorWrap _pgpNewDig _pgpCleanDig _pgpFreeDig _pgpDigGetParams _pgpDigParamsCmp _pgpDigParamsAlgo _pgpDigParamsSignID _pgpDigParamsUserID _pgpDigParamsVersion _pgpDigParamsCreationTime _pgpDigParamsFree _pgpPubkeyMerge _pgpVerifySignature _pgpVerifySignature2 _pgpVerifySig _pgpSignatureType # Implemented by rpmpgp.c. # pgpIdentItem # #include _rpmInitCrypto _rpmFreeCrypto _rpmDigestDup _rpmDigestLength _rpmDigestInit _rpmDigestUpdate _rpmDigestFinal # These are implemented in terms of the above. # # rpmDigestBundleFree # rpmDigestBundleAdd # rpmDigestBundleAddID # rpmDigestBundleUpdate # rpmDigestBundleDupCtx # Threse symbols are exposed by Rust :/ # See: https://gitlab.com/sequoia-pgp/rpm-sequoia/-/issues/3 ?rust_eh_personality rpm-sequoia-1.7.0/tests/symbols.rs000064400000000000000000000100031046102023000152650ustar 00000000000000use std::env; use std::fs::File; use std::io::Read; use std::path::PathBuf; use assert_cmd::Command; use assert_cmd::assert::OutputAssertExt; #[test] fn symbols() -> anyhow::Result<()> { // We want the location of the build directory (e.g., // `/tmp/rpm-sequoia/debug`). // // OUT_DIR gives us // `/tmp/rpm-sequoia/debug/build/rpm-sequoia-HASH/out`. let out_dir = PathBuf::from(env!("OUT_DIR")); let mut build_dir = out_dir; let lib = loop { let mut lib = build_dir.clone(); lib.push("librpm_sequoia.so"); if lib.exists() { break lib; } if ! build_dir.pop() { panic!("Failed to find librpm_sequoia.so"); } }; let cmd = Command::new("objdump") .arg("-T") .arg(lib) .unwrap(); let assert = cmd.assert().success(); let output = String::from_utf8_lossy(&assert.get_output().stdout); let mut symbols = Vec::new(); for line in output.split("\n") { if line.contains("g DF .text") || line.contains("g DO .data") || line.contains("g DF .opd") { let symbol = line.split(' ').last().expect("a word"); symbols.push(symbol); } } symbols.sort(); eprintln!("Found {} symbols:", symbols.len()); for symbol in symbols.iter() { eprintln!(" {}", symbol); } let mut expected_symbols_txt_fn = PathBuf::from(env!("CARGO_MANIFEST_DIR")); expected_symbols_txt_fn.push("src/symbols.txt"); let mut expected_symbols_txt = Vec::new(); File::open(expected_symbols_txt_fn) .expect("src/symbols.txt exists") .read_to_end(&mut expected_symbols_txt) .unwrap(); let expected_symbols_txt = String::from_utf8_lossy(&expected_symbols_txt); let mut expected_symbols = Vec::new(); for symbol in expected_symbols_txt.split("\n") { if symbol.starts_with("#") { continue; } let symbol = symbol.trim(); if symbol.is_empty() { continue; } if symbol.chars().nth(0) == Some('?') { expected_symbols.push((&symbol[1..], true)); } else { expected_symbols.push((symbol, false)); } } expected_symbols.sort(); eprintln!("Expected {} symbols:", expected_symbols.len()); for (symbol, optional) in expected_symbols.iter() { eprint!(" {}", symbol); if *optional { eprintln!(" (optional)"); } else { eprintln!(""); } } let mut i = 0; let mut j = 0; let mut bad = false; loop { if i == symbols.len() && j == expected_symbols.len() { break; } if i < symbols.len() && j < expected_symbols.len() && symbols[i] == expected_symbols[j].0 { i += 1; j += 1; } else if (i < symbols.len() && j < expected_symbols.len() && symbols[i] < expected_symbols[j].0) || j == expected_symbols.len() { eprintln!("Found unexpected symbol {}", symbols[i]); if symbols[i] == "bz_internal_error" { eprintln!(" It looks like you forgot to disable compression.") } i += 1; bad = true; } else if (i < symbols.len() && j < expected_symbols.len() && symbols[i] > expected_symbols[j].0) || i == symbols.len() { if ! expected_symbols[j].1 { eprintln!("Missing expected symbol {}", expected_symbols[j].0); bad = true; } j += 1; } else { unreachable!(); } } if bad { eprintln!("\ *** If you see unexpected symbols like SHA1DCInit..., \ then you need version 0.2.6 or later of \ sha1collisiondetection. ***"); Err(anyhow::anyhow!("symbol mismatch")) } else { Ok(()) } }