├── .dockerignore ├── .github └── workflows │ ├── ci.yml │ └── publish.yml ├── .gitignore ├── CHANGELOG.md ├── COPYING ├── Cargo.toml ├── README.md ├── docker ├── Dockerfile.static └── Dockerfile.windows ├── examples ├── decrypt.rs ├── encrypt.rs ├── export.rs ├── import.rs ├── keylist.rs ├── keysign.rs ├── sign.rs ├── swdb.rs └── verify.rs ├── gpgme-sys ├── Cargo.toml ├── build.rs └── src │ └── lib.rs ├── src ├── bin │ └── pinentry.rs ├── callbacks.rs ├── context.rs ├── data.rs ├── edit.rs ├── engine.rs ├── flags.rs ├── keys.rs ├── lib.rs ├── notation.rs ├── results.rs ├── tofu.rs └── utils.rs └── tests ├── common ├── data │ ├── gpg-agent.conf │ ├── gpg.conf │ ├── ownertrust.txt │ ├── pubdemo.asc │ └── secdemo.asc └── mod.rs ├── create_key.rs ├── data.rs ├── edit.rs ├── encrypt_simple.rs ├── encrypt_symmetric.rs ├── export.rs ├── keylist.rs ├── keysign.rs └── verify.rs /.dockerignore: -------------------------------------------------------------------------------- 1 | **/.* 2 | **/Cargo.lock 3 | **/target/ 4 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | on: [push, pull_request] 3 | env: 4 | CARGO_TERM_COLOR: always 5 | jobs: 6 | basic-tests: 7 | name: Test Suite (${{ matrix.os }}, rust-${{ matrix.rust }}) 8 | runs-on: ${{ matrix.os }} 9 | strategy: 10 | fail-fast: false 11 | matrix: 12 | include: 13 | - os: ubuntu-latest 14 | rust: stable 15 | - os: ubuntu-latest 16 | rust: nightly 17 | - os: macos-latest 18 | rust: stable 19 | env: 20 | GPGME_DEBUG: 9 21 | steps: 22 | - name: Checkout repository 23 | uses: actions/checkout@v4 24 | 25 | - name: Install dependencies (Linux) 26 | if: startsWith(matrix.os, 'ubuntu') 27 | run: sudo apt-get install -y --no-install-recommends libgpgme11-dev 28 | 29 | - name: Install dependencies (macOS) 30 | if: startsWith(matrix.os, 'macos') 31 | run: brew install gpgme 32 | 33 | - name: Install rust 34 | run: rustup toolchain install --no-self-update --profile minimal ${{ matrix.rust }} 35 | 36 | - run: rustup default ${{ matrix.rust }} 37 | 38 | - name: Build 39 | run: cargo build --verbose 40 | 41 | - name: Run tests 42 | run: cargo test --verbose --no-fail-fast 43 | 44 | docker-static-test: 45 | name: Test Suite (linux, docker, musl) 46 | runs-on: ubuntu-latest 47 | steps: 48 | - name: Checkout repository 49 | uses: actions/checkout@v4 50 | 51 | - name: Build docker container 52 | run: docker build -f docker/Dockerfile.static -t test-build . 53 | 54 | - name: Run tests in container 55 | run: docker run test-build 56 | 57 | docker-windows-test: 58 | name: Test Suite (windows, docker) 59 | runs-on: windows-2022 60 | continue-on-error: true 61 | steps: 62 | - name: Checkout repository 63 | uses: actions/checkout@v4 64 | 65 | - name: Build docker container 66 | run: docker build --build-arg WIN_VARIANT=ltsc2022 -f docker/Dockerfile.windows -t test-build . 67 | 68 | - name: Run tests in container 69 | run: docker run test-build 70 | 71 | semver: 72 | name: Check semver violations 73 | runs-on: ubuntu-latest 74 | steps: 75 | - name: Checkout repository 76 | uses: actions/checkout@v4 77 | 78 | - name: Install dependencies 79 | run: sudo apt-get install -y --no-install-recommends libgpgme11-dev 80 | 81 | - name: Check semver 82 | uses: obi1kenobi/cargo-semver-checks-action@v2 83 | -------------------------------------------------------------------------------- /.github/workflows/publish.yml: -------------------------------------------------------------------------------- 1 | name: publish 2 | 3 | on: 4 | push: 5 | tags: 6 | - "v*" 7 | 8 | jobs: 9 | publish: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - name: Checkout repository 13 | uses: actions/checkout@v4 14 | 15 | - name: Install dependencies (Linux) 16 | run: sudo apt-get install -y --no-install-recommends libgpgme11-dev 17 | 18 | - name: Install rust toolchain 19 | run: rustup update --no-self-update stable 20 | 21 | - id: cargo_release_cache 22 | uses: actions/cache@v4 23 | with: 24 | path: ~/.cargo/bin/cargo-release 25 | key: ${{ runner.os }}-cargo-release 26 | 27 | - run: cargo install cargo-release 28 | if: steps.cargo_release_cache.outputs.cache-hit != 'true' 29 | 30 | - name: Publish crate 31 | env: 32 | CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }} 33 | run: cargo release publish --workspace --allow-branch HEAD --no-confirm --execute 34 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .* 2 | !.gitignore 3 | !.gitmodules 4 | !.travis.yml 5 | 6 | # Compiled files 7 | *.o 8 | *.so 9 | *.rlib 10 | *.dll 11 | *.exe 12 | 13 | # Generated by Cargo 14 | Cargo.lock 15 | target/ 16 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file. 4 | 5 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), 6 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 7 | 8 | ## [Unreleased] 9 | ### Removed 10 | - Removed the `EngineInfoGuard::new` function as it was mistakenly made `pub` 11 | (instead of `pub(crate)`) and was unsound. The intended method of creation 12 | is via `Gpgme::engine_info`. (**BREAKING CHANGE**) 13 | 14 | ### Added 15 | - Added `TryFrom` implementations for `Data` from common IO types 16 | - Added new bindings for GPGMe 1.23 17 | 18 | ### Changed 19 | - Updated `bitflags` dependency to major version two. Compatibility shims were 20 | added where neccessary to maintain backwards compatibility. 21 | 22 | ### Fixed 23 | - Fixed soundness bug in `ContextWithCallbacks` 24 | - Fixed soundness bugs related to raw pointer to reference to slice conversions 25 | 26 | ### Deprecated 27 | - Deprecated the `from_bits_unchecked` functions on all flags types, as they are no longer 28 | generated by `bitflags@2` and are now shims. 29 | - Deprecated `Data::from_fd` in favor of `Data::from_borrowed_fd` which uses the new safe raw-IO 30 | type (`BorrowedFd`) 31 | - Deprecated `Data::from_{read,write,stream}` in favor of `Data::builder` and `DataBuilder` which 32 | reduces the number of functions needed to support all combinations of IO traits 33 | - Deprecated the safety of `gpgme::set_flag`. This function is not thread safe and has 34 | similar safety issues to [`std::env::set_var`](https://doc.rust-lang.org/stable/std/env/fn.set_var.html) 35 | 36 | ### Other 37 | - Added install instructions for required dependencies on Windows via `winget` 38 | - Fixed install instructions for apt base distributions (#43) 39 | - Removed redundant global context engine lock 40 | -------------------------------------------------------------------------------- /COPYING: -------------------------------------------------------------------------------- 1 | GNU LESSER GENERAL PUBLIC LICENSE 2 | Version 2.1, February 1999 3 | 4 | Copyright (C) 1991, 1999 Free Software Foundation, Inc. 5 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 6 | Everyone is permitted to copy and distribute verbatim copies 7 | of this license document, but changing it is not allowed. 8 | 9 | [This is the first released version of the Lesser GPL. It also counts 10 | as the successor of the GNU Library Public License, version 2, hence 11 | the version number 2.1.] 12 | 13 | Preamble 14 | 15 | The licenses for most software are designed to take away your 16 | freedom to share and change it. By contrast, the GNU General Public 17 | Licenses are intended to guarantee your freedom to share and change 18 | free software--to make sure the software is free for all its users. 19 | 20 | This license, the Lesser General Public License, applies to some 21 | specially designated software packages--typically libraries--of the 22 | Free Software Foundation and other authors who decide to use it. You 23 | can use it too, but we suggest you first think carefully about whether 24 | this license or the ordinary General Public License is the better 25 | strategy to use in any particular case, based on the explanations below. 26 | 27 | When we speak of free software, we are referring to freedom of use, 28 | not price. Our General Public Licenses are designed to make sure that 29 | you have the freedom to distribute copies of free software (and charge 30 | for this service if you wish); that you receive source code or can get 31 | it if you want it; that you can change the software and use pieces of 32 | it in new free programs; and that you are informed that you can do 33 | these things. 34 | 35 | To protect your rights, we need to make restrictions that forbid 36 | distributors to deny you these rights or to ask you to surrender these 37 | rights. These restrictions translate to certain responsibilities for 38 | you if you distribute copies of the library or if you modify it. 39 | 40 | For example, if you distribute copies of the library, whether gratis 41 | or for a fee, you must give the recipients all the rights that we gave 42 | you. You must make sure that they, too, receive or can get the source 43 | code. If you link other code with the library, you must provide 44 | complete object files to the recipients, so that they can relink them 45 | with the library after making changes to the library and recompiling 46 | it. And you must show them these terms so they know their rights. 47 | 48 | We protect your rights with a two-step method: (1) we copyright the 49 | library, and (2) we offer you this license, which gives you legal 50 | permission to copy, distribute and/or modify the library. 51 | 52 | To protect each distributor, we want to make it very clear that 53 | there is no warranty for the free library. Also, if the library is 54 | modified by someone else and passed on, the recipients should know 55 | that what they have is not the original version, so that the original 56 | author's reputation will not be affected by problems that might be 57 | introduced by others. 58 | 59 | Finally, software patents pose a constant threat to the existence of 60 | any free program. We wish to make sure that a company cannot 61 | effectively restrict the users of a free program by obtaining a 62 | restrictive license from a patent holder. Therefore, we insist that 63 | any patent license obtained for a version of the library must be 64 | consistent with the full freedom of use specified in this license. 65 | 66 | Most GNU software, including some libraries, is covered by the 67 | ordinary GNU General Public License. This license, the GNU Lesser 68 | General Public License, applies to certain designated libraries, and 69 | is quite different from the ordinary General Public License. We use 70 | this license for certain libraries in order to permit linking those 71 | libraries into non-free programs. 72 | 73 | When a program is linked with a library, whether statically or using 74 | a shared library, the combination of the two is legally speaking a 75 | combined work, a derivative of the original library. The ordinary 76 | General Public License therefore permits such linking only if the 77 | entire combination fits its criteria of freedom. The Lesser General 78 | Public License permits more lax criteria for linking other code with 79 | the library. 80 | 81 | We call this license the "Lesser" General Public License because it 82 | does Less to protect the user's freedom than the ordinary General 83 | Public License. It also provides other free software developers Less 84 | of an advantage over competing non-free programs. These disadvantages 85 | are the reason we use the ordinary General Public License for many 86 | libraries. However, the Lesser license provides advantages in certain 87 | special circumstances. 88 | 89 | For example, on rare occasions, there may be a special need to 90 | encourage the widest possible use of a certain library, so that it becomes 91 | a de-facto standard. To achieve this, non-free programs must be 92 | allowed to use the library. A more frequent case is that a free 93 | library does the same job as widely used non-free libraries. In this 94 | case, there is little to gain by limiting the free library to free 95 | software only, so we use the Lesser General Public License. 96 | 97 | In other cases, permission to use a particular library in non-free 98 | programs enables a greater number of people to use a large body of 99 | free software. For example, permission to use the GNU C Library in 100 | non-free programs enables many more people to use the whole GNU 101 | operating system, as well as its variant, the GNU/Linux operating 102 | system. 103 | 104 | Although the Lesser General Public License is Less protective of the 105 | users' freedom, it does ensure that the user of a program that is 106 | linked with the Library has the freedom and the wherewithal to run 107 | that program using a modified version of the Library. 108 | 109 | The precise terms and conditions for copying, distribution and 110 | modification follow. Pay close attention to the difference between a 111 | "work based on the library" and a "work that uses the library". The 112 | former contains code derived from the library, whereas the latter must 113 | be combined with the library in order to run. 114 | 115 | GNU LESSER GENERAL PUBLIC LICENSE 116 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 117 | 118 | 0. This License Agreement applies to any software library or other 119 | program which contains a notice placed by the copyright holder or 120 | other authorized party saying it may be distributed under the terms of 121 | this Lesser General Public License (also called "this License"). 122 | Each licensee is addressed as "you". 123 | 124 | A "library" means a collection of software functions and/or data 125 | prepared so as to be conveniently linked with application programs 126 | (which use some of those functions and data) to form executables. 127 | 128 | The "Library", below, refers to any such software library or work 129 | which has been distributed under these terms. A "work based on the 130 | Library" means either the Library or any derivative work under 131 | copyright law: that is to say, a work containing the Library or a 132 | portion of it, either verbatim or with modifications and/or translated 133 | straightforwardly into another language. (Hereinafter, translation is 134 | included without limitation in the term "modification".) 135 | 136 | "Source code" for a work means the preferred form of the work for 137 | making modifications to it. For a library, complete source code means 138 | all the source code for all modules it contains, plus any associated 139 | interface definition files, plus the scripts used to control compilation 140 | and installation of the library. 141 | 142 | Activities other than copying, distribution and modification are not 143 | covered by this License; they are outside its scope. The act of 144 | running a program using the Library is not restricted, and output from 145 | such a program is covered only if its contents constitute a work based 146 | on the Library (independent of the use of the Library in a tool for 147 | writing it). Whether that is true depends on what the Library does 148 | and what the program that uses the Library does. 149 | 150 | 1. You may copy and distribute verbatim copies of the Library's 151 | complete source code as you receive it, in any medium, provided that 152 | you conspicuously and appropriately publish on each copy an 153 | appropriate copyright notice and disclaimer of warranty; keep intact 154 | all the notices that refer to this License and to the absence of any 155 | warranty; and distribute a copy of this License along with the 156 | Library. 157 | 158 | You may charge a fee for the physical act of transferring a copy, 159 | and you may at your option offer warranty protection in exchange for a 160 | fee. 161 | 162 | 2. You may modify your copy or copies of the Library or any portion 163 | of it, thus forming a work based on the Library, and copy and 164 | distribute such modifications or work under the terms of Section 1 165 | above, provided that you also meet all of these conditions: 166 | 167 | a) The modified work must itself be a software library. 168 | 169 | b) You must cause the files modified to carry prominent notices 170 | stating that you changed the files and the date of any change. 171 | 172 | c) You must cause the whole of the work to be licensed at no 173 | charge to all third parties under the terms of this License. 174 | 175 | d) If a facility in the modified Library refers to a function or a 176 | table of data to be supplied by an application program that uses 177 | the facility, other than as an argument passed when the facility 178 | is invoked, then you must make a good faith effort to ensure that, 179 | in the event an application does not supply such function or 180 | table, the facility still operates, and performs whatever part of 181 | its purpose remains meaningful. 182 | 183 | (For example, a function in a library to compute square roots has 184 | a purpose that is entirely well-defined independent of the 185 | application. Therefore, Subsection 2d requires that any 186 | application-supplied function or table used by this function must 187 | be optional: if the application does not supply it, the square 188 | root function must still compute square roots.) 189 | 190 | These requirements apply to the modified work as a whole. If 191 | identifiable sections of that work are not derived from the Library, 192 | and can be reasonably considered independent and separate works in 193 | themselves, then this License, and its terms, do not apply to those 194 | sections when you distribute them as separate works. But when you 195 | distribute the same sections as part of a whole which is a work based 196 | on the Library, the distribution of the whole must be on the terms of 197 | this License, whose permissions for other licensees extend to the 198 | entire whole, and thus to each and every part regardless of who wrote 199 | it. 200 | 201 | Thus, it is not the intent of this section to claim rights or contest 202 | your rights to work written entirely by you; rather, the intent is to 203 | exercise the right to control the distribution of derivative or 204 | collective works based on the Library. 205 | 206 | In addition, mere aggregation of another work not based on the Library 207 | with the Library (or with a work based on the Library) on a volume of 208 | a storage or distribution medium does not bring the other work under 209 | the scope of this License. 210 | 211 | 3. You may opt to apply the terms of the ordinary GNU General Public 212 | License instead of this License to a given copy of the Library. To do 213 | this, you must alter all the notices that refer to this License, so 214 | that they refer to the ordinary GNU General Public License, version 2, 215 | instead of to this License. (If a newer version than version 2 of the 216 | ordinary GNU General Public License has appeared, then you can specify 217 | that version instead if you wish.) Do not make any other change in 218 | these notices. 219 | 220 | Once this change is made in a given copy, it is irreversible for 221 | that copy, so the ordinary GNU General Public License applies to all 222 | subsequent copies and derivative works made from that copy. 223 | 224 | This option is useful when you wish to copy part of the code of 225 | the Library into a program that is not a library. 226 | 227 | 4. You may copy and distribute the Library (or a portion or 228 | derivative of it, under Section 2) in object code or executable form 229 | under the terms of Sections 1 and 2 above provided that you accompany 230 | it with the complete corresponding machine-readable source code, which 231 | must be distributed under the terms of Sections 1 and 2 above on a 232 | medium customarily used for software interchange. 233 | 234 | If distribution of object code is made by offering access to copy 235 | from a designated place, then offering equivalent access to copy the 236 | source code from the same place satisfies the requirement to 237 | distribute the source code, even though third parties are not 238 | compelled to copy the source along with the object code. 239 | 240 | 5. A program that contains no derivative of any portion of the 241 | Library, but is designed to work with the Library by being compiled or 242 | linked with it, is called a "work that uses the Library". Such a 243 | work, in isolation, is not a derivative work of the Library, and 244 | therefore falls outside the scope of this License. 245 | 246 | However, linking a "work that uses the Library" with the Library 247 | creates an executable that is a derivative of the Library (because it 248 | contains portions of the Library), rather than a "work that uses the 249 | library". The executable is therefore covered by this License. 250 | Section 6 states terms for distribution of such executables. 251 | 252 | When a "work that uses the Library" uses material from a header file 253 | that is part of the Library, the object code for the work may be a 254 | derivative work of the Library even though the source code is not. 255 | Whether this is true is especially significant if the work can be 256 | linked without the Library, or if the work is itself a library. The 257 | threshold for this to be true is not precisely defined by law. 258 | 259 | If such an object file uses only numerical parameters, data 260 | structure layouts and accessors, and small macros and small inline 261 | functions (ten lines or less in length), then the use of the object 262 | file is unrestricted, regardless of whether it is legally a derivative 263 | work. (Executables containing this object code plus portions of the 264 | Library will still fall under Section 6.) 265 | 266 | Otherwise, if the work is a derivative of the Library, you may 267 | distribute the object code for the work under the terms of Section 6. 268 | Any executables containing that work also fall under Section 6, 269 | whether or not they are linked directly with the Library itself. 270 | 271 | 6. As an exception to the Sections above, you may also combine or 272 | link a "work that uses the Library" with the Library to produce a 273 | work containing portions of the Library, and distribute that work 274 | under terms of your choice, provided that the terms permit 275 | modification of the work for the customer's own use and reverse 276 | engineering for debugging such modifications. 277 | 278 | You must give prominent notice with each copy of the work that the 279 | Library is used in it and that the Library and its use are covered by 280 | this License. You must supply a copy of this License. If the work 281 | during execution displays copyright notices, you must include the 282 | copyright notice for the Library among them, as well as a reference 283 | directing the user to the copy of this License. Also, you must do one 284 | of these things: 285 | 286 | a) Accompany the work with the complete corresponding 287 | machine-readable source code for the Library including whatever 288 | changes were used in the work (which must be distributed under 289 | Sections 1 and 2 above); and, if the work is an executable linked 290 | with the Library, with the complete machine-readable "work that 291 | uses the Library", as object code and/or source code, so that the 292 | user can modify the Library and then relink to produce a modified 293 | executable containing the modified Library. (It is understood 294 | that the user who changes the contents of definitions files in the 295 | Library will not necessarily be able to recompile the application 296 | to use the modified definitions.) 297 | 298 | b) Use a suitable shared library mechanism for linking with the 299 | Library. A suitable mechanism is one that (1) uses at run time a 300 | copy of the library already present on the user's computer system, 301 | rather than copying library functions into the executable, and (2) 302 | will operate properly with a modified version of the library, if 303 | the user installs one, as long as the modified version is 304 | interface-compatible with the version that the work was made with. 305 | 306 | c) Accompany the work with a written offer, valid for at 307 | least three years, to give the same user the materials 308 | specified in Subsection 6a, above, for a charge no more 309 | than the cost of performing this distribution. 310 | 311 | d) If distribution of the work is made by offering access to copy 312 | from a designated place, offer equivalent access to copy the above 313 | specified materials from the same place. 314 | 315 | e) Verify that the user has already received a copy of these 316 | materials or that you have already sent this user a copy. 317 | 318 | For an executable, the required form of the "work that uses the 319 | Library" must include any data and utility programs needed for 320 | reproducing the executable from it. However, as a special exception, 321 | the materials to be distributed need not include anything that is 322 | normally distributed (in either source or binary form) with the major 323 | components (compiler, kernel, and so on) of the operating system on 324 | which the executable runs, unless that component itself accompanies 325 | the executable. 326 | 327 | It may happen that this requirement contradicts the license 328 | restrictions of other proprietary libraries that do not normally 329 | accompany the operating system. Such a contradiction means you cannot 330 | use both them and the Library together in an executable that you 331 | distribute. 332 | 333 | 7. You may place library facilities that are a work based on the 334 | Library side-by-side in a single library together with other library 335 | facilities not covered by this License, and distribute such a combined 336 | library, provided that the separate distribution of the work based on 337 | the Library and of the other library facilities is otherwise 338 | permitted, and provided that you do these two things: 339 | 340 | a) Accompany the combined library with a copy of the same work 341 | based on the Library, uncombined with any other library 342 | facilities. This must be distributed under the terms of the 343 | Sections above. 344 | 345 | b) Give prominent notice with the combined library of the fact 346 | that part of it is a work based on the Library, and explaining 347 | where to find the accompanying uncombined form of the same work. 348 | 349 | 8. You may not copy, modify, sublicense, link with, or distribute 350 | the Library except as expressly provided under this License. Any 351 | attempt otherwise to copy, modify, sublicense, link with, or 352 | distribute the Library is void, and will automatically terminate your 353 | rights under this License. However, parties who have received copies, 354 | or rights, from you under this License will not have their licenses 355 | terminated so long as such parties remain in full compliance. 356 | 357 | 9. You are not required to accept this License, since you have not 358 | signed it. However, nothing else grants you permission to modify or 359 | distribute the Library or its derivative works. These actions are 360 | prohibited by law if you do not accept this License. Therefore, by 361 | modifying or distributing the Library (or any work based on the 362 | Library), you indicate your acceptance of this License to do so, and 363 | all its terms and conditions for copying, distributing or modifying 364 | the Library or works based on it. 365 | 366 | 10. Each time you redistribute the Library (or any work based on the 367 | Library), the recipient automatically receives a license from the 368 | original licensor to copy, distribute, link with or modify the Library 369 | subject to these terms and conditions. You may not impose any further 370 | restrictions on the recipients' exercise of the rights granted herein. 371 | You are not responsible for enforcing compliance by third parties with 372 | this License. 373 | 374 | 11. If, as a consequence of a court judgment or allegation of patent 375 | infringement or for any other reason (not limited to patent issues), 376 | conditions are imposed on you (whether by court order, agreement or 377 | otherwise) that contradict the conditions of this License, they do not 378 | excuse you from the conditions of this License. If you cannot 379 | distribute so as to satisfy simultaneously your obligations under this 380 | License and any other pertinent obligations, then as a consequence you 381 | may not distribute the Library at all. For example, if a patent 382 | license would not permit royalty-free redistribution of the Library by 383 | all those who receive copies directly or indirectly through you, then 384 | the only way you could satisfy both it and this License would be to 385 | refrain entirely from distribution of the Library. 386 | 387 | If any portion of this section is held invalid or unenforceable under any 388 | particular circumstance, the balance of the section is intended to apply, 389 | and the section as a whole is intended to apply in other circumstances. 390 | 391 | It is not the purpose of this section to induce you to infringe any 392 | patents or other property right claims or to contest validity of any 393 | such claims; this section has the sole purpose of protecting the 394 | integrity of the free software distribution system which is 395 | implemented by public license practices. Many people have made 396 | generous contributions to the wide range of software distributed 397 | through that system in reliance on consistent application of that 398 | system; it is up to the author/donor to decide if he or she is willing 399 | to distribute software through any other system and a licensee cannot 400 | impose that choice. 401 | 402 | This section is intended to make thoroughly clear what is believed to 403 | be a consequence of the rest of this License. 404 | 405 | 12. If the distribution and/or use of the Library is restricted in 406 | certain countries either by patents or by copyrighted interfaces, the 407 | original copyright holder who places the Library under this License may add 408 | an explicit geographical distribution limitation excluding those countries, 409 | so that distribution is permitted only in or among countries not thus 410 | excluded. In such case, this License incorporates the limitation as if 411 | written in the body of this License. 412 | 413 | 13. The Free Software Foundation may publish revised and/or new 414 | versions of the Lesser General Public License from time to time. 415 | Such new versions will be similar in spirit to the present version, 416 | but may differ in detail to address new problems or concerns. 417 | 418 | Each version is given a distinguishing version number. If the Library 419 | specifies a version number of this License which applies to it and 420 | "any later version", you have the option of following the terms and 421 | conditions either of that version or of any later version published by 422 | the Free Software Foundation. If the Library does not specify a 423 | license version number, you may choose any version ever published by 424 | the Free Software Foundation. 425 | 426 | 14. If you wish to incorporate parts of the Library into other free 427 | programs whose distribution conditions are incompatible with these, 428 | write to the author to ask for permission. For software which is 429 | copyrighted by the Free Software Foundation, write to the Free 430 | Software Foundation; we sometimes make exceptions for this. Our 431 | decision will be guided by the two goals of preserving the free status 432 | of all derivatives of our free software and of promoting the sharing 433 | and reuse of software generally. 434 | 435 | NO WARRANTY 436 | 437 | 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO 438 | WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. 439 | EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR 440 | OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY 441 | KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE 442 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 443 | PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE 444 | LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME 445 | THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 446 | 447 | 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN 448 | WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY 449 | AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU 450 | FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR 451 | CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE 452 | LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING 453 | RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A 454 | FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF 455 | SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH 456 | DAMAGES. 457 | 458 | END OF TERMS AND CONDITIONS 459 | 460 | How to Apply These Terms to Your New Libraries 461 | 462 | If you develop a new library, and you want it to be of the greatest 463 | possible use to the public, we recommend making it free software that 464 | everyone can redistribute and change. You can do so by permitting 465 | redistribution under these terms (or, alternatively, under the terms of the 466 | ordinary General Public License). 467 | 468 | To apply these terms, attach the following notices to the library. It is 469 | safest to attach them to the start of each source file to most effectively 470 | convey the exclusion of warranty; and each file should have at least the 471 | "copyright" line and a pointer to where the full notice is found. 472 | 473 | 474 | Copyright (C) 475 | 476 | This library is free software; you can redistribute it and/or 477 | modify it under the terms of the GNU Lesser General Public 478 | License as published by the Free Software Foundation; either 479 | version 2.1 of the License, or (at your option) any later version. 480 | 481 | This library is distributed in the hope that it will be useful, 482 | but WITHOUT ANY WARRANTY; without even the implied warranty of 483 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 484 | Lesser General Public License for more details. 485 | 486 | You should have received a copy of the GNU Lesser General Public 487 | License along with this library; if not, write to the Free Software 488 | Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 489 | 490 | Also add information on how to contact you by electronic and paper mail. 491 | 492 | You should also get your employer (if you work as a programmer) or your 493 | school, if any, to sign a "copyright disclaimer" for the library, if 494 | necessary. Here is a sample; alter the names: 495 | 496 | Yoyodyne, Inc., hereby disclaims all copyright interest in the 497 | library `Frob' (a library for tweaking knobs) written by James Random Hacker. 498 | 499 | , 1 April 1990 500 | Ty Coon, President of Vice 501 | 502 | That's all there is to it! 503 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "gpgme" 3 | version.workspace = true 4 | edition.workspace = true 5 | license.workspace = true 6 | repository.workspace = true 7 | categories = ["api-bindings"] 8 | keywords = ["gpg", "gpgme", "crypto", "cryptography"] 9 | description = "GPGme bindings for Rust" 10 | 11 | [package.metadata.docs.rs] 12 | features = "latest" 13 | 14 | [package.metadata.cargo-semver-checks.lints] 15 | function_must_use_added = "allow" 16 | inherent_method_must_use_added = "allow" 17 | struct_must_use_added = "allow" 18 | enum_must_use_added = "allow" 19 | trait_must_use_added = "allow" 20 | union_must_use_added = "allow" 21 | 22 | [features] 23 | windows_raw_dylib = ["ffi/windows_raw_dylib"] 24 | latest = ["v1_23"] 25 | "v1_23" = ["v1_22"] 26 | "v1_22" = ["v1_21"] 27 | "v1_21" = ["v1_20"] 28 | "v1_20" = ["v1_19"] 29 | "v1_19" = ["v1_18"] 30 | "v1_18" = ["v1_17"] 31 | "v1_17" = ["v1_16"] 32 | "v1_16" = ["v1_15"] 33 | "v1_15" = ["v1_14"] 34 | "v1_14" = ["v1_13"] 35 | "v1_13" = [] 36 | 37 | [dev-dependencies] 38 | clap = { version = "4.5.20", features = ["derive"] } 39 | sealed_test = "1.1.0" 40 | 41 | [dependencies] 42 | bitflags = "2" 43 | cfg-if = "1" 44 | conv = "0.3" 45 | cstr-argument = "0.1" 46 | gpg-error = "0.6.2" 47 | libc.workspace = true 48 | smallvec = "1" 49 | static_assertions = "1.1" 50 | 51 | [dependencies.ffi] 52 | package = "gpgme-sys" 53 | path = "gpgme-sys" 54 | version = "0.11.0" 55 | 56 | [[bin]] 57 | name = "pinentry" 58 | test = false 59 | 60 | [lints.rust] 61 | missing_debug_implementations = "warn" 62 | 63 | [lints.clippy] 64 | useless_conversion = "allow" 65 | 66 | [workspace.package] 67 | version = "0.11.0" 68 | edition = "2021" 69 | license = "LGPL-2.1" 70 | repository = "https://github.com/gpg-rs/gpgme" 71 | 72 | [workspace.dependencies] 73 | libc = "0.2" 74 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # gpgme-rs 2 | 3 | [![Build Status][build]][ci] 4 | [![crates.io version][version]][crate] 5 | [![LGPL-2.1 licensed][license]](./COPYING) 6 | [![downloads][downloads]][crate] 7 | 8 | [GPGME][upstream] bindings for Rust. 9 | 10 | [Documentation][docs] 11 | 12 | ## Using 13 | 14 | To use the crate, add it to your dependencies: 15 | ```sh 16 | $ cargo add gpgme 17 | ``` 18 | 19 | ### Requirements 20 | These crates require the gpgme library (version 1.13 or later) and its development files to be 21 | installed. The build script uses the [system-deps] crate to attempt to locate 22 | them (or the registry on Windows). 23 | 24 | On Debian/Ubuntu based systems: 25 | ```sh 26 | $ sudo apt-get install libgpgme-dev 27 | ``` 28 | 29 | On Fedora/RHEL based systems: 30 | ```sh 31 | $ sudo dnf install gpgme-devel 32 | ``` 33 | 34 | On MacOS systems: 35 | ```sh 36 | $ brew install gnupg 37 | ``` 38 | 39 | On Windows 10 (1709 or later) systems: 40 | ```pwsh 41 | $ winget install --id GnuPG.Gpg4win 42 | ``` 43 | 44 | On Windows systems, download and install the official [Gpg4win] installer. Only 45 | the `i686-pc-windows-gnu` target is supported. 46 | 47 | **NOTE**: These crates also depend on the gpg-error crate which has its own 48 | [requirements](https://github.com/gpg-rs/libgpg-error). 49 | 50 | ## Examples 51 | Some simple example programs based on those in the GPGME sources can be found 52 | in [examples](./examples). 53 | 54 | They can be run with cargo: 55 | ```shell 56 | $ cargo run --example keylist -- 57 | keyid : 89ABCDEF01234567 58 | fpr : 0123456789ABCDEF0123456789ABCDEF01234567 59 | caps : esc 60 | flags : 61 | userid 0: Example 62 | valid 0: Unknown 63 | ``` 64 | ## License 65 | These crates are licensed under the [LGPL-2.1 license](./COPYING). 66 | 67 | [crate]: https://crates.io/crates/gpgme 68 | [ci]: https://github.com/gpg-rs/gpgme/actions/workflows/ci.yml 69 | [build]: https://img.shields.io/github/actions/workflow/status/gpg-rs/gpgme/ci.yml?style=flat-square 70 | [version]: https://img.shields.io/crates/v/gpgme?style=flat-square 71 | [license]: https://img.shields.io/crates/l/gpgme?style=flat-square 72 | [downloads]: https://img.shields.io/crates/d/gpgme?style=flat-square 73 | 74 | [upstream]: https://www.gnupg.org/\(it\)/related_software/gpgme/index.html 75 | [docs]: https://docs.rs/gpgme 76 | [system-deps]: https://crates.io/crates/system-deps 77 | [Gpg4win]: https://www.gpg4win.org/ 78 | -------------------------------------------------------------------------------- /docker/Dockerfile.static: -------------------------------------------------------------------------------- 1 | FROM clux/muslrust:stable as builder 2 | 3 | RUN apt-get update && apt-get install -y --no-install-recommends bzip2 gnupg && rm -rf /var/lib/apt/lists/* 4 | 5 | ENV TARGET "x86_64-unknown-linux-musl" 6 | ENV SYSTEM_DEPS_LINK static 7 | 8 | # Optional localization support: 9 | # To enable uncomment the following commands, replace "--disable-nls" with 10 | # "--with-libintl-prefix=$PREFIX". 11 | # ARG GETTEXT_VER=0.21 12 | # WORKDIR /usr/src 13 | # ADD https://ftp.gnu.org/gnu/gettext/gettext-${GETTEXT_VER}.tar.bz2 ./ 14 | # RUN tar -xjf gettext-${GETTEXT_VER}.tar.bz2 15 | # WORKDIR gettext-$GETTEXT_VER 16 | # RUN ./configure --host "$TARGET" --prefix="$PREFIX" --with-pic --enable-fast-install --disable-dependency-tracking --without-emacs --disable-java --disable-csharp --disable-c++ 17 | # RUN make -j$(nproc) install 18 | 19 | ARG LIBGPG_ERROR_VER=1.50 20 | WORKDIR /usr/src 21 | ADD https://www.gnupg.org/ftp/gcrypt/libgpg-error/libgpg-error-${LIBGPG_ERROR_VER}.tar.bz2 ./ 22 | RUN tar -xjf libgpg-error-${LIBGPG_ERROR_VER}.tar.bz2 23 | WORKDIR libgpg-error-$LIBGPG_ERROR_VER 24 | RUN ./configure --host "$TARGET" --prefix="$PREFIX" --with-pic --enable-fast-install --disable-dependency-tracking --enable-static --disable-shared --disable-nls --disable-doc --disable-languages --disable-tests --enable-install-gpg-error-config 25 | RUN make -j$(nproc) install 26 | 27 | ARG LIBASSUAN_VER=2.5.6 28 | WORKDIR /usr/src 29 | ADD https://www.gnupg.org/ftp/gcrypt/libassuan/libassuan-${LIBASSUAN_VER}.tar.bz2 ./ 30 | RUN tar -xjf libassuan-${LIBASSUAN_VER}.tar.bz2 31 | WORKDIR libassuan-$LIBASSUAN_VER 32 | RUN ./configure --host "$TARGET" --prefix="$PREFIX" --with-pic --enable-fast-install --disable-dependency-tracking --enable-static --disable-shared --disable-doc --with-gpg-error-prefix="$PREFIX" 33 | RUN make -j$(nproc) install 34 | 35 | ARG GPGME_VER=1.23.2 36 | WORKDIR /usr/src 37 | ADD https://www.gnupg.org/ftp/gcrypt/gpgme/gpgme-${GPGME_VER}.tar.bz2 ./ 38 | RUN tar -xjf gpgme-${GPGME_VER}.tar.bz2 39 | WORKDIR gpgme-$GPGME_VER 40 | RUN ./configure --host "$TARGET" --prefix="$PREFIX" --with-pic --enable-fast-install --disable-dependency-tracking --enable-static --disable-shared --disable-languages --disable-gpg-test --with-gpg-error-prefix="$PREFIX" --with-libassuan-prefix="$PREFIX" 41 | RUN make -j$(nproc) install 42 | 43 | FROM builder 44 | WORKDIR /root/ws 45 | COPY ./ ./ 46 | CMD ["cargo", "test", "--no-fail-fast", "--features", "latest"] 47 | -------------------------------------------------------------------------------- /docker/Dockerfile.windows: -------------------------------------------------------------------------------- 1 | # escape=` 2 | ARG WIN_VARIANT=ltsc2022 3 | FROM mcr.microsoft.com/windows/servercore:${WIN_VARIANT} 4 | 5 | ENV RUSTUP_HOME=C:\rustup CARGO_HOME=C:\cargo 6 | 7 | SHELL ["powershell", "-Command", "$ErrorActionPreference = 'Stop'; $ProgressPreference = 'SilentlyContinue';"] 8 | 9 | ADD https://win.rustup.rs/ C:\TEMP\rustup-init.exe 10 | 11 | RUN C:\TEMP\rustup-init.exe -y --profile minimal 12 | 13 | RUN setx /M PATH $(${Env:PATH} + \";${Env:CARGO_HOME}\bin\") 14 | 15 | ARG GNUPG_VERSION=2.5.1_20240912 16 | ADD https://gnupg.org/ftp/gcrypt/binary/gnupg-w32-${GNUPG_VERSION}.exe C:\TEMP\gnupg-w32.exe 17 | 18 | RUN C:\TEMP\gnupg-w32.exe /S 19 | 20 | WORKDIR C:\workspace 21 | COPY ./ ./ 22 | ENV GPGME_DEBUG 9 23 | CMD ["cargo", "test", "--no-fail-fast", "--features", "latest"] 24 | -------------------------------------------------------------------------------- /examples/decrypt.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | error::Error, 3 | fs::File, 4 | io::{self, prelude::*}, 5 | path::PathBuf, 6 | }; 7 | 8 | use clap::Parser; 9 | use gpgme::{Context, Protocol}; 10 | 11 | #[derive(Debug, Parser)] 12 | struct Cli { 13 | #[arg(long)] 14 | /// Use the CMS protocol 15 | cms: bool, 16 | /// File to decrypt 17 | filename: PathBuf, 18 | } 19 | 20 | fn main() -> Result<(), Box> { 21 | let args = Cli::parse(); 22 | let proto = if args.cms { 23 | Protocol::Cms 24 | } else { 25 | Protocol::OpenPgp 26 | }; 27 | 28 | let mut ctx = Context::from_protocol(proto)?; 29 | let mut input = File::open(&args.filename)?; 30 | let mut output = Vec::new(); 31 | ctx.decrypt(&mut input, &mut output) 32 | .map_err(|e| format!("decrypting failed: {e:?}"))?; 33 | 34 | println!("Begin Output:"); 35 | io::stdout().write_all(&output)?; 36 | println!("End Output."); 37 | Ok(()) 38 | } 39 | -------------------------------------------------------------------------------- /examples/encrypt.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | error::Error, 3 | fs::File, 4 | io::{self, prelude::*}, 5 | path::PathBuf, 6 | }; 7 | 8 | use clap::Parser; 9 | use gpgme::{Context, Protocol}; 10 | 11 | #[derive(Debug, Parser)] 12 | struct Cli { 13 | #[arg(long)] 14 | /// Use the CMS protocol 15 | cms: bool, 16 | #[arg(short, long = "recipient")] 17 | /// For whom to encrypt the messages 18 | recipients: Vec, 19 | /// Files to encrypt 20 | filename: PathBuf, 21 | } 22 | 23 | fn main() -> Result<(), Box> { 24 | let args = Cli::parse(); 25 | let proto = if args.cms { 26 | Protocol::Cms 27 | } else { 28 | Protocol::OpenPgp 29 | }; 30 | 31 | let mut ctx = Context::from_protocol(proto)?; 32 | ctx.set_armor(true); 33 | 34 | let keys = if !args.recipients.is_empty() { 35 | ctx.find_keys(args.recipients)? 36 | .filter_map(|x| x.ok()) 37 | .filter(|k| k.can_encrypt()) 38 | .collect() 39 | } else { 40 | Vec::new() 41 | }; 42 | 43 | let filename = &args.filename; 44 | let mut input = File::open(&args.filename) 45 | .map_err(|e| format!("can't open file `{}': {e:?}", filename.display()))?; 46 | let mut output = Vec::new(); 47 | ctx.encrypt(&keys, &mut input, &mut output) 48 | .map_err(|e| format!("encrypting failed: {e:?}"))?; 49 | 50 | println!("Begin Output:"); 51 | io::stdout().write_all(&output)?; 52 | println!("End Output."); 53 | Ok(()) 54 | } 55 | -------------------------------------------------------------------------------- /examples/export.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | error::Error, 3 | io::{self, prelude::*}, 4 | }; 5 | 6 | use clap::Parser; 7 | use gpgme::{Context, ExportMode, Protocol}; 8 | 9 | #[derive(Debug, Parser)] 10 | struct Cli { 11 | #[arg(long = "extern")] 12 | /// Send keys to the keyserver 13 | external: bool, 14 | #[arg(num_args(1..))] 15 | /// Keys to export 16 | users: Vec, 17 | } 18 | 19 | fn main() -> Result<(), Box> { 20 | let args = Cli::parse(); 21 | let mode = if args.external { 22 | ExportMode::EXTERN 23 | } else { 24 | ExportMode::empty() 25 | }; 26 | 27 | let mut ctx = Context::from_protocol(Protocol::OpenPgp)?; 28 | ctx.set_armor(true); 29 | 30 | let keys = { 31 | let mut key_iter = ctx.find_keys(args.users)?; 32 | let keys: Vec<_> = key_iter.by_ref().collect::>()?; 33 | for key in &keys { 34 | println!( 35 | "keyid: {} (fpr: {})", 36 | key.id().unwrap_or("?"), 37 | key.fingerprint().unwrap_or("?") 38 | ); 39 | } 40 | if key_iter.finish()?.is_truncated() { 41 | Err("key listing unexpectedly truncated")?; 42 | } 43 | keys 44 | }; 45 | 46 | if mode.contains(ExportMode::EXTERN) { 47 | println!("sending keys to keyserver"); 48 | ctx.export_keys_extern(&keys, mode) 49 | .map_err(|e| format!("export failed: {e:?}"))?; 50 | } else { 51 | let mut output = Vec::new(); 52 | ctx.export_keys(&keys, mode, &mut output) 53 | .map_err(|e| format!("export failed: {e:?}"))?; 54 | 55 | println!("Begin Result:"); 56 | io::stdout().write_all(&output)?; 57 | println!("End Result."); 58 | } 59 | Ok(()) 60 | } 61 | -------------------------------------------------------------------------------- /examples/import.rs: -------------------------------------------------------------------------------- 1 | use std::{error::Error, fs::File, path::PathBuf}; 2 | 3 | use clap::Parser; 4 | use gpgme::{data, Context, Data, ImportFlags}; 5 | 6 | #[derive(Debug, Parser)] 7 | struct Cli { 8 | #[arg(long)] 9 | /// Import from given URLs 10 | url: bool, 11 | #[arg(short = '0')] 12 | /// URLS are delimited by a null 13 | nul: bool, 14 | #[arg(num_args(1..))] 15 | filenames: Vec, 16 | } 17 | 18 | fn main() -> Result<(), Box> { 19 | let args = Cli::parse(); 20 | let mode = if args.url { 21 | if args.nul { 22 | Some(data::Encoding::Url0) 23 | } else { 24 | Some(data::Encoding::Url) 25 | } 26 | } else { 27 | None 28 | }; 29 | 30 | let mut ctx = Context::from_protocol(gpgme::Protocol::OpenPgp)?; 31 | for file in args.filenames { 32 | println!("reading file `{}'", file.display()); 33 | 34 | let input = File::open(file)?; 35 | let mut data = Data::try_from(input)?; 36 | mode.map(|m| data.set_encoding(m)); 37 | print_import_result( 38 | ctx.import(&mut data) 39 | .map_err(|e| format!("import failed {e:?}"))?, 40 | ); 41 | } 42 | Ok(()) 43 | } 44 | 45 | fn print_import_result(result: gpgme::ImportResult) { 46 | for import in result.imports() { 47 | print!( 48 | " fpr: {} err: {:?} status:", 49 | import.fingerprint().unwrap_or("[none]"), 50 | import.result().err() 51 | ); 52 | let status = import.status(); 53 | if status.contains(ImportFlags::NEW) { 54 | print!(" new"); 55 | } 56 | if status.contains(ImportFlags::UID) { 57 | print!(" uid"); 58 | } 59 | if status.contains(ImportFlags::SIG) { 60 | print!(" sig"); 61 | } 62 | if status.contains(ImportFlags::SUBKEY) { 63 | print!(" subkey"); 64 | } 65 | if status.contains(ImportFlags::SECRET) { 66 | print!(" secret"); 67 | } 68 | println!(); 69 | } 70 | println!("key import summary:"); 71 | println!(" considered: {}", result.considered()); 72 | println!(" no user id: {}", result.without_user_id()); 73 | println!(" imported: {}", result.imported()); 74 | println!(" imported rsa: {}", result.imported_rsa()); 75 | println!(" unchanged: {}", result.unchanged()); 76 | println!(" new user ids: {}", result.new_user_ids()); 77 | println!(" new subkeys: {}", result.new_subkeys()); 78 | println!(" new signatures: {}", result.new_signatures()); 79 | println!(" new revocations: {}", result.new_revocations()); 80 | println!(" secret read: {}", result.secret_considered()); 81 | println!(" secret imported: {}", result.secret_imported()); 82 | println!(" secret unchanged: {}", result.secret_unchanged()); 83 | println!(" not imported: {}", result.not_imported()); 84 | } 85 | -------------------------------------------------------------------------------- /examples/keylist.rs: -------------------------------------------------------------------------------- 1 | use std::error::Error; 2 | 3 | use clap::Parser; 4 | use gpgme::{Context, KeyListMode, Protocol}; 5 | 6 | #[derive(Debug, Parser)] 7 | struct Cli { 8 | #[arg(long)] 9 | /// Use the CMS protocol 10 | cms: bool, 11 | #[arg(long)] 12 | /// Use GPGME_KEYLIST_MODE_LOCAL 13 | local: bool, 14 | #[arg(long = "extern")] 15 | /// Use GPGME_KEYLIST_MODE_EXTERN 16 | external: bool, 17 | #[arg(long)] 18 | /// Use GPGME_KEYLIST_MODE_SIGS 19 | sigs: bool, 20 | #[arg(long = "sig-notations")] 21 | /// Use GPGME_KEYLIST_MODE_SIG_NOTATIONS 22 | notations: bool, 23 | #[arg(long)] 24 | /// Use GPGME_KEYLIST_MODE_EPHEMERAL 25 | ephemeral: bool, 26 | #[arg(long)] 27 | /// Use GPGME_KEYLIST_MODE_VALIDATE 28 | validate: bool, 29 | users: Vec, 30 | } 31 | 32 | fn main() -> Result<(), Box> { 33 | let args = Cli::parse(); 34 | let proto = if args.cms { 35 | Protocol::Cms 36 | } else { 37 | Protocol::OpenPgp 38 | }; 39 | 40 | let mut mode = KeyListMode::empty(); 41 | if args.local { 42 | mode.insert(KeyListMode::LOCAL); 43 | } 44 | if args.external { 45 | mode.insert(KeyListMode::EXTERN); 46 | } 47 | if args.sigs { 48 | mode.insert(KeyListMode::SIGS); 49 | } 50 | if args.notations { 51 | mode.insert(KeyListMode::SIG_NOTATIONS); 52 | } 53 | if args.ephemeral { 54 | mode.insert(KeyListMode::EPHEMERAL); 55 | } 56 | if args.validate { 57 | mode.insert(KeyListMode::VALIDATE); 58 | } 59 | 60 | let mut ctx = Context::from_protocol(proto)?; 61 | ctx.set_key_list_mode(mode)?; 62 | let mut keys = ctx.find_keys(args.users)?; 63 | for key in keys.by_ref().filter_map(|x| x.ok()) { 64 | println!("keyid : {}", key.id().unwrap_or("?")); 65 | println!("fpr : {}", key.fingerprint().unwrap_or("?")); 66 | println!( 67 | "caps : {}{}{}{}", 68 | if key.can_encrypt() { "e" } else { "" }, 69 | if key.can_sign() { "s" } else { "" }, 70 | if key.can_certify() { "c" } else { "" }, 71 | if key.can_authenticate() { "a" } else { "" } 72 | ); 73 | println!( 74 | "flags :{}{}{}{}{}{}", 75 | if key.has_secret() { " secret" } else { "" }, 76 | if key.is_revoked() { " revoked" } else { "" }, 77 | if key.is_expired() { " expired" } else { "" }, 78 | if key.is_disabled() { " disabled" } else { "" }, 79 | if key.is_invalid() { " invalid" } else { "" }, 80 | if key.is_qualified() { " qualified" } else { "" } 81 | ); 82 | for (i, user) in key.user_ids().enumerate() { 83 | println!("userid {i}: {}", user.id().unwrap_or("[none]")); 84 | println!("valid {i}: {:?}", user.validity()) 85 | } 86 | println!(); 87 | } 88 | 89 | if keys.finish()?.is_truncated() { 90 | Err("key listing unexpectedly truncated")?; 91 | } 92 | Ok(()) 93 | } 94 | -------------------------------------------------------------------------------- /examples/keysign.rs: -------------------------------------------------------------------------------- 1 | use std::error::Error; 2 | 3 | use clap::Parser; 4 | use gpgme::{Context, Protocol}; 5 | 6 | #[derive(Debug, Parser)] 7 | struct Cli { 8 | #[arg(long)] 9 | /// Use the CMS protocol 10 | cms: bool, 11 | #[arg(long)] 12 | /// Key to use for signing. Default key is used otherwise 13 | key: Option, 14 | /// Key to sign 15 | keyid: String, 16 | } 17 | 18 | fn main() -> Result<(), Box> { 19 | let args = Cli::parse(); 20 | let proto = if args.cms { 21 | Protocol::Cms 22 | } else { 23 | Protocol::OpenPgp 24 | }; 25 | 26 | let mut ctx = Context::from_protocol(proto)?; 27 | let key_to_sign = ctx 28 | .get_key(&args.keyid) 29 | .map_err(|e| format!("no key matched given key-id: {e:?}"))?; 30 | 31 | if let Some(key) = args.key { 32 | let key = ctx 33 | .get_secret_key(key) 34 | .map_err(|e| format!("unable to find signing key: {e:?}"))?; 35 | ctx.add_signer(&key) 36 | .map_err(|e| format!("add_signer() failed: {e:?}"))?; 37 | } 38 | 39 | ctx.sign_key(&key_to_sign, None::, Default::default()) 40 | .map_err(|e| format!("signing failed: {e:?}"))?; 41 | 42 | println!("Signed key for {}", args.keyid); 43 | Ok(()) 44 | } 45 | -------------------------------------------------------------------------------- /examples/sign.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | error::Error, 3 | fs::File, 4 | io::{self, prelude::*}, 5 | path::PathBuf, 6 | }; 7 | 8 | use clap::Parser; 9 | use gpgme::{Context, Protocol}; 10 | 11 | #[derive(Debug, Parser)] 12 | struct Cli { 13 | #[arg(long)] 14 | /// Use the CMS protocol 15 | cms: bool, 16 | #[arg(long)] 17 | /// Create a detached signature 18 | detach: bool, 19 | #[arg(long, conflicts_with = "detach")] 20 | /// Create a clear text signature 21 | clear: bool, 22 | #[arg(long)] 23 | /// Key to use for signing. Default key is used otherwise 24 | key: Option, 25 | /// File to sign 26 | filename: PathBuf, 27 | } 28 | 29 | fn main() -> Result<(), Box> { 30 | let args = Cli::parse(); 31 | let proto = if args.cms { 32 | Protocol::Cms 33 | } else { 34 | Protocol::OpenPgp 35 | }; 36 | 37 | let mode = if args.detach { 38 | gpgme::SignMode::Detached 39 | } else if args.clear { 40 | gpgme::SignMode::Clear 41 | } else { 42 | gpgme::SignMode::Normal 43 | }; 44 | 45 | let mut ctx = Context::from_protocol(proto)?; 46 | ctx.set_armor(true); 47 | 48 | if let Some(key) = args.key { 49 | let key = ctx 50 | .get_secret_key(key) 51 | .map_err(|e| format!("unable to find signing key: {e:?}"))?; 52 | ctx.add_signer(&key) 53 | .map_err(|e| format!("add_signer() failed: {e:?}"))?; 54 | } 55 | 56 | let filename = &args.filename; 57 | let mut input = File::open(filename) 58 | .map_err(|e| format!("can't open file `{}': {e:?}", filename.display()))?; 59 | let mut output = Vec::new(); 60 | let result = ctx 61 | .sign(mode, &mut input, &mut output) 62 | .map_err(|e| format!("signing failed {e:?}"))?; 63 | print_result(&result); 64 | 65 | println!("Begin Output:"); 66 | io::stdout().write_all(&output)?; 67 | println!("End Output."); 68 | Ok(()) 69 | } 70 | 71 | fn print_result(result: &gpgme::SigningResult) { 72 | for sig in result.new_signatures() { 73 | println!("Key fingerprint: {}", sig.fingerprint().unwrap_or("[none]")); 74 | println!("Signature type : {:?}", sig.mode()); 75 | println!("Public key algo: {}", sig.key_algorithm()); 76 | println!("Hash algo .....: {}", sig.hash_algorithm()); 77 | println!("Creation time .: {:?}", sig.creation_time()); 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /examples/swdb.rs: -------------------------------------------------------------------------------- 1 | extern crate gpgme; 2 | 3 | use std::error::Error; 4 | 5 | use clap::Parser; 6 | use gpgme::{Context, Protocol}; 7 | 8 | #[derive(Debug, Parser)] 9 | struct Cli { 10 | name: Option, 11 | version: Option, 12 | } 13 | 14 | fn main() -> Result<(), Box> { 15 | let args = Cli::parse(); 16 | let mut ctx = Context::from_protocol(Protocol::GpgConf)?; 17 | let result = ctx 18 | .query_swdb(args.name, args.version) 19 | .map_err(|e| format!("query failed: {e:?}"))?; 20 | println!("{result:#?}"); 21 | Ok(()) 22 | } 23 | -------------------------------------------------------------------------------- /examples/verify.rs: -------------------------------------------------------------------------------- 1 | use std::{error::Error, fs::File, path::PathBuf}; 2 | 3 | use clap::Parser; 4 | use gpgme::{Context, Protocol, SignatureSummary}; 5 | 6 | #[derive(Debug, Parser)] 7 | struct Cli { 8 | #[arg(long)] 9 | /// Use the CMS protocol 10 | cms: bool, 11 | sigfile: PathBuf, 12 | filename: Option, 13 | } 14 | 15 | fn main() -> Result<(), Box> { 16 | let args = Cli::parse(); 17 | let proto = if args.cms { 18 | Protocol::Cms 19 | } else { 20 | Protocol::OpenPgp 21 | }; 22 | 23 | let mut ctx = Context::from_protocol(proto)?; 24 | let sigfile = &args.sigfile; 25 | let signature = 26 | File::open(sigfile).map_err(|e| format!("can't open '{}': {e:?}", sigfile.display()))?; 27 | let result = if let Some(filename) = args.filename.as_ref() { 28 | let signed = File::open(filename) 29 | .map_err(|e| format!("can't open '{}': {e:?}", filename.display()))?; 30 | ctx.verify_detached(signature, signed) 31 | } else { 32 | ctx.verify_opaque(signature, Vec::new()) 33 | }; 34 | print_result(&result.map_err(|e| format!("verification failed: {e:?}"))?); 35 | Ok(()) 36 | } 37 | 38 | fn print_summary(summary: SignatureSummary) { 39 | if summary.contains(SignatureSummary::VALID) { 40 | print!(" valid"); 41 | } 42 | if summary.contains(SignatureSummary::GREEN) { 43 | print!(" green"); 44 | } 45 | if summary.contains(SignatureSummary::RED) { 46 | print!(" red"); 47 | } 48 | if summary.contains(SignatureSummary::KEY_REVOKED) { 49 | print!(" revoked"); 50 | } 51 | if summary.contains(SignatureSummary::KEY_EXPIRED) { 52 | print!(" key-expired"); 53 | } 54 | if summary.contains(SignatureSummary::SIG_EXPIRED) { 55 | print!(" sig-expired"); 56 | } 57 | if summary.contains(SignatureSummary::KEY_MISSING) { 58 | print!(" key-missing"); 59 | } 60 | if summary.contains(SignatureSummary::CRL_MISSING) { 61 | print!(" crl-missing"); 62 | } 63 | if summary.contains(SignatureSummary::CRL_TOO_OLD) { 64 | print!(" crl-too-old"); 65 | } 66 | if summary.contains(SignatureSummary::BAD_POLICY) { 67 | print!(" bad-policy"); 68 | } 69 | if summary.contains(SignatureSummary::SYS_ERROR) { 70 | print!(" sys-error"); 71 | } 72 | } 73 | 74 | fn print_result(result: &gpgme::VerificationResult) { 75 | println!( 76 | "Original file name: {}", 77 | result.filename().unwrap_or("[none]") 78 | ); 79 | for (i, sig) in result.signatures().enumerate() { 80 | println!("Signature {i}"); 81 | println!(" status ....: {:?}", sig.status()); 82 | print!(" summary ...:"); 83 | print_summary(sig.summary()); 84 | println!(); 85 | println!(" fingerprint: {}", sig.fingerprint().unwrap_or("[none]")); 86 | println!(" created ...: {:?}", sig.creation_time()); 87 | println!(" expires ...: {:?}", sig.expiration_time()); 88 | println!(" validity ..: {:?}", sig.validity()); 89 | println!(" val.reason : {:?}", sig.nonvalidity_reason()); 90 | println!(" pubkey algo: {}", sig.key_algorithm()); 91 | println!(" digest algo: {}", sig.hash_algorithm()); 92 | println!(" pka address: {}", sig.pka_address().unwrap_or("[none]")); 93 | println!(" pka trust .: {:?}", sig.pka_trust()); 94 | println!( 95 | " other flags: {}{}", 96 | if sig.is_wrong_key_usage() { 97 | " wrong-key-usage" 98 | } else { 99 | "" 100 | }, 101 | if sig.verified_by_chain() { 102 | " chain-model" 103 | } else { 104 | "" 105 | } 106 | ); 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /gpgme-sys/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "gpgme-sys" 3 | version.workspace = true 4 | edition.workspace = true 5 | license.workspace = true 6 | repository.workspace = true 7 | categories = ["external-ffi-bindings"] 8 | keywords = ["gpg", "gpgme", "crypto", "cryptography"] 9 | description = "Raw bindings for gpgme" 10 | links = "gpgme" 11 | 12 | [package.metadata.system-deps] 13 | gpgme = "1.13" 14 | 15 | [features] 16 | windows_raw_dylib = ["libgpg-error-sys/windows_raw_dylib"] 17 | 18 | [build-dependencies] 19 | build-rs = "0.1.2" 20 | system-deps = "6.2.2" 21 | 22 | [dependencies] 23 | libc.workspace = true 24 | libgpg-error-sys = "0.6.2" 25 | 26 | [target.'cfg(windows)'.build-dependencies] 27 | winreg = "0.52.0" 28 | 29 | [lints.rust] 30 | nonstandard-style = { level = "allow", priority = 1 } 31 | -------------------------------------------------------------------------------- /gpgme-sys/build.rs: -------------------------------------------------------------------------------- 1 | use std::error::Error; 2 | 3 | fn main() -> Result<(), Box> { 4 | build::rerun_if_changed("build.rs"); 5 | 6 | if build::cargo_cfg_windows() && (build::cargo_feature("windows_raw_dylib") || try_registry()) { 7 | return Ok(()); 8 | } 9 | 10 | system_deps::Config::new().probe()?; 11 | Ok(()) 12 | } 13 | 14 | #[cfg(not(windows))] 15 | fn try_registry() -> bool { 16 | false 17 | } 18 | 19 | #[cfg(windows)] 20 | fn try_registry() -> bool { 21 | use std::{ffi::OsString, path::PathBuf}; 22 | 23 | use winreg::{enums::*, RegKey}; 24 | 25 | fn try_key(path: &str, wide: bool) -> bool { 26 | let flags = if wide { 27 | KEY_WOW64_64KEY 28 | } else { 29 | KEY_WOW64_32KEY 30 | }; 31 | let hklm = RegKey::predef(HKEY_LOCAL_MACHINE); 32 | let Ok(key) = hklm.open_subkey_with_flags(path, KEY_READ | flags) else { 33 | return false; 34 | }; 35 | let Ok(root) = key 36 | .get_value::("Install Directory") 37 | .map(PathBuf::from) 38 | else { 39 | return false; 40 | }; 41 | 42 | match (build::cargo_cfg_pointer_width(), wide) { 43 | (64, true) | (32, false) => { 44 | println!("detected install via registry: {}", root.display()); 45 | build::rustc_link_search(root.join("lib")); 46 | build::rustc_link_lib("dylib:+verbatim=libgpgme.imp"); 47 | true 48 | } 49 | _ => { 50 | eprintln!( 51 | "An incompatible installation of GnuPG was detected: {}\n\ 52 | Try switching the target from 64-bit to 32-bit or 32-bit to 64-bit.\n", 53 | root.display() 54 | ); 55 | false 56 | } 57 | } 58 | } 59 | 60 | [r"SOFTWARE\Gpg4win", r"SOFTWARE\GnuPG"] 61 | .iter() 62 | .any(|s| try_key(s, true) || try_key(s, false)) 63 | } 64 | -------------------------------------------------------------------------------- /src/bin/pinentry.rs: -------------------------------------------------------------------------------- 1 | use std::{io, io::prelude::*}; 2 | 3 | #[allow(dead_code)] 4 | fn main() { 5 | println!("OK Your orders please"); 6 | 7 | let stdin = io::stdin(); 8 | let mut lines = stdin.lock().lines(); 9 | while let Some(Ok(cmd)) = lines.next() { 10 | match cmd.split(' ').next() { 11 | Some("GETPIN") => { 12 | println!("D abc"); 13 | println!("OK"); 14 | } 15 | _ => println!("OK"), 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/callbacks.rs: -------------------------------------------------------------------------------- 1 | #![allow(deprecated)] 2 | use std::{ 3 | ffi::CStr, 4 | io::{self, prelude::*}, 5 | panic::{self, UnwindSafe}, 6 | str::Utf8Error, 7 | thread, 8 | }; 9 | 10 | use static_assertions::assert_obj_safe; 11 | 12 | use crate::{ 13 | edit, 14 | utils::{self, FdWriter}, 15 | Data, Error, Result, 16 | }; 17 | 18 | assert_obj_safe!(PassphraseProvider); 19 | assert_obj_safe!(ProgressReporter); 20 | assert_obj_safe!(StatusHandler); 21 | assert_obj_safe!(EditInteractor); 22 | assert_obj_safe!(Interactor); 23 | 24 | #[derive(Debug, Copy, Clone)] 25 | pub struct PassphraseRequest<'a> { 26 | uid_hint: Option<&'a CStr>, 27 | desc: Option<&'a CStr>, 28 | pub prev_attempt_failed: bool, 29 | } 30 | 31 | impl<'a> PassphraseRequest<'a> { 32 | pub fn user_id_hint(&self) -> Result<&'a str, Option> { 33 | self.uid_hint 34 | .map_or(Err(None), |s| s.to_str().map_err(Some)) 35 | } 36 | 37 | pub fn user_id_hint_raw(&self) -> Option<&'a CStr> { 38 | self.uid_hint 39 | } 40 | 41 | pub fn description(&self) -> Result<&'a str, Option> { 42 | self.desc.map_or(Err(None), |s| s.to_str().map_err(Some)) 43 | } 44 | 45 | pub fn description_raw(&self) -> Option<&'a CStr> { 46 | self.desc 47 | } 48 | } 49 | 50 | /// Upstream documentation: 51 | /// [`gpgme_passphrase_cb_t`](https://www.gnupg.org/documentation/manuals/gpgme/Passphrase-Callback.html#index-gpgme_005fpassphrase_005fcb_005ft) 52 | pub trait PassphraseProvider: UnwindSafe + Send { 53 | fn get_passphrase(&mut self, request: PassphraseRequest<'_>, out: &mut dyn Write) 54 | -> Result<()>; 55 | } 56 | 57 | impl PassphraseProvider for T 58 | where 59 | T: FnMut(PassphraseRequest<'_>, &mut dyn io::Write) -> Result<()>, 60 | { 61 | fn get_passphrase( 62 | &mut self, 63 | request: PassphraseRequest<'_>, 64 | out: &mut dyn Write, 65 | ) -> Result<()> { 66 | (*self)(request, out) 67 | } 68 | } 69 | 70 | #[derive(Debug, Copy, Clone)] 71 | pub struct ProgressInfo<'a> { 72 | what: Option<&'a CStr>, 73 | pub typ: i64, 74 | pub current: i64, 75 | pub total: i64, 76 | } 77 | 78 | impl<'a> ProgressInfo<'a> { 79 | pub fn what(&self) -> Result<&'a str, Option> { 80 | self.what.map_or(Err(None), |s| s.to_str().map_err(Some)) 81 | } 82 | 83 | pub fn what_raw(&self) -> Option<&'a CStr> { 84 | self.what 85 | } 86 | } 87 | 88 | /// Upstream documentation: 89 | /// [`gpgme_progress_cb_t`](https://www.gnupg.org/documentation/manuals/gpgme/Progress-Meter-Callback.html#index-gpgme_005fprogress_005fcb_005ft) 90 | pub trait ProgressReporter: UnwindSafe + Send { 91 | fn report(&mut self, info: ProgressInfo<'_>); 92 | } 93 | 94 | impl ProgressReporter for T 95 | where 96 | T: FnMut(ProgressInfo<'_>), 97 | { 98 | fn report(&mut self, info: ProgressInfo<'_>) { 99 | (*self)(info); 100 | } 101 | } 102 | 103 | /// Upstream documentation: 104 | /// [`gpgme_status_cb_t`](https://www.gnupg.org/documentation/manuals/gpgme/Status-Message-Callback.html#index-gpgme_005fstatus_005fcb_005ft) 105 | pub trait StatusHandler: UnwindSafe + Send { 106 | fn handle(&mut self, keyword: Option<&CStr>, args: Option<&CStr>) -> Result<()>; 107 | } 108 | 109 | impl StatusHandler for T 110 | where 111 | T: FnMut(Option<&CStr>, Option<&CStr>) -> Result<()>, 112 | { 113 | fn handle(&mut self, keyword: Option<&CStr>, args: Option<&CStr>) -> Result<()> { 114 | (*self)(keyword, args) 115 | } 116 | } 117 | 118 | #[derive(Debug)] 119 | pub struct EditInteractionStatus<'a> { 120 | pub code: edit::StatusCode, 121 | args: Option<&'a CStr>, 122 | pub response: &'a mut Data<'a>, 123 | } 124 | 125 | impl<'a> EditInteractionStatus<'a> { 126 | pub fn args(&self) -> Result<&'a str, Option> { 127 | match self.args { 128 | Some(s) => s.to_str().map_err(Some), 129 | None => Err(None), 130 | } 131 | } 132 | 133 | pub fn args_raw(&self) -> Option<&'a CStr> { 134 | self.args 135 | } 136 | } 137 | 138 | /// Upstream documentation: 139 | /// [`gpgme_edit_cb_t`](https://www.gnupg.org/documentation/manuals/gpgme/Deprecated-Functions.html#index-gpgme_005fedit_005fcb_005ft) 140 | #[deprecated(since = "0.9.2")] 141 | pub trait EditInteractor: UnwindSafe + Send { 142 | fn interact( 143 | &mut self, 144 | status: EditInteractionStatus<'_>, 145 | out: Option<&mut dyn Write>, 146 | ) -> Result<()>; 147 | } 148 | 149 | #[derive(Debug)] 150 | pub struct InteractionStatus<'a> { 151 | keyword: Option<&'a CStr>, 152 | args: Option<&'a CStr>, 153 | pub response: &'a mut Data<'a>, 154 | } 155 | 156 | impl<'a> InteractionStatus<'a> { 157 | pub fn keyword(&self) -> Result<&'a str, Option> { 158 | self.keyword.map_or(Err(None), |s| s.to_str().map_err(Some)) 159 | } 160 | 161 | pub fn keyword_raw(&self) -> Option<&'a CStr> { 162 | self.keyword 163 | } 164 | 165 | pub fn args(&self) -> Result<&'a str, Option> { 166 | self.args.map_or(Err(None), |s| s.to_str().map_err(Some)) 167 | } 168 | 169 | pub fn args_raw(&self) -> Option<&'a CStr> { 170 | self.args 171 | } 172 | } 173 | 174 | /// Upstream documentation: 175 | /// [`gpgme_interact_cb_t`](https://www.gnupg.org/documentation/manuals/gpgme/Advanced-Key-Editing.html#index-gpgme_005finteract_005fcb_005ft) 176 | pub trait Interactor: UnwindSafe + Send { 177 | fn interact( 178 | &mut self, 179 | status: InteractionStatus<'_>, 180 | out: Option<&mut dyn Write>, 181 | ) -> Result<(), Error>; 182 | } 183 | 184 | pub(crate) struct Hook(Option>); 185 | 186 | impl Drop for Hook { 187 | fn drop(&mut self) { 188 | if let Some(Err(err)) = self.0.take() { 189 | panic::resume_unwind(err); 190 | } 191 | } 192 | } 193 | 194 | impl From for Hook { 195 | fn from(hook: T) -> Self { 196 | Self(Some(Ok(hook))) 197 | } 198 | } 199 | 200 | impl Hook { 201 | fn update(&mut self, f: F) -> ffi::gpgme_error_t 202 | where 203 | F: UnwindSafe + FnOnce(&mut T) -> Result<()>, 204 | { 205 | let mut provider = match self.0.take() { 206 | Some(Ok(p)) => p, 207 | other => { 208 | self.0 = other; 209 | return ffi::GPG_ERR_GENERAL; 210 | } 211 | }; 212 | 213 | let result = panic::catch_unwind(move || { 214 | let result = f(&mut provider); 215 | (provider, result) 216 | }); 217 | match result { 218 | Ok((provider, result)) => { 219 | self.0 = Some(Ok(provider)); 220 | result.err().map_or(0, |err| err.raw()) 221 | } 222 | Err(err) => { 223 | self.0 = Some(Err(err)); 224 | ffi::GPG_ERR_GENERAL 225 | } 226 | } 227 | } 228 | } 229 | 230 | pub(crate) struct PassphraseCbGuard { 231 | pub ctx: ffi::gpgme_ctx_t, 232 | pub old: (ffi::gpgme_passphrase_cb_t, *mut libc::c_void), 233 | } 234 | 235 | impl Drop for PassphraseCbGuard { 236 | fn drop(&mut self) { 237 | unsafe { 238 | ffi::gpgme_set_passphrase_cb(self.ctx, self.old.0, self.old.1); 239 | } 240 | } 241 | } 242 | 243 | pub(crate) struct ProgressCbGuard { 244 | pub ctx: ffi::gpgme_ctx_t, 245 | pub old: (ffi::gpgme_progress_cb_t, *mut libc::c_void), 246 | } 247 | 248 | impl Drop for ProgressCbGuard { 249 | fn drop(&mut self) { 250 | unsafe { 251 | ffi::gpgme_set_progress_cb(self.ctx, self.old.0, self.old.1); 252 | } 253 | } 254 | } 255 | 256 | pub(crate) struct StatusCbGuard { 257 | pub ctx: ffi::gpgme_ctx_t, 258 | pub old: (ffi::gpgme_status_cb_t, *mut libc::c_void), 259 | } 260 | 261 | impl Drop for StatusCbGuard { 262 | fn drop(&mut self) { 263 | unsafe { 264 | ffi::gpgme_set_status_cb(self.ctx, self.old.0, self.old.1); 265 | } 266 | } 267 | } 268 | 269 | pub(crate) struct InteractorHook<'a, I> { 270 | pub inner: Hook, 271 | pub response: *mut Data<'a>, 272 | } 273 | 274 | pub(crate) unsafe extern "C" fn passphrase_cb( 275 | hook: *mut libc::c_void, 276 | uid_hint: *const libc::c_char, 277 | info: *const libc::c_char, 278 | was_bad: libc::c_int, 279 | fd: libc::c_int, 280 | ) -> ffi::gpgme_error_t { 281 | (*hook.cast::>()).update(move |h| { 282 | let info = PassphraseRequest { 283 | uid_hint: utils::convert_raw_str(uid_hint), 284 | desc: utils::convert_raw_str(info), 285 | prev_attempt_failed: was_bad != 0, 286 | }; 287 | let mut writer = FdWriter::new(fd); 288 | h.get_passphrase(info, &mut writer) 289 | .and_then(|_| writer.write_all(b"\n").map_err(Error::from)) 290 | }) 291 | } 292 | 293 | pub(crate) unsafe extern "C" fn progress_cb( 294 | hook: *mut libc::c_void, 295 | what: *const libc::c_char, 296 | typ: libc::c_int, 297 | current: libc::c_int, 298 | total: libc::c_int, 299 | ) { 300 | (*hook.cast::>()).update(move |h| { 301 | let info = ProgressInfo { 302 | what: utils::convert_raw_str(what), 303 | typ: typ.into(), 304 | current: current.into(), 305 | total: total.into(), 306 | }; 307 | h.report(info); 308 | Ok(()) 309 | }); 310 | } 311 | 312 | pub(crate) unsafe extern "C" fn status_cb( 313 | hook: *mut libc::c_void, 314 | keyword: *const libc::c_char, 315 | args: *const libc::c_char, 316 | ) -> ffi::gpgme_error_t { 317 | (*hook.cast::>()).update(move |h| { 318 | let keyword = utils::convert_raw_str(keyword); 319 | let args = utils::convert_raw_str(args); 320 | h.handle(args, keyword) 321 | }) 322 | } 323 | 324 | pub(crate) unsafe extern "C" fn edit_cb( 325 | hook: *mut libc::c_void, 326 | status: ffi::gpgme_status_code_t, 327 | args: *const libc::c_char, 328 | fd: libc::c_int, 329 | ) -> ffi::gpgme_error_t { 330 | let hook = &mut *hook.cast::>(); 331 | let response = hook.response; 332 | hook.inner.update(move |h| { 333 | let status = EditInteractionStatus { 334 | code: edit::StatusCode::from_raw(status), 335 | args: utils::convert_raw_str(args), 336 | response: &mut *response, 337 | }; 338 | if fd < 0 { 339 | h.interact(status, None) 340 | } else { 341 | h.interact(status, Some(&mut FdWriter::new(fd))) 342 | } 343 | }) 344 | } 345 | 346 | pub(crate) unsafe extern "C" fn interact_cb( 347 | hook: *mut libc::c_void, 348 | keyword: *const libc::c_char, 349 | args: *const libc::c_char, 350 | fd: libc::c_int, 351 | ) -> ffi::gpgme_error_t { 352 | let hook = &mut *hook.cast::>(); 353 | let response = hook.response; 354 | hook.inner.update(move |h| { 355 | let status = InteractionStatus { 356 | keyword: utils::convert_raw_str(keyword), 357 | args: utils::convert_raw_str(args), 358 | response: &mut *response, 359 | }; 360 | if fd < 0 { 361 | h.interact(status, None) 362 | } else { 363 | h.interact(status, Some(&mut FdWriter::new(fd))) 364 | } 365 | }) 366 | } 367 | -------------------------------------------------------------------------------- /src/data.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | borrow::BorrowMut, 3 | error::Error as StdError, 4 | ffi::CStr, 5 | fmt, 6 | fs::File, 7 | io::{self, prelude::*, Cursor}, 8 | marker::PhantomData, 9 | ptr, slice, 10 | str::Utf8Error, 11 | }; 12 | 13 | #[cfg(unix)] 14 | use std::os::fd::{AsRawFd, BorrowedFd}; 15 | 16 | use conv::{UnwrapOrSaturate, ValueInto}; 17 | use ffi::{self, gpgme_off_t}; 18 | use libc; 19 | use static_assertions::{assert_impl_all, assert_not_impl_any}; 20 | 21 | use crate::{ 22 | utils::{self, convert_err, CStrArgument}, 23 | Error, NonNull, Result, 24 | }; 25 | 26 | assert_impl_all!(Data<'_>: Send); 27 | assert_not_impl_any!(Data<'_>: Sync); 28 | 29 | ffi_enum_wrapper! { 30 | /// Upstream documentation: 31 | /// [`gpgme_data_encoding_t`](https://www.gnupg.org/documentation/manuals/gpgme/Data-Buffer-Meta_002dData.html#index-enum-gpgme_005fdata_005fencoding_005ft) 32 | #[non_exhaustive] 33 | pub enum Encoding: ffi::gpgme_data_encoding_t { 34 | None = ffi::GPGME_DATA_ENCODING_NONE, 35 | Binary = ffi::GPGME_DATA_ENCODING_BINARY, 36 | Base64 = ffi::GPGME_DATA_ENCODING_BASE64, 37 | Armor = ffi::GPGME_DATA_ENCODING_ARMOR, 38 | Url = ffi::GPGME_DATA_ENCODING_URL, 39 | UrlEscaped = ffi::GPGME_DATA_ENCODING_URLESC, 40 | Url0 = ffi::GPGME_DATA_ENCODING_URL0, 41 | Mime = ffi::GPGME_DATA_ENCODING_MIME, 42 | } 43 | } 44 | 45 | ffi_enum_wrapper! { 46 | /// Upstream documentation: 47 | /// [`gpgme_data_type_t`](https://www.gnupg.org/documentation/manuals/gpgme/Data-Buffer-Convenience.html#index-enum-gpgme_005fdata_005ftype_005ft) 48 | #[non_exhaustive] 49 | pub enum Type: ffi::gpgme_data_type_t { 50 | Unknown = ffi::GPGME_DATA_TYPE_UNKNOWN, 51 | Invalid = ffi::GPGME_DATA_TYPE_INVALID, 52 | PgpSigned = ffi::GPGME_DATA_TYPE_PGP_SIGNED, 53 | PgpEncrypted = ffi::GPGME_DATA_TYPE_PGP_ENCRYPTED, 54 | PgpOther = ffi::GPGME_DATA_TYPE_PGP_OTHER, 55 | PgpKey = ffi::GPGME_DATA_TYPE_PGP_KEY, 56 | PgpSignature = ffi::GPGME_DATA_TYPE_PGP_SIGNATURE, 57 | CmsSigned = ffi::GPGME_DATA_TYPE_CMS_SIGNED, 58 | CmsEncrypted = ffi::GPGME_DATA_TYPE_CMS_ENCRYPTED, 59 | CmsOther = ffi::GPGME_DATA_TYPE_CMS_OTHER, 60 | X509Certificate = ffi::GPGME_DATA_TYPE_X509_CERT, 61 | Pkcs12 = ffi::GPGME_DATA_TYPE_PKCS12, 62 | } 63 | } 64 | 65 | #[derive(Clone)] 66 | pub struct WrappedError(Error, S); 67 | 68 | impl WrappedError { 69 | pub fn error(&self) -> Error { 70 | self.0 71 | } 72 | 73 | pub fn into_inner(self) -> S { 74 | self.1 75 | } 76 | } 77 | 78 | impl fmt::Debug for WrappedError { 79 | fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { 80 | fmt::Debug::fmt(&self.0, fmt) 81 | } 82 | } 83 | 84 | impl fmt::Display for WrappedError { 85 | fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { 86 | fmt::Display::fmt(&self.0, fmt) 87 | } 88 | } 89 | 90 | impl StdError for WrappedError { 91 | fn description(&self) -> &str { 92 | #[allow(deprecated)] 93 | StdError::description(&self.0) 94 | } 95 | 96 | fn cause(&self) -> Option<&dyn StdError> { 97 | Some(&self.0) 98 | } 99 | 100 | fn source(&self) -> Option<&(dyn StdError + 'static)> { 101 | Some(&self.0) 102 | } 103 | } 104 | 105 | /// Upstream documentation: 106 | /// [`gpgme_data_t`](https://www.gnupg.org/documentation/manuals/gpgme/Exchanging-Data.html#Exchanging-Data) 107 | #[must_use] 108 | #[derive(Debug)] 109 | pub struct Data<'data>(NonNull, PhantomData<&'data mut ()>); 110 | 111 | unsafe impl Send for Data<'_> {} 112 | 113 | impl Drop for Data<'_> { 114 | #[inline] 115 | fn drop(&mut self) { 116 | unsafe { 117 | ffi::gpgme_data_release(self.as_raw()); 118 | } 119 | } 120 | } 121 | 122 | impl<'data> Data<'data> { 123 | impl_wrapper!(ffi::gpgme_data_t, PhantomData); 124 | 125 | #[inline] 126 | pub fn stdin() -> Result { 127 | Self::try_from(io::stdin()) 128 | } 129 | 130 | #[inline] 131 | pub fn stdout() -> Result { 132 | Self::try_from(io::stdout()) 133 | } 134 | 135 | #[inline] 136 | pub fn stderr() -> Result { 137 | Self::try_from(io::stderr()) 138 | } 139 | 140 | /// Constructs an empty data object. 141 | /// 142 | /// Upstream documentation: 143 | /// [`gpgme_data_new`](https://www.gnupg.org/documentation/manuals/gpgme/Memory-Based-Data-Buffers.html#index-gpgme_005fdata_005fnew) 144 | #[inline] 145 | pub fn new() -> Result { 146 | crate::init(); 147 | unsafe { 148 | let mut data = ptr::null_mut(); 149 | convert_err(ffi::gpgme_data_new(&mut data))?; 150 | Ok(Data::from_raw(data)) 151 | } 152 | } 153 | 154 | /// Constructs a data object and fills it with the contents of the file 155 | /// referenced by `path`. 156 | /// 157 | /// Upstream documentation: 158 | /// [`gpgme_data_new_from_file`](https://www.gnupg.org/documentation/manuals/gpgme/Memory-Based-Data-Buffers.html#index-gpgme_005fdata_005fnew_005ffrom_005ffile) 159 | #[inline] 160 | pub fn load(path: impl CStrArgument) -> Result { 161 | crate::init(); 162 | let path = path.into_cstr(); 163 | unsafe { 164 | let mut data = ptr::null_mut(); 165 | convert_err(ffi::gpgme_data_new_from_file( 166 | &mut data, 167 | path.as_ref().as_ptr(), 168 | 1, 169 | ))?; 170 | Ok(Data::from_raw(data)) 171 | } 172 | } 173 | 174 | /// Constructs a data object and fills it with a copy of `bytes`. 175 | /// 176 | /// Upstream documentation: 177 | /// [`gpgme_data_new_from_mem`](https://www.gnupg.org/documentation/manuals/gpgme/Memory-Based-Data-Buffers.html#index-gpgme_005fdata_005fnew_005ffrom_005fmem) 178 | #[inline] 179 | pub fn from_bytes(bytes: impl AsRef<[u8]>) -> Result { 180 | crate::init(); 181 | let bytes = bytes.as_ref(); 182 | unsafe { 183 | let mut data = ptr::null_mut(); 184 | convert_err(ffi::gpgme_data_new_from_mem( 185 | &mut data, 186 | bytes.as_ptr().cast(), 187 | bytes.len(), 188 | 1, 189 | ))?; 190 | Ok(Data::from_raw(data)) 191 | } 192 | } 193 | 194 | /// Constructs a data object which copies from `buf` as needed. 195 | /// 196 | /// Upstream documentation: 197 | /// [`gpgme_data_new_from_mem`](https://www.gnupg.org/documentation/manuals/gpgme/Memory-Based-Data-Buffers.html#index-gpgme_005fdata_005fnew_005ffrom_005fmem) 198 | #[inline] 199 | pub fn from_buffer(buf: &'data (impl AsRef<[u8]> + ?Sized)) -> Result { 200 | crate::init(); 201 | let buf = buf.as_ref(); 202 | unsafe { 203 | let mut data = ptr::null_mut(); 204 | convert_err(ffi::gpgme_data_new_from_mem( 205 | &mut data, 206 | buf.as_ptr().cast(), 207 | buf.len(), 208 | 0, 209 | ))?; 210 | Ok(Data::from_raw(data)) 211 | } 212 | } 213 | 214 | /// Upstream documentation: 215 | /// [`gpgme_data_new_from_fd`](https://www.gnupg.org/documentation/manuals/gpgme/File-Based-Data-Buffers.html#index-gpgme_005fdata_005fnew_005ffrom_005ffd) 216 | #[inline] 217 | #[cfg(unix)] 218 | pub fn from_borrowed_fd(fd: BorrowedFd<'data>) -> Result { 219 | crate::init(); 220 | unsafe { 221 | let mut data = ptr::null_mut(); 222 | convert_err(ffi::gpgme_data_new_from_fd(&mut data, fd.as_raw_fd()))?; 223 | Ok(Data::from_raw(data)) 224 | } 225 | } 226 | 227 | /// Upstream documentation: 228 | /// [`gpgme_data_new_from_fd`](https://www.gnupg.org/documentation/manuals/gpgme/File-Based-Data-Buffers.html#index-gpgme_005fdata_005fnew_005ffrom_005ffd) 229 | #[inline] 230 | #[cfg(unix)] 231 | #[deprecated(note = "Use Data::from_borrowed_fd", since = "0.11.1")] 232 | pub fn from_fd(file: &'data (impl AsRawFd + ?Sized)) -> Result { 233 | crate::init(); 234 | unsafe { 235 | let mut data = ptr::null_mut(); 236 | convert_err(ffi::gpgme_data_new_from_fd(&mut data, file.as_raw_fd()))?; 237 | Ok(Data::from_raw(data)) 238 | } 239 | } 240 | 241 | /// Upstream documentation: 242 | /// [`gpgme_data_new_from_stream`](https://www.gnupg.org/documentation/manuals/gpgme/File-Based-Data-Buffers.html#index-gpgme_005fdata_005fnew_005ffrom_005fstream) 243 | /// 244 | /// # Safety 245 | /// 246 | /// The provided `FILE` object must be valid. 247 | #[inline] 248 | pub unsafe fn from_raw_file(file: *mut libc::FILE) -> Result { 249 | crate::init(); 250 | let mut data = ptr::null_mut(); 251 | convert_err(ffi::gpgme_data_new_from_stream(&mut data, file))?; 252 | Ok(Data::from_raw(data)) 253 | } 254 | 255 | unsafe fn from_callbacks(cbs: ffi::gpgme_data_cbs, src: S) -> Result> 256 | where 257 | S: Send + 'data, 258 | { 259 | crate::init(); 260 | let src = Box::into_raw(Box::new(CallbackWrapper { inner: src, cbs })); 261 | let cbs = ptr::addr_of_mut!((*src).cbs); 262 | let mut data = ptr::null_mut(); 263 | match convert_err(ffi::gpgme_data_new_from_cbs(&mut data, cbs, src.cast())) { 264 | Ok(()) => Ok(Data::from_raw(data)), 265 | Err(e) => Err(WrappedError(e, Box::from_raw(src).inner)), 266 | } 267 | } 268 | 269 | /// Returns a new [`DataBuilder`] wrapping the provided value. 270 | #[inline] 271 | pub fn builder(inner: T) -> DataBuilder { 272 | DataBuilder::new(inner) 273 | } 274 | 275 | #[inline] 276 | #[deprecated(note = "Use Data::builder instead.", since = "0.11.1")] 277 | pub fn from_reader(r: R) -> Result> 278 | where 279 | R: Read + Send + 'data, 280 | { 281 | Self::builder(r).readable().try_build() 282 | } 283 | 284 | #[inline] 285 | #[deprecated(note = "Use Data::builder instead.", since = "0.11.1")] 286 | pub fn from_seekable_reader(r: R) -> Result> 287 | where 288 | R: Read + Seek + Send + 'data, 289 | { 290 | Self::builder(r).readable().seekable().try_build() 291 | } 292 | 293 | #[inline] 294 | #[deprecated(note = "Use Data::builder instead.", since = "0.11.1")] 295 | pub fn from_writer(w: W) -> Result> 296 | where 297 | W: Write + Send + 'data, 298 | { 299 | Self::builder(w).writable().try_build() 300 | } 301 | 302 | #[inline] 303 | #[deprecated(note = "Use Data::builder instead.", since = "0.11.1")] 304 | pub fn from_seekable_writer(w: W) -> Result> 305 | where 306 | W: Write + Seek + Send + 'data, 307 | { 308 | Self::builder(w).writable().seekable().try_build() 309 | } 310 | 311 | #[inline] 312 | #[deprecated(note = "Use Data::builder instead.", since = "0.11.1")] 313 | pub fn from_stream(s: S) -> Result> 314 | where 315 | S: Read + Write + Send + 'data, 316 | { 317 | Self::builder(s).readable().writable().try_build() 318 | } 319 | 320 | #[inline] 321 | #[deprecated(note = "Use Data::builder instead.", since = "0.11.1")] 322 | pub fn from_seekable_stream(s: S) -> Result> 323 | where 324 | S: Read + Write + Seek + Send + 'data, 325 | { 326 | Self::builder(s) 327 | .readable() 328 | .writable() 329 | .seekable() 330 | .try_build() 331 | } 332 | 333 | /// Upstream documentation: 334 | /// [`gpgme_data_get_file_name`](https://www.gnupg.org/documentation/manuals/gpgme/Data-Buffer-Meta_002dData.html#index-gpgme_005fdata_005fget_005ffile_005fname) 335 | #[inline] 336 | pub fn filename(&self) -> Result<&str, Option> { 337 | self.filename_raw() 338 | .map_or(Err(None), |s| s.to_str().map_err(Some)) 339 | } 340 | 341 | /// Upstream documentation: 342 | /// [`gpgme_data_get_file_name`](https://www.gnupg.org/documentation/manuals/gpgme/Data-Buffer-Meta_002dData.html#index-gpgme_005fdata_005fget_005ffile_005fname) 343 | #[inline] 344 | pub fn filename_raw(&self) -> Option<&CStr> { 345 | unsafe { utils::convert_raw_str(ffi::gpgme_data_get_file_name(self.as_raw())) } 346 | } 347 | 348 | /// Upstream documentation: 349 | /// [`gpgme_data_set_file_name`](https://www.gnupg.org/documentation/manuals/gpgme/Data-Buffer-Meta_002dData.html#index-gpgme_005fdata_005fset_005ffile_005fname) 350 | #[inline] 351 | pub fn clear_filename(&mut self) -> Result<()> { 352 | unsafe { convert_err(ffi::gpgme_data_set_file_name(self.as_raw(), ptr::null())) } 353 | } 354 | 355 | /// Upstream documentation: 356 | /// [`gpgme_data_set_file_name`](https://www.gnupg.org/documentation/manuals/gpgme/Data-Buffer-Meta_002dData.html#index-gpgme_005fdata_005fset_005ffile_005fname) 357 | #[inline] 358 | pub fn set_filename(&mut self, name: impl CStrArgument) -> Result<()> { 359 | let name = name.into_cstr(); 360 | unsafe { 361 | convert_err(ffi::gpgme_data_set_file_name( 362 | self.as_raw(), 363 | name.as_ref().as_ptr(), 364 | )) 365 | } 366 | } 367 | 368 | /// Upstream documentation: 369 | /// [`gpgme_data_get_encoding`](https://www.gnupg.org/documentation/manuals/gpgme/Data-Buffer-Meta_002dData.html#index-gpgme_005fdata_005fget_005fencoding) 370 | #[inline] 371 | pub fn encoding(&self) -> Encoding { 372 | unsafe { Encoding::from_raw(ffi::gpgme_data_get_encoding(self.as_raw())) } 373 | } 374 | 375 | /// Upstream documentation: 376 | /// [`gpgme_data_set_encoding`](https://www.gnupg.org/documentation/manuals/gpgme/Data-Buffer-Meta_002dData.html#index-gpgme_005fdata_005fset_005fencoding) 377 | #[inline] 378 | pub fn set_encoding(&mut self, enc: Encoding) -> Result<()> { 379 | unsafe { convert_err(ffi::gpgme_data_set_encoding(self.as_raw(), enc.raw())) } 380 | } 381 | 382 | /// Upstream documentation: 383 | /// [`gpgme_data_set_flag`](https://www.gnupg.org/documentation/manuals/gpgme/Data-Buffer-Meta_002dData.html#index-gpgme_005fdata_005fset_005fflag) 384 | #[inline] 385 | pub fn set_flag(&mut self, name: impl CStrArgument, value: impl CStrArgument) -> Result<()> { 386 | let name = name.into_cstr(); 387 | let value = value.into_cstr(); 388 | unsafe { 389 | convert_err(ffi::gpgme_data_set_flag( 390 | self.as_raw(), 391 | name.as_ref().as_ptr(), 392 | value.as_ref().as_ptr(), 393 | )) 394 | } 395 | } 396 | 397 | /// Upstream documentation: 398 | /// [`gpgme_data_set_flag`](https://www.gnupg.org/documentation/manuals/gpgme/Data-Buffer-Meta_002dData.html#index-gpgme_005fdata_005fset_005fflag) 399 | #[inline] 400 | pub fn set_size_hint(&mut self, value: u64) -> Result<()> { 401 | self.set_flag(c"size-hint", value.to_string()) 402 | } 403 | 404 | /// Upstream documentation: 405 | /// [`gpgme_data_identify`](https://www.gnupg.org/documentation/manuals/gpgme/Data-Buffer-Convenience.html#index-gpgme_005fdata_005fidentify) 406 | #[inline] 407 | pub fn identify(&mut self) -> Type { 408 | unsafe { Type::from_raw(ffi::gpgme_data_identify(self.as_raw(), 0)) } 409 | } 410 | 411 | /// Upstream documentation: 412 | /// [`gpgme_data_release_and_get_mem`](https://www.gnupg.org/documentation/manuals/gpgme/Destroying-Data-Buffers.html#index-gpgme_005fdata_005frelease_005fand_005fget_005fmem) 413 | #[inline] 414 | pub fn try_into_bytes(self) -> Option> { 415 | unsafe { 416 | let mut len = 0; 417 | let mem = ffi::gpgme_data_release_and_get_mem(self.into_raw(), &mut len); 418 | ptr::slice_from_raw_parts_mut(mem.cast::(), len) 419 | .as_mut() 420 | .map(|s| { 421 | let r = s.to_vec(); 422 | ffi::gpgme_free(s.as_mut_ptr().cast()); 423 | r 424 | }) 425 | } 426 | } 427 | } 428 | 429 | impl Read for Data<'_> { 430 | #[inline] 431 | fn read(&mut self, buf: &mut [u8]) -> io::Result { 432 | let result = unsafe { 433 | let (buf, len) = (buf.as_mut_ptr(), buf.len()); 434 | ffi::gpgme_data_read(self.as_raw(), buf.cast(), len) 435 | }; 436 | Ok(usize::try_from(result).map_err(|_| Error::last_os_error())?) 437 | } 438 | } 439 | 440 | impl Write for Data<'_> { 441 | #[inline] 442 | fn write(&mut self, buf: &[u8]) -> io::Result { 443 | let result = 444 | unsafe { ffi::gpgme_data_write(self.as_raw(), buf.as_ptr().cast(), buf.len()) }; 445 | Ok(usize::try_from(result).map_err(|_| Error::last_os_error())?) 446 | } 447 | 448 | #[inline] 449 | fn flush(&mut self) -> io::Result<()> { 450 | Ok(()) 451 | } 452 | } 453 | 454 | impl Seek for Data<'_> { 455 | #[inline] 456 | fn seek(&mut self, pos: io::SeekFrom) -> io::Result { 457 | let (off, whence) = match pos { 458 | io::SeekFrom::Start(off) => { 459 | (off.try_into().unwrap_or(gpgme_off_t::MAX), libc::SEEK_SET) 460 | } 461 | io::SeekFrom::End(off) => (off.value_into().unwrap_or_saturate(), libc::SEEK_END), 462 | io::SeekFrom::Current(off) => (off.value_into().unwrap_or_saturate(), libc::SEEK_CUR), 463 | }; 464 | let result = unsafe { ffi::gpgme_data_seek(self.as_raw(), off, whence) }; 465 | Ok(u64::try_from(result).map_err(|_| Error::last_os_error())?) 466 | } 467 | } 468 | 469 | struct CallbackWrapper { 470 | cbs: ffi::gpgme_data_cbs, 471 | inner: S, 472 | } 473 | 474 | unsafe extern "C" fn read_callback( 475 | handle: *mut libc::c_void, 476 | buffer: *mut libc::c_void, 477 | size: libc::size_t, 478 | ) -> libc::ssize_t { 479 | let handle = handle.cast::>(); 480 | let slice = slice::from_raw_parts_mut(buffer.cast::(), size); 481 | (*handle) 482 | .inner 483 | .read(slice) 484 | .map_err(Error::from) 485 | .and_then(|n| n.try_into().or(Err(Error::EOVERFLOW))) 486 | .unwrap_or_else(|err| { 487 | ffi::gpgme_err_set_errno(err.to_errno()); 488 | -1 489 | }) 490 | } 491 | 492 | unsafe extern "C" fn write_callback( 493 | handle: *mut libc::c_void, 494 | buffer: *const libc::c_void, 495 | size: libc::size_t, 496 | ) -> libc::ssize_t { 497 | let handle = handle.cast::>(); 498 | let slice = slice::from_raw_parts(buffer.cast::(), size); 499 | (*handle) 500 | .inner 501 | .write(slice) 502 | .map_err(Error::from) 503 | .and_then(|n| n.try_into().or(Err(Error::EOVERFLOW))) 504 | .unwrap_or_else(|err| { 505 | ffi::gpgme_err_set_errno(err.to_errno()); 506 | -1 507 | }) 508 | } 509 | 510 | unsafe extern "C" fn seek_callback( 511 | handle: *mut libc::c_void, 512 | offset: gpgme_off_t, 513 | whence: libc::c_int, 514 | ) -> gpgme_off_t { 515 | let handle = handle.cast::>(); 516 | let pos = match whence { 517 | libc::SEEK_SET => io::SeekFrom::Start(offset.value_into().unwrap_or_saturate()), 518 | libc::SEEK_END => io::SeekFrom::End(offset.value_into().unwrap_or_saturate()), 519 | libc::SEEK_CUR => io::SeekFrom::Current(offset.value_into().unwrap_or_saturate()), 520 | _ => { 521 | ffi::gpgme_err_set_errno(Error::EINVAL.to_errno()); 522 | return -1; 523 | } 524 | }; 525 | (*handle) 526 | .inner 527 | .seek(pos) 528 | .map_err(Error::from) 529 | .and_then(|n| n.try_into().or(Err(Error::EOVERFLOW))) 530 | .unwrap_or_else(|err| { 531 | ffi::gpgme_err_set_errno(err.to_errno()); 532 | -1 533 | }) 534 | } 535 | 536 | unsafe extern "C" fn release_callback(handle: *mut libc::c_void) { 537 | drop(Box::from_raw(handle.cast::>())); 538 | } 539 | 540 | /// A trait for converting compatible types into data objects. 541 | pub trait IntoData<'a> { 542 | type Output: BorrowMut>; 543 | 544 | fn into_data(self) -> Result; 545 | } 546 | 547 | impl<'a> IntoData<'a> for &mut Data<'a> { 548 | type Output = Self; 549 | 550 | fn into_data(self) -> Result { 551 | Ok(self) 552 | } 553 | } 554 | 555 | impl<'a, T> IntoData<'a> for T 556 | where 557 | T: TryInto, Error = Error>, 558 | { 559 | type Output = Data<'a>; 560 | 561 | fn into_data(self) -> Result { 562 | self.try_into() 563 | } 564 | } 565 | 566 | impl<'a> TryFrom<&'a [u8]> for Data<'a> { 567 | type Error = Error; 568 | 569 | #[inline] 570 | fn try_from(value: &'a [u8]) -> Result { 571 | Self::from_buffer(value) 572 | } 573 | } 574 | 575 | impl<'a> TryFrom<&'a mut [u8]> for Data<'a> { 576 | type Error = Error; 577 | 578 | #[inline] 579 | fn try_from(value: &'a mut [u8]) -> Result { 580 | Self::builder(Cursor::new(value)) 581 | .readable() 582 | .writable() 583 | .seekable() 584 | .try_build() 585 | .map_err(|e| e.error()) 586 | } 587 | } 588 | 589 | impl<'a> TryFrom<&'a str> for Data<'a> { 590 | type Error = Error; 591 | 592 | #[inline] 593 | fn try_from(value: &'a str) -> Result { 594 | value.as_bytes().try_into() 595 | } 596 | } 597 | 598 | impl<'a> TryFrom<&'a Vec> for Data<'a> { 599 | type Error = Error; 600 | 601 | #[inline] 602 | fn try_from(value: &'a Vec) -> Result { 603 | value.as_slice().try_into() 604 | } 605 | } 606 | 607 | impl<'a> TryFrom<&'a mut Vec> for Data<'a> { 608 | type Error = Error; 609 | 610 | #[inline] 611 | fn try_from(value: &'a mut Vec) -> Result { 612 | Self::builder(Cursor::new(value)) 613 | .readable() 614 | .writable() 615 | .seekable() 616 | .try_build() 617 | .map_err(|e| e.error()) 618 | } 619 | } 620 | 621 | impl<'a> TryFrom> for Data<'a> { 622 | type Error = Error; 623 | 624 | #[inline] 625 | fn try_from(value: Vec) -> Result { 626 | Self::builder(Cursor::new(value)) 627 | .readable() 628 | .writable() 629 | .seekable() 630 | .try_build() 631 | .map_err(|e| e.error()) 632 | } 633 | } 634 | 635 | impl<'a> TryFrom for Data<'a> { 636 | type Error = Error; 637 | 638 | #[inline] 639 | fn try_from(value: String) -> Result { 640 | value.into_bytes().try_into() 641 | } 642 | } 643 | 644 | impl<'a> TryFrom<&'a File> for Data<'a> { 645 | type Error = Error; 646 | 647 | #[inline] 648 | fn try_from(value: &'a File) -> Result { 649 | Self::builder(value) 650 | .readable() 651 | .writable() 652 | .seekable() 653 | .try_build() 654 | .map_err(|e| e.error()) 655 | } 656 | } 657 | 658 | impl<'a> TryFrom<&'a mut File> for Data<'a> { 659 | type Error = Error; 660 | 661 | #[inline] 662 | fn try_from(value: &'a mut File) -> Result { 663 | Self::try_from(&*value) 664 | } 665 | } 666 | 667 | impl<'a> TryFrom for Data<'a> { 668 | type Error = Error; 669 | 670 | #[inline] 671 | fn try_from(value: File) -> Result { 672 | Self::builder(value) 673 | .readable() 674 | .writable() 675 | .seekable() 676 | .try_build() 677 | .map_err(|e| e.error()) 678 | } 679 | } 680 | 681 | impl<'a> TryFrom for Data<'a> { 682 | type Error = Error; 683 | 684 | #[inline] 685 | fn try_from(value: io::Stdout) -> Result { 686 | Self::builder(value) 687 | .writable() 688 | .try_build() 689 | .map_err(|e| e.error()) 690 | } 691 | } 692 | 693 | impl<'a> TryFrom for Data<'a> { 694 | type Error = Error; 695 | 696 | #[inline] 697 | fn try_from(value: io::Stderr) -> Result { 698 | Self::builder(value) 699 | .writable() 700 | .try_build() 701 | .map_err(|e| e.error()) 702 | } 703 | } 704 | 705 | impl<'a> TryFrom for Data<'a> { 706 | type Error = Error; 707 | 708 | #[inline] 709 | fn try_from(value: io::Stdin) -> Result { 710 | Self::builder(value) 711 | .readable() 712 | .try_build() 713 | .map_err(|e| e.error()) 714 | } 715 | } 716 | 717 | #[cfg(unix)] 718 | impl<'a> TryFrom> for Data<'a> { 719 | type Error = Error; 720 | 721 | #[inline] 722 | fn try_from(value: BorrowedFd<'a>) -> Result { 723 | Data::from_borrowed_fd(value) 724 | } 725 | } 726 | 727 | /// A struct that helps with creating a [`Data`] object from a wrapped object 728 | /// that implements [`Read`]/[`Write`]/[`Seek`]. 729 | #[must_use] 730 | #[derive(Clone)] 731 | pub struct DataBuilder { 732 | inner: T, 733 | cbs: ffi::gpgme_data_cbs, 734 | } 735 | 736 | impl DataBuilder { 737 | /// Returns a new builder wrapping the provided value. 738 | #[inline] 739 | pub fn new(inner: T) -> Self { 740 | Self { 741 | inner, 742 | cbs: ffi::gpgme_data_cbs { 743 | read: None, 744 | write: None, 745 | seek: None, 746 | release: Some(release_callback::), 747 | }, 748 | } 749 | } 750 | 751 | /// Enables reading from the wrapped object. 752 | #[inline] 753 | pub fn readable(mut self) -> Self 754 | where 755 | T: Read, 756 | { 757 | self.cbs.read = Some(read_callback::); 758 | self 759 | } 760 | 761 | /// Enables writing to the wrapped object. 762 | #[inline] 763 | pub fn writable(mut self) -> Self 764 | where 765 | T: Write, 766 | { 767 | self.cbs.write = Some(write_callback::); 768 | self 769 | } 770 | 771 | /// Enables seeking within the wrapped object. 772 | #[inline] 773 | pub fn seekable(mut self) -> Self 774 | where 775 | T: Seek, 776 | { 777 | self.cbs.seek = Some(seek_callback::); 778 | self 779 | } 780 | 781 | /// Attempts to build a new [`Data`] object using the wrapped 782 | /// value as a backing source/sink. 783 | #[inline] 784 | pub fn try_build<'a>(self) -> Result, WrappedError> 785 | where 786 | T: 'a, 787 | { 788 | unsafe { Data::from_callbacks(self.cbs, self.inner) } 789 | } 790 | } 791 | 792 | impl fmt::Debug for DataBuilder 793 | where 794 | T: fmt::Debug, 795 | { 796 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 797 | f.debug_struct("DataBuilder") 798 | .field("inner", &self.inner) 799 | .field("readable", &self.cbs.read.is_some()) 800 | .field("writable", &self.cbs.write.is_some()) 801 | .field("seekable", &self.cbs.seek.is_some()) 802 | .finish() 803 | } 804 | } 805 | -------------------------------------------------------------------------------- /src/edit.rs: -------------------------------------------------------------------------------- 1 | #![allow(non_camel_case_types, deprecated)] 2 | use std::{fmt, io::prelude::*, panic::UnwindSafe}; 3 | 4 | use ffi; 5 | 6 | use crate::{Error, Result}; 7 | 8 | pub use crate::{EditInteractionStatus, EditInteractor}; 9 | 10 | ffi_enum_wrapper! { 11 | #[non_exhaustive] 12 | pub enum StatusCode: ffi::gpgme_status_code_t { 13 | Eof = ffi::GPGME_STATUS_EOF, 14 | Enter = ffi::GPGME_STATUS_ENTER, 15 | Leave = ffi::GPGME_STATUS_LEAVE, 16 | Abort = ffi::GPGME_STATUS_ABORT, 17 | GoodSig = ffi::GPGME_STATUS_GOODSIG, 18 | BadSig = ffi::GPGME_STATUS_BADSIG, 19 | ErrSig = ffi::GPGME_STATUS_ERRSIG, 20 | BadArmor = ffi::GPGME_STATUS_BADARMOR, 21 | RsaOrIdea = ffi::GPGME_STATUS_RSA_OR_IDEA, 22 | KeyExpired = ffi::GPGME_STATUS_KEYEXPIRED, 23 | KeyRevoked = ffi::GPGME_STATUS_KEYREVOKED, 24 | TrustUndefined = ffi::GPGME_STATUS_TRUST_UNDEFINED, 25 | TrustNever = ffi::GPGME_STATUS_TRUST_NEVER, 26 | TrustMarginal = ffi::GPGME_STATUS_TRUST_MARGINAL, 27 | TrustFully = ffi::GPGME_STATUS_TRUST_FULLY, 28 | TrustUltimate = ffi::GPGME_STATUS_TRUST_ULTIMATE, 29 | ShmInfo = ffi::GPGME_STATUS_SHM_INFO, 30 | ShmGet = ffi::GPGME_STATUS_SHM_GET, 31 | ShmGetBool = ffi::GPGME_STATUS_SHM_GET_BOOL, 32 | ShmGetHidden = ffi::GPGME_STATUS_SHM_GET_HIDDEN, 33 | NeedPassphrase = ffi::GPGME_STATUS_NEED_PASSPHRASE, 34 | ValidSig = ffi::GPGME_STATUS_VALIDSIG, 35 | SigId = ffi::GPGME_STATUS_SIG_ID, 36 | EncTo = ffi::GPGME_STATUS_ENC_TO, 37 | NoData = ffi::GPGME_STATUS_NODATA, 38 | BadPassphrase = ffi::GPGME_STATUS_BAD_PASSPHRASE, 39 | NoPubKey = ffi::GPGME_STATUS_NO_PUBKEY, 40 | NoSecKey = ffi::GPGME_STATUS_NO_SECKEY, 41 | NeedPassphraseSym = ffi::GPGME_STATUS_NEED_PASSPHRASE_SYM, 42 | DecryptionFailed = ffi::GPGME_STATUS_DECRYPTION_FAILED, 43 | DecryptionOkay = ffi::GPGME_STATUS_DECRYPTION_OKAY, 44 | MissingPassphrase = ffi::GPGME_STATUS_MISSING_PASSPHRASE, 45 | GoodPassphrase = ffi::GPGME_STATUS_GOOD_PASSPHRASE, 46 | GoodMdc = ffi::GPGME_STATUS_GOODMDC, 47 | BadMdc = ffi::GPGME_STATUS_BADMDC, 48 | ErrMdc = ffi::GPGME_STATUS_ERRMDC, 49 | Imported = ffi::GPGME_STATUS_IMPORTED, 50 | ImportOk = ffi::GPGME_STATUS_IMPORT_OK, 51 | ImportProblem = ffi::GPGME_STATUS_IMPORT_PROBLEM, 52 | ImportRes = ffi::GPGME_STATUS_IMPORT_RES, 53 | FileStart = ffi::GPGME_STATUS_FILE_START, 54 | FileDone = ffi::GPGME_STATUS_FILE_DONE, 55 | FileError = ffi::GPGME_STATUS_FILE_ERROR, 56 | BeginDecryption = ffi::GPGME_STATUS_BEGIN_DECRYPTION, 57 | EndDecryption = ffi::GPGME_STATUS_END_DECRYPTION, 58 | BeginEncryption = ffi::GPGME_STATUS_BEGIN_ENCRYPTION, 59 | EndEncryption = ffi::GPGME_STATUS_END_ENCRYPTION, 60 | DeleteProblem = ffi::GPGME_STATUS_DELETE_PROBLEM, 61 | GetBool = ffi::GPGME_STATUS_GET_BOOL, 62 | GetLine = ffi::GPGME_STATUS_GET_LINE, 63 | GetHidden = ffi::GPGME_STATUS_GET_HIDDEN, 64 | GotIt = ffi::GPGME_STATUS_GOT_IT, 65 | Progress = ffi::GPGME_STATUS_PROGRESS, 66 | SigCreated = ffi::GPGME_STATUS_SIG_CREATED, 67 | SessionKey = ffi::GPGME_STATUS_SESSION_KEY, 68 | NotationName = ffi::GPGME_STATUS_NOTATION_NAME, 69 | NotationData = ffi::GPGME_STATUS_NOTATION_DATA, 70 | PolicyUrl = ffi::GPGME_STATUS_POLICY_URL, 71 | BeginStream = ffi::GPGME_STATUS_BEGIN_STREAM, 72 | EndStream = ffi::GPGME_STATUS_END_STREAM, 73 | KeyCreated = ffi::GPGME_STATUS_KEY_CREATED, 74 | UserIdHint = ffi::GPGME_STATUS_USERID_HINT, 75 | Unexpected = ffi::GPGME_STATUS_UNEXPECTED, 76 | InvRecp = ffi::GPGME_STATUS_INV_RECP, 77 | NoRecp = ffi::GPGME_STATUS_NO_RECP, 78 | AlreadySigned = ffi::GPGME_STATUS_ALREADY_SIGNED, 79 | SigExpired = ffi::GPGME_STATUS_SIGEXPIRED, 80 | ExpSig = ffi::GPGME_STATUS_EXPSIG, 81 | ExpKeySig = ffi::GPGME_STATUS_EXPKEYSIG, 82 | Truncated = ffi::GPGME_STATUS_TRUNCATED, 83 | Error = ffi::GPGME_STATUS_ERROR, 84 | NewSig = ffi::GPGME_STATUS_NEWSIG, 85 | RevKeySig = ffi::GPGME_STATUS_REVKEYSIG, 86 | SigSubpacket = ffi::GPGME_STATUS_SIG_SUBPACKET, 87 | NeedPassphrasePin = ffi::GPGME_STATUS_NEED_PASSPHRASE_PIN, 88 | ScOpFailure = ffi::GPGME_STATUS_SC_OP_FAILURE, 89 | ScOpSuccess = ffi::GPGME_STATUS_SC_OP_SUCCESS, 90 | CardCtrl = ffi::GPGME_STATUS_CARDCTRL, 91 | BackupKeyCreated = ffi::GPGME_STATUS_BACKUP_KEY_CREATED, 92 | PkaTrustBad = ffi::GPGME_STATUS_PKA_TRUST_BAD, 93 | PkaTrustGood = ffi::GPGME_STATUS_PKA_TRUST_GOOD, 94 | Plaintext = ffi::GPGME_STATUS_PLAINTEXT, 95 | InvSgnr = ffi::GPGME_STATUS_INV_SGNR, 96 | NoSgnr = ffi::GPGME_STATUS_NO_SGNR, 97 | Success = ffi::GPGME_STATUS_SUCCESS, 98 | DecryptionInfo = ffi::GPGME_STATUS_DECRYPTION_INFO, 99 | PlaintextLength = ffi::GPGME_STATUS_PLAINTEXT_LENGTH, 100 | Mountpoint = ffi::GPGME_STATUS_MOUNTPOINT, 101 | PinentryLaunched = ffi::GPGME_STATUS_PINENTRY_LAUNCHED, 102 | Attribute = ffi::GPGME_STATUS_ATTRIBUTE, 103 | BeginSigning = ffi::GPGME_STATUS_BEGIN_SIGNING, 104 | KeyNotCreated = ffi::GPGME_STATUS_KEY_NOT_CREATED, 105 | InquireMaxLen = ffi::GPGME_STATUS_INQUIRE_MAXLEN, 106 | Failure = ffi::GPGME_STATUS_FAILURE, 107 | KeyConsidered = ffi::GPGME_STATUS_KEY_CONSIDERED, 108 | TofuUser = ffi::GPGME_STATUS_TOFU_USER, 109 | TofuStats = ffi::GPGME_STATUS_TOFU_STATS, 110 | TofuStatsLong = ffi::GPGME_STATUS_TOFU_STATS_LONG, 111 | NotationFlags = ffi::GPGME_STATUS_NOTATION_FLAGS, 112 | } 113 | } 114 | 115 | impl StatusCode { 116 | pub fn needs_response(&self) -> bool { 117 | matches!( 118 | self, 119 | Self::AlreadySigned 120 | | Self::Error 121 | | Self::GetBool 122 | | Self::GetLine 123 | | Self::KeyCreated 124 | | Self::NeedPassphraseSym 125 | | Self::ScOpFailure 126 | | Self::CardCtrl 127 | | Self::BackupKeyCreated 128 | ) 129 | } 130 | 131 | pub fn into_result(self) -> Result<()> { 132 | match self { 133 | Self::MissingPassphrase => Err(Error::NO_PASSPHRASE), 134 | Self::AlreadySigned => Err(Error::USER_1), 135 | Self::SigExpired => Err(Error::SIG_EXPIRED), 136 | _ => Ok(()), 137 | } 138 | } 139 | } 140 | 141 | // Actions 142 | pub const QUIT: &str = "quit"; 143 | pub const SAVE: &str = "save"; 144 | pub const YES: &str = "Y"; 145 | pub const NO: &str = "N"; 146 | 147 | // Keywords 148 | pub const PROMPT: &str = "keyedit.prompt"; 149 | pub const CONFIRM_SAVE: &str = "keyedit.save.okay"; 150 | pub const CONFIRM_CANCEL: &str = "keyedit.cancel.okay"; 151 | pub const CONFIRM_KEY_VALID: &str = "keygen.valid.okay"; 152 | pub const CONFIRM_CREATE_KEY: &str = "keygen.sub.okay"; 153 | pub const KEY_NAME: &str = "keygen.name"; 154 | pub const KEY_EMAIL: &str = "keygen.email"; 155 | pub const KEY_COMMENT: &str = "keygen.comment"; 156 | pub const KEY_VALID: &str = "keygen.valid"; 157 | pub const KEY_FLAGS: &str = "keygen.flags"; 158 | pub const KEY_SIZE: &str = "keygen.size"; 159 | pub const KEY_ALGORITHM: &str = "keygen.algo"; 160 | pub const KEY_UID_COMMAND: &str = "keygen.userid.cmd"; 161 | pub const KEY_CURVE: &str = "keygen.curve"; 162 | 163 | pub trait Editor: UnwindSafe + Send { 164 | type State: fmt::Debug + Default + Eq + Copy + UnwindSafe + Send; 165 | 166 | fn next_state( 167 | state: Result, 168 | status: EditInteractionStatus<'_>, 169 | need_response: bool, 170 | ) -> Result; 171 | fn action(&self, state: Self::State, out: &mut dyn Write) -> Result<()>; 172 | } 173 | 174 | #[derive(Debug)] 175 | pub struct EditorWrapper { 176 | editor: E, 177 | state: Result, 178 | } 179 | 180 | impl EditorWrapper { 181 | pub fn new(editor: E) -> EditorWrapper { 182 | EditorWrapper { 183 | editor, 184 | state: Ok(E::State::default()), 185 | } 186 | } 187 | } 188 | 189 | impl EditInteractor for EditorWrapper { 190 | fn interact( 191 | &mut self, 192 | status: EditInteractionStatus<'_>, 193 | out: Option<&mut dyn Write>, 194 | ) -> Result<()> { 195 | let old_state = self.state; 196 | self.state = status 197 | .code 198 | .into_result() 199 | .and_then(|_| E::next_state(self.state, status, out.is_some())) 200 | .and_then(|state| { 201 | if old_state == Ok(state) { 202 | return Ok(state); 203 | } 204 | 205 | out.map_or(Ok(()), |out| { 206 | self.editor 207 | .action(state, out) 208 | .and_then(|_| out.write_all(b"\n").map_err(Error::from)) 209 | }) 210 | .and(Ok(state)) 211 | }); 212 | self.state.and(Ok(())) 213 | } 214 | } 215 | -------------------------------------------------------------------------------- /src/engine.rs: -------------------------------------------------------------------------------- 1 | use std::{ffi::CStr, fmt, marker::PhantomData, str::Utf8Error}; 2 | 3 | use ffi; 4 | 5 | use crate::{utils, Context, NonNull, Protocol, Result}; 6 | 7 | /// Upstream documentation: 8 | /// [`gpgme_engine_info_t`](https://www.gnupg.org/documentation/manuals/gpgme/Engine-Information.html#index-gpgme_005fengine_005finfo_005ft) 9 | #[derive(Copy, Clone)] 10 | pub struct EngineInfo<'a>(NonNull, PhantomData<&'a ()>); 11 | 12 | unsafe impl Send for EngineInfo<'_> {} 13 | unsafe impl Sync for EngineInfo<'_> {} 14 | 15 | impl<'e> EngineInfo<'e> { 16 | impl_wrapper!(ffi::gpgme_engine_info_t, PhantomData); 17 | 18 | /// Returns the `Protocol` implemented by the engine. 19 | #[inline] 20 | pub fn protocol(&self) -> Protocol { 21 | unsafe { Protocol::from_raw((*self.as_raw()).protocol) } 22 | } 23 | 24 | #[inline] 25 | pub fn path(&self) -> Result<&'e str, Option> { 26 | self.path_raw() 27 | .map_or(Err(None), |s| s.to_str().map_err(Some)) 28 | } 29 | 30 | #[inline] 31 | pub fn path_raw(&self) -> Option<&'e CStr> { 32 | unsafe { utils::convert_raw_str((*self.as_raw()).file_name) } 33 | } 34 | 35 | #[inline] 36 | pub fn home_dir(&self) -> Result<&str, Option> { 37 | self.home_dir_raw() 38 | .map_or(Err(None), |s| s.to_str().map_err(Some)) 39 | } 40 | 41 | #[inline] 42 | pub fn home_dir_raw(&self) -> Option<&'e CStr> { 43 | unsafe { utils::convert_raw_str((*self.as_raw()).home_dir) } 44 | } 45 | 46 | #[inline] 47 | pub fn check_version(&self, v: &str) -> bool { 48 | self.version() 49 | .map(|s| { 50 | let it1 = s.split('.').scan((), |_, x| x.parse::().ok()); 51 | let it2 = v.split('.').scan((), |_, x| x.parse::().ok()); 52 | Iterator::ge(it1, it2) 53 | }) 54 | .unwrap_or(false) 55 | } 56 | 57 | #[inline] 58 | pub fn version(&self) -> Result<&'e str, Option> { 59 | self.version_raw() 60 | .map_or(Err(None), |s| s.to_str().map_err(Some)) 61 | } 62 | 63 | #[inline] 64 | pub fn version_raw(&self) -> Option<&'e CStr> { 65 | unsafe { utils::convert_raw_str((*self.as_raw()).version) } 66 | } 67 | 68 | #[inline] 69 | pub fn required_version(&self) -> Result<&'e str, Option> { 70 | self.required_version_raw() 71 | .map_or(Err(None), |s| s.to_str().map_err(Some)) 72 | } 73 | 74 | #[inline] 75 | pub fn required_version_raw(&self) -> Option<&'e CStr> { 76 | unsafe { utils::convert_raw_str((*self.as_raw()).req_version) } 77 | } 78 | } 79 | 80 | impl fmt::Debug for EngineInfo<'_> { 81 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 82 | f.debug_struct("EngineInfo") 83 | .field("raw", &self.as_raw()) 84 | .field("protocol", &self.protocol()) 85 | .field("path", &self.path_raw()) 86 | .field("home_dir", &self.home_dir_raw()) 87 | .field("version", &self.version_raw()) 88 | .field("required_version", &self.required_version_raw()) 89 | .finish() 90 | } 91 | } 92 | 93 | impl_list_iterator!(pub struct EngineInfos(EngineInfo: ffi::gpgme_engine_info_t)); 94 | 95 | /// A RAII guard type that ensures the global engine information list is not modified 96 | /// while it is being iterated. 97 | pub struct EngineInfoGuard { 98 | snapshot: Context, 99 | } 100 | 101 | unsafe impl Sync for EngineInfoGuard {} 102 | 103 | impl EngineInfoGuard { 104 | pub(crate) fn new() -> Result { 105 | Ok(Self { 106 | snapshot: Context::new()?, 107 | }) 108 | } 109 | 110 | #[inline] 111 | pub fn get(&self, proto: Protocol) -> Option> { 112 | self.into_iter().find(|info| info.protocol() == proto) 113 | } 114 | 115 | #[inline] 116 | pub fn iter(&self) -> EngineInfos<'_> { 117 | self.into_iter() 118 | } 119 | } 120 | 121 | impl<'a> IntoIterator for &'a EngineInfoGuard { 122 | type Item = EngineInfo<'a>; 123 | type IntoIter = EngineInfos<'a>; 124 | 125 | #[inline] 126 | fn into_iter(self) -> Self::IntoIter { 127 | self.snapshot.engines() 128 | } 129 | } 130 | 131 | impl fmt::Debug for EngineInfoGuard { 132 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 133 | f.write_str("EngineInfoGuard(..)") 134 | } 135 | } 136 | -------------------------------------------------------------------------------- /src/flags.rs: -------------------------------------------------------------------------------- 1 | #![allow(trivial_numeric_casts)] 2 | use std::{ffi::CStr, fmt, str::Utf8Error}; 3 | 4 | use bitflags::bitflags; 5 | 6 | use crate::utils; 7 | 8 | bitflags! { 9 | /// Upstream documentation: 10 | /// [`gpgme_keylist_mode_t`](https://www.gnupg.org/documentation/manuals/gpgme/Key-Listing-Mode.html#Key-Listing-Mode) 11 | #[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)] 12 | pub struct KeyListMode: ffi::gpgme_keylist_mode_t { 13 | const LOCAL = ffi::GPGME_KEYLIST_MODE_LOCAL; 14 | const EXTERN = ffi::GPGME_KEYLIST_MODE_EXTERN; 15 | const SIGS = ffi::GPGME_KEYLIST_MODE_SIGS; 16 | const SIG_NOTATIONS = ffi::GPGME_KEYLIST_MODE_SIG_NOTATIONS; 17 | const WITH_SECRET = ffi::GPGME_KEYLIST_MODE_WITH_SECRET; 18 | const WITH_KEYGRIP = ffi::GPGME_KEYLIST_MODE_WITH_KEYGRIP; 19 | const WITH_TOFU = ffi::GPGME_KEYLIST_MODE_WITH_TOFU; 20 | const WITH_V5FPR = ffi::GPGME_KEYLIST_MODE_WITH_V5FPR; 21 | const EPHEMERAL = ffi::GPGME_KEYLIST_MODE_EPHEMERAL; 22 | const VALIDATE = ffi::GPGME_KEYLIST_MODE_VALIDATE; 23 | const FORCE_EXTERN = ffi::GPGME_KEYLIST_MODE_FORCE_EXTERN; 24 | 25 | const LOCATE = ffi::GPGME_KEYLIST_MODE_LOCATE; 26 | const LOCATE_EXTERNAL = ffi::GPGME_KEYLIST_MODE_LOCATE_EXTERNAL; 27 | } 28 | } 29 | 30 | impl KeyListMode { 31 | #[deprecated = "use the safe `from_bits_retain` method instead"] 32 | pub const unsafe fn from_bits_unchecked(bits: ffi::gpgme_keylist_mode_t) -> Self { 33 | Self::from_bits_retain(bits) 34 | } 35 | } 36 | 37 | bitflags! { 38 | #[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)] 39 | pub struct InteractFlags: libc::c_uint { 40 | const CARD = ffi::GPGME_INTERACT_CARD; 41 | } 42 | } 43 | 44 | impl InteractFlags { 45 | #[deprecated = "use the safe `from_bits_retain` method instead"] 46 | pub const unsafe fn from_bits_unchecked(bits: libc::c_uint) -> Self { 47 | Self::from_bits_retain(bits) 48 | } 49 | } 50 | 51 | bitflags! { 52 | /// Upstream documentation: 53 | /// [`gpgme_op_createkey`](https://www.gnupg.org/documentation/manuals/gpgme/Generating-Keys.html#index-gpgme_005fop_005fcreatekey) 54 | #[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)] 55 | pub struct CreateKeyFlags: libc::c_uint { 56 | const SIGN = ffi::GPGME_CREATE_SIGN; 57 | const ENCR = ffi::GPGME_CREATE_ENCR; 58 | const CERT = ffi::GPGME_CREATE_CERT; 59 | const AUTH = ffi::GPGME_CREATE_AUTH; 60 | const NOPASSWD = ffi::GPGME_CREATE_NOPASSWD; 61 | const SELFSIGNED = ffi::GPGME_CREATE_SELFSIGNED; 62 | const NOSTORE = ffi::GPGME_CREATE_NOSTORE; 63 | const WANTPUB = ffi::GPGME_CREATE_WANTPUB; 64 | const WANTSEC = ffi::GPGME_CREATE_WANTSEC; 65 | const FORCE = ffi::GPGME_CREATE_FORCE; 66 | const NOEXPIRE = ffi::GPGME_CREATE_NOEXPIRE; 67 | } 68 | } 69 | 70 | impl CreateKeyFlags { 71 | #[deprecated = "use the safe `from_bits_retain` method instead"] 72 | pub const unsafe fn from_bits_unchecked(bits: libc::c_uint) -> Self { 73 | Self::from_bits_retain(bits) 74 | } 75 | } 76 | 77 | bitflags! { 78 | /// Upstream documentation: 79 | /// [`gpgme_op_delete_ext`](https://www.gnupg.org/documentation/manuals/gpgme/Deleting-Keys.html#index-gpgme_005fop_005fdelete_005fext) 80 | #[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)] 81 | pub struct DeleteKeyFlags: libc::c_uint { 82 | const ALLOW_SECRET = ffi::GPGME_DELETE_ALLOW_SECRET; 83 | const FORCE = ffi::GPGME_DELETE_FORCE; 84 | } 85 | } 86 | 87 | impl DeleteKeyFlags { 88 | #[deprecated = "use the safe `from_bits_retain` method instead"] 89 | pub const unsafe fn from_bits_unchecked(bits: libc::c_uint) -> Self { 90 | Self::from_bits_retain(bits) 91 | } 92 | } 93 | 94 | bitflags! { 95 | /// Upstream documentation: 96 | /// [`gpgme_op_keysign`](https://www.gnupg.org/documentation/manuals/gpgme/Signing-Keys.html#index-gpgme_005fop_005fkeysign) 97 | #[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)] 98 | pub struct KeySigningFlags: libc::c_uint { 99 | const LOCAL = ffi::GPGME_KEYSIGN_LOCAL; 100 | const LFSEP = ffi::GPGME_KEYSIGN_LFSEP; 101 | const NOEXPIRE = ffi::GPGME_KEYSIGN_NOEXPIRE; 102 | const FORCE = ffi::GPGME_KEYSIGN_FORCE; 103 | } 104 | } 105 | 106 | impl KeySigningFlags { 107 | #[deprecated = "use the safe `from_bits_retain` method instead"] 108 | pub const unsafe fn from_bits_unchecked(bits: libc::c_uint) -> Self { 109 | Self::from_bits_retain(bits) 110 | } 111 | } 112 | 113 | bitflags! { 114 | /// Upstream documentation: 115 | /// [`gpgme_import_status_t`](https://www.gnupg.org/documentation/manuals/gpgme/Importing-Keys.html#index-gpgme_005fimport_005fstatus_005ft) 116 | #[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)] 117 | pub struct ImportFlags: libc::c_uint { 118 | const NEW = ffi::GPGME_IMPORT_NEW; 119 | const UID = ffi::GPGME_IMPORT_UID; 120 | const SIG = ffi::GPGME_IMPORT_SIG; 121 | const SUBKEY = ffi::GPGME_IMPORT_SUBKEY; 122 | const SECRET = ffi::GPGME_IMPORT_SECRET; 123 | } 124 | } 125 | 126 | impl ImportFlags { 127 | #[deprecated = "use the safe `from_bits_retain` method instead"] 128 | pub const unsafe fn from_bits_unchecked(bits: libc::c_uint) -> Self { 129 | Self::from_bits_retain(bits) 130 | } 131 | } 132 | 133 | bitflags! { 134 | /// Upstream documentation: 135 | /// [`gpgme_export_mode_t`](https://www.gnupg.org/documentation/manuals/gpgme/Exporting-Keys.html) 136 | #[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)] 137 | pub struct ExportMode: ffi::gpgme_export_mode_t { 138 | const EXTERN = ffi::GPGME_EXPORT_MODE_EXTERN; 139 | const MINIMAL = ffi::GPGME_EXPORT_MODE_MINIMAL; 140 | const SECRET = ffi::GPGME_EXPORT_MODE_SECRET; 141 | const RAW = ffi::GPGME_EXPORT_MODE_RAW; 142 | const PKCS12 = ffi::GPGME_EXPORT_MODE_PKCS12; 143 | const SSH = ffi::GPGME_EXPORT_MODE_SSH; 144 | const SECRET_SUBKEY = ffi::GPGME_EXPORT_MODE_SECRET_SUBKEY; 145 | } 146 | } 147 | 148 | impl ExportMode { 149 | #[deprecated = "use the safe `from_bits_retain` method instead"] 150 | pub const unsafe fn from_bits_unchecked(bits: ffi::gpgme_export_mode_t) -> Self { 151 | Self::from_bits_retain(bits) 152 | } 153 | } 154 | 155 | bitflags! { 156 | /// Upstream documentation: 157 | /// [`gpgme_op_encrypt`](https://www.gnupg.org/documentation/manuals/gpgme/Encrypting-a-Plaintext.html#index-gpgme_005fop_005fencrypt) 158 | #[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)] 159 | pub struct EncryptFlags: ffi::gpgme_encrypt_flags_t { 160 | const ALWAYS_TRUST = ffi::GPGME_ENCRYPT_ALWAYS_TRUST; 161 | const NO_ENCRYPT_TO = ffi::GPGME_ENCRYPT_NO_ENCRYPT_TO; 162 | const PREPARE = ffi::GPGME_ENCRYPT_PREPARE; 163 | const EXPECT_SIGN = ffi::GPGME_ENCRYPT_EXPECT_SIGN; 164 | const NO_COMPRESS= ffi::GPGME_ENCRYPT_NO_COMPRESS; 165 | const SYMMETRIC = ffi::GPGME_ENCRYPT_SYMMETRIC; 166 | const THROW_KEYIDS = ffi::GPGME_ENCRYPT_THROW_KEYIDS; 167 | const WRAP = ffi::GPGME_ENCRYPT_WRAP; 168 | const WANT_ADDRESS = ffi::GPGME_ENCRYPT_WANT_ADDRESS; 169 | const ARCHIVE = ffi::GPGME_ENCRYPT_ARCHIVE; 170 | } 171 | } 172 | 173 | impl EncryptFlags { 174 | #[deprecated = "use the safe `from_bits_retain` method instead"] 175 | pub const unsafe fn from_bits_unchecked(bits: ffi::gpgme_encrypt_flags_t) -> Self { 176 | Self::from_bits_retain(bits) 177 | } 178 | } 179 | 180 | bitflags! { 181 | /// Upstream documentation: 182 | /// [`gpgme_op_decrypt_ext`](https://www.gnupg.org/documentation/manuals/gpgme/Decrypt.html#index-gpgme_005fop_005fdecrypt_005fext) 183 | #[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)] 184 | pub struct DecryptFlags: ffi::gpgme_decrypt_flags_t { 185 | const VERIFY = ffi::GPGME_DECRYPT_VERIFY; 186 | const ARCHIVE = ffi::GPGME_DECRYPT_ARCHIVE; 187 | const UNWRAP = ffi::GPGME_DECRYPT_UNWRAP; 188 | } 189 | } 190 | 191 | impl DecryptFlags { 192 | #[deprecated = "use the safe `from_bits_retain` method instead"] 193 | pub const unsafe fn from_bits_unchecked(bits: ffi::gpgme_decrypt_flags_t) -> Self { 194 | Self::from_bits_retain(bits) 195 | } 196 | } 197 | 198 | bitflags! { 199 | /// Upstream documentation: 200 | /// [`gpgme_op_verify_ext`](https://www.gnupg.org/documentation/manuals/gpgme/Verify.html#index-gpgme_005fop_005fverify_005fext) 201 | #[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)] 202 | pub struct VerifyFlags: ffi::gpgme_verify_flags_t { 203 | const ARCHIVE = ffi::GPGME_VERIFY_ARCHIVE; 204 | } 205 | } 206 | 207 | impl VerifyFlags { 208 | #[deprecated = "use the safe `from_bits_retain` method instead"] 209 | pub const unsafe fn from_bits_unchecked(bits: ffi::gpgme_verify_flags_t) -> Self { 210 | Self::from_bits_retain(bits) 211 | } 212 | } 213 | 214 | bitflags! { 215 | /// Upstream documentation: 216 | /// [`gpgme_sigsum_t`](https://www.gnupg.org/documentation/manuals/gpgme/Verify.html#index-gpgme_005fsignature_005ft) 217 | #[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)] 218 | pub struct SignatureSummary: ffi::gpgme_sigsum_t { 219 | const VALID = ffi::GPGME_SIGSUM_VALID; 220 | const GREEN = ffi::GPGME_SIGSUM_GREEN; 221 | const RED = ffi::GPGME_SIGSUM_RED; 222 | const KEY_REVOKED = ffi::GPGME_SIGSUM_KEY_REVOKED; 223 | const KEY_EXPIRED = ffi::GPGME_SIGSUM_KEY_EXPIRED; 224 | const SIG_EXPIRED = ffi::GPGME_SIGSUM_SIG_EXPIRED; 225 | const KEY_MISSING = ffi::GPGME_SIGSUM_KEY_MISSING; 226 | const CRL_MISSING = ffi::GPGME_SIGSUM_CRL_MISSING; 227 | const CRL_TOO_OLD = ffi::GPGME_SIGSUM_CRL_TOO_OLD; 228 | const BAD_POLICY = ffi::GPGME_SIGSUM_BAD_POLICY; 229 | const SYS_ERROR = ffi::GPGME_SIGSUM_SYS_ERROR; 230 | const TOFU_CONFLICT = ffi::GPGME_SIGSUM_TOFU_CONFLICT; 231 | } 232 | } 233 | 234 | impl SignatureSummary { 235 | #[deprecated = "use the safe `from_bits_retain` method instead"] 236 | pub const unsafe fn from_bits_unchecked(bits: ffi::gpgme_sigsum_t) -> Self { 237 | Self::from_bits_retain(bits) 238 | } 239 | } 240 | 241 | bitflags! { 242 | /// Upstream documentation: 243 | /// [`gpgme_sig_notation_flags_t`](https://www.gnupg.org/documentation/manuals/gpgme/Verify.html#index-gpgme_005fsig_005fnotation_005ft) 244 | #[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)] 245 | pub struct SignatureNotationFlags: ffi::gpgme_sig_notation_flags_t { 246 | const HUMAN_READABLE = ffi::GPGME_SIG_NOTATION_HUMAN_READABLE; 247 | const CRITICAL = ffi::GPGME_SIG_NOTATION_CRITICAL; 248 | } 249 | } 250 | 251 | impl SignatureNotationFlags { 252 | #[deprecated = "use the safe `from_bits_retain` method instead"] 253 | pub const unsafe fn from_bits_unchecked(bits: ffi::gpgme_sig_notation_flags_t) -> Self { 254 | Self::from_bits_retain(bits) 255 | } 256 | } 257 | 258 | bitflags! { 259 | /// Upstream documentation: 260 | /// [`gpgme_op_getauditlog`](https://www.gnupg.org/documentation/manuals/gpgme/Additional-Logs.html#index-gpgme_005fop_005fgetauditlog) 261 | #[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)] 262 | pub struct AuditLogFlags: libc::c_uint { 263 | const DEFAULT = ffi::GPGME_AUDITLOG_DEFAULT; 264 | const HTML = ffi::GPGME_AUDITLOG_HTML; 265 | const DIAG = ffi::GPGME_AUDITLOG_DIAG; 266 | const WITH_HELP = ffi::GPGME_AUDITLOG_WITH_HELP; 267 | } 268 | } 269 | 270 | impl AuditLogFlags { 271 | #[deprecated = "use the safe `from_bits_retain` method instead"] 272 | pub const unsafe fn from_bits_unchecked(bits: libc::c_uint) -> Self { 273 | Self::from_bits_retain(bits) 274 | } 275 | } 276 | 277 | ffi_enum_wrapper! { 278 | /// Upstream documentation: 279 | /// [`gpgme_keyorg_t`](https://www.gnupg.org/documentation/manuals/gpgme/Key-objects.html#index-gpgme_005fuser_005fid_005ft) 280 | #[non_exhaustive] 281 | pub enum KeyOrigin: ffi::gpgme_keyorg_t { 282 | Unknown = ffi::GPGME_KEYORG_UNKNOWN, 283 | KeyServer = ffi::GPGME_KEYORG_KS, 284 | Dane = ffi::GPGME_KEYORG_DANE, 285 | Wkd = ffi::GPGME_KEYORG_WKD, 286 | Url = ffi::GPGME_KEYORG_URL, 287 | File = ffi::GPGME_KEYORG_FILE, 288 | Self_ = ffi::GPGME_KEYORG_SELF, 289 | } 290 | } 291 | 292 | ffi_enum_wrapper! { 293 | /// Upstream documentation: 294 | /// [`gpgme_sig_mode_t`](https://www.gnupg.org/documentation/manuals/gpgme/Creating-a-Signature.html#index-enum-gpgme_005fsig_005fmode_005ft) 295 | pub enum SignMode: ffi::gpgme_sig_mode_t { 296 | Normal = ffi::GPGME_SIG_MODE_NORMAL, 297 | Detached = ffi::GPGME_SIG_MODE_DETACH, 298 | Clear = ffi::GPGME_SIG_MODE_CLEAR, 299 | // FIXME: breaking change needed to convert to bitflags 300 | // Archive = ffi::GPGME_SIG_MODE_ARCHIVE, 301 | } 302 | } 303 | 304 | ffi_enum_wrapper! { 305 | /// Upstream documentation: 306 | /// [`gpgme_pubkey_algo_t`](https://www.gnupg.org/documentation/manuals/gpgme/Public-Key-Algorithms.html#index-gpgme_005fpubkey_005falgo_005ft) 307 | #[non_exhaustive] 308 | pub enum KeyAlgorithm: ffi::gpgme_pubkey_algo_t { 309 | Rsa = ffi::GPGME_PK_RSA, 310 | RsaEncrypt = ffi::GPGME_PK_RSA_E, 311 | RsaSign = ffi::GPGME_PK_RSA_S, 312 | ElgamalEncrypt = ffi::GPGME_PK_ELG_E, 313 | Dsa = ffi::GPGME_PK_DSA, 314 | Ecc = ffi::GPGME_PK_ECC, 315 | Elgamal = ffi::GPGME_PK_ELG, 316 | Ecdsa = ffi::GPGME_PK_ECDSA, 317 | Ecdh = ffi::GPGME_PK_ECDH, 318 | Eddsa = ffi::GPGME_PK_EDDSA, 319 | } 320 | } 321 | 322 | impl KeyAlgorithm { 323 | /// Upstream documentation: 324 | /// [`gpgme_pubkey_algo_name`](https://www.gnupg.org/documentation/manuals/gpgme/Public-Key-Algorithms.html#index-gpgme_005fpubkey_005falgo_005fname) 325 | #[inline] 326 | pub fn name(&self) -> Result<&'static str, Option> { 327 | self.name_raw() 328 | .map_or(Err(None), |s| s.to_str().map_err(Some)) 329 | } 330 | 331 | /// Upstream documentation: 332 | /// [`gpgme_pubkey_algo_name`](https://www.gnupg.org/documentation/manuals/gpgme/Public-Key-Algorithms.html#index-gpgme_005fpubkey_005falgo_005fname) 333 | #[inline] 334 | pub fn name_raw(&self) -> Option<&'static CStr> { 335 | unsafe { utils::convert_raw_str(ffi::gpgme_pubkey_algo_name(self.raw())) } 336 | } 337 | } 338 | 339 | impl fmt::Display for KeyAlgorithm { 340 | #[inline] 341 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 342 | f.write_str(self.name().unwrap_or("Unknown")) 343 | } 344 | } 345 | 346 | ffi_enum_wrapper! { 347 | /// Upstream documentation: 348 | /// [`gpgme_hash_algo_t`](https://www.gnupg.org/documentation/manuals/gpgme/Hash-Algorithms.html#index-enum-gpgme_005fhash_005falgo_005ft) 349 | #[non_exhaustive] 350 | pub enum HashAlgorithm: ffi::gpgme_hash_algo_t { 351 | None = ffi::GPGME_MD_NONE, 352 | Md2 = ffi::GPGME_MD_MD2, 353 | Md4 = ffi::GPGME_MD_MD4, 354 | Md5 = ffi::GPGME_MD_MD5, 355 | Sha1 = ffi::GPGME_MD_SHA1, 356 | Sha224 = ffi::GPGME_MD_SHA224, 357 | Sha256 = ffi::GPGME_MD_SHA256, 358 | Sha384 = ffi::GPGME_MD_SHA384, 359 | Sha512 = ffi::GPGME_MD_SHA512, 360 | RipeMd160 = ffi::GPGME_MD_RMD160, 361 | Tiger = ffi::GPGME_MD_TIGER, 362 | Haval = ffi::GPGME_MD_HAVAL, 363 | Crc32 = ffi::GPGME_MD_CRC32, 364 | Crc32Rfc1510 = ffi::GPGME_MD_CRC32_RFC1510, 365 | CrC24Rfc2440 = ffi::GPGME_MD_CRC24_RFC2440, 366 | } 367 | } 368 | 369 | impl HashAlgorithm { 370 | /// Upstream documentation: 371 | /// [`gpgme_hash_algo_name`](https://www.gnupg.org/documentation/manuals/gpgme/Hash-Algorithms.html#index-gpgme_005fhash_005falgo_005fname) 372 | #[inline] 373 | pub fn name(&self) -> Result<&'static str, Option> { 374 | self.name_raw() 375 | .map_or(Err(None), |s| s.to_str().map_err(Some)) 376 | } 377 | 378 | /// Upstream documentation: 379 | /// [`gpgme_hash_algo_name`](https://www.gnupg.org/documentation/manuals/gpgme/Hash-Algorithms.html#index-gpgme_005fhash_005falgo_005fname) 380 | #[inline] 381 | pub fn name_raw(&self) -> Option<&'static CStr> { 382 | unsafe { utils::convert_raw_str(ffi::gpgme_hash_algo_name(self.raw())) } 383 | } 384 | } 385 | 386 | impl fmt::Display for HashAlgorithm { 387 | #[inline] 388 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 389 | f.write_str(self.name().unwrap_or("Unknown")) 390 | } 391 | } 392 | 393 | ffi_enum_wrapper! { 394 | /// Upstream documentation: 395 | /// [`gpgme_pinentry_mode_t`](https://www.gnupg.org/documentation/manuals/gpgme/Pinentry-Mode.html#index-gpgme_005fpinentry_005fmode_005ft) 396 | pub enum PinentryMode: ffi::gpgme_pinentry_mode_t { 397 | Default = ffi::GPGME_PINENTRY_MODE_DEFAULT, 398 | Ask = ffi::GPGME_PINENTRY_MODE_ASK, 399 | Cancel = ffi::GPGME_PINENTRY_MODE_CANCEL, 400 | Error = ffi::GPGME_PINENTRY_MODE_ERROR, 401 | Loopback = ffi::GPGME_PINENTRY_MODE_LOOPBACK, 402 | } 403 | } 404 | -------------------------------------------------------------------------------- /src/keys.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | ffi::CStr, 3 | fmt, 4 | marker::PhantomData, 5 | str::Utf8Error, 6 | time::{Duration, SystemTime, UNIX_EPOCH}, 7 | }; 8 | 9 | use cstr_argument::CStrArgument; 10 | use ffi; 11 | 12 | use crate::{ 13 | notation::SignatureNotations, utils, Context, Error, KeyAlgorithm, KeyListMode, NonNull, 14 | Protocol, Result, Validity, 15 | }; 16 | 17 | /// Upstream documentation: 18 | /// [`gpgme_key_t`](https://www.gnupg.org/documentation/manuals/gpgme/Key-objects.html#index-gpgme_005fkey_005ft) 19 | pub struct Key(NonNull); 20 | 21 | unsafe impl Send for Key {} 22 | unsafe impl Sync for Key {} 23 | 24 | impl Drop for Key { 25 | #[inline] 26 | fn drop(&mut self) { 27 | unsafe { 28 | ffi::gpgme_key_unref(self.as_raw()); 29 | } 30 | } 31 | } 32 | 33 | impl Clone for Key { 34 | #[inline] 35 | fn clone(&self) -> Key { 36 | unsafe { 37 | ffi::gpgme_key_ref(self.as_raw()); 38 | Key(self.0) 39 | } 40 | } 41 | } 42 | 43 | impl Key { 44 | impl_wrapper!(ffi::gpgme_key_t); 45 | 46 | #[inline] 47 | pub fn is_bad(&self) -> bool { 48 | self.is_revoked() || self.is_expired() || self.is_disabled() || self.is_invalid() 49 | } 50 | 51 | #[inline] 52 | pub fn is_revoked(&self) -> bool { 53 | unsafe { (*self.as_raw()).revoked() } 54 | } 55 | 56 | #[inline] 57 | pub fn is_expired(&self) -> bool { 58 | unsafe { (*self.as_raw()).expired() } 59 | } 60 | 61 | #[inline] 62 | pub fn is_disabled(&self) -> bool { 63 | unsafe { (*self.as_raw()).disabled() } 64 | } 65 | 66 | #[inline] 67 | pub fn is_invalid(&self) -> bool { 68 | unsafe { (*self.as_raw()).invalid() } 69 | } 70 | 71 | #[inline] 72 | pub fn can_encrypt(&self) -> bool { 73 | unsafe { (*self.as_raw()).can_encrypt() } 74 | } 75 | 76 | #[inline] 77 | pub fn can_sign(&self) -> bool { 78 | unsafe { (*self.as_raw()).can_sign() } 79 | } 80 | 81 | #[inline] 82 | pub fn can_certify(&self) -> bool { 83 | unsafe { (*self.as_raw()).can_certify() } 84 | } 85 | 86 | #[inline] 87 | pub fn can_authenticate(&self) -> bool { 88 | unsafe { (*self.as_raw()).can_authenticate() } 89 | } 90 | 91 | #[inline] 92 | pub fn is_qualified(&self) -> bool { 93 | unsafe { (*self.as_raw()).is_qualified() } 94 | } 95 | 96 | #[inline] 97 | pub fn is_de_vs(&self) -> bool { 98 | self.subkeys().all(|x| x.is_de_vs()) 99 | } 100 | 101 | #[inline] 102 | pub fn has_secret(&self) -> bool { 103 | unsafe { (*self.as_raw()).secret() } 104 | } 105 | 106 | #[inline] 107 | #[cfg(feature = "v1_23")] 108 | pub fn has_encrypt(&self) -> bool { 109 | unsafe { (*self.as_raw()).has_encrypt() } 110 | } 111 | 112 | #[inline] 113 | #[cfg(feature = "v1_23")] 114 | pub fn has_sign(&self) -> bool { 115 | unsafe { (*self.as_raw()).has_sign() } 116 | } 117 | 118 | #[inline] 119 | #[cfg(feature = "v1_23")] 120 | pub fn has_certify(&self) -> bool { 121 | unsafe { (*self.as_raw()).has_certify() } 122 | } 123 | 124 | #[inline] 125 | #[cfg(feature = "v1_23")] 126 | pub fn has_authenticate(&self) -> bool { 127 | unsafe { (*self.as_raw()).has_authenticate() } 128 | } 129 | 130 | #[inline] 131 | pub fn is_root(&self) -> bool { 132 | if let (Some(fpr), Some(chain_id)) = (self.fingerprint_raw(), self.chain_id_raw()) { 133 | fpr.to_bytes().eq_ignore_ascii_case(chain_id.to_bytes()) 134 | } else { 135 | false 136 | } 137 | } 138 | 139 | #[inline] 140 | pub fn owner_trust(&self) -> Validity { 141 | unsafe { Validity::from_raw((*self.as_raw()).owner_trust) } 142 | } 143 | 144 | #[inline] 145 | pub fn protocol(&self) -> Protocol { 146 | unsafe { Protocol::from_raw((*self.as_raw()).protocol) } 147 | } 148 | 149 | #[inline] 150 | pub fn issuer_serial(&self) -> Result<&str, Option> { 151 | self.issuer_serial_raw() 152 | .map_or(Err(None), |s| s.to_str().map_err(Some)) 153 | } 154 | 155 | #[inline] 156 | pub fn issuer_serial_raw(&self) -> Option<&CStr> { 157 | unsafe { utils::convert_raw_str((*self.as_raw()).issuer_serial) } 158 | } 159 | 160 | #[inline] 161 | pub fn issuer_name(&self) -> Result<&str, Option> { 162 | self.issuer_name_raw() 163 | .map_or(Err(None), |s| s.to_str().map_err(Some)) 164 | } 165 | 166 | #[inline] 167 | pub fn issuer_name_raw(&self) -> Option<&CStr> { 168 | unsafe { utils::convert_raw_str((*self.as_raw()).issuer_name) } 169 | } 170 | 171 | #[inline] 172 | pub fn chain_id(&self) -> Result<&str, Option> { 173 | self.chain_id_raw() 174 | .map_or(Err(None), |s| s.to_str().map_err(Some)) 175 | } 176 | 177 | #[inline] 178 | pub fn chain_id_raw(&self) -> Option<&CStr> { 179 | unsafe { utils::convert_raw_str((*self.as_raw()).chain_id) } 180 | } 181 | 182 | #[inline] 183 | pub fn id(&self) -> Result<&str, Option> { 184 | self.primary_key().map_or(Err(None), |k| k.id()) 185 | } 186 | 187 | #[inline] 188 | pub fn id_raw(&self) -> Option<&CStr> { 189 | self.primary_key()?.id_raw() 190 | } 191 | 192 | #[inline] 193 | pub fn short_id(&self) -> Result<&str, Option> { 194 | self.short_id_raw() 195 | .map_or(Err(None), |s| s.to_str().map_err(Some)) 196 | } 197 | 198 | #[inline] 199 | pub fn short_id_raw(&self) -> Option<&CStr> { 200 | self.id_raw().map(|s| { 201 | let bytes = s.to_bytes_with_nul(); 202 | // One extra for the null terminator 203 | if let Some((_, short)) = bytes.split_last_chunk::<9>() { 204 | unsafe { CStr::from_bytes_with_nul_unchecked(short) } 205 | } else { 206 | s 207 | } 208 | }) 209 | } 210 | 211 | #[inline] 212 | pub fn fingerprint(&self) -> Result<&str, Option> { 213 | self.fingerprint_raw() 214 | .map_or(Err(None), |s| s.to_str().map_err(Some)) 215 | } 216 | 217 | #[inline] 218 | pub fn fingerprint_raw(&self) -> Option<&CStr> { 219 | unsafe { 220 | utils::convert_raw_str((*self.as_raw()).fpr) 221 | .or_else(|| self.primary_key()?.fingerprint_raw()) 222 | } 223 | } 224 | 225 | #[inline] 226 | pub fn key_list_mode(&self) -> KeyListMode { 227 | unsafe { KeyListMode::from_bits_retain((*self.as_raw()).keylist_mode) } 228 | } 229 | 230 | #[inline] 231 | pub fn origin(&self) -> crate::KeyOrigin { 232 | unsafe { crate::KeyOrigin::from_raw((*self.as_raw()).origin()) } 233 | } 234 | 235 | #[inline] 236 | pub fn primary_key(&self) -> Option> { 237 | self.subkeys().next() 238 | } 239 | 240 | #[inline] 241 | pub fn user_ids(&self) -> UserIds<'_> { 242 | unsafe { UserIds::from_list((*self.as_raw()).uids) } 243 | } 244 | 245 | #[inline] 246 | pub fn subkeys(&self) -> Subkeys<'_> { 247 | unsafe { Subkeys::from_list((*self.as_raw()).subkeys) } 248 | } 249 | 250 | #[inline] 251 | pub fn last_update(&self) -> SystemTime { 252 | let timestamp = unsafe { (*self.as_raw()).last_update }; 253 | UNIX_EPOCH + Duration::from_secs(timestamp.into()) 254 | } 255 | 256 | #[inline] 257 | pub fn update(&mut self) -> Result<()> { 258 | *self = self.updated()?; 259 | Ok(()) 260 | } 261 | 262 | #[inline] 263 | pub fn updated(&self) -> Result { 264 | let mut ctx = Context::from_protocol(self.protocol())?; 265 | let _ = ctx.set_key_list_mode(self.key_list_mode()); 266 | ctx.refresh_key(self) 267 | } 268 | 269 | #[inline] 270 | pub fn add_uid(&self, userid: impl CStrArgument) -> Result<()> { 271 | let mut ctx = Context::from_protocol(self.protocol())?; 272 | ctx.add_uid(self, userid) 273 | } 274 | } 275 | 276 | impl fmt::Debug for Key { 277 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 278 | f.debug_struct("Key") 279 | .field("raw", &self.as_raw()) 280 | .field("fingerprint", &self.fingerprint_raw()) 281 | .field("protocol", &self.protocol()) 282 | .field("owner_trust", &self.owner_trust()) 283 | .field("issuer", &self.issuer_name_raw()) 284 | .field("origin", &self.origin()) 285 | .field("last_update", &self.last_update()) 286 | .field("list_mode", &self.key_list_mode()) 287 | .field("has_secret", &self.has_secret()) 288 | .field("expired", &self.is_expired()) 289 | .field("revoked", &self.is_revoked()) 290 | .field("invalid", &self.is_invalid()) 291 | .field("disabled", &self.is_disabled()) 292 | .field("can_sign", &self.can_sign()) 293 | .field("can_encrypt", &self.can_encrypt()) 294 | .field("can_certify", &self.can_certify()) 295 | .field("can_auth", &self.can_authenticate()) 296 | .field("user_ids", &self.user_ids()) 297 | .field("subkeys", &self.subkeys()) 298 | .finish() 299 | } 300 | } 301 | 302 | /// Upstream documentation: [`gpgme_subkey_t`](https://www.gnupg.org/documentation/manuals/gpgme/Key-objects.html#index-gpgme_005fsubkey_005ft) 303 | #[derive(Copy, Clone)] 304 | pub struct Subkey<'key>(NonNull, PhantomData<&'key Key>); 305 | 306 | unsafe impl Send for Subkey<'_> {} 307 | unsafe impl Sync for Subkey<'_> {} 308 | 309 | impl<'key> Subkey<'key> { 310 | impl_wrapper!(ffi::gpgme_subkey_t, PhantomData); 311 | 312 | #[inline] 313 | pub fn id(&self) -> Result<&'key str, Option> { 314 | self.id_raw() 315 | .map_or(Err(None), |s| s.to_str().map_err(Some)) 316 | } 317 | 318 | #[inline] 319 | pub fn id_raw(&self) -> Option<&'key CStr> { 320 | unsafe { utils::convert_raw_str((*self.as_raw()).keyid) } 321 | } 322 | 323 | #[inline] 324 | pub fn fingerprint(&self) -> Result<&'key str, Option> { 325 | self.fingerprint_raw() 326 | .map_or(Err(None), |s| s.to_str().map_err(Some)) 327 | } 328 | 329 | #[inline] 330 | pub fn fingerprint_raw(&self) -> Option<&'key CStr> { 331 | unsafe { utils::convert_raw_str((*self.as_raw()).fpr) } 332 | } 333 | 334 | #[inline] 335 | pub fn creation_time(&self) -> Option { 336 | let timestamp = unsafe { (*self.as_raw()).timestamp }; 337 | if timestamp > 0 { 338 | Some(UNIX_EPOCH + Duration::from_secs(timestamp as u64)) 339 | } else { 340 | None 341 | } 342 | } 343 | 344 | #[inline] 345 | pub fn expiration_time(&self) -> Option { 346 | let expires = unsafe { (*self.as_raw()).expires }; 347 | if expires > 0 { 348 | Some(UNIX_EPOCH + Duration::from_secs(expires as u64)) 349 | } else { 350 | None 351 | } 352 | } 353 | 354 | #[inline] 355 | pub fn never_expires(&self) -> bool { 356 | self.expiration_time().is_none() 357 | } 358 | 359 | #[inline] 360 | pub fn is_bad(&self) -> bool { 361 | self.is_revoked() || self.is_expired() || self.is_disabled() || self.is_invalid() 362 | } 363 | 364 | #[inline] 365 | pub fn is_revoked(&self) -> bool { 366 | unsafe { (*self.as_raw()).revoked() } 367 | } 368 | 369 | #[inline] 370 | pub fn is_expired(&self) -> bool { 371 | unsafe { (*self.as_raw()).expired() } 372 | } 373 | 374 | #[inline] 375 | pub fn is_invalid(&self) -> bool { 376 | unsafe { (*self.as_raw()).invalid() } 377 | } 378 | 379 | #[inline] 380 | pub fn is_disabled(&self) -> bool { 381 | unsafe { (*self.as_raw()).disabled() } 382 | } 383 | 384 | #[inline] 385 | pub fn can_encrypt(&self) -> bool { 386 | unsafe { (*self.as_raw()).can_encrypt() } 387 | } 388 | 389 | #[inline] 390 | pub fn can_sign(&self) -> bool { 391 | unsafe { (*self.as_raw()).can_sign() } 392 | } 393 | 394 | #[inline] 395 | pub fn can_certify(&self) -> bool { 396 | unsafe { (*self.as_raw()).can_certify() } 397 | } 398 | 399 | #[inline] 400 | pub fn can_authenticate(&self) -> bool { 401 | unsafe { (*self.as_raw()).can_authenticate() } 402 | } 403 | 404 | #[inline] 405 | pub fn is_qualified(&self) -> bool { 406 | unsafe { (*self.as_raw()).is_qualified() } 407 | } 408 | 409 | #[inline] 410 | pub fn is_card_key(&self) -> bool { 411 | unsafe { (*self.as_raw()).is_cardkey() } 412 | } 413 | 414 | #[inline] 415 | pub fn is_secret(&self) -> bool { 416 | unsafe { (*self.as_raw()).secret() } 417 | } 418 | 419 | #[inline] 420 | pub fn is_de_vs(&self) -> bool { 421 | unsafe { (*self.as_raw()).is_de_vs() } 422 | } 423 | 424 | #[cfg(feature = "v1_20")] 425 | #[inline] 426 | pub fn can_renc(&self) -> bool { 427 | unsafe { (*self.as_raw()).can_renc() } 428 | } 429 | 430 | #[cfg(feature = "v1_20")] 431 | #[inline] 432 | pub fn can_timestamp(&self) -> bool { 433 | unsafe { (*self.as_raw()).can_timestamp() } 434 | } 435 | 436 | #[cfg(feature = "v1_20")] 437 | #[inline] 438 | pub fn is_group_owned(&self) -> bool { 439 | unsafe { (*self.as_raw()).is_group_owned() } 440 | } 441 | 442 | #[inline] 443 | pub fn algorithm(&self) -> KeyAlgorithm { 444 | unsafe { KeyAlgorithm::from_raw((*self.as_raw()).pubkey_algo) } 445 | } 446 | 447 | /// Upstream documentation: [`gpgme_pubkey_algo_string`](https://www.gnupg.org/documentation/manuals/gpgme/Public-Key-Algorithms.html#index-gpgme_005fpubkey_005falgo_005fstring) 448 | #[inline] 449 | pub fn algorithm_name(&self) -> Result { 450 | unsafe { 451 | let s = ffi::gpgme_pubkey_algo_string(self.as_raw()); 452 | if !s.is_null() { 453 | let res = CStr::from_ptr(s) 454 | .to_str() 455 | .expect("algorithm name is not valid utf-8") 456 | .to_owned(); 457 | ffi::gpgme_free(s.cast()); 458 | Ok(res) 459 | } else { 460 | Err(Error::last_os_error()) 461 | } 462 | } 463 | } 464 | 465 | #[inline] 466 | pub fn keygrip(&self) -> Result<&'key str, Option> { 467 | self.keygrip_raw() 468 | .map_or(Err(None), |s| s.to_str().map_err(Some)) 469 | } 470 | 471 | #[inline] 472 | pub fn keygrip_raw(&self) -> Option<&'key CStr> { 473 | unsafe { utils::convert_raw_str((*self.as_raw()).keygrip) } 474 | } 475 | 476 | #[inline] 477 | pub fn length(&self) -> usize { 478 | unsafe { (*self.as_raw()).length as usize } 479 | } 480 | 481 | #[inline] 482 | pub fn card_serial_number(&self) -> Result<&'key str, Option> { 483 | self.card_serial_number_raw() 484 | .map_or(Err(None), |s| s.to_str().map_err(Some)) 485 | } 486 | 487 | #[inline] 488 | pub fn card_serial_number_raw(&self) -> Option<&'key CStr> { 489 | unsafe { utils::convert_raw_str((*self.as_raw()).card_number) } 490 | } 491 | 492 | #[inline] 493 | pub fn curve(&self) -> Result<&'key str, Option> { 494 | self.curve_raw() 495 | .map_or(Err(None), |s| s.to_str().map_err(Some)) 496 | } 497 | 498 | #[inline] 499 | pub fn curve_raw(&self) -> Option<&'key CStr> { 500 | unsafe { utils::convert_raw_str((*self.as_raw()).curve) } 501 | } 502 | } 503 | 504 | impl fmt::Debug for Subkey<'_> { 505 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 506 | f.debug_struct("Subkey") 507 | .field("raw", &self.as_raw()) 508 | .field("fingerprint", &self.fingerprint_raw()) 509 | .field("secret", &self.is_secret()) 510 | .field("algorithm", &self.algorithm()) 511 | .field("expired", &self.is_expired()) 512 | .field("creation_time", &self.creation_time()) 513 | .field("expiration_time", &self.expiration_time()) 514 | .field("curve", &self.curve_raw()) 515 | .field("length", &self.length()) 516 | .field("card_key", &self.is_card_key()) 517 | .field("card_serial_number", &self.card_serial_number_raw()) 518 | .field("revoked", &self.is_revoked()) 519 | .field("invalid", &self.is_invalid()) 520 | .field("disabled", &self.is_disabled()) 521 | .field("can_sign", &self.can_sign()) 522 | .field("can_encrypt", &self.can_encrypt()) 523 | .field("can_certify", &self.can_certify()) 524 | .field("can_auth", &self.can_authenticate()) 525 | .finish() 526 | } 527 | } 528 | 529 | impl_list_iterator!(pub struct Subkeys(Subkey: ffi::gpgme_subkey_t)); 530 | 531 | /// Upstream documentation: [`gpgme_user_id_t`](https://www.gnupg.org/documentation/manuals/gpgme/Key-objects.html#index-gpgme_005fuser_005fid_005ft) 532 | #[derive(Copy, Clone)] 533 | pub struct UserId<'key>(NonNull, PhantomData<&'key Key>); 534 | 535 | unsafe impl Send for UserId<'_> {} 536 | unsafe impl Sync for UserId<'_> {} 537 | 538 | impl<'key> UserId<'key> { 539 | impl_wrapper!(ffi::gpgme_user_id_t, PhantomData); 540 | 541 | #[inline] 542 | pub fn id(&self) -> Result<&'key str, Option> { 543 | self.id_raw() 544 | .map_or(Err(None), |s| s.to_str().map_err(Some)) 545 | } 546 | 547 | #[inline] 548 | pub fn id_raw(&self) -> Option<&'key CStr> { 549 | unsafe { utils::convert_raw_str((*self.as_raw()).uid) } 550 | } 551 | 552 | #[inline] 553 | pub fn name(&self) -> Result<&'key str, Option> { 554 | self.name_raw() 555 | .map_or(Err(None), |s| s.to_str().map_err(Some)) 556 | } 557 | 558 | #[inline] 559 | pub fn name_raw(&self) -> Option<&'key CStr> { 560 | unsafe { utils::convert_raw_str((*self.as_raw()).name) } 561 | } 562 | 563 | #[inline] 564 | pub fn email(&self) -> Result<&'key str, Option> { 565 | self.email_raw() 566 | .map_or(Err(None), |s| s.to_str().map_err(Some)) 567 | } 568 | 569 | #[inline] 570 | pub fn email_raw(&self) -> Option<&'key CStr> { 571 | unsafe { utils::convert_raw_str((*self.as_raw()).email) } 572 | } 573 | 574 | #[inline] 575 | pub fn comment(&self) -> Result<&'key str, Option> { 576 | self.comment_raw() 577 | .map_or(Err(None), |s| s.to_str().map_err(Some)) 578 | } 579 | 580 | #[inline] 581 | pub fn comment_raw(&self) -> Option<&'key CStr> { 582 | unsafe { utils::convert_raw_str((*self.as_raw()).comment) } 583 | } 584 | 585 | #[inline] 586 | #[cfg(feature = "v1_14")] 587 | pub fn uidhash(&self) -> Result<&'key str, Option> { 588 | self.uidhash_raw() 589 | .map_or(Err(None), |s| s.to_str().map_err(Some)) 590 | } 591 | 592 | #[inline] 593 | #[cfg(feature = "v1_14")] 594 | pub fn uidhash_raw(&self) -> Option<&'key CStr> { 595 | unsafe { utils::convert_raw_str((*self.as_raw()).uidhash) } 596 | } 597 | 598 | #[inline] 599 | pub fn address(&self) -> Result<&'key str, Option> { 600 | self.address_raw() 601 | .map_or(Err(None), |s| s.to_str().map_err(Some)) 602 | } 603 | 604 | #[inline] 605 | pub fn address_raw(&self) -> Option<&'key CStr> { 606 | unsafe { utils::convert_raw_str((*self.as_raw()).address) } 607 | } 608 | 609 | #[inline] 610 | pub fn validity(&self) -> Validity { 611 | unsafe { Validity::from_raw((*self.as_raw()).validity) } 612 | } 613 | 614 | #[inline] 615 | pub fn is_bad(&self) -> bool { 616 | self.is_revoked() || self.is_invalid() 617 | } 618 | 619 | #[inline] 620 | pub fn is_revoked(&self) -> bool { 621 | unsafe { (*self.as_raw()).revoked() } 622 | } 623 | 624 | #[inline] 625 | pub fn is_invalid(&self) -> bool { 626 | unsafe { (*self.as_raw()).invalid() } 627 | } 628 | 629 | #[inline] 630 | pub fn origin(&self) -> crate::KeyOrigin { 631 | unsafe { crate::KeyOrigin::from_raw((*self.as_raw()).origin()) } 632 | } 633 | 634 | #[inline] 635 | pub fn last_update(&self) -> SystemTime { 636 | let timestamp = unsafe { (*self.as_raw()).last_update }; 637 | UNIX_EPOCH + Duration::from_secs(timestamp.into()) 638 | } 639 | 640 | #[inline] 641 | pub fn signature(&self, key: &Key) -> Option> { 642 | if key.protocol() != Protocol::OpenPgp { 643 | return None; 644 | } 645 | 646 | self.signatures() 647 | .filter(|s| { 648 | s.signer_key_id_raw() == key.id_raw() 649 | && !(s.is_bad() || s.is_revocation()) 650 | && (s.status() == Error::NO_ERROR) 651 | }) 652 | .max_by_key(|s| s.creation_time()) 653 | } 654 | 655 | #[inline] 656 | pub fn signatures(&self) -> UserIdSignatures<'key> { 657 | unsafe { UserIdSignatures::from_list((*self.as_raw()).signatures) } 658 | } 659 | 660 | #[inline] 661 | pub fn tofu_info(&self) -> Option> { 662 | unsafe { 663 | (*self.as_raw()) 664 | .tofu 665 | .as_mut() 666 | .map(|t| crate::TofuInfo::from_raw(t)) 667 | } 668 | } 669 | } 670 | 671 | impl fmt::Debug for UserId<'_> { 672 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 673 | f.debug_struct("UserId") 674 | .field("raw", &self.as_raw()) 675 | .field("name", &self.name_raw()) 676 | .field("email", &self.email_raw()) 677 | .field("comment", &self.comment_raw()) 678 | .field("validity", &self.validity()) 679 | .field("revoked", &self.is_revoked()) 680 | .field("invalid", &self.is_invalid()) 681 | .field("origin", &self.origin()) 682 | .field("tofu_info", &self.tofu_info()) 683 | .field("signatures", &self.signatures()) 684 | .finish() 685 | } 686 | } 687 | 688 | impl fmt::Display for UserId<'_> { 689 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 690 | f.write_str( 691 | &self 692 | .id_raw() 693 | .map(|s| s.to_string_lossy()) 694 | .unwrap_or("".into()), 695 | ) 696 | } 697 | } 698 | 699 | impl_list_iterator!(pub struct UserIds(UserId: ffi::gpgme_user_id_t)); 700 | 701 | #[derive(Debug)] 702 | pub enum SignatureTrust { 703 | None, 704 | Partial, 705 | Complete, 706 | } 707 | 708 | /// Upstream documentation: [`gpgme_key_sig_t`](https://www.gnupg.org/documentation/manuals/gpgme/Key-objects.html#index-gpgme_005fkey_005fsig_005ft) 709 | #[derive(Copy, Clone)] 710 | pub struct UserIdSignature<'key>(NonNull, PhantomData<&'key Key>); 711 | 712 | unsafe impl Send for UserIdSignature<'_> {} 713 | unsafe impl Sync for UserIdSignature<'_> {} 714 | 715 | impl<'key> UserIdSignature<'key> { 716 | impl_wrapper!(ffi::gpgme_key_sig_t, PhantomData); 717 | 718 | #[inline] 719 | pub fn signer_key_id(&self) -> Result<&'key str, Option> { 720 | self.signer_key_id_raw() 721 | .map_or(Err(None), |s| s.to_str().map_err(Some)) 722 | } 723 | 724 | #[inline] 725 | pub fn signer_key_id_raw(&self) -> Option<&'key CStr> { 726 | unsafe { utils::convert_raw_str((*self.as_raw()).keyid) } 727 | } 728 | 729 | #[inline] 730 | pub fn algorithm(&self) -> KeyAlgorithm { 731 | unsafe { KeyAlgorithm::from_raw((*self.as_raw()).pubkey_algo) } 732 | } 733 | 734 | #[inline] 735 | pub fn creation_time(&self) -> Option { 736 | let timestamp = unsafe { (*self.as_raw()).timestamp }; 737 | if timestamp > 0 { 738 | Some(UNIX_EPOCH + Duration::from_secs(timestamp as u64)) 739 | } else { 740 | None 741 | } 742 | } 743 | 744 | #[inline] 745 | pub fn expiration_time(&self) -> Option { 746 | let expires = unsafe { (*self.as_raw()).expires }; 747 | if expires > 0 { 748 | Some(UNIX_EPOCH + Duration::from_secs(expires as u64)) 749 | } else { 750 | None 751 | } 752 | } 753 | 754 | #[inline] 755 | pub fn never_expires(&self) -> bool { 756 | self.expiration_time().is_none() 757 | } 758 | 759 | #[inline] 760 | pub fn is_bad(&self) -> bool { 761 | self.is_expired() || self.is_invalid() 762 | } 763 | 764 | #[inline] 765 | pub fn is_revocation(&self) -> bool { 766 | unsafe { (*self.as_raw()).revoked() } 767 | } 768 | 769 | #[inline] 770 | pub fn is_invalid(&self) -> bool { 771 | unsafe { (*self.as_raw()).invalid() } 772 | } 773 | 774 | #[inline] 775 | pub fn is_expired(&self) -> bool { 776 | unsafe { (*self.as_raw()).expired() } 777 | } 778 | 779 | #[inline] 780 | pub fn is_exportable(&self) -> bool { 781 | unsafe { (*self.as_raw()).exportable() } 782 | } 783 | 784 | #[inline] 785 | pub fn signer_user_id(&self) -> Result<&'key str, Option> { 786 | self.signer_user_id_raw() 787 | .map_or(Err(None), |s| s.to_str().map_err(Some)) 788 | } 789 | 790 | #[inline] 791 | pub fn signer_user_id_raw(&self) -> Option<&'key CStr> { 792 | unsafe { utils::convert_raw_str((*self.as_raw()).uid) } 793 | } 794 | 795 | #[inline] 796 | pub fn signer_name(&self) -> Result<&'key str, Option> { 797 | self.signer_name_raw() 798 | .map_or(Err(None), |s| s.to_str().map_err(Some)) 799 | } 800 | 801 | #[inline] 802 | pub fn signer_name_raw(&self) -> Option<&'key CStr> { 803 | unsafe { utils::convert_raw_str((*self.as_raw()).name) } 804 | } 805 | 806 | #[inline] 807 | pub fn signer_email(&self) -> Result<&'key str, Option> { 808 | self.signer_email_raw() 809 | .map_or(Err(None), |s| s.to_str().map_err(Some)) 810 | } 811 | 812 | #[inline] 813 | pub fn signer_email_raw(&self) -> Option<&'key CStr> { 814 | unsafe { utils::convert_raw_str((*self.as_raw()).email) } 815 | } 816 | 817 | #[inline] 818 | pub fn signer_comment(&self) -> Result<&'key str, Option> { 819 | self.signer_comment_raw() 820 | .map_or(Err(None), |s| s.to_str().map_err(Some)) 821 | } 822 | 823 | #[inline] 824 | pub fn signer_comment_raw(&self) -> Option<&'key CStr> { 825 | unsafe { utils::convert_raw_str((*self.as_raw()).comment) } 826 | } 827 | 828 | #[inline] 829 | pub fn cert_class(&self) -> u64 { 830 | unsafe { (*self.as_raw()).sig_class.into() } 831 | } 832 | 833 | #[inline] 834 | pub fn status(&self) -> Error { 835 | unsafe { Error::new((*self.as_raw()).status) } 836 | } 837 | 838 | #[inline] 839 | pub fn policy_url(&self) -> Result<&'key str, Option> { 840 | self.policy_url_raw() 841 | .map_or(Err(None), |s| s.to_str().map_err(Some)) 842 | } 843 | 844 | #[inline] 845 | pub fn policy_url_raw(&self) -> Option<&'key CStr> { 846 | self.notations().find_map(|n| { 847 | if n.name_raw().is_none() { 848 | n.value_raw() 849 | } else { 850 | None 851 | } 852 | }) 853 | } 854 | 855 | #[inline] 856 | pub fn remark(&self) -> Result<&'key str, Option> { 857 | self.remark_raw() 858 | .map_or(Err(None), |s| s.to_str().map_err(Some)) 859 | } 860 | 861 | #[inline] 862 | pub fn remark_raw(&self) -> Option<&'key CStr> { 863 | self.notations().find_map(|n| { 864 | if n.name() == Ok("rem@gnupg.org") { 865 | n.value_raw() 866 | } else { 867 | None 868 | } 869 | }) 870 | } 871 | 872 | #[inline] 873 | pub fn notations(&self) -> SignatureNotations<'key> { 874 | unsafe { SignatureNotations::from_list((*self.as_raw()).notations) } 875 | } 876 | 877 | #[inline] 878 | #[cfg(feature = "v1_16")] 879 | pub fn is_trust_signature(&self) -> bool { 880 | self.trust_depth() != 0 881 | } 882 | 883 | #[inline] 884 | #[cfg(feature = "v1_16")] 885 | pub fn trust_value(&self) -> SignatureTrust { 886 | let value = unsafe { (*self.as_raw()).trust_value() }; 887 | if !self.is_trust_signature() { 888 | SignatureTrust::None 889 | } else if value >= 120 { 890 | SignatureTrust::Complete 891 | } else { 892 | SignatureTrust::Partial 893 | } 894 | } 895 | 896 | #[inline] 897 | #[cfg(feature = "v1_16")] 898 | pub fn trust_depth(&self) -> u8 { 899 | unsafe { (*self.as_raw()).trust_depth() } 900 | } 901 | 902 | #[inline] 903 | #[cfg(feature = "v1_16")] 904 | pub fn trust_scope(&self) -> Result<&'key str, Option> { 905 | self.trust_scope_raw() 906 | .map_or(Err(None), |s| s.to_str().map_err(Some)) 907 | } 908 | 909 | #[inline] 910 | #[cfg(feature = "v1_16")] 911 | pub fn trust_scope_raw(&self) -> Option<&'key CStr> { 912 | unsafe { utils::convert_raw_str((*self.as_raw()).trust_scope) } 913 | } 914 | } 915 | 916 | impl fmt::Debug for UserIdSignature<'_> { 917 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 918 | f.debug_struct("UserIdSignature") 919 | .field("raw", &self.as_raw()) 920 | .field("signer_key", &self.signer_key_id_raw()) 921 | .field("signer", &self.signer_user_id_raw()) 922 | .field("algorithm", &self.algorithm()) 923 | .field("expired", &self.is_expired()) 924 | .field("creation_time", &self.creation_time()) 925 | .field("expiration_time", &self.expiration_time()) 926 | .field("invalid", &self.is_invalid()) 927 | .field("revoked", &self.is_revocation()) 928 | .field("exportable", &self.is_exportable()) 929 | .field("status", &self.status()) 930 | .field("notations", &self.notations()) 931 | .finish() 932 | } 933 | } 934 | 935 | impl_list_iterator!(pub struct UserIdSignatures(UserIdSignature: ffi::gpgme_key_sig_t)); 936 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | ffi::CStr, 3 | fmt, mem, ptr, 4 | str::Utf8Error, 5 | sync::{Mutex, OnceLock}, 6 | }; 7 | 8 | use self::{ 9 | engine::EngineInfoGuard, 10 | utils::{convert_err, CStrArgument}, 11 | }; 12 | 13 | #[doc(inline)] 14 | #[allow(deprecated)] 15 | pub use self::{ 16 | callbacks::{ 17 | EditInteractionStatus, EditInteractor, InteractionStatus, Interactor, PassphraseProvider, 18 | PassphraseRequest, ProgressInfo, ProgressReporter, StatusHandler, 19 | }, 20 | context::{Context, ContextWithCallbacks}, 21 | data::{Data, IntoData}, 22 | engine::EngineInfo, 23 | error::{Error, Result}, 24 | flags::*, 25 | keys::{Key, Subkey, UserId, UserIdSignature}, 26 | notation::SignatureNotation, 27 | results::{ 28 | DecryptionResult, EncryptionResult, Import, ImportResult, InvalidKey, KeyGenerationResult, 29 | KeyListResult, NewSignature, PkaTrust, QuerySwdbResult, Recipient, Signature, 30 | SigningResult, VerificationResult, 31 | }, 32 | tofu::{TofuInfo, TofuPolicy}, 33 | }; 34 | #[doc(inline)] 35 | pub use gpg_error as error; 36 | 37 | #[macro_use] 38 | mod utils; 39 | mod callbacks; 40 | pub mod context; 41 | pub mod data; 42 | pub mod edit; 43 | pub mod engine; 44 | mod flags; 45 | pub mod keys; 46 | pub mod notation; 47 | pub mod results; 48 | pub mod tofu; 49 | 50 | ffi_enum_wrapper! { 51 | /// A cryptographic protocol that may be used with the library. 52 | /// 53 | /// Each protocol is implemented by an engine that the library communicates with 54 | /// to perform various operations. 55 | /// 56 | /// Upstream documentation: 57 | /// [`gpgme_protocol_t`](https://www.gnupg.org/documentation/manuals/gpgme/Protocols-and-Engines.html#index-enum-gpgme_005fprotocol_005ft) 58 | #[non_exhaustive] 59 | pub enum Protocol: ffi::gpgme_protocol_t { 60 | OpenPgp = ffi::GPGME_PROTOCOL_OpenPGP, 61 | Cms = ffi::GPGME_PROTOCOL_CMS, 62 | GpgConf = ffi::GPGME_PROTOCOL_GPGCONF, 63 | Assuan = ffi::GPGME_PROTOCOL_ASSUAN, 64 | G13 = ffi::GPGME_PROTOCOL_G13, 65 | UiServer = ffi::GPGME_PROTOCOL_UISERVER, 66 | Spawn = ffi::GPGME_PROTOCOL_SPAWN, 67 | Default = ffi::GPGME_PROTOCOL_DEFAULT, 68 | Unknown = ffi::GPGME_PROTOCOL_UNKNOWN, 69 | } 70 | } 71 | 72 | impl Protocol { 73 | /// Upstream documentation: 74 | /// [`gpgme_get_protocol_name`](https://www.gnupg.org/documentation/manuals/gpgme/Protocols-and-Engines.html#index-gpgme_005fget_005fprotocol_005fname) 75 | #[inline] 76 | pub fn name(&self) -> Result<&'static str, Option> { 77 | self.name_raw() 78 | .map_or(Err(None), |s| s.to_str().map_err(Some)) 79 | } 80 | 81 | /// Upstream documentation: 82 | /// [`gpgme_get_protocol_name`](https://www.gnupg.org/documentation/manuals/gpgme/Protocols-and-Engines.html#index-gpgme_005fget_005fprotocol_005fname) 83 | #[inline] 84 | pub fn name_raw(&self) -> Option<&'static CStr> { 85 | unsafe { utils::convert_raw_str(ffi::gpgme_get_protocol_name(self.raw())) } 86 | } 87 | } 88 | 89 | impl fmt::Display for Protocol { 90 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 91 | f.write_str(self.name().unwrap_or("Unknown")) 92 | } 93 | } 94 | 95 | ffi_enum_wrapper! { 96 | /// Upstream documentation: 97 | /// [`gpgme_validity_t`](https://www.gnupg.org/documentation/manuals/gpgme/Information-About-Keys.html#index-gpgme_005fvalidity_005ft) 98 | pub enum Validity(Unknown): ffi::gpgme_validity_t { 99 | Unknown = ffi::GPGME_VALIDITY_UNKNOWN, 100 | Undefined = ffi::GPGME_VALIDITY_UNDEFINED, 101 | Never = ffi::GPGME_VALIDITY_NEVER, 102 | Marginal = ffi::GPGME_VALIDITY_MARGINAL, 103 | Full = ffi::GPGME_VALIDITY_FULL, 104 | Ultimate = ffi::GPGME_VALIDITY_ULTIMATE, 105 | } 106 | } 107 | 108 | impl fmt::Display for Validity { 109 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 110 | match *self { 111 | Validity::Undefined => write!(f, "q"), 112 | Validity::Never => write!(f, "n"), 113 | Validity::Marginal => write!(f, "m"), 114 | Validity::Full => write!(f, "f"), 115 | Validity::Ultimate => write!(f, "u"), 116 | _ => write!(f, "?"), 117 | } 118 | } 119 | } 120 | 121 | fn get_flag_lock() -> &'static Mutex<()> { 122 | static FLAG_LOCK: OnceLock> = OnceLock::new(); 123 | FLAG_LOCK.get_or_init(Mutex::default) 124 | } 125 | 126 | /// Upstream documentation: 127 | /// [`gpgme_set_global_flag`](https://www.gnupg.org/documentation/manuals/gpgme/Library-Version-Check.html#index-gpgme_005fset_005fglobal_005fflag) 128 | /// 129 | /// # Safety 130 | /// 131 | /// This function is not thread safe and should only be called when it can be 132 | /// guaranteed that no other threads are executing *any* GPGme, libgcrypt or 133 | /// libgpg-error functions. This function has similar safety issues to 134 | /// [`std::env::set_var()`]. 135 | #[deprecated( 136 | note = "This function is not thread safe and will eventually be marked unsafe", 137 | since = "0.11.1" 138 | )] 139 | pub fn set_flag(name: impl CStrArgument, val: impl CStrArgument) -> Result<()> { 140 | let name = name.into_cstr(); 141 | let val = val.into_cstr(); 142 | let _lock = get_flag_lock().lock().unwrap_or_else(|e| e.into_inner()); 143 | unsafe { 144 | if ffi::gpgme_set_global_flag(name.as_ref().as_ptr(), val.as_ref().as_ptr()) == 0 { 145 | Ok(()) 146 | } else { 147 | Err(Error::GENERAL) 148 | } 149 | } 150 | } 151 | 152 | cfg_if::cfg_if! { 153 | if #[cfg(feature = "v1_23")] { 154 | const MIN_VERSION: &CStr = c"1.23.0"; 155 | } else if #[cfg(feature = "v1_22")] { 156 | const MIN_VERSION: &CStr = c"1.22.0"; 157 | } else if #[cfg(feature = "v1_21")] { 158 | const MIN_VERSION: &CStr = c"1.21.0"; 159 | } else if #[cfg(feature = "v1_20")] { 160 | const MIN_VERSION: &CStr = c"1.20.0"; 161 | } else if #[cfg(feature = "v1_19")] { 162 | const MIN_VERSION: &CStr = c"1.19.0"; 163 | } else if #[cfg(feature = "v1_18")] { 164 | const MIN_VERSION: &CStr = c"1.18.0"; 165 | } else if #[cfg(feature = "v1_17")] { 166 | const MIN_VERSION: &CStr = c"1.17.0"; 167 | } else if #[cfg(feature = "v1_16")] { 168 | const MIN_VERSION: &CStr = c"1.16.0"; 169 | } else if #[cfg(feature = "v1_15")] { 170 | const MIN_VERSION: &CStr = c"1.15.0"; 171 | } else if #[cfg(feature = "v1_14")] { 172 | const MIN_VERSION: &CStr = c"1.14.0"; 173 | } else { 174 | const MIN_VERSION: &CStr = c"1.13.0"; 175 | } 176 | } 177 | 178 | /// Initializes the gpgme library. 179 | /// 180 | /// A token is returned through which various global options of the library can 181 | /// be queried and modified. 182 | /// 183 | /// Upstream documentation: 184 | /// [`gpgme_check_version`](https://www.gnupg.org/documentation/manuals/gpgme/Library-Version-Check.html#index-gpgme_005fcheck_005fversion) 185 | /// 186 | /// # Examples 187 | /// 188 | /// ```no_run 189 | /// let gpgme = gpgme::init(); 190 | /// ``` 191 | #[inline] 192 | pub fn init() -> Gpgme { 193 | static TOKEN: OnceLock<&str> = OnceLock::new(); 194 | let token = TOKEN.get_or_init(|| unsafe { 195 | let offset = mem::offset_of!(ffi::_gpgme_signature, validity); 196 | let result = ffi::gpgme_check_version_internal(MIN_VERSION.as_ptr(), offset); 197 | assert!( 198 | !result.is_null(), 199 | "the library linked is not the correct version" 200 | ); 201 | CStr::from_ptr(result) 202 | .to_str() 203 | .expect("gpgme version string is not valid utf-8") 204 | }); 205 | Gpgme { version: token } 206 | } 207 | 208 | /// A type for managing the library's configuration. 209 | #[derive(Debug, Clone)] 210 | pub struct Gpgme { 211 | version: &'static str, 212 | } 213 | 214 | impl Gpgme { 215 | pub const HOME_DIR: &'static str = "homedir"; 216 | pub const AGENT_SOCKET: &'static str = "agent-socket"; 217 | pub const UISERVER_SOCKET: &'static str = "uiserver-socket"; 218 | pub const GPGCONF_NAME: &'static str = "gpgconf-name"; 219 | pub const GPG_NAME: &'static str = "gpg-name"; 220 | pub const GPGSM_NAME: &'static str = "gpgsm-name"; 221 | pub const G13_NAME: &'static str = "g13-name"; 222 | 223 | /// Checks that the linked version of the library is at least the 224 | /// specified version. 225 | /// 226 | /// Note: `false` is returned, if `version` is not in the format `MAJOR.MINOR.MICRO`. 227 | /// 228 | /// Upstream documentation: 229 | /// [`gpgme_check_version`](https://www.gnupg.org/documentation/manuals/gpgme/Library-Version-Check.html#index-gpgme_005fcheck_005fversion) 230 | /// 231 | /// # Examples 232 | /// 233 | /// ```no_run 234 | /// let gpgme = gpgme::init(); 235 | /// assert!(gpgme.check_version(c"1.4.0")); 236 | /// ``` 237 | #[inline] 238 | pub fn check_version(&self, version: impl CStrArgument) -> bool { 239 | let version = version.into_cstr(); 240 | unsafe { !ffi::gpgme_check_version(version.as_ref().as_ptr()).is_null() } 241 | } 242 | 243 | /// Returns the version string for the library. 244 | #[inline] 245 | pub fn version(&self) -> &'static str { 246 | self.version 247 | } 248 | 249 | /// Returns the default value for specified configuration option. 250 | /// 251 | /// Commonly supported values for `what` are provided as associated constants. 252 | /// 253 | /// Upstream documentation: 254 | /// [`gpgme_get_dirinfo`](https://www.gnupg.org/documentation/manuals/gpgme/Engine-Version-Check.html#index-gpgme_005fget_005fdirinfo) 255 | #[inline] 256 | pub fn get_dir_info(&self, what: impl CStrArgument) -> Result<&'static str, Option> { 257 | self.get_dir_info_raw(what) 258 | .map_or(Err(None), |s| s.to_str().map_err(Some)) 259 | } 260 | 261 | /// Returns the default value for specified configuration option. 262 | /// 263 | /// Commonly supported values for `what` are provided as associated constants. 264 | /// 265 | /// Upstream documentation: 266 | /// [`gpgme_get_dirinfo`](https://www.gnupg.org/documentation/manuals/gpgme/Engine-Version-Check.html#index-gpgme_005fget_005fdirinfo) 267 | #[inline] 268 | pub fn get_dir_info_raw(&self, what: impl CStrArgument) -> Option<&'static CStr> { 269 | let what = what.into_cstr(); 270 | unsafe { 271 | ffi::gpgme_get_dirinfo(what.as_ref().as_ptr()) 272 | .as_ref() 273 | .map(|s| CStr::from_ptr(s)) 274 | } 275 | } 276 | 277 | /// Checks that the engine implementing the specified protocol is available. 278 | /// 279 | /// Upstream documentation: 280 | /// [`gpgme_engine_check_version`](https://www.gnupg.org/documentation/manuals/gpgme/Engine-Version-Check.html#index-gpgme_005fengine_005fcheck_005fversion) 281 | pub fn check_engine_version(&self, proto: Protocol) -> Result<()> { 282 | unsafe { 283 | convert_err(ffi::gpgme_engine_check_version(proto.raw()))?; 284 | } 285 | Ok(()) 286 | } 287 | 288 | /// Returns an iterator yielding information on each of the globally configured engines. 289 | /// 290 | /// Upstream documentation: 291 | /// [`gpgme_get_engine_info`](https://www.gnupg.org/documentation/manuals/gpgme/Engine-Information.html#index-gpgme_005fget_005fengine_005finfo) 292 | #[inline] 293 | pub fn engine_info(&self) -> Result { 294 | EngineInfoGuard::new() 295 | } 296 | 297 | #[inline] 298 | pub fn set_engine_path(&self, proto: Protocol, path: impl CStrArgument) -> Result<()> { 299 | let info = self.engine_info()?; 300 | let home_dir = info.get(proto).as_ref().and_then(|x| x.home_dir_raw()); 301 | self.set_engine_info(proto, Some(path), home_dir) 302 | } 303 | 304 | #[inline] 305 | pub fn set_engine_home_dir(&self, proto: Protocol, home_dir: impl CStrArgument) -> Result<()> { 306 | let info = self.engine_info()?; 307 | let path = info.get(proto).as_ref().and_then(|x| x.path_raw()); 308 | self.set_engine_info(proto, path, Some(home_dir)) 309 | } 310 | 311 | /// Upstream documentation: 312 | /// [`gpgme_set_engine_info`](https://www.gnupg.org/documentation/manuals/gpgme/Engine-Configuration.html#index-gpgme_005fset_005fengine_005finfo) 313 | #[inline] 314 | pub fn set_engine_info( 315 | &self, 316 | proto: Protocol, 317 | path: Option, 318 | home_dir: Option, 319 | ) -> Result<()> { 320 | let path = path.map(CStrArgument::into_cstr); 321 | let home_dir = home_dir.map(CStrArgument::into_cstr); 322 | unsafe { 323 | convert_err(ffi::gpgme_set_engine_info( 324 | proto.raw(), 325 | path.as_ref().map_or(ptr::null(), |s| s.as_ref().as_ptr()), 326 | home_dir 327 | .as_ref() 328 | .map_or(ptr::null(), |s| s.as_ref().as_ptr()), 329 | ))?; 330 | } 331 | Ok(()) 332 | } 333 | } 334 | 335 | type NonNull = ptr::NonNull<::Inner>; 336 | -------------------------------------------------------------------------------- /src/notation.rs: -------------------------------------------------------------------------------- 1 | use std::{ffi::CStr, fmt, marker::PhantomData, str::Utf8Error}; 2 | 3 | use ffi; 4 | 5 | use crate::{utils, NonNull, SignatureNotationFlags}; 6 | 7 | /// Upstream documentation: 8 | /// [`gpgme_sig_notation_t`](https://www.gnupg.org/documentation/manuals/gpgme/Verify.html#index-gpgme_005fsig_005fnotation_005ft) 9 | #[derive(Copy, Clone)] 10 | pub struct SignatureNotation<'a>(NonNull, PhantomData<&'a ()>); 11 | 12 | unsafe impl Send for SignatureNotation<'_> {} 13 | unsafe impl Sync for SignatureNotation<'_> {} 14 | 15 | impl<'a> SignatureNotation<'a> { 16 | impl_wrapper!(ffi::gpgme_sig_notation_t, PhantomData); 17 | 18 | #[inline] 19 | pub fn is_human_readable(&self) -> bool { 20 | unsafe { (*self.as_raw()).human_readable() } 21 | } 22 | 23 | #[inline] 24 | pub fn is_critical(&self) -> bool { 25 | unsafe { (*self.as_raw()).critical() } 26 | } 27 | 28 | #[inline] 29 | pub fn flags(&self) -> SignatureNotationFlags { 30 | unsafe { SignatureNotationFlags::from_bits_retain((*self.as_raw()).flags) } 31 | } 32 | 33 | #[inline] 34 | pub fn name(&self) -> Result<&'a str, Option> { 35 | self.name_raw() 36 | .map_or(Err(None), |s| s.to_str().map_err(Some)) 37 | } 38 | 39 | #[inline] 40 | pub fn name_raw(&self) -> Option<&'a CStr> { 41 | unsafe { utils::convert_raw_str((*self.as_raw()).name) } 42 | } 43 | 44 | #[inline] 45 | pub fn value(&self) -> Result<&'a str, Option> { 46 | self.value_raw() 47 | .map_or(Err(None), |s| s.to_str().map_err(Some)) 48 | } 49 | 50 | #[inline] 51 | pub fn value_raw(&self) -> Option<&'a CStr> { 52 | unsafe { utils::convert_raw_str((*self.as_raw()).value) } 53 | } 54 | } 55 | 56 | impl fmt::Debug for SignatureNotation<'_> { 57 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 58 | f.debug_struct("SignatureNotation") 59 | .field("raw", &self.as_raw()) 60 | .field("name", &self.name_raw()) 61 | .field("value", &self.value_raw()) 62 | .field("critical", &self.is_critical()) 63 | .field("human_readable", &self.is_human_readable()) 64 | .finish() 65 | } 66 | } 67 | 68 | impl_list_iterator!(pub struct SignatureNotations(SignatureNotation: ffi::gpgme_sig_notation_t)); 69 | -------------------------------------------------------------------------------- /src/tofu.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | ffi::CStr, 3 | fmt, 4 | marker::PhantomData, 5 | str::Utf8Error, 6 | time::{Duration, SystemTime, UNIX_EPOCH}, 7 | }; 8 | 9 | use ffi; 10 | 11 | use crate::{utils, NonNull}; 12 | 13 | ffi_enum_wrapper! { 14 | /// Upstream documentation: 15 | /// [`gpgme_tofu_policy_t`](https://www.gnupg.org/documentation/manuals/gpgme/Changing-TOFU-Data.html#index-gpgme_005ftofu_005fpolicy_005ft) 16 | #[non_exhaustive] 17 | pub enum TofuPolicy: ffi::gpgme_tofu_policy_t { 18 | None = ffi::GPGME_TOFU_POLICY_NONE, 19 | Auto = ffi::GPGME_TOFU_POLICY_AUTO, 20 | Good = ffi::GPGME_TOFU_POLICY_GOOD, 21 | Unknown = ffi::GPGME_TOFU_POLICY_UNKNOWN, 22 | Bad = ffi::GPGME_TOFU_POLICY_BAD, 23 | Ask = ffi::GPGME_TOFU_POLICY_ASK, 24 | } 25 | } 26 | 27 | /// Upstream documentation: 28 | /// [`gpgme_tofu_info_t`](https://www.gnupg.org/documentation/manuals/gpgme/Key-objects.html#index-gpgme_005ftofu_005finfo_005ft) 29 | #[derive(Copy, Clone)] 30 | pub struct TofuInfo<'a>(NonNull, PhantomData<&'a ()>); 31 | 32 | unsafe impl Send for TofuInfo<'_> {} 33 | unsafe impl Sync for TofuInfo<'_> {} 34 | 35 | impl<'a> TofuInfo<'a> { 36 | impl_wrapper!(ffi::gpgme_tofu_info_t, PhantomData); 37 | 38 | #[inline] 39 | pub fn validity(&self) -> u32 { 40 | unsafe { (*self.as_raw()).validity() } 41 | } 42 | 43 | #[inline] 44 | pub fn policy(&self) -> TofuPolicy { 45 | unsafe { TofuPolicy::from_raw((*self.as_raw()).policy()) } 46 | } 47 | 48 | #[inline] 49 | pub fn signature_count(&self) -> u64 { 50 | unsafe { (*self.as_raw()).signcount.into() } 51 | } 52 | 53 | #[inline] 54 | pub fn encrypted_count(&self) -> u64 { 55 | unsafe { (*self.as_raw()).encrcount.into() } 56 | } 57 | 58 | // TODO: Unwrap return value for next major release 59 | #[inline] 60 | pub fn first_signed(&self) -> Option { 61 | let sign_first = unsafe { (*self.as_raw()).signfirst }; 62 | Some(UNIX_EPOCH + Duration::from_secs(sign_first.into())) 63 | } 64 | 65 | #[inline] 66 | pub fn last_signed(&self) -> Option { 67 | let sign_last = unsafe { (*self.as_raw()).signlast }; 68 | Some(UNIX_EPOCH + Duration::from_secs(sign_last.into())) 69 | } 70 | 71 | #[inline] 72 | pub fn first_encrypted(&self) -> Option { 73 | let encr_first = unsafe { (*self.as_raw()).encrfirst }; 74 | Some(UNIX_EPOCH + Duration::from_secs(encr_first.into())) 75 | } 76 | 77 | #[inline] 78 | pub fn last_encrypted(&self) -> Option { 79 | let encr_last = unsafe { (*self.as_raw()).encrlast }; 80 | Some(UNIX_EPOCH + Duration::from_secs(encr_last.into())) 81 | } 82 | 83 | #[inline] 84 | pub fn description(&self) -> Result<&'a str, Option> { 85 | self.description_raw() 86 | .map_or(Err(None), |s| s.to_str().map_err(Some)) 87 | } 88 | 89 | #[inline] 90 | pub fn description_raw(&self) -> Option<&'a CStr> { 91 | unsafe { utils::convert_raw_str((*self.as_raw()).description) } 92 | } 93 | } 94 | 95 | impl fmt::Debug for TofuInfo<'_> { 96 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 97 | f.debug_struct("TofuInfo") 98 | .field("raw", &self.as_raw()) 99 | .field("description", &self.description_raw()) 100 | .field("validity", &self.validity()) 101 | .field("policy", &self.policy()) 102 | .field("signature_count", &self.signature_count()) 103 | .field("first_signed", &self.first_signed()) 104 | .field("last_signed", &self.last_signed()) 105 | .field("encrypted_count", &self.encrypted_count()) 106 | .field("first_encrypt", &self.first_encrypted()) 107 | .field("last_encrypt", &self.last_encrypted()) 108 | .finish() 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /src/utils.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | ffi::CStr, 3 | io::{self, prelude::*}, 4 | }; 5 | 6 | use crate::Error; 7 | 8 | pub use cstr_argument::CStrArgument; 9 | pub type SmallVec = ::smallvec::SmallVec<[T; 8]>; 10 | 11 | macro_rules! impl_wrapper { 12 | ($T:ty$(, $Args:expr)*) => { 13 | /// # Safety 14 | /// The provided instance must be valid. 15 | #[inline] 16 | pub unsafe fn from_raw(raw: $T) -> Self { 17 | Self(NonNull::<$T>::new(raw).unwrap()$(, $Args)*) 18 | } 19 | 20 | #[inline] 21 | pub fn as_raw(&self) -> $T { 22 | self.0.as_ptr() 23 | } 24 | 25 | #[inline] 26 | pub fn into_raw(self) -> $T { 27 | ::std::mem::ManuallyDrop::new(self).as_raw() 28 | } 29 | }; 30 | } 31 | 32 | macro_rules! impl_list_iterator { 33 | ($Vis:vis struct $Name:ident($Item:ident: $Raw:ty)) => { 34 | #[derive(Clone)] 35 | $Vis struct $Name<'a>(Option<$Item<'a>>); 36 | 37 | impl $Name<'_> { 38 | /// # Safety 39 | /// The provided instance must be valid. 40 | #[inline] 41 | pub unsafe fn from_list(first: $Raw) -> Self { 42 | $Name(first.as_mut().map(|r| $Item::from_raw(r))) 43 | } 44 | } 45 | 46 | impl<'a> Iterator for $Name<'a> { 47 | type Item = $Item<'a>; 48 | 49 | #[inline] 50 | fn next(&mut self) -> Option { 51 | unsafe { 52 | self.0.take().map(|c| { 53 | self.0 = (*c.as_raw()).next.as_mut().map(|r| $Item::from_raw(r)); 54 | c 55 | }) 56 | } 57 | } 58 | } 59 | 60 | impl ::std::iter::FusedIterator for $Name<'_> {} 61 | 62 | impl ::std::fmt::Debug for $Name<'_> { 63 | fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result { 64 | f.debug_list().entries(self.clone()).finish() 65 | } 66 | } 67 | }; 68 | } 69 | 70 | macro_rules! ffi_enum_wrapper { 71 | ($(#[$Attr:meta])* $Vis:vis enum $Name:ident($Default:ident): $T:ty { 72 | $($(#[$ItemAttr:meta])* $Item:ident = $Value:expr),+ $(,)? 73 | }) => { 74 | $(#[$Attr])* 75 | #[derive(Copy, Clone, Eq, PartialEq, Hash)] 76 | $Vis enum $Name { 77 | $($(#[$ItemAttr])* $Item,)+ 78 | } 79 | 80 | impl $Name { 81 | #[inline] 82 | pub fn from_raw(raw: $T) -> $Name { 83 | $(if raw == $Value { 84 | $Name::$Item 85 | } else )+ { 86 | $Name::$Default 87 | } 88 | } 89 | 90 | #[inline] 91 | pub fn raw(&self) -> $T { 92 | match *self { 93 | $($Name::$Item => $Value,)+ 94 | } 95 | } 96 | } 97 | 98 | impl ::std::fmt::Debug for $Name { 99 | fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result { 100 | match *self { 101 | $($Name::$Item => { 102 | write!(f, concat!(stringify!($Name), "::", 103 | stringify!($Item), "({:?})"), self.raw()) 104 | })+ 105 | } 106 | } 107 | } 108 | }; 109 | ($(#[$Attr:meta])* $Vis:vis enum $Name:ident: $T:ty { 110 | $($(#[$ItemAttr:meta])* $Item:ident = $Value:expr),+ $(,)? 111 | }) => { 112 | $(#[$Attr])* 113 | #[derive(Copy, Clone, Eq, PartialEq, Hash)] 114 | $Vis enum $Name { 115 | $($(#[$ItemAttr])* $Item,)+ 116 | Other($T), 117 | } 118 | 119 | impl $Name { 120 | #[inline] 121 | pub fn from_raw(raw: $T) -> $Name { 122 | $(if raw == $Value { 123 | $Name::$Item 124 | } else )+ { 125 | $Name::Other(raw) 126 | } 127 | } 128 | 129 | #[inline] 130 | pub fn raw(&self) -> $T { 131 | match *self { 132 | $($Name::$Item => $Value,)+ 133 | $Name::Other(other) => other, 134 | } 135 | } 136 | } 137 | 138 | impl ::std::fmt::Debug for $Name { 139 | fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result { 140 | match *self { 141 | $($Name::$Item => { 142 | write!(f, concat!(stringify!($Name), "::", 143 | stringify!($Item), "({:?})"), self.raw()) 144 | })+ 145 | _ => write!(f, concat!(stringify!($Name), "({:?})"), self.raw()), 146 | } 147 | } 148 | } 149 | }; 150 | } 151 | 152 | pub(crate) struct FdWriter(libc::c_int); 153 | 154 | impl FdWriter { 155 | pub unsafe fn new(fd: libc::c_int) -> FdWriter { 156 | Self(fd) 157 | } 158 | } 159 | 160 | impl Write for FdWriter { 161 | fn write(&mut self, buf: &[u8]) -> io::Result { 162 | let result = unsafe { ffi::gpgme_io_write(self.0, buf.as_ptr().cast(), buf.len()) }; 163 | usize::try_from(result).map_err(|_| Error::last_os_error().into()) 164 | } 165 | 166 | fn flush(&mut self) -> io::Result<()> { 167 | Ok(()) 168 | } 169 | } 170 | 171 | pub(crate) trait Ptr { 172 | type Inner; 173 | } 174 | 175 | impl Ptr for *mut T { 176 | type Inner = T; 177 | } 178 | 179 | impl Ptr for *const T { 180 | type Inner = T; 181 | } 182 | 183 | #[inline(always)] 184 | pub(crate) fn convert_err(err: ffi::gpgme_error_t) -> Result<(), Error> { 185 | match err { 186 | 0 => Ok(()), 187 | _ => Err(Error::new(err)), 188 | } 189 | } 190 | 191 | /// # Safety 192 | /// 193 | /// The provided pointer must point to a valid (for reading) nul-terminated string, if it 194 | /// is non-null. 195 | #[inline(always)] 196 | pub(crate) unsafe fn convert_raw_str<'r>(s: *const libc::c_char) -> Option<&'r CStr> { 197 | (!s.is_null()).then(|| CStr::from_ptr(s)) 198 | } 199 | -------------------------------------------------------------------------------- /tests/common/data/gpg-agent.conf: -------------------------------------------------------------------------------- 1 | disable-scdaemon 2 | ignore-invalid-option allow-loopback-pinentry 3 | allow-loopback-pinentry 4 | ignore-invalid-option pinentry-mode 5 | pinentry-mode loopback 6 | -------------------------------------------------------------------------------- /tests/common/data/gpg.conf: -------------------------------------------------------------------------------- 1 | ignore-invalid-option no-force-v3-sigs 2 | ignore-invalid-option allow-weak-key-signatures 3 | # This is required for t-sig-notations. 4 | no-force-v3-sigs 5 | 6 | # This is required for t-edit-sign. 7 | allow-weak-key-signatures 8 | -------------------------------------------------------------------------------- /tests/common/data/ownertrust.txt: -------------------------------------------------------------------------------- 1 | # List of assigned trustvalues, created Mi 08 Feb 2023 09:52:04 CET 2 | # (Use "gpg --import-ownertrust" to restore them) 3 | A0FF4590BB6122EDEF6E3C542D727CC768697734:6: 4 | -------------------------------------------------------------------------------- /tests/common/data/secdemo.asc: -------------------------------------------------------------------------------- 1 | -----BEGIN PGP PRIVATE KEY BLOCK----- 2 | Version: GnuPG v2.1.0-gitb3c71eb (GNU/Linux) 3 | 4 | lQHpBDbjjp4RBAC2ZbFDX0wmJI8yLDYQdIiZeAuHLmfyHsqXaLGUMZtWiAvn/hNp 5 | ctwahmzKm5oXinHUvUkLOQ0s8rOlu15nhw4azc30rTP1LsIkn5zORNnFdgYC6RKy 6 | hOeim/63+/yGtdnTm49lVfaCqwsEmBCEkXaeWDGq+ie1b89J89T6n/JquwCgoQkj 7 | VeVGG+B/SzJ6+yifdHWQVkcD/RXDyLXX4+WHGP2aet51XlKojWGwsZmc9LPPYhwU 8 | /RcUO7ce1QQb0XFlUVFBhY0JQpM/ty/kNi+aGWFzigbQ+HAWZkUvA8+VIAVneN+p 9 | +SHhGIyLTXKpAYTq46AwvllZ5Cpvf02Cp/+W1aVyA0qnBWMyeIxXmR9HOi6lxxn5 10 | cjajA/9VZufOXWqCXkBvz4Oy3Q5FbjQQ0/+ty8rDn8OTaiPi41FyUnEi6LO+qyBS 11 | 09FjnZj++PkcRcXW99SNxmEJRY7MuNHt5wIvEH2jNEOJ9lszzZFBDbuwsjXHK35+ 12 | lPbGEy69xCP26iEafysKKbRXJhE1C+tk8SnK+Gm62sivmK/5av4HAwJXxtv1ynxO 13 | DtS0nVDdzgGHGC3F520qQpUb+rrWSMvo4f2/ODb6HbQt8FB2G0zFxN9DurBh1Rq1 14 | ILvFIIs0T5K/YZ29tClBbHBoYSBUZXN0IChkZW1vIGtleSkgPGFscGhhQGV4YW1w 15 | bGUubmV0PohVBBMRAgAVBQI2446eAwsKAwMVAwIDFgIBAheAAAoJEC1yfMdoaXc0 16 | OXgAoIEuZGmW//xl9Kp6nkiOoQC5pe9bAKCXo0TNP79Z7A9MZzBlj6kuTJwu/YhV 17 | BBMRAgAVBQI2446eAwsKAwMVAwIDFgIBAheAAAoJEC1yfMdoaXc0OXgAniui4cH4 18 | ukKQ2LkLn2McRrWRsA3MAKCZ122s1KPXI/JMLBTBGCE9SiYQJLQQQWxpY2UgKGRl 19 | bW8ga2V5KYhVBBMRAgAVBQI247arAwsKAwMVAwIDFgIBAheAAAoJEC1yfMdoaXc0 20 | J4wAn0x5RWtqCjklzo93B143k4zBvLftAKCFbrlxlNCUPVsGUir9AzxvP0A3gbQn 21 | QWxmYSBUZXN0IChkZW1vIGtleSkgPGFsZmFAZXhhbXBsZS5uZXQ+iFUEExECABUF 22 | AjbjuFgDCwoDAxUDAgMWAgECF4AACgkQLXJ8x2hpdzS3wgCgk/BrqP5WblWLc2+6 23 | jwlmuLg8n8MAn12puZol0HwV0mcd8aHWtcrfL8lynQHABDbjjw8QBACcjdcfV/S7 24 | I319mfDvbOwczDvTqDsRbb2cPhQNAbg7NFlWJKtRrmff14jtCt9M77WZ5W+zTLwX 25 | 8+8Wy3mMfrys8ucZKtfPixOXVPhyinUUGSq68IArA8vLSUTuOO0LIi05LAg6jzGh 26 | N9jgkQReZyqxub4oe/3JhIX9grgJ/tsjNwADBwP9GeXmMrGi5wMD3qkPbzb1Mqws 27 | VBJq75eLLxu85JIN2XIAGw6Q0FJp4o7d4BAQqAMzt3ONU1OcCWlDQRDxj1nynE5Z 28 | gRBiVoyudEELgNnYhp3MSEuUg7PkFWn+N+GuvyhVUHApleyvP09kvP57hif6yJRS 29 | +V6L1ugP0vZmBI4dqQ/+BwMCZD+ecL2Wy7jUELEqiGi2L9T8zyQKP2d7/8YTIez/ 30 | HxRO6mMvs7YHx87imq1eAFFqXsxNOGbBOT0oUY8zkYV4R3pC/hNX2lsWq/TbfaUS 31 | i+qK5yKNm7ccniHUgFoCeA3esILIUh73TuaBpk2eWy7RLXHr+BvkbkC1gZ4HzWlx 32 | QLjzovsYVpbq3/cofktJN0O+4UjKcVEYmUtunmBV9+6FJuAsz/sYSVi3RTgqI0+g 33 | YYhGBBgRAgAGBQI2448PAAoJEC1yfMdoaXc0IKkAn3A15g/LjVXSoPwvb6iNyUp3 34 | apJ7AJ0cc1Xh4v4ie9zgirbxax21fRqIKpUB6QQ247XLEQQAgQyThl/Qv8cQlWTT 35 | +jh8+nC+bzNz4plAIVfvRwFVT0FYk5xSq5GD0kMkX1s4zlPETtU6eQh8++O6Dm+o 36 | /T++Mh9bsu/MhYOFLoVwVop4bgiiquCCFsCZAigRa9VPH7vGumOjXI6ogwNCphkS 37 | azD5l3p15CaRRhxu/K1LzYvSDH8AoLoMzSC4f912QmVPgVD2Hly/p1ABBACA12YY 38 | 9bxVx4IvZZooyg4yaHBAaGpjf7WkMujsdUUQ+h7XwD2OUxEdZ+8ZvYTMxPjr9SCq 39 | R/xPO9kYWtartb+3jmunk7jVhdDb5kkfeeX63kbDbkfCTSG+krSNhEfacwVH48pA 40 | vaYNsD3gu8KUCSBfUxfiWtQbxtiPoWtsSe/OgAP7BxFLwDrHOfGGz5WyD8qdiXRB 41 | 7100U9jSElUbkzELIPL1ffZzGEdglIdu9Lj8stsWWg/5GHCff9Z4GOwvaW2zVqFe 42 | 9D5BDDv6o+uziFYllT81ISHVEaK26RobnN6Ac1MToImpeyGyEj0SLQ4INqGaGOIa 43 | skDcfAo9mWQMw6TNrwr+BwMCQUUVllgNCNzUZi7YINDlwhj1tLE8IdDJ14WJ29TS 44 | 5BgjrBaMLDetvYvnYPwrpwh/ZIRUm0bg5/K2DQXYQLbuBE02u7QnWnVsdSBUZXN0 45 | IChkZW1vIGtleSkgPHp1bHVAZXhhbXBsZS5uZXQ+iFUEExECABUFAjbjtcsDCwoD 46 | AxUDAgMWAgECF4AACgkQa8R3gFSs0kZA6wCeJUyRzuFbsZ0uQulvpgOIRTLTKscA 47 | oLd3InVEj20peTUQ5b2NOimSXnKxiFUEExECABUFAjbjtcsDCwoDAxUDAgMWAgEC 48 | F4AACgkQa8R3gFSs0kZA6wCeOBSNOP3/J4LLMGDC7YWzVnYcH1oAoJh1THc6xw3d 49 | CapVWt7enBljkaZInQHABDbjtfIQBADMfPDBQoMzv52Mmjb8SdaYKKNzqDd9K1oY 50 | 2hcMSi+LcHag+KJFOyKBf3SoHmcU/vCEN+LyTgljYSKDmEf4wZ2+eLfqFgSdBJp2 51 | xm55ih+9CHXg3dXx9SbHiGJCIxfJaIsnNz3VmJGPDDjBlaf/hjl/7SZvR+MJpVLF 52 | PGjj7uOhTwADBQP/Sgv0abeCXVdVXwGEmhdV0VDo833IQRdRu1yt+QLnWRMGTY1o 53 | QapsH6QLwYSZfDJlxbsBA3tfqKStpRSbdGNNTsK+RIehsGddi3sWGplRGm5Xt5Kp 54 | kY/mc/tLFaYJNMqAgfWQcKlZHBp7EoWMgiRiDJUWq0TH1wRDoPaRc+H5Gdr+BwMC 55 | RQr6jr/dSR7UxBJhvbow5H8f24gW0461q02MigdIzk00fAjc8xNZI9dN0HaICqif 56 | tbbPCezutLGtXEb4rOhAttuMVswdGF8aerhA6lwVF8lbvLTOyf2HbLAgVs/zvEgy 57 | LVHmXwNhoaLMcytlRL7ZpLA59C6mywH83OMYF+NHLsMRu5VwSF0ZHE3VMLb6APdI 58 | J1qfpeQesrudHES5wb5OgX8TosiEeJ0RmEB8oU+/MIhGBBgRAgAGBQI247XyAAoJ 59 | EGvEd4BUrNJGfWMAoLkanmwcz2xZ1l4zqp+7ngXY7AxAAJ9ONhd+kwCkBE4+SOGE 60 | U2ofR3zHkQ== 61 | =c9V4 62 | -----END PGP PRIVATE KEY BLOCK----- 63 | -------------------------------------------------------------------------------- /tests/common/mod.rs: -------------------------------------------------------------------------------- 1 | #![allow(dead_code)] 2 | use std::{ 3 | collections::HashMap, 4 | env, 5 | ffi::{OsStr, OsString}, 6 | fs, 7 | io::prelude::*, 8 | path::{Path, PathBuf}, 9 | process::{Command, Stdio}, 10 | }; 11 | 12 | use gpgme::{self, Context, PassphraseRequest, PinentryMode}; 13 | 14 | #[allow(unused_macros)] 15 | macro_rules! assert_matches { 16 | ($left:expr, $(|)? $($pattern:pat_param)|+ $(if $guard:expr)? $(,)?) => { 17 | match $left { 18 | $($pattern)|+ $(if $guard)? => {} 19 | ref left_val => { 20 | panic!(r#"assertion `(left matches right)` failed: 21 | left: `{left_val:?}` 22 | right: `{}`"#, stringify!($($pattern)|+ $(if $guard)?)) 23 | } 24 | } 25 | }; 26 | ($left:expr, $(|)? $( $pattern:pat_param )|+ $( if $guard: expr )?, $($arg:tt)+) => { 27 | match $left { 28 | $($pattern)|+ $(if $guard)? => {} 29 | ref left_val => { 30 | panic!(r#"assertion `(left matches right)` failed: {} 31 | left: `{left_val:?}` 32 | right: `{}`"#, format_args!($($arg)+), stringify!($($pattern)|+ $(if $guard)?)) 33 | } 34 | } 35 | }; 36 | } 37 | 38 | pub fn passphrase_cb(_req: PassphraseRequest<'_>, out: &mut dyn Write) -> gpgme::Result<()> { 39 | out.write_all(b"abc")?; 40 | Ok(()) 41 | } 42 | 43 | struct TestHarness { 44 | wd: PathBuf, 45 | env: HashMap<&'static str, OsString>, 46 | gpg: OsString, 47 | } 48 | 49 | impl Drop for TestHarness { 50 | fn drop(&mut self) { 51 | let _ = self.cmd("gpgconf").args(["--kill", "all"]).status(); 52 | } 53 | } 54 | 55 | impl TestHarness { 56 | fn new() -> Self { 57 | let wd = env::current_dir().unwrap(); 58 | let env = HashMap::from_iter([ 59 | ("GNUPGHOME", wd.as_os_str().to_owned()), 60 | ("GPG_AGENT_INFO", OsString::new()), 61 | ]); 62 | let gpg = env::var_os("GPG").unwrap_or("gpg".into()); 63 | Self { wd, env, gpg } 64 | } 65 | 66 | fn cmd(&self, program: impl AsRef) -> Command { 67 | let mut cmd = Command::new(program); 68 | cmd.envs(&self.env) 69 | .stdin(Stdio::piped()) 70 | .stdout(Stdio::null()) 71 | .stderr(Stdio::null()); 72 | cmd 73 | } 74 | 75 | fn setup_agent(&self) { 76 | let pinentry = Path::new(env!("CARGO_BIN_EXE_pinentry")); 77 | if !pinentry.exists() { 78 | panic!("Unable to find pinentry program"); 79 | } 80 | 81 | let conf = self.wd.join("gpg.conf"); 82 | fs::write(conf, include_str!("./data/gpg.conf")).unwrap(); 83 | 84 | let agent_conf = self.wd.join("gpg-agent.conf"); 85 | fs::write( 86 | agent_conf, 87 | format!( 88 | concat!( 89 | include_str!("./data/gpg-agent.conf"), 90 | "pinentry-program {}\n" 91 | ), 92 | pinentry.to_str().unwrap() 93 | ), 94 | ) 95 | .unwrap(); 96 | } 97 | 98 | fn import_key(&self, key: &[u8]) { 99 | let mut child = self 100 | .cmd(&self.gpg) 101 | .args([ 102 | "--batch", 103 | "--no-permission-warning", 104 | "--passphrase", 105 | "abc", 106 | "--import", 107 | ]) 108 | .spawn() 109 | .unwrap(); 110 | child.stdin.as_mut().unwrap().write_all(key).unwrap(); 111 | assert!(child.wait().unwrap().success()); 112 | } 113 | 114 | fn import_ownertrust(&self) { 115 | let mut child = self 116 | .cmd(&self.gpg) 117 | .args([ 118 | "--batch", 119 | "--no-permission-warning", 120 | "--passphrase", 121 | "abc", 122 | "--import", 123 | ]) 124 | .spawn() 125 | .unwrap(); 126 | child 127 | .stdin 128 | .as_mut() 129 | .unwrap() 130 | .write_all(include_bytes!("./data/ownertrust.txt")) 131 | .unwrap(); 132 | let _ = child.wait(); 133 | } 134 | 135 | fn setup(&self) { 136 | self.setup_agent(); 137 | self.import_key(include_bytes!("./data/pubdemo.asc")); 138 | self.import_key(include_bytes!("./data/secdemo.asc")); 139 | self.import_ownertrust(); 140 | 141 | let token = gpgme::init(); 142 | token 143 | .set_engine_home_dir( 144 | gpgme::Protocol::OpenPgp, 145 | self.wd.as_os_str().as_encoded_bytes(), 146 | ) 147 | .unwrap(); 148 | token 149 | .check_engine_version(gpgme::Protocol::OpenPgp) 150 | .unwrap(); 151 | } 152 | } 153 | 154 | pub fn with_test_harness(f: impl FnOnce()) { 155 | let harness = TestHarness::new(); 156 | harness.setup(); 157 | f() 158 | } 159 | 160 | pub fn create_context() -> Context { 161 | let mut ctx = Context::from_protocol(gpgme::Protocol::OpenPgp).unwrap(); 162 | let _ = ctx.set_pinentry_mode(PinentryMode::Loopback); 163 | ctx 164 | } 165 | -------------------------------------------------------------------------------- /tests/create_key.rs: -------------------------------------------------------------------------------- 1 | use std::time::{Duration, SystemTime}; 2 | 3 | use gpgme::CreateKeyFlags; 4 | use sealed_test::prelude::*; 5 | 6 | use self::common::passphrase_cb; 7 | 8 | #[macro_use] 9 | mod common; 10 | 11 | #[sealed_test] 12 | fn test_create_key() { 13 | common::with_test_harness(|| { 14 | let mut ctx = common::create_context().set_passphrase_provider(passphrase_cb); 15 | if !ctx.engine_info().check_version("2.1.13") { 16 | return; 17 | } 18 | 19 | let expiration = Duration::from_secs(3600); 20 | let res = ctx 21 | .create_key_with_flags( 22 | "test user ", 23 | "future-default", 24 | expiration, 25 | CreateKeyFlags::CERT, 26 | ) 27 | .unwrap(); 28 | let creation = SystemTime::now(); 29 | 30 | let fpr = res.fingerprint_raw().unwrap(); 31 | assert!(res.has_primary_key()); 32 | let mut key = ctx.get_key(fpr).unwrap(); 33 | assert!(!key.is_bad()); 34 | assert!(key.can_certify()); 35 | assert!(!key.can_sign()); 36 | assert!(!key.can_encrypt()); 37 | assert!(!key.can_authenticate()); 38 | 39 | let primary = key.primary_key().unwrap(); 40 | assert!(!primary.is_bad()); 41 | assert!(primary.can_certify()); 42 | assert!(!primary.can_sign()); 43 | assert!(!primary.can_encrypt()); 44 | assert!(!primary.can_authenticate()); 45 | assert!( 46 | primary 47 | .expiration_time() 48 | .unwrap() 49 | .duration_since(creation) 50 | .unwrap() 51 | <= expiration 52 | ); 53 | 54 | assert_eq!(key.subkeys().count(), 1); 55 | assert_eq!(key.user_ids().count(), 1); 56 | let uid = key.user_ids().next().unwrap(); 57 | assert!(!uid.is_bad()); 58 | assert_matches!(uid.name(), Ok("test user")); 59 | assert_matches!(uid.email(), Ok("test@example.com")); 60 | assert_matches!(uid.id(), Ok("test user ")); 61 | 62 | let res = ctx 63 | .create_subkey_with_flags(&key, "future-default", expiration, CreateKeyFlags::AUTH) 64 | .unwrap(); 65 | let creation = SystemTime::now(); 66 | assert!(res.has_sub_key()); 67 | key.update().unwrap(); 68 | assert!(key.can_authenticate()); 69 | assert_eq!(key.subkeys().count(), 2); 70 | 71 | let sub = key 72 | .subkeys() 73 | .find(|k| k.fingerprint_raw() == res.fingerprint_raw()) 74 | .unwrap(); 75 | assert!(!sub.is_bad()); 76 | assert!(!sub.can_certify()); 77 | assert!(!sub.can_sign()); 78 | assert!(!sub.can_encrypt()); 79 | assert!(sub.can_authenticate()); 80 | assert!( 81 | sub.expiration_time() 82 | .unwrap() 83 | .duration_since(creation) 84 | .unwrap() 85 | <= expiration 86 | ); 87 | 88 | let res = ctx 89 | .create_subkey_with_flags( 90 | &key, 91 | "future-default", 92 | expiration, 93 | CreateKeyFlags::ENCR | CreateKeyFlags::NOEXPIRE, 94 | ) 95 | .unwrap(); 96 | assert!(res.has_sub_key()); 97 | key.update().unwrap(); 98 | assert!(key.can_authenticate()); 99 | assert_eq!(key.subkeys().count(), 3); 100 | 101 | let sub = key 102 | .subkeys() 103 | .find(|k| k.fingerprint_raw() == res.fingerprint_raw()) 104 | .unwrap(); 105 | assert!(!sub.is_bad()); 106 | assert!(!sub.can_certify()); 107 | assert!(!sub.can_sign()); 108 | assert!(sub.can_encrypt()); 109 | assert!(!sub.can_authenticate()); 110 | assert_matches!(sub.expiration_time(), None); 111 | }) 112 | } 113 | -------------------------------------------------------------------------------- /tests/data.rs: -------------------------------------------------------------------------------- 1 | use std::io::{prelude::*, SeekFrom}; 2 | 3 | use gpgme::Data; 4 | use sealed_test::prelude::*; 5 | 6 | #[macro_use] 7 | mod common; 8 | 9 | type Round = u32; 10 | const TEST_INITIALIZER: Round = 0; 11 | const TEST_INOUT_NONE: Round = 1; 12 | const TEST_INOUT_MEM_NO_COPY: Round = 2; 13 | const TEST_INOUT_MEM_COPY: Round = 3; 14 | const TEST_INOUT_MEM_FROM_FILE_COPY: Round = 4; 15 | const TEST_INOUT_MEM_FROM_INEXISTANT_FILE: Round = 5; 16 | const TEST_INOUT_VEC_NONE: Round = 6; 17 | const TEST_INOUT_VEC: Round = 7; 18 | const TEST_END: Round = 8; 19 | 20 | const TEXT: &str = "Just GNU it!\n"; 21 | const TEXT2: &str = "Just GNU it!\nJust GNU it!\n"; 22 | 23 | fn read_once_test(rnd: Round, data: &mut Data) { 24 | let mut buffer = [0u8; 1024]; 25 | 26 | let read = data.read(&mut buffer[..]); 27 | assert_matches!(read, Ok(1..), "round {rnd:?}"); 28 | assert_eq!(&buffer[..read.unwrap()], TEXT.as_bytes(), "round {rnd:?}"); 29 | 30 | let read = data.read(&mut buffer[..]); 31 | assert_matches!(read, Ok(0), "round {rnd:?}"); 32 | } 33 | 34 | fn read_test(rnd: Round, data: &mut Data) { 35 | let mut buffer = [0u8; 1024]; 36 | if matches!(rnd, TEST_INOUT_NONE | TEST_INOUT_VEC_NONE) { 37 | let read = data.read(&mut buffer); 38 | assert_matches!(read, Err(_) | Ok(0), "round {rnd:?}"); 39 | return; 40 | } 41 | read_once_test(rnd, data); 42 | data.seek(SeekFrom::Start(0)).unwrap(); 43 | read_once_test(rnd, data); 44 | } 45 | 46 | fn write_test(rnd: Round, data: &mut Data) { 47 | let mut buffer = [0u8; 1024]; 48 | let amt = data.write(TEXT.as_bytes()).unwrap(); 49 | assert_eq!(amt, TEXT.len(), "round {rnd:?}"); 50 | 51 | data.seek(SeekFrom::Start(0)).unwrap(); 52 | if matches!(rnd, TEST_INOUT_NONE | TEST_INOUT_VEC_NONE) { 53 | read_once_test(rnd, data); 54 | } else { 55 | let amt = data.read(&mut buffer[..]); 56 | assert_matches!(amt, Ok(1..), "round {rnd:?}"); 57 | assert_eq!(&buffer[..amt.unwrap()], TEXT2.as_bytes()); 58 | 59 | let amt = data.read(&mut buffer[..]); 60 | assert_matches!(amt, Ok(0), "round {rnd:?}"); 61 | } 62 | } 63 | 64 | const MISSING_FILE_NAME: &str = "this-file-surely-does-not-exist"; 65 | 66 | #[sealed_test] 67 | fn test_data() { 68 | common::with_test_harness(|| { 69 | let mut rnd = TEST_INITIALIZER; 70 | 71 | loop { 72 | rnd += 1; 73 | let mut data = match rnd { 74 | TEST_INOUT_NONE => Data::new().unwrap(), 75 | TEST_INOUT_MEM_NO_COPY => Data::from_buffer(TEXT.as_bytes()).unwrap(), 76 | TEST_INOUT_MEM_COPY => Data::from_bytes(TEXT.as_bytes()).unwrap(), 77 | TEST_INOUT_MEM_FROM_FILE_COPY => continue, 78 | TEST_INOUT_MEM_FROM_INEXISTANT_FILE => { 79 | let res = Data::load(MISSING_FILE_NAME); 80 | assert_matches!(res, Err(_), "round {rnd}"); 81 | continue; 82 | } 83 | TEST_INOUT_VEC_NONE => Data::try_from(Vec::new()).unwrap(), 84 | TEST_INOUT_VEC => Data::try_from(TEXT.as_bytes().to_vec()).unwrap(), 85 | TEST_END => break, 86 | _ => panic!("unexpected round {rnd}"), 87 | }; 88 | 89 | read_test(rnd, &mut data); 90 | write_test(rnd, &mut data); 91 | } 92 | }) 93 | } 94 | -------------------------------------------------------------------------------- /tests/edit.rs: -------------------------------------------------------------------------------- 1 | #![allow(deprecated)] 2 | use std::io::{prelude::*, stdout}; 3 | 4 | use gpgme::{ 5 | edit::{self, EditInteractionStatus, Editor}, 6 | Error, Result, 7 | }; 8 | use sealed_test::prelude::*; 9 | 10 | use self::common::passphrase_cb; 11 | 12 | #[macro_use] 13 | mod common; 14 | 15 | #[derive(Debug, Default, Copy, Clone, Eq, PartialEq)] 16 | enum TestEditorState { 17 | #[default] 18 | Start, 19 | Fingerprint, 20 | Expire, 21 | Valid, 22 | Uid, 23 | Primary, 24 | Quit, 25 | Save, 26 | } 27 | 28 | struct TestEditor; 29 | 30 | impl Editor for TestEditor { 31 | type State = TestEditorState; 32 | 33 | fn next_state( 34 | state: Result, 35 | status: EditInteractionStatus<'_>, 36 | need_response: bool, 37 | ) -> Result { 38 | use self::TestEditorState as State; 39 | 40 | println!("[-- Code: {:?}, {:?} --]", status.code, status.args()); 41 | if !need_response { 42 | return state; 43 | } 44 | 45 | if status.args() == Ok(edit::PROMPT) { 46 | match state { 47 | Ok(State::Start) => Ok(State::Fingerprint), 48 | Ok(State::Fingerprint) => Ok(State::Expire), 49 | Ok(State::Valid) => Ok(State::Uid), 50 | Ok(State::Uid) => Ok(State::Primary), 51 | Ok(State::Quit) => state, 52 | Ok(State::Primary) | Err(_) => Ok(State::Quit), 53 | _ => Err(Error::GENERAL), 54 | } 55 | } else if (status.args() == Ok(edit::KEY_VALID)) && (state == Ok(State::Expire)) { 56 | Ok(State::Valid) 57 | } else if (status.args() == Ok(edit::CONFIRM_SAVE)) && (state == Ok(State::Quit)) { 58 | Ok(State::Save) 59 | } else { 60 | state.and(Err(Error::GENERAL)) 61 | } 62 | } 63 | 64 | fn action(&self, state: Self::State, out: &mut dyn Write) -> Result<()> { 65 | use self::TestEditorState as State; 66 | 67 | match state { 68 | State::Fingerprint => out.write_all(b"fpr")?, 69 | State::Expire => out.write_all(b"expire")?, 70 | State::Valid => out.write_all(b"0")?, 71 | State::Uid => out.write_all(b"1")?, 72 | State::Primary => out.write_all(b"primary")?, 73 | State::Quit => write!(out, "{}", edit::QUIT)?, 74 | State::Save => write!(out, "{}", edit::YES)?, 75 | _ => return Err(Error::GENERAL), 76 | } 77 | Ok(()) 78 | } 79 | } 80 | 81 | #[sealed_test] 82 | fn test_edit() { 83 | common::with_test_harness(|| { 84 | common::create_context().with_passphrase_provider(passphrase_cb, |ctx| { 85 | let key = ctx 86 | .find_keys(Some("Alpha")) 87 | .unwrap() 88 | .next() 89 | .unwrap() 90 | .unwrap(); 91 | ctx.edit_key_with(&key, TestEditor, stdout()).unwrap(); 92 | }); 93 | }) 94 | } 95 | -------------------------------------------------------------------------------- /tests/encrypt_simple.rs: -------------------------------------------------------------------------------- 1 | use sealed_test::prelude::*; 2 | 3 | use self::common::passphrase_cb; 4 | 5 | #[macro_use] 6 | mod common; 7 | 8 | #[sealed_test] 9 | fn test_simple_encrypt_decrypt() { 10 | common::with_test_harness(|| { 11 | let mut ctx = common::create_context(); 12 | 13 | let key = ctx 14 | .find_keys(Some("alfa@example.net")) 15 | .unwrap() 16 | .next() 17 | .unwrap() 18 | .unwrap(); 19 | 20 | ctx.set_armor(true); 21 | ctx.set_text_mode(true); 22 | 23 | let mut ciphertext = Vec::new(); 24 | ctx.encrypt_with_flags( 25 | Some(&key), 26 | "Hello World", 27 | &mut ciphertext, 28 | gpgme::EncryptFlags::ALWAYS_TRUST, 29 | ) 30 | .unwrap(); 31 | assert!(ciphertext.starts_with(b"-----BEGIN PGP MESSAGE-----")); 32 | drop(ctx); 33 | 34 | let mut plaintext = Vec::new(); 35 | common::create_context().with_passphrase_provider(passphrase_cb, |ctx| { 36 | ctx.decrypt(&ciphertext, &mut plaintext).unwrap(); 37 | }); 38 | assert_eq!(plaintext, b"Hello World"); 39 | 40 | let mut plaintext = Vec::new(); 41 | let mut ctx = common::create_context().set_passphrase_provider(passphrase_cb); 42 | ctx.decrypt(&ciphertext, &mut plaintext).unwrap(); 43 | assert_eq!(plaintext, b"Hello World"); 44 | }) 45 | } 46 | -------------------------------------------------------------------------------- /tests/encrypt_symmetric.rs: -------------------------------------------------------------------------------- 1 | use sealed_test::prelude::*; 2 | 3 | use self::common::passphrase_cb; 4 | 5 | #[macro_use] 6 | mod common; 7 | 8 | #[sealed_test] 9 | fn test_symmetric_encrypt_decrypt() { 10 | common::with_test_harness(|| { 11 | let mut ciphertext = Vec::new(); 12 | common::create_context().with_passphrase_provider(passphrase_cb, |ctx| { 13 | ctx.set_armor(true); 14 | ctx.set_text_mode(true); 15 | 16 | ctx.encrypt_symmetric("Hello World", &mut ciphertext) 17 | .unwrap(); 18 | }); 19 | assert!(ciphertext.starts_with(b"-----BEGIN PGP MESSAGE-----")); 20 | 21 | let mut plaintext = Vec::new(); 22 | common::create_context().with_passphrase_provider(passphrase_cb, |ctx| { 23 | ctx.decrypt(&ciphertext, &mut plaintext).unwrap(); 24 | }); 25 | assert_eq!(plaintext, b"Hello World"); 26 | 27 | let mut plaintext = Vec::new(); 28 | let mut ctx = common::create_context().set_passphrase_provider(passphrase_cb); 29 | ctx.decrypt(&ciphertext, &mut plaintext).unwrap(); 30 | assert_eq!(plaintext, b"Hello World"); 31 | }) 32 | } 33 | -------------------------------------------------------------------------------- /tests/export.rs: -------------------------------------------------------------------------------- 1 | use gpgme::{CreateKeyFlags, ExportMode}; 2 | use sealed_test::prelude::*; 3 | 4 | use self::common::passphrase_cb; 5 | 6 | #[macro_use] 7 | mod common; 8 | 9 | #[sealed_test] 10 | fn test_export() { 11 | common::with_test_harness(|| { 12 | let mut ctx = common::create_context().set_passphrase_provider(passphrase_cb); 13 | ctx.set_offline(true); 14 | ctx.set_armor(true); 15 | 16 | let key = ctx 17 | .find_keys(Some("alfa@example.net")) 18 | .unwrap() 19 | .next() 20 | .unwrap() 21 | .unwrap(); 22 | 23 | let mut data = Vec::new(); 24 | println!("{:?}", key.fingerprint()); 25 | ctx.export(key.fingerprint_raw(), ExportMode::empty(), &mut data) 26 | .unwrap(); 27 | assert!(!data.is_empty()); 28 | }) 29 | } 30 | 31 | #[sealed_test] 32 | fn test_export_secret() { 33 | common::with_test_harness(|| { 34 | let mut ctx = common::create_context(); 35 | ctx.set_offline(true); 36 | ctx.set_armor(true); 37 | 38 | let res = match ctx.create_key_with_flags( 39 | "test user ", 40 | "future-default", 41 | Default::default(), 42 | CreateKeyFlags::NOPASSWD, 43 | ) { 44 | Ok(r) => r, 45 | Err(e) if e.code() == gpgme::Error::NOT_SUPPORTED.code() => return, 46 | Err(e) => panic!("error: {e:?}"), 47 | }; 48 | let fpr = res.fingerprint_raw().unwrap(); 49 | 50 | let mut data = Vec::new(); 51 | ctx.export(Some(fpr), ExportMode::SECRET, &mut data) 52 | .unwrap(); 53 | assert!(!data.is_empty()); 54 | }) 55 | } 56 | -------------------------------------------------------------------------------- /tests/keylist.rs: -------------------------------------------------------------------------------- 1 | use sealed_test::prelude::*; 2 | 3 | #[macro_use] 4 | mod common; 5 | 6 | #[sealed_test] 7 | fn test_key_list() { 8 | common::with_test_harness(|| { 9 | let mut ctx = common::create_context(); 10 | let keys: Vec<_> = ctx 11 | .find_keys(Some("alfa@example.net")) 12 | .unwrap() 13 | .collect::>() 14 | .unwrap(); 15 | assert_eq!(keys.len(), 1, "incorrect number of keys"); 16 | 17 | let key = &keys[0]; 18 | assert_eq!(key.id(), Ok("2D727CC768697734")); 19 | assert_eq!(key.subkeys().count(), 2); 20 | let subkeys: Vec<_> = key.subkeys().collect(); 21 | assert_eq!(subkeys[0].algorithm(), gpgme::KeyAlgorithm::Dsa); 22 | assert_eq!(subkeys[1].algorithm(), gpgme::KeyAlgorithm::ElgamalEncrypt); 23 | }) 24 | } 25 | -------------------------------------------------------------------------------- /tests/keysign.rs: -------------------------------------------------------------------------------- 1 | use gpgme::KeyListMode; 2 | use sealed_test::prelude::*; 3 | 4 | use self::common::passphrase_cb; 5 | 6 | #[macro_use] 7 | mod common; 8 | 9 | #[sealed_test] 10 | fn test_sign_key() { 11 | common::with_test_harness(|| { 12 | let mut ctx = common::create_context(); 13 | if !ctx.engine_info().check_version("2.1.12") { 14 | return; 15 | } 16 | 17 | ctx.add_key_list_mode(KeyListMode::SIGS).unwrap(); 18 | let signer = ctx 19 | .find_secret_keys(Some("alfa@example.net")) 20 | .unwrap() 21 | .next() 22 | .unwrap() 23 | .unwrap(); 24 | ctx.add_signer(&signer).unwrap(); 25 | 26 | let mut key = ctx 27 | .find_keys(Some("bravo@example.net")) 28 | .unwrap() 29 | .next() 30 | .unwrap() 31 | .unwrap(); 32 | assert!(!key 33 | .user_ids() 34 | .next() 35 | .unwrap() 36 | .signatures() 37 | .any(|s| { signer.id_raw() == s.signer_key_id_raw() })); 38 | 39 | ctx.with_passphrase_provider(passphrase_cb, |ctx| { 40 | ctx.sign_key(&key, None::, Default::default()) 41 | .unwrap(); 42 | }); 43 | 44 | key.update().unwrap(); 45 | assert!(key 46 | .user_ids() 47 | .next() 48 | .unwrap() 49 | .signatures() 50 | .any(|s| { signer.id_raw() == s.signer_key_id_raw() })); 51 | }) 52 | } 53 | -------------------------------------------------------------------------------- /tests/verify.rs: -------------------------------------------------------------------------------- 1 | use sealed_test::prelude::*; 2 | 3 | #[macro_use] 4 | mod common; 5 | 6 | const TEST_MSG1: &[u8] = b"-----BEGIN PGP MESSAGE-----\n\ 7 | \n\ 8 | owGbwMvMwCSoW1RzPCOz3IRxjXQSR0lqcYleSUWJTZOvjVdpcYmCu1+oQmaJIleH\n\ 9 | GwuDIBMDGysTSIqBi1MApi+nlGGuwDeHao53HBr+FoVGP3xX+kvuu9fCMJvl6IOf\n\ 10 | y1kvP4y+8D5a11ang0udywsA\n\ 11 | =Crq6\n\ 12 | -----END PGP MESSAGE-----\n"; 13 | 14 | #[sealed_test] 15 | fn test_signature_key() { 16 | common::with_test_harness(|| { 17 | let mut ctx = common::create_context(); 18 | let mut output = Vec::new(); 19 | let result = ctx.verify_opaque(TEST_MSG1, &mut output).unwrap(); 20 | assert_eq!(result.signatures().count(), 1); 21 | 22 | let sig = result.signatures().next().unwrap(); 23 | let key = ctx.get_key(sig.fingerprint_raw().unwrap()).unwrap(); 24 | for subkey in key.subkeys() { 25 | if subkey.fingerprint_raw() == sig.fingerprint_raw() { 26 | return; 27 | } 28 | } 29 | panic!("verification key not found"); 30 | }) 31 | } 32 | --------------------------------------------------------------------------------