sq user documentation
by Franziska Schmidtke and Malte Meiboom
This guide is about sq
, the primary command line interface for Sequoia PGP. Here you will find an overview of the most common use cases and commands. There are also chapters about cryptography in general as well as some of Sequoia PGP's underlying concepts.
Very specific examples are only presented occasionally. If you can't find something about a command here, please take a look at its --help
output or its man page. This book may not cover the exact version of sq
that you are using, so check that when details seem wrong or incomplete, bearing in mind that this book is still under development. It is built from the 'sequoia-pgp/user-documentation' git repository, where changes are made and tracked.
Conventions, type setting and license
Suffixes
This guide uses the following filename suffixes:
- certificates:
.cert
- keys:
.key
- encrypted messages:
.pgp
- cleartext messages:
.txt
- detached signatures:
.sig
- revocation certificates:
.rev
Type settings
For shell commands, we use the unix-typical $
character for the shell prompt (which may differ from yours), followed by the actual command. The output of the command is appended if needed:
$ sq version
sq 1.3.0
using sequoia-openpgp 2.0.0
with cryptographic backend Nettle 3.8 (Cv448: true, OCB: false)
Copyright license
This guide is licensed under the Creative Commons Attribution-ShareAlike (CC-BY-SA) 4.0 International license. It is based on https://creativecommons.org/licenses/by-sa/4.0/legalcode.txt, but typeset via Markdown, using commit b1a347d40d2a50ed345a9b152248cca6b9f0f803 of Creative-Commons-Markdown.git
. It matches the plain text version, linked above, except for typesetting. In case of differences, the plain text version is authoritative.
About sq and Sequoia PGP
Sequoia PGP is an implementation of the OpenPGP standard written in Rust. It comes in the form of a library that applications can integrate to gain OpenPGP functionality without reinventing the wheel. sq
is a command line application built using Sequoia PGP in this way.
sq
aims to expose a useful subset of OpenPGP features for common tasks: encryption and decryption, signing and verifying, key and certificate management, certification, and so on.
OpenPGP is a complex and elaborate standard, addressing difficult issues of privacy and authenticity. As such it's not easy to digest, so sq
takes an opinionated approach, making OpenPGP accessible and (hopefully) easy to use without sacrificing OpenPGP's goals.
sq
aims to be safe by default. That doesn't mean that you can't do stupid or dangerous things with sq
, but the easy, default way of using sq
is safe.
Sequoia PGP has implemented the OpenPGP standard defined in RFC9580 (published in 2024, also known as v6) since version 2.0.0, which in turn has been used in sq
since version 1.3.0.
Design and specialties
sq
(and Sequoia PGP for that matter) uses specific terminology when referring to keys (public and private) – please read the chapter 'Getting started' for an overview.
There is no "automatic trust". Having a (secret) key in your key store doesn't imply that you "trust" signatures and certifications issued by that key. There is an extra step necessary, which protects against unwanted consequences of inadvertently imported keys. The same holds true for certificates ('public keys'); sq
wants you to be certain when designating a certificate as "trusted".
sq
doesn't use keyrings by default; it uses stores. While the key store operates similarly to a keyring, the certificate store is more like a cache. Trust relationships are managed within the PKI, which is a separate entity.
sq
tries to minimize user interaction, for instance, in the form of yes/no questions. The design of the user interface aims to make this kind of question superfluous.
sq
uses subcommands to structure its interface. It has help
-pages for each subcommand in the hierarchy to help with orientation. Alternatively, tab expansion gives you a preview of possible options, and helps to avoid typing long option names or subcommands.
sq
is stateful. It updates its stores directly, according to the operations applied to them. While there are ways to avoid these updates and work in a stateless fashion, it's not the default mode of operation. If you are looking for statelessness, have a look at sqop
.
Getting started
Let's start with a short overview of some terminology, the toplevel structure of sq
, and some features to support you when using sq
.
Terminology
Keys and certificates
sq
uses a different terminology when it comes to certain cryptographic artifacts. What is known as a "public key" in other OpenPGP software (like gpg), is called a "certificate" in sq
. "Private keys" are just called "keys". For the rationale behind this renaming, see chapter Background: Keys and certificates.
Stores instead of keyrings
Some popular OpenPGP implementations store their artifacts in keyrings (secring.pgp
, pubring.pgp
); sq
uses stores for that. Keys are stored in the key store, certificates in the cert store. sq
has (limited) support for keyrings to help (for instance) with migration.
"Authenticated"
"Authenticated" in the context of sq
means "The right thing". Its main usage is in the selection or designation of a certificate. If you designate a certificate by its fingerprint, this designation is distinct, as fingerprints are unique. Designating by User ID is not, as these cannot be deduced from the key material like a fingerprint can. To use the binding between a certificate and a User ID to designate a certificate (for instance to select a certificate by an email address), this binding has to be authenticated; it has to be manually marked as valid or inherit authenticity by being certified by a trust introducer.
Trust introducer
A trust introducer is a person or institution considered sufficiently trustworthy (and competent) that certifications they have done are accepted. Trust introducers can be limited in scope:
- so that only specific certifications are considered,
- in spreading trust — can a trust introducer introduce other entities as further introducers?
- and trustworthiness, affecting the amount of authenticity a certification adds to a specific certificate.
Technically, a trust introducer is represented by a certificate. Each user has to individually promote a certificate to become a trust introducer. There is no limit to the number of trust introducers a user can have.
Public Key Infrastructure (PKI)
sq
does not support implicit authentication; it's not enough to just import a certificate (aka public key) to use it. The User IDs must first be authenticated, which declares a "trust relationship" with the imported certificates.
These relationships are managed within the PKI. You can think of this as your local Web of Trust.
Structure of the interface
The functions of sq
are split into categories. Related functions are located in the same category, sometimes even in subcategories.
For orientation, these are the top-level categories:
encrypt
Encryption of messages or data files.decrypt
Decryption of messages or data files.sign
Signing messages or data files.verify
Verify signed messages or detached signatures.download
Download and authenticate a file in one go.inspect
Inspect data, likefile(1)
. Works on files, but also on objects in the different stores like certificates or keys.cert
Manage certificates. Import, export, list and the like.key
Manage keys. Generating, revoking, listing, import/export, updating metadata like expiry date and more.pki
This is where your "trust management" lives. Authenticating, lookup, trust paths and amounts and more.network
Retrieve and publish certificates over the network.keyring
Manage collections of keys or certs in files.packet
Low-level packet manipulation.config
Get configuration options.version
Detailed version and output version information.help
Print this message or the help of the given subcommand(s).
For example, you can list your keys with:
$ sq key list
Help
sq
has help
pages for every command. If in doubt, use:
$ sq some command --help
to get a short overview of parameters and functionality of said command. You can also use:
$ sq help some command
Tab completion
sq
comes with tab completion. Pressing the tab key will complete partially typed commands; a second press will present a set of alternatives that are available at that point in the command line. For example:
$ sq command <TAB><TAB>
will show you possible continuations of the command, available subcommands, parameters, and other options. Likewise sq com<TAB><TAB>
will expand to sq command
.
Hints
After completing a command, sq
prints some hints for possible follow-up commands:
$ sq key generate --own-key --email 'alice@example.com'
[...]
Hint: Because you supplied the `--own-key` flag, the user IDs on this key have been marked as authenticated, and this key has been marked as a fully
trusted introducer. If that was a mistake, you can undo that with:
$ sq pki link retract --cert=0F761BE91983E9B50F2180CA8747303171399A43 --all
Hint: You can export your certificate as follows:
$ sq cert export --cert=0F761BE91983E9B50F2180CA8747303171399A43
Hint: Once you are happy you can upload it to public directories using:
$ sq network keyserver publish --cert=0F761BE91983E9B50F2180CA8747303171399A43
The hints pick up the results of the command and include them appropriately. In this example, the displayed fingerprint is indeed the fingerprint of the key just generated.
Named parameters can be written like --email alice@example.com
or --email=alice@example.com
, with or without a =
between the parameter name and value.
Installing sq
Linux
Install sq
by using your Linux distribution's package manager.
Debian
sq
has been part of the Debian distribution since "bookworm" (Debian 12). Up-to-date versions are part of "trixie" (Debian 13) and later.
$ sudo apt install sq
Fedora
sq
is included in Fedora 40 and later. To install sq
, use yum
; note that it is not called sq
:
$ sudo yum install sequoia-sq
macOS
There is a package containing sq
in homebrew:
$ brew install sequoia-sq
The homebrew package is not called
sq
; that is a different program; see disambiguation below.
Windows
The easiest way to get sq
on Windows is via the Windows Subsystem for Linux (WSL). By default, this installs Ubuntu Linux, which is based on Debian, so after installing WSL, follow the instructions for Debian above.
Install from source
sq
is written in Rust. To compile it, you will need a recent Rust toolchain. Your Linux distribution very likely already has the relevant packages. If you don't want to use it or can't (because it's too old, or you are not using Linux), there is also the option to get the toolchain from the Rust project directly. See the rust tools documentation for further information.
Install the dependencies (Debian 12 "bookworm" / Ubuntu 24.04):
$ sudo apt install clang nettle-dev pkg-config libssl-dev capnproto libsqlite3-dev
Install the dependencies (macOS):
If you are using homebrew, install the dependencies with:
% brew install pkgconfig nettle openssl capnp
Install using cargo
After installing the dependencies, sq
can be installed using cargo. Cargo will take care of downloading the source code and its dependencies (only the crates). Cargo will then compile and install the resulting binary.
$ cargo install --locked sequoia-sq
Using this method will install the latest tagged release of sq
. If you are interested in HEAD
(the latest revision from the git repository), please continue reading.
Get the source
Checkout the sources from the Sequoia PGP repository at gitlab.com:
$ git clone https://gitlab.com/sequoia-pgp/sequoia-sq.git
Build the binary
Change into the project directory and build the binary:
$ cd sequoia-sq
$ cargo build --release
The compiled sq
binary will be found in the ./target/release
subfolder; Copy or soft-link it to a convenient location.
Use cargo install
to compile and install in one step (recommended):
$ cargo install --locked --path .
sq
will be installed in $CARGO_HOME
, usually ~/.cargo/bin
. For further information and options consult cargo help install
.
Check your installation
A simple "hello world"-like check is running:
$ sq version
sq 1.1.0
using sequoia-openpgp 1.22.0
with cryptographic backend Nettle 3.8 (Cv448: true, OCB: false)
Your output might be different. This check only shows that everything is in place and the binary can be executed.
Updating from source
If you installed from source, to update sq
, pull the latest changes from the git repository and repeat the build process. Change into the project directory and:
$ git pull
$ cargo install --locked --path .
Disambiguation
There are other programs called sq
that are not this sq
:
- There is a data wrangling tool called
sq
. sq
is the CLI executable for the Squirrel language.
Confidentiality
This chapter deals with subjects related to confidentiality. It concerns key generation, maintaining certificates, using the key material for encrypting and decrypting files, as well as the publication and revocation of certificates.
Confidentiality - Quick start
This is a quick walk through on the topics of key generation and typical uses of certificates.
Generating a key
Generate a key with name and email address User IDs:
$ sq key generate --own-key --name 'alice' --email 'alice@example.com'
Export a key from the keystore into a file
$ sq key export --cert $FINGERPRINT > $KEYFILE
Import a key into keystore
If a key is only available as a file, it can be imported into the keystore:
$ sq key import $KEYFILE
Encrypt and decrypt a file
To encrypt a message or a file with another person's certificate:
$ sq encrypt --for-email=alice@example.com --signer-email=bob@example.org message.txt
To decrypt a file using a key in the keystore:
$ sq decrypt message.pgp
Key generation
Generate a key
You can generate keys with different parameters for different purposes. The default settings include an expiration of three years and elliptic curves as cryptographic algorithm. These settings can be modified.
It is not possible to explicitly generate a certificate; you only ever generate a key, as a certificate is always a part of a key containing only public key material.
The sq key generate
command is a stateful operation which imports the key directly into the key store. If you want to avoid this, you could also use the --output
option to specify a location for the key file. The co-generated emergency revocation file will automatically be stored in $HOME/.local/share/sequoia/revocation-certificates
(see 'File structure and backup' for more information) if you don't specify a different location.
You don't have to separate different User IDs, but could instead combine them to one statement --userid 'alice <alice@example.com>'
. However, this is not the recommended practice. Instead, Sequoia suggests having separate User IDs for name and email address, which allows them to be certified or revoked separately.
$ sq key generate --own-key --name "Alice Example" --email alice@example.com
This example generates a key with the two User IDs Alice Example
and <alice@example.com>
. --own-key
tells sq
to promote the new key as an unconstrained trust introducer. If you plan to share the key (for instance to use it as a group key), use --shared-key
instead. This way, the new key will not be made a trust introducer.
You can use sq pki link {add | authorize | retract}
to change the trust introducer state at a later time.
For understanding in detail what sq pki link
does, see 'Authenticating certificates'.
Choosing a cipher-suite
The default cipher suite that sq uses when generating a key is cv25519
. To select a specific suite, use --cipher-suite
with one of the following options:
- rsa2k RSA with 2048 bits
- rsa3k RSA with 3072 bits
- rsa4k RSA with 4096 bits
- cv25519 Elliptic curve of the same name with 256 bits (default)
For example:
$ sq key generate --own-key --name "Alice Example" --email alice@example.com --cipher-suite rsa4k
RSA keys are very common, and may be the only option when dealing with legacy systems, but elliptic curve keys are smaller, faster, and stronger for their size, so using them as the default is a sensible choice.
Opting out of password protection
The default settings for generating a key include setting a password on the key material, which is requested from the user after entering the sq key generate
command. To create a key without password protection, specify the --without-password
option.
$ sq key generate --own-key --name "Alice Example" --email alice@example.com --without-password
Choose expiration time
The default setting is an expiration time of three years. To define a different one, you can either choose any ISO 8601 date string (like 2038-01-19
) or a custom duration using N[ymwds]
(a number followed by a letter denoting years, months, weeks, days or seconds, so 2y
represents 2 years). Both options can also use a reference time using --time
. You can also change the expiration date of an existing key to advance or shorten the expiration; see 'Maintaining a key' for more information. For more thoughts on why setting an expiration date is a useful thing, see 'Key expiration'.
$ sq key generate --own-key --name "Alice Example" --email alice@example.com --expiration 2y
All of these key generating options shown above can be used at once:
$ sq key generate --own-key --name "Alice Example" --email alice@example.com --expiration 2y --cipher-suite rsa4k
Generating v6 keys
Version 6 (v6) keys were introduced in RFC9580, which is implemented in version 2.0.0 of Sequoia PGP and used by sq
since version 1.3.0. To generate a v6 key, use the --profile
option:
$ sq key generate --profile rfc9580 ...
Available values are rfc4880
and rfc9580
. rfc4880
is the default value, which causes sq
to generate a v4 key.
Maintaining a key
It's possible to change certain settings of existing key material, such as updating the expiration time or adding a User ID. Other settings, for example, the cipher suite, can't be updated later.
Adding a User ID
If you want to add a User ID, such as an additional name or another email address, to a certificate, sq key userid add
is the subcommand to use:
$ sq key userid add --cert $FINGERPRINT --email alice@work.example.com --name alice_at_work
If you accidentally added and published an incorrect User ID and want to remove it, you have to revoke the certificate. There is no other way to get rid of it, as stripping a User ID only works on locally stored certificates, not on certificates on keyservers.
Change expiration date
To change the expiration time, use the sq key expire
subcommand:
$ sq key expire --expiration 2y --cert $FINGERPRINT
This command does not change the associated subkeys.
To amend the expiration of a subkey, choose the sq key subkey expire
subcommand. Note that --key
requires the fingerprint of the subkey you want to change and --cert
the fingerprint of your primary certificate.
$ sq key subkey expire --expiration never --key $SUBKEY_FINGERPRINT --cert $FINGERPRINT
Rotating a certificate
If you want to replace a certificate with a new one that takes over its capabilities, you can use sq key rotate
.
The new certificate will contain the same self-signed User IDs, and both old and new certificates will cross-certify each other as unconstrained trust introducers. The new cert will also replay (re-sign) any certifications made by the old certificate.
The new certificate will be marked in the same way as the old one as 'own key' or 'shared key'. By default, its expiration will be set to three years. If the old primary key was given additional capabilities, these will not be recreated for the new primary key. Instead, separate subkeys will be assigned to these capabilities. Only the ability to issue certifications will be transferred to the new primary key. These settings are all adjustable.
One more important thing is the replacement date of the old certificate. A revocation certificate will be generated which indicates that the retirement of the old certificate will occur in, by default, 182 days. The idea is to have a decent amount of time for the transition period. Of course, this can be changed, as shown in the example below.
Here you can see the result of applying the command to an existing certificate. First, we have a look at it:
$ sq cert list 740927F8A0188AA02FCE4B672D8E4019B47A6891
- 740927F8A0188AA02FCE4B672D8E4019B47A6891
- created 2024-12-16 16:07:20 UTC
- will expire 2027-12-17T09:33:41Z
- [ ✓ ] <alice@example.com>
[...]
Now we apply the rotation command, using a very short retirement period to show the results immediately:
$ sq key rotate --cert 740927F8A0188AA02FCE4B672D8E4019B47A6891 --retire-in 1s
Please enter the password to protect the new key (press enter to not use a password):
Please repeat the password:
Cross signing the old and new certificates.
Replaying the old certificate's links:
Copying link for <alice@example.com>:
- created at 2024‑12‑16 16:07:20
- linked as a fully trusted CA
Replaying certifications made by the old certificate:
Considering the source certificate's certification of the binding:
- ┌ 1C88780EF239586AF463758094119887B3462B88
└ <bob@example.com>
Source certificate's active certification:
- created at 2024‑12‑16 16:25:59
- certified as a partially trusted CA
- expiration: 2034‑12‑17
- trust depth: 1
- regular expressions: <[^>]+[@.]example\.com>$
Replayed certification.
Retiring the old certificate as of 2025‑02‑06 13:21:15.
Transferable Secret Key.
[...]
If we now look at the original certificate again, we see that it has been revoked:
$ sq cert list 740927F8A0188AA02FCE4B672D8E4019B47A6891
No bindings matching "740927F8A0188AA02FCE4B672D8E4019B47A6891" could be authenticated.
- Warning: 740927F8A0188AA02FCE4B672D8E4019B47A6891 is revoked: Key is retired and no longer usedThis certificate has been retired, and is replaced by
03D91FD4DDEE0B5A4AECC52CA0950246DF806DAF..
1 binding found.
Skipped 1 binding, which is unusable.
Error: No bindings matching the query could be authenticated.
Replaying certifications
One part of the operation of sq key rotate
is (re)creating all the certifications made by the old key using the new one. This operation can be done on its own using:
$ sq pki vouch replay --source $SOURCE_FPR --target $TARGET_FPR
This command takes all certifications made with $SOURCE_FPR
and certifies them using $TARGET_FPR
. Both source and target can be designated by:
- fingerprint (as shown above)
- email:
--source-email
,--target-email
- userid:
--source-userid
,--target-userid
- file:
--source-file
,--target-file
, both of which take the corresponding certificate from a file instead of the certificate store.
sq pki vouch replay
checks that the target certificate shares at least one User ID with the source certificate and aborts if it fails. --allow-dissimilar-userids
skips this check.
The replay of certifications is limited to active certifications. Certifications made for expired, revoked, or invalid certificates are skipped.
Retracting a key
A key has an expiry date, after which it is no longer valid. This can either occur when you don't want to extend the expired key because you no longer want to use it, or if you want to end the validity prematurely. In a more severe case, you might need to end it because the key was lost or has been compromised.
Since you not only use the key yourself, but in most cases have also given the certificate—the public part of the key—to other people or uploaded it to keyservers, you would have to revoke it.
This is because it's important to let the world know about the invalidation, to prevent future usage such as encrypting data with that certificate. Otherwise, people could still use the certificate to encrypt messages, which then could not be decrypted, or, in case of compromise, someone else might be able to read it.
The expiration of the certificate does not affect the secret key material; encrypted data can still be decrypted with an expired certificate.
Hard and soft revocation
Revocation is done by creating a revocation certificate and publishing it, or your revoked certificate, to the keyservers. A revocation certificate is stand-alone, so you can (but don't have to) add it to a key to publish it. The keyservers will add it to the origin certificate, and other people will get this revoked certificate from there. Nevertheless, sq key revoke
will automatically mark your own key as revoked while creating the revocation certificate, so you don't have to apply it and can just distribute your updated certificate. If your certificate is not published on keyservers, but only passed by, let's say email, you could either send the revocation certificate to somebody for them to import (importing will automatically add it to the certificate) or the already-revoked certificate.
There are two different ways a revocation can be seen. They concern the validity of signatures and certifications created by the revoked key. If the key was compromised, meaning an attacker got hold of the secret key material, new signatures/certifications could be created by the attacker (and probably even backdated). This implies that signatures and certificates made by a compromised key cannot be trusted (even if they appear to have been made before the compromise).
But keys do not have to be compromised to be revoked; they can also be retired or superseded. In this case, the secret key material would still be under the sole control of the legitimate keyholder, so signatures and certifications retain their validity.
So there are two classes of revocation. In the event of a compromise, a revocation that invalidates all signatures and certifications is called "hard". The other—the retirement case—is called "soft".
The revocation certificate generated at the same time as the key is an emergency measure in case you lose your key. This is unrelated to any possible breach. This certificate is stored in the $HOME/.local/share/sequoia/revocation-certificates
(unless stated otherwise, as described below) and its reason for revocation is given as unspecified
in the certificate (as it is unknown at the time it's created), and this cannot be changed later on. If you still have access to your key (even if compromised), you can (and should) generate a more meaningful revocation certificate. Besides the revocation reason this also includes—if desired—an announcement of a new certificate's fingerprint, using the message
option.
In either case, you can write the revocation certificate to a file. For the emergency revocation certificate, you must provide the option --rev-cert
followed by a file name when generating your key. It then can be copied to another data store so that you can still access it, even if your key on your computer is no longer available.
When creating a soft revocation certificate, for example, to have a more precisely specified one prepared for future use, you would have to use the --output
option to write the revocation certificate to a file instead of importing it directly into the keystore. Otherwise sq
would automatically import the revocation, resulting in your key being revoked! If you're storing it as a file, you would have to import it into the keystore to add it to the key, or publish it independently, as described below.
Creating and publishing a revocation certificate for soft revocation
As described above, you can generate a revocation certificate for a certificate using sq key revoke
.
In the following example --cert
specifies the fingerprint of the cert to revoke. The --reason
option must be one of compromised
, superseded
, retired
or unspecified
, and --message
contains a plain-text explanation. Both options are mandatory.
$ sq key revoke --cert $FINGERPRINT --reason retired --message 'quit my job'
This command does not produce any output, but if you inspect
the certificate using the original fingerprint, you will see that it has been marked as revoked. The revocation certificate has been created and at the same time imported into the keystore and applied to the key:
sq inspect --cert $FINGERPRINT
OpenPGP Certificate.
Fingerprint: $FINGERPRINT
Revoked:
- Retired
On: 2024-12-05 13:57:43 UTC
Message: "quit my job"
[...]
To create a revocation certificate for later use, use the --output
switch as follows:
$ sq key revoke --cert $FINGERPRINT --reason $REASON --message $MESSAGE --output $FILE
This stores the revocation certificate in a file - but does not revoke the key.
To use this revocation cert (and revoke the key), import it:
$ sq cert import $FILE
Publishing
After creating a revocation certificate, you should publish the revoked certificate on the keyservers (assuming you want to revoke the key it applies to):
$ sq network keyserver publish --cert $FINGERPRINT
As described at the beginning of this chapter, you can also publish the revocation certificate by itself if you previously saved it in a file:
$ sq network keyserver publish FILE
Read more about publishing certificates.
Revoke a User ID
It's also possible to revoke a User ID such as a name or an email address using the userid
subcommand. The process is very similar to the process of generating and publishing a revocation certificate:
$ sq key userid revoke --cert $FINGERPRINT --userid 'alice' --reason retired --message 'changed my name'
$ sq inspect --cert $FINGERPRINT
OpenPGP Certificate.
[...]
UserID: alice
Revoked:
- User ID information is no longer valid
On: 2024-10-03 06:20:09 UTC
Message: "changed my name"
[...]
$ sq network keyserver publish --cert $FINGERPRINT
Delete a key
There are some specific scenarios in which someone might want to keep a valid certificate, while deleting the secret key material.
For example, if you work in stateless mode and manage keys only in files (and not the keystore), one way to extract the certificate from a key is to delete the secret key material from that file. If you want to retain both parts, make a copy of the key beforehand.
A second scenario could arise during (sub)key rotation. Deleting the secret key material would improve confidentiality by not being able to expose it at some point in the future, which would otherwise affect previously sent and stored messages.
For these use cases, sq
offers the command sq key delete
to delete the secret key material from a key, leaving only the certificate:
$ sq key delete --cert $FINGERPRINT
It's also possible to select keys for deletion by providing the User ID that includes a specific email address:
$ sq key delete --cert-email alice@example.org
Be careful: if more than one key contains this User ID, they will all be deleted.
Key import and export
Import a key
To import a key from a file into the keystore:
$ sq key import $KEYFILE
$KEYFILE
can contain more than one key, and sq key import
will import all the keys it finds, skipping any certificates also in this file.
Export a key
To export a key from the keystore into a file to, for instance, create a backup or import it into other software like Thunderbird, redirect it into file:
$ sq key export --cert $FINGERPRINT > $KEYFILE
or use the --output
option:
$ sq key export --cert $FINGERPRINT --output $KEYFILE
You can also export a subkey instead of the complete key material:
$ sq key subkey export --cert $SUBKEY_FINGERPRINT > $KEYFILE
Please note: If exporting an unprotected key (one without a passphrase), the exported key will also be unprotected.
sq key export
will export the key as-is. To set a password for the exported key, use:
$ sq key export ... | sq key password --cert-file - --output $EXPORTED_KEY_FILE
Encrypt and decrypt a file
Encryption
sq encrypt
takes data from a file or from STDIN
and encrypts it using key material from certificates passed to it.
If you encrypt a file for someone else's User ID and want to be able to read it afterwards, you have to also add your own User ID.
Certificates for encryption—the recipients—can be selected by:
--for $FINGERPRINT
– select the certificate identified by$FINGERPRINT
--for-email $EMAIL
– select all certificates with User IDs containing$EMAIL
--for-userid $USERID
– select all certificates with User ID$USERID
--for-file $FILE
– use the certificates in$FILE
--for-self
– uses certificate(s) specified in the configuration file underencrypt.for-self
One thing to keep in mind: If you use --for-email
or --for-userid
, sq
only considers certificates which are authenticated. If you want to use an unauthenticated certificate, you can use the fingerprint as a selector (as fingerprints are self-authenticating) or --for-file
.
By default sq encrypt
also signs encrypted messages. The key to use for signing can be passed by:
--signer $FINGERPRINT
– use the key identified by its fingerprint--signer-email $EMAIL
– use all keys with User ID$EMAIL
--signer-userid $USERID
– use all keys with User ID$USERID
--signer-file $FILE
– use key contained in$FILE
--signer-self
– use key specified in the configuration file undersign.signer-self
--without-signature
– don't sign the message
$ sq encrypt --for-email alice@example.com message.txt --signer-email bob@example.com --output message.pgp
This encrypts the file message.txt
using any certificate containing the email alice@example.com
, the result is written to message.pgp
. Without --output
the encrypted file is printed to STDOUT
.
$ sq encrypt --for $FINGERPRINT message.txt --signer-email bob@example.com --output message.pgp
Does the same, but selects the certificate used for encryption by its fingerprint.
You can create an encrypted file using just a password by providing --with-password
- sq
will prompt you for the password.
All this can be combined:
$ sq encrypt --for $FINGERPRINT \
--for-email alice@example.com \
--for-userid "Bob Example" \
--with-password \
--without-signature \
message.txt --output message.pgp
The input—the message to encrypt—does not have to be in a file; if no file is provided in the parameter list, the message is read from STDIN
.
$ echo "Hello world" | sq encrypt --for $FINGERPRINT --without-signature
-----BEGIN PGP MESSAGE-----
wV4D+zMBYd4zQtASAQdAM/WW6LvAEEc7SdDEYgo0s38DtywJEB5A8XIt1JhzbTcw
WMqpUI3xbb4ZBqWK9R8/DyIAOqAO1rH55vkdU63OTkj4WKo6f6c8lfMxD8JvYaGV
0j0BMEm+mp706Kpg2Ac/f3Hdn9IHb+jbeCUH/Rem2y+Wr9PrOPyL6vc1MFhCTrd+
9a2XDB3avQcYruJBSxmL
=IX5I
-----END PGP MESSAGE-----
To encrypt a message for yourself, you need to set the recipient(s) in the configuration file beforehand to enable --for-self
to apply it:
$ mkdir -p ~/.config/sequoia/sq/
$ sq config template --output ~/.config/sequoia/sq/config.toml
$ $EDITOR ~/.config/sequoia/sq/config.toml
[...]
[encrypt]
for-self = ["1C88780EF239586AF463758094119887B3462B88"]
[...]
[sign]
signer-self = ["1C88780EF239586AF463758094119887B3462B88"]
[...]
$ sq encrypt --for-self --signer-self message.txt
For more information about the configuration file, see the chapter on configuration.
Using v6 (RFC9580)
When encrypting, the parameters are taken from the certificate, and this includes the format of the encryption container. If the certificate is version v4, sq
will use a corresponding container. However, if there is no basis for selecting a format—for instance when using --with-password
—sq
can be directed to use a specific encryption container format by passing --profile
.
The value of --profile
can be rfc4880
, which uses a v4 container, or rfc9580
, which uses a v6 container (see RFC9580).
Decryption
Decrypting an encrypted file and writing it to a file works as follows:
$ sq decrypt message.pgp --output message.txt
As the encrypted message (usually) contains the ids used during encryption, decryption needs no further help to select the right key. To have sq decrypt
sending its output to STDOUT
, just leave out the --output
parameter.
Generating subkeys and keys for special purposes
Adding subkeys to a certificate
Generating new subkeys
sq
can generate additional subkeys for specific purposes and add them to an existing certificate. The sq key subkey add
command will generate and add a new subkey to the certificate.
In contrast to a primary key, the capabilities of a subkey need to be determined explicitly. Depending on the purpose the subkey is to fulfill, it has to be equipped with the corresponding capabilities such as signing (--can-sign
), authentication (--can-authenticate
) and encryption type (see example below).
The default setting is to create a new subkey that is password protected, which is queried interactively and may differ from the password of the existing primary key. Nevertheless, it is also possible to specify the option --without-password
for a new subkey.
The determination of the expiration date is also slightly different. While a primary key—unless otherwise specified—is assigned an expiration of 3 years, a new subkey automatically inherits the expiration time of the associated primary key, unless one defines a different value using the option --expiration
. The reference time can be changed by passing the --time
argument.
Here is an example of using the sq key subkey add
command:
$ sq key subkey add --cert $FINGERPRINT --can-encrypt 'transport'
Other values for
--can-encrypt
arestorage
anduniversal
, the latter being the default.
To see the result, have a look at the certificate to which the subkey was added; it will be listed together with all other subkeys:
$ sq inspect --cert $FINGERPRINT`
Subkeys cannot be removed from a key once added, but they can be revoked:
$ sq key subkey revoke --cert $PRIMARY_KEY_FPR --key $SUBKEY_FPR --reason retired --message 'not used'
Adjust --reason
and --message
to your needs—see deleting a key for more details.
Binding a subkey to another certificate
In addition to generating new subkeys and attaching them to a certificate, you can bind an existing subkey (or key) to a certificate other than the original one. This might be needed, for example, if you want to continue using an old subkey, but want to retire the associated primary key. Note that the subkey also remains linked with the original key as long as you don't revoke it.
$ sq key subkey bind --key $KEY_TO_BE_BINDED_FINGERPRINT --cert $FINGERPRINT
Generating keys for special purposes
By default sq
generates a key that is usable for certification as well as for signing, authentication, and encryption. Nevertheless, it can be useful to generate keys with limited functionalities. Depending on what purpose the key is supposed to serve, you have to either opt out of default features or specify the features you want explicitly. It's also possible to generate subkeys for specified purposes and add them to the existing primary key, as shown above.
The following example shows how to generate a key for certification only. Further options, and combinations of them, can be found using sq key generate --help
.
$ sq key generate --own-key --email=alice@example.com --cannot-sign --cannot-authenticate --cannot-encrypt
Please enter the password to protect key (press enter to not use a password):
Please repeat the password:
Certifying "<alice@example.com>" for 565C0F136EA97A9E19436CD67D697CFD08BBC29B.
Transferable Secret Key.
Fingerprint: 565C0F136EA97A9E19436CD67D697CFD08BBC29B
Public-key algo: EdDSA
Public-key size: 256 bits
Secret key: Unencrypted
Creation time: 2024-12-05 10:03:50 UTC
Expiration time: 2027-12-06 03:30:11 UTC (creation time + 2years 11months 30days 9h 16m 45s)
Key flags: certification
UserID: <alice@example.com>
Hint: Because you supplied the `--own-key` flag, the user IDs on this key have been marked as authenticated, and this key has been marked as a fully
trusted introducer. If that was a mistake, you can undo that with:
$ sq pki link retract --cert=565C0F136EA97A9E19436CD67D697CFD08BBC29B --all
Hint: You can export your certificate as follows:
$ sq cert export --cert=565C0F136EA97A9E19436CD67D697CFD08BBC29B
Hint: Once you are happy you can upload it to public directories using:
$ sq network keyserver publish --cert=565C0F136EA97A9E19436CD67D697CFD08BBC29B
Authenticity
This chapter covers certificates, especially their application in the realm of authenticity, including creating and verifying certifications and signatures, revocation, publishing and retrieving, and other aspects of managing certificates.
Quick start
This is an overview of operations relating to authenticity. It is by no means complete, but will help you get up to speed quickly.
Getting a certificate from the internet
Search default keyservers, WKDs, and DNS servers for certificates with User IDs or fingerprints matching $QUERY
:
$ sq network search $QUERY
Publish a certificate
Publish the certificate with fingerprint $FINGERPRINT
on the default keyservers:
$ sq network keyserver publish --cert $FINGERPRINT
Authenticate a certificate
sq
only uses authenticated certificates. That means that certificates can only be identified by a User ID if the binding between this User ID and a certificate is marked as valid. This doesn't apply if you select a certificate by fingerprint (or provide it directly from a file), as fingerprints are regarded as self-authenticated. To mark a binding as authenticated, use:
$ sq pki link add --cert $FINGERPRINT --userid $USERID
This only authenticates this specific User ID; other User IDs of the certificate are not affected. For convenience, you can authenticate all User IDs in a certificate in one go:
$ sq pki link add --cert $FINGERPRINT --all
Retract authentication from a certificate
To reverse an authentication (removing a link between a User ID and a certificate), use:
$ sq pki link retract --cert $FINGERPRINT --userid $USERID
or the following to retract all of a certificate's associated links:
$ sq pki link retract --cert $FINGERPRINT
Import a certificate from a file
$ sq cert import $FILE
Export a certificate to a file
$ sq cert export --cert $FINGERPRINT > $FILE
Certify a certificate
Also known as "signing a key":
$ sq pki vouch certify --certifier $MY_FINGERPRINT --cert $OTHER_FINGERPRINT --userid $USERID
This way the binding between $OTHER_FINGERPRINT
and $USERID
is certified as authentic by the certificate $MY_FINGERPRINT
. Don't forget to publish your newly created certification.
Approving a certification
To avoid flooding certificates with bogus certifications, keyservers might insist on an approval by the keyholder of the certified certificate. To approve all pending certifications:
$ sq key approvals update --add-all --cert $FINGERPRINT
Verify a data signature
To verify a detached signature (where the signature is stored in a separate file), use:
$ sq verify --signature-file $DETACHED_SIGNATURE_FILE $FILE
To verify a signed message use:
$ sq verify $FILE
To verify successfully, the signing certificate must be authenticated.
Sign files and messages
Sign a file using the key with fingerprint $FINGERPRINT
:
$ sq sign --signer $FINGERPRINT $FILE
Sign with detached signature
$ sq sign --signature-file $DETACHED_SIGNATURE_FILE --signer $FINGERPRINT $FILE
Authenticating certificates
If you want to send someone an encrypted email, you will need public key material to do the encryption. Public key material is part of a certificate, and getting the right material is equivalent to getting the right certificate. Certificates also contain User IDs, claiming that the corresponding user published this certificate and that the public key material within is indeed the right one to use. This suggests that once you know the ID of a user (for instance, by their email address), identifying the right certificate should be easy.
User IDs, however, are arbitrary claims which need to be verified before it's advisable to rely on any public key material linked to them. This process of verification is called authentication. This process, however, is not well-defined. How much "evidence" do I need before I can trust the claimed binding between a User ID and the public key material?
If the intended receiver of my email has created and published a certificate, there is a "right" certificate. Certificates have unique, unambiguous identifiers called fingerprints. In contrast to User IDs, these are not claims but checksums over (some of) the content. Knowing the fingerprint allows identifying a certificate with near-absolute certainty.
This shifts the problem from identifying the right certificate to identifying the right fingerprint. Fortunately, fingerprints are much smaller – small enough to, for instance, print on a business card. The downside of fingerprints is that they are completely unintuitive sequences of characters with high entropy and no redundancy – they have no obvious correlation with a User ID.
Identifying a certificate—the waterproof way
Assuming that you are certain you have the right fingerprint—let's name it $FPR
—retrieving its corresponding certificate can be done with:
$ sq network search $FPR
If the fingerprint is found, this downloads the certificate from online sources and stores it in the local certificate store. You can read more about retrieving certificates.
The next step is to tell Sequoia PGP that the binding between a User ID and this certificate is valid. This might come as a surprise as we skip the manual comparison of fingerprints – let sq
do the job:
$ sq pki link add --cert $FPR --userid $USER_ID
Since sq network search $FPR
retrieved a certificate with that fingerprint, all that remains is to verify that this certificate contains the expected User ID. sq pki link add
does that and will return an error if that User ID is not present, or you made a typo in the fingerprint.
After this step, everything is set up to proceed with the encryption of the email.
By the way, you can get the fingerprint of your own key using:
$ sq key list $USER_ID
Identifying a certificate—softer approaches
The scenario in the preceding section is a bit idealistic—it assumes that:
- you are in possession of the right fingerprint
- the corresponding certificate has been published on a well-known server that is reachable on the internet
Real circumstances are often less optimal. But no matter how you obtained the fingerprint (or certificate), it happened in a context which can be taken into account and used as evidence.
If you received a fingerprint printed on a slip of paper handed over by the alleged creator of the corresponding key, this is strong evidence that you got the right fingerprint. The same could be said if you obtained the certificate itself on a USB stick.
Things become less reliable if the fingerprint reached you in an email, especially if that email was not signed. If that email came in response to a request ("Please send me your certificate ..."), it adds to its trustworthiness. If you know the sender personally, this might be another plus.
Some keyservers require an attestation. They send an email message to each User ID in a certificate which looks like an email address, containing a link. Clicking that link sends a specific request back to the server, which is taken as a confirmation of the email addresses authenticity, and hence the User ID, reasonably binding the User ID to the certificate. If you receive a certificate from such a server, you can take this into account.
Some websites publish corresponding fingerprints or even certificates alongside their contact email address. Assuming that only a limited number of people are able to modify the content of this website and all of them have honest intentions, this can also be taken as evidence of authenticity.
However, all the above examples (and there are many more) are circumstantial. They reflect social relationships, assumptions of integrity, diligence and probably more.
On top of this, not every email is sent in a life or death scenario; many of them are quite mundane, and it might even be acceptable to send them unencrypted.
All of this makes trade-offs a viable option. The amount of evidence one wants to see before relying on a certificate is likely to correlate with the importance or significance of the message that it authenticates. These trade-offs are probably the main way to handle "authentication" in practice.
Using the result
Once you are willing to rely on a certificate, you need to add a link in the PKI of Sequoia PGP. This enables Sequoia PGP to remember your decision and treat the certificate as authenticated from that moment onward.
Adding a link is done with:
$ sq pki link add --cert $FPR --userid $USER_ID
Instead of --userid
, --email
would work. If you specify --all
, all bindings in the designated certificate will be added.
This command does several things:
- It adds a signature to the certificate.
- This signature indicates that you consider the binding between the certificate and the User ID to be authentic.
- Future invocations of
sq
(or any other software using Sequoia PGP) will recognize this signature and its implied authenticity.
You can see the effect with:
$ sq pki link list --cert-email alice@example.com
- ┌ 60B00B3173854BA69689B19CCAC5C827BE11485F
└ "<alice@example.com>"
- is linked
- ┌ 60B00B3173854BA69689B19CCAC5C827BE11485F
└ "Alice"
- is linked
The key used to generate the signature is not an arbitrary key, but the trust root key located in the certificate store. For details see chapter Shadow CAs.
The above command will also add a trust-depth to the binding between certificate and User ID. Using sq pki link add
as above, the trust depth will be zero, which means that the authenticity implied by the signature does not "spread out"; it's limited to exactly this binding.
A trust depth of one means that not only the specific binding is considered authentic, but also all certifications made by this certificate.
Consider the following example:
There are 4 certificates (A, B, C, D), each with just one User ID, and these bindings:
- Alice's certificate
[ A, alice@example.com ]
- Bob's certificate
[ B, bob@example.com ]
- Carol's certificate
[ C, carol@example.com ]
- Dave's certificate
[ D, dave@example.com ]
Assuming that Alice is certain that Bob's certificate (especially the binding
[ B, bob@example.com ]
) is authentic, and Bob is equally certain about Carol's certificate, as is Carol of Dave's, each of them certifies the corresponding certificates:
- Alice's certificate
[ A, alice@example.com ]
- Bob's certificate
[ B, bob@example.com ](certified using A)
- Carol's certificate
[ C, carol@example.com ](certified using B)
- Dave's certificate
[ D, dave@example.com ](certified using C)
If Alice certifies Bob's certificate with a trust depth of zero, then—from Alice's perspective—Bob's certificate is authenticated. That's all.
If Alice uses a trust depth of one, then not only is Bob's certificate considered authentic, but Carol's is too, because Bob's certificate certifies Carol's. By assigning a trust depth of one, Alice marks Bob as a trust introducer, and his certifications are now considered as good as if Alice had done the certifications herself.
Carol has certified Dave's certificate. From Alice's perspective, however, Dave's certificate is not authenticated. A trust depth of one only effects the immediate certifications from Bob. To also include Dave's certificate, Alice has to assign Bob a trust depth of two, thus allowing Bob to also introduce trust introducers to Alice, in this case: Carol. Alice would then accept her certification of Dave's certificate.
To visualize the effect of trust depth from Alice's perspective:
trust-depth Trusted certificates 0 Bob 1 Bob, Carol 2 Bob, Carol, Dave Please note that Alice assigns the trust depth when certifying Bob's certificate. She doesn't have to touch Carol's certificate (or even Dave's) to achieve the effect. They don't have to be in Alice's local certificate store, and they don't even have to exist at the time of the certification. They do, however, have to be available locally when Alice calculates the authenticity of the certificates.
Trust depth is a counter which indicates how many hops in a certification chain will be covered by the initial assertion of authenticity. The maximum depth is 255.
The above sq pki link add
with a trust depth of 1 looks like (note the authorize
):
$ sq pki link authorize --depth 1 --cert $FPR --email bob@example.com
...
Certificates can have more than one User ID. Instead of treating each User ID separately—repeating sq pki link add
for each of them—you can pass --all
on the command line.
$ sq pki link add --cert $FPR --all
Links created by sq pki link add
will not expire, but this can be changed by using --expiration DATE
. DATE
can either be in ISO 8601 format (2025-12-31
) or a time interval string like 3y
(3 years).
$ sq pki link add --cert $FPR --all --expiration 3y
Links can be retracted at any point in time. Use
$ sq pki link retract --cert $FPR --email bob@example.com
to retract a specific certificate to User ID binding, or
$ sq pki link retract --cert $FPR --all
as a convenience to remove all links associated with the given certificate.
The existing links within the PKI can be listed with:
$ sq pki link list
- ┌ 042C3AA73E1566E90FDC31B05C1EA3A3B894010E
└ "Public Directories"
- is linked as a partially trusted CA
- trust amount: 40
- ┌ 4D735E0D7DDE1B338E05F2B80AF4CE6DF697FB28
└ "<alice@example.com>"
- is linked
- ┌ 4D735E0D7DDE1B338E05F2B80AF4CE6DF697FB28
└ "Alice"
- is linked
- ┌ D4A2E8E858B215BD4E0A775C2904DCFB85ED0E9B
└ "<bob@example.com>"
- is linked
- ┌ D4A2E8E858B215BD4E0A775C2904DCFB85ED0E9B
└ "Bob"
- is linked
This example shows a list with two certificates, each of them has two User IDs. Additionally, the shadow CA for "Public Directories" is displayed.
Importing and exporting certificates
Certificates can be disseminated in many ways: as a download from a website, as an attachment to an email, and so on. In these cases, you end up with a certificate contained in a file. To use that, you have to import it into the certificate store with:
$ sq cert import $FILE
This will try to import the certificate inside $FILE
. If $FILE
is skipped, sq cert import
expects the certificate on STDIN
:
$ cat $FILE | sq cert import
Importing certificates from a file has no influence on any implied authenticity. It's imported into the certificate store as-is, without attesting to its authenticity. To attest to its authenticity, use sq pki link add
.
Certificates can be exported from the cert store with:
$ sq cert export $QUERY
where $QUERY
can consist of:
--cert $FPR
– a certificate designated by its fingerprint--cert-email $EMAIL
– all certificates containing a user id matching$EMAIL
--cert-userid $USERID
– all certificates containing a user id$USERID
--cert-domain $DOMAIN
– all certificates containing a user id with an email address from domain$DOAMIN
--cert-grep $PATTERN
– all certificates containing a user id matching$PATTERN
These queries can be combined, and sq cert export
will then export any valid certificates matching any of these queries. Invalid certificates, such as those using outdated hash functions in signatures, are excluded. You can work around this restriction by passing --policy-as-of
using a date which pre-dates the deprecation date of said function.
$ sq cert export --cert-email alice@example.com
$ sq cert export --cert EE99C48D11A4BE940569C4B3919EA6F609043A04 --cert-domain example.com
The result of the query is printed to STDOUT
.
A special query is --all
: This exports all certificates, even the invalid ones.
sq cert export
will not export non-exportable signatures, including components bound by these signatures. Passing --local
will include these components — this feature is meant for backups, or if you want to synchronize between different devices or locations (like a USB stick).
$ sq cert export --local --all
Import a certificate into GnuPG
To import a cert into the GnuPG Keyring, export it as above and pipe it directly into the gpg --import
command:
$ sq cert export alice@example.com | gpg --import
Search for a certificate on the internet
Certificates are necessary for encryption or for signature verification. The users of certificates are usually not the ones who created them, and this raises the question of how to make certificates available to interested parties. Since certificates in wire format are just plain files, they can be published on webservers or sent as email attachments, but these are individual solutions which differ for each certificate.
There are solutions which are more generic, in particular, servers in the internet that can be queried for certificates.
The first generation of these servers simply accept uploaded certificates and have a standardized interface for searching the inventory for User IDs. Anyone can upload any certificate, which makes the inventory a highly unreliable source.
Later solutions to the dissemination introduce hurdles to prevent the publishing of arbitrary certificates. As a side effect, this increases the likelihood that certificates stored on these servers are authentic.
Sequoia PGP uses four different types of these servers:
- SKS servers: These are "first generation" servers with unrestricted publishing.
- Keyservers which require a confirmation: An email is sent to each User ID in the certificate that resembles an email address containing a link which has to be clicked to acknowledge the upload.
keys.openpgp.org
works this way. - Web Key Directories (WKD): The certificate is stored on a webserver, and its URL is constructed from the User ID (see Publishing certificates). As a side effect, WKD cannot be queried for fingerprints. WKD implies the control of a webserver related to the email domain of the User ID.
- DNS-based Authentication of Named Entities (DANE): Although it was not originally designed for OpenPGP certificates, DANE can be used for their dissemination. DANE implies control over the DNS zone file of the mail domain.
When retrieving certificates from these servers, Sequoia PGP stores not only the answer, but also its origin. Based on the origin, Sequoia PGP assigns an "authenticity value" to the certificate ranging from 0/120 (unauthenticated) to 120/120 (fully authenticated). These values accumulate, so certificates found on multiple servers gain authenticity. However, there is a cap of 40/120, which cannot be exceeded by this mechanism, thus preventing fully authenticated certificates generated simply by flooding the servers with duplicates.
This trust signature concept comes from the RFC9580 standard, which replaced RFC4880, used by OpenPGP.
Retrieving certificates
The easiest way to get a certificate is using the sq network search $USER_ID
subcommand.
$ sq network search alice@example.com
Note: Created a local CA to record provenance information.
Note: See `sq pki link list --ca` and `sq pki link --help` for more information.
Importing 1 certificate into the certificate store:
1. EE99C48D11A4BE940569C4B3919EA6F609043A04 Alice (partially authenticated, 2/120)
Imported 1 new certificates, updated 0 certificate, 0 certificates unchanged, 0 errors.
Hint: After checking that the certificate EE99C48D11A4BE940569C4B3919EA6F609043A04 really belongs to
the stated owner, you can mark the certificate as authenticated. Each stated user ID can be
marked individually using:
sq pki link add EE99C48D11A4BE940569C4B3919EA6F609043A04 --userid "<alice@example.com>"
sq pki link add EE99C48D11A4BE940569C4B3919EA6F609043A04 --userid "Alice"
Alternatively, all user IDs can be marked as authenticated using:
sq pki link add EE99C48D11A4BE940569C4B3919EA6F609043A04 --all
Using sq network search
this way will:
- query all default keyservers (
keys.openpgp.org
,mail-api.proton.me
,keys.mailvelope.com
,keyserver.ubuntu.com
, andsks.pod01.fleetstreetops.com
); - try to download a certificate via WKD;
- and query the DNS for a DANE entry.
In this example sq network search
found exactly one certificate, and stored it in the local certificate store.
To keep track of the certificate's origin, Sequoia PGP creates intermediate certificate authorities, which certify the received certificate. This is the mechanism by which Sequoia PGP determines implicit authenticity by origin.
Each successful attempt adds 1/120 to the "authenticity value"; in this case, the same certificate was found on two different keyservers, giving it a score of 2/120.
The list of keyservers can be customized by passing --server $URI
; this option can be used multiple times:
$ sq network search --server hkps://keys.openpgp.org --server hkps://my.own.keyserver.net alice@example.com
This customization however only affects the list of keyservers; WKD and DANE are still used.
Please note that server addresses passed via --server
must also specify their protocol (e.g. hkps://
).
Using the --output $FILE
option prevents sq
from importing the certificate into the cert store, writing it to the supplied $FILE
location instead. In this case, sq
will also consult the local cert store for matching certificates.
$ sq network search --output alice.cert alice@example.com
sq network search
takes a query as parameter. In the examples above, this was a User ID in the form of an email address, but it can also be a fingerprint. This will fetch Alice's certificate from the example above:
$ sq network search EE99C48D11A4BE940569C4B3919EA6F609043A04
Surprising results
When querying network servers for certificates, it's possible that seemingly unrelated certificates are returned. This is especially true for WKD and DANE. sq network search
will fetch all these certificates, as they might be usable or be better matches than what was originally requested.
Remember that that fetched certificates are not authenticated automatically, so they will not undermine your security.
Fine-tuning the list of sources for retrieval
sq
allows you to restrict the sources it queries for answers:
sq network keyserver search
sq network wkd search
sq network dane search
This will limit the search to the respective sources. Keep in mind that not all sources can deal with all query types: WKD, for instance, can only be queried for User IDs in the form of email addresses.
Authenticating
sq network search
will download matching certificates into the cert store. In this state, the certificates are of limited use, as they do not (yet) contain authenticated User ID to certificate bindings; The certificate is treated as unauthenticated. To change this, use sq pki link add
to add authentication to bindings. For details see Authenticate certificates.
By the way:
$ sq network search alice@example.com
Note: Created a local CA to record provenance information.
Note: See `sq pki link list --ca` and `sq pki link --help` for more information.
Importing 1 certificate into the certificate store:
1. EE99C48D11A4BE940569C4B3919EA6F609043A04 Alice (partially authenticated, 2/120)
----------------------------------------
^- this is the fingerprint
Publishing certificates
Publishing a certificate is a bit more involved than retrieving them. sq
can publish directly to keyservers; for other distribution methods, sq
can generate the necessary data or files.
The simplest way to publish to keyservers is:
$ sq network keyserver publish --cert EE99C48D11A4BE940569C4B3919EA6F609043A04
or more generally:
$ sq network keyserver publish $QUERY
sq
searches inside the cert store for certificates matching $QUERY
and publishes all results. $QUERY
can be
- a fingerprint:
--cert $FINGERPRINT
- a User ID:
--userid "Alice Example"
- an email address:
--email alice@example.com
- a file:
--file $FILE
This example publishes the certificate to the default keyservers, which are keys.openpgp.org
, mail-api.proton.me
, keys.mailvelope.com
, keyserver.ubuntu.com
, and sks.pod01.fleetstreetops.com
. This list can be customized by adding --server
to the command:
$ sq network keyserver publish --server hkps://keys.openpgp.org --server hkps://some.other.keyserver.tld --email alice@example.com
If --server
is used, the default servers are ignored. Note that --server
expects the parameter to specify the protocol (e.g. hkps://
), not just the hostname.
Generating and publishing files for WKD
Web Key Directory (WKD) is another way of distributing certificates. It defines a scheme which translates an email address into a URL (there are actually two schemes) that corresponds with a suitably configured webserver that will return the certificates upon request.
This requires the generation of files and directories complying with the scheme. sq
can help by generating the required file structure:
$ sq network wkd publish --create --domain example.com /tmp/foo
This will create all files and directories under /tmp/foo
, starting with the subdirectory .well-known
. Only certificates with User IDs in the domain example.com
will be included.
The above command will generate a directory structure according to the advanced
method, to be used as content for https://openpgpkey.example.com/.well-known/openpgp/...
. If you need the direct
method (the second scheme, https://example.com/.well-known/openpgp/...
), add --method direct
to the command:
$ sq network wkd publish --create --method direct --domain example.com /tmp/foo
If there is already a directory structure present, using --create
will return an error:
Error: Cannot create WKD because /tmp/foo already contains one
The certificates to include in the WKD directory structure can be selected using the following designators:
--cert $FINGERPRINT
to use a certificate with that fingerprint--userid $USER_ID
any certificates matching$USER_ID
--email $EMAIL
any certificates matching$EMAIL
Instead of using the cert store, certificates can also be taken from a keyring using --cert-file $FILE
:
$ sq network wkd publish --cert-file /path/to/some/keyring.pgp --domain example.com --email alice@example.com /tmp/foo
or even all certificates for a domain in the cert store by omitting a filtering query:
$ sq network wkd publish --domain example.com /tmp/foo --all
If supplied with --rsync
, sq
invokes a local copy of rsync
, passing the destination as-is. This is a useful way to update an existing published set of files efficiently:
$ sq network wkd publish --rysnc --domain example.com alice@myserver:/var/www/html --all
More on hosting a WKD: https://wiki.gnupg.org/WKDHosting
Generating records for DANE
For publishing certificates via DNS (aka DANE), a TXT
record must be added to the zone file of the mail domain. The record can be generated like this:
$ sq network dane generate --domain example.com --cert-email alice@example.com
The result will be printed to STDOUT
.
This example will generate a TXT
record in OPENPGPKEY
format. If your DNS server cannot handle this type of record, you can generate a generic record by adding --type generic
.
Instead of specifying the certificates to publish (by --cert-email
, --cert-userid
or via fingerprint with --cert
), you can choose --all
.
$ sq network dane generate --type generic --domain example.com --cert-email alice@example.com
A more compact form of an OPENPGPKEY
record can be generated by adding a size limit; sq
will try to match that limit as best as it can:
$ sq network dane generate --size-limit 1024 --domain example.com --all
More on DANE can be found here: https://datatracker.ietf.org/doc/draft-ietf-dane-openpgpkey/12/.
Certificate management
Listing available certificates
Certificates are usually stored in the certificate store. To list the content of this store use:
$ sq cert list
This command outputs all certificates in the store which are authenticated. sq
only uses authenticated certificates for cryptographic operations.
A list of all usable certificates in the store is available via:
$ sq cert list --gossip
Usable is this context means, that these certificates are authenticated or can be authenticated if needed. This list does not include revoked or expired certificates. If you want to see them as well, use --gossip
with --unusable
:
$ sq cert list --gossip --unusable
The list of certificates can be filtered with:
$ sq cert list $PATTERN
In this case, $PATTERN
is a UTF-8 string. Every certificate that contains a User ID containing this string will be displayed (i.e., it's not a regular expression). In this example, $PATTERN
contains an incomplete email address, which matches one certificate:
$ sq cert list bob@example
- 61E064A38CD0230666D40B823CB405E519E599A1
- created 2024-10-23 09:08:22 UTC
- will expire on 2027-10-24 02:34:43 UTC
- [ ✓ ] <bob@example.com>
Hint: To view why a user ID is considered valid, pass `--show-paths`.
Hint: To see more details about a certificate, for example 61E064A38CD0230666D40B823CB405E519E599A1,
run:
$ sq inspect --cert=61E064A38CD0230666D40B823CB405E519E599A1
Paths
Certificates are considered authenticated if there is a path from the "Local Trust Root" certificate. Use --show-paths
to display existing paths:
$ sq cert list bob@example --show-paths
- 259392E02F8DCC447ED77A3114A33DD1C6BD5288
- created 2024-11-07 11:33:24 UTC
- will expire 2027-11-08T04:59:45Z
- [ ✓ ] <bob@example.com>
◯─┬ 686D08D93C582976252805A6695224B3564396B2
│ └ ("Local Trust Root")
│
│ certified the following binding on 2024‑11‑07 as a meta-introducer (depth: unconstrained)
│
└─┬ 259392E02F8DCC447ED77A3114A33DD1C6BD5288
└ "<bob@example.com>"
Hint: To see more details about a certificate, for example 259392E02F8DCC447ED77A3114A33DD1C6BD5288,
run:
$ sq inspect --cert=259392E02F8DCC447ED77A3114A33DD1C6BD5288
This example shows that Local Trust Root
certified the binding of bob@example.com
directly with its own certificate, when this certification was made, and the assigned trust depth.
Finding certificates in the local cert store
sq
implements further commands that are more focused on specific operations on authenticated (directly usable) certificates:
sq pki identify --cert $FPR
shows the certificate$FPR
likesq cert list $FPR
does. The difference is thatsq pki identify
will not try to use$FPR
to match User IDs.sq pki lookup
will explicitly interpret its parameters as User IDs. Specific--userid
and--email
designators are available.sq pki authenticate
takes a certificate and a User ID and checks if there is an authenticated binding between them in the cert store.
These three commands can also be used on unauthenticated bindings if you pass --gossip
.
$ sq pki lookup --email alice@example.com --gossip
Modifying a certificate
To modify or add a User ID, or update expiration dates, you must use the corresponding key instead. User IDs and metadata like expiration dates are (self)signed by the primary key, and the necessary secret key material is not part of a certificate.
After the key is modified, the certificate must be exported again; It will then contain the modifications applied to the key, and can be published in the usual way.
Along the same lines, certificates cannot be revoked using a User ID alone; this must also be done using the key.
There are ways to modify a certificate—for instance, by adding a certification—but these modifications will not be self-signed. This self-signature is used to show that the signed component in a certificate is aligned with the intent of the keyholder.
Certify and authorize certificates
Certifying a User ID for a certificate
While sq pki link add
generates a signature and adds it to the certificate, that signature is not exportable. sq cert export
will skip this signature. If you want to share your attestation, you have to generate an exportable signature by using sq pki vouch certify
.
Other than sq pki link add
the key which creates the signature when doing sq pki vouch certify
has to be passed as a parameter. The originator of the certification is important information for others to use to evaluate its usefulness.
A certification is generated as follows, assuming Alice's certificate fingerprint is FBF875C726E742953B265D8F74704F2E76BE2F9D
and Bob's is 0AF31DA1380F66AB2600BBECF43AFD52BB107C5D
:
$ sq pki vouch add --certifier FBF875C726E742953B265D8F74704F2E76BE2F9D --cert 0AF31DA1380F66AB2600BBECF43AFD52BB107C5D --email bob@example.com
- ┌ 0AF31DA1380F66AB2600BBECF43AFD52BB107C5D
└ <bob@example.com>
- certification created
Hint: Imported updated cert into the cert store. To make the update effective, it has to be published so that others can find it, for example using:
$ sq network keyserver publish --cert=0AF31DA1380F66AB2600BBECF43AFD52BB107C5D
With
--certifier $FPR_CERTIFIER
being the fingerprint of the originator of the attestation--cert $FPR
the fingerprint of the certificate to attest--email $EMAIL
the email address identifying the binding to certify (you can also use--all
)
This basically says that alice@example.com
is certain that the binding between $FPR
and bob@example.com
is authentic and is willing to make that attestation public. Keep in mind that publishing an attestation is an explicit step; generating a certification does not include its publication.
sq pki vouch add
prints the resulting certificate (the original certificate with the newly created signature) to STDOUT
or to a file specified by --output FILE
. The modified certificate is also stored in the local certificate store and can be exported at a later time.
The certification can contain additional information or restrictions:
- A certification created as above has a lifetime of 5 years; after that period, the certification is considered expired. Add
--expiration DATE
or even--expiration "never"
to change this behavior. The DATE has to be in ISO 8601 format, like2024-07-02
or a time interval like3y
(3 years).
The certificate with the new certification in it can now be distributed—this, however, is not recommended. Instead, send the new certificate to its respective keyholder, so that they can approve your attestation, basically signing your certification.
Approving a certification
This sounds complicated, but it prevents attackers (or trolls) from simply adding lots of certifications to a certificate, blowing it up to a size which cannot be handled anymore. For this reason, modern keyservers will filter out certifications which do not have an approval signature.
After receiving a newly certified certificate, a keyholder can check that certification by saving it into a file and then inspecting, importing, and subsequently approving that certification as follows:
$ sq inspect alice.cert --certifications
$ sq cert import alice.cert
$ sq key approvals update --all --cert $FINGERPRINT
This will add an approval signature to all certifications in the certificate identified by $FINGERPRINT
. Use --cert-file $FILE
if you have the certificate as a file and don't want to import it.
After that step, the keyholder has to publish that certificate again to make it available to others.
Querying for certifications
sq pki vouch list
allows you to get information about certificates in your local cert store.
To get a list of certifications made by a specific key—for instance, a certificate authority—use:
$ sq pki vouch list --certifier FBF875C726E742953B265D8F74704F2E76BE2F9D
- ┌ 0AF31DA1380F66AB2600BBECF43AFD52BB107C5D
└ <bob@example.com>
- created at 2025‑01‑28 15:10:29
- expiration: 2035‑01‑29
In the other direction: Given a certificate, list the certificates that have certified it:
$ sq pki vouch list --cert 0AF31DA1380F66AB2600BBECF43AFD52BB107C5D
- Certifier:
- ┌ FBF875C726E742953B265D8F74704F2E76BE2F9D
└ Alice (authenticated)
- Certified the binding:
- ┌ 0AF31DA1380F66AB2600BBECF43AFD52BB107C5D
└ <bob@example.com>
- created at 2025‑01‑28 15:10:29
- expiration: 2035‑01‑29
Authorize a certificate as a trusted introducer
A trusted introducer is the equivalent of a certificate authority (CA). It expresses the certifier's trust in the certifications made by the trusted introducer / the CA to be valid. Such a certification as a trust introducer has a default trust depth of 1, which can be modified by passing --depth NUMBER
or even --unconstrained
, which doesn't impose a limit to the depth. This is one way to restrict the depth of that certification by limiting the ability of the trust introducer to specify further introducers. Additionally, such a certification can be limited to one or more domains. The trust introducer can create certifications for all kinds of domains; the aforementioned certification will restrict its effect to the specified domains.
A certification to mark a certificate as a trusted introducer is done like this:
$ sq pki vouch authorize --certifier $FPR_CERTIFIER --cert $FPR --domain=example.com --email bob@example.com
Certifying "<bob@example.com>" for 1C88780EF239586AF463758094119887B3462B88.
Hint: Imported updated cert into the cert store. To make the update effective, it has to be published so that others can find it, for example using:
$ sq network keyserver publish --cert=1C88780EF239586AF463758094119887B3462B88
Verify a signature
Signatures over data are used to verify the authenticity of that data. They are created with a key and are verified with the corresponding certificate. A signature is tied to the data it certifies—there are no "stand-alone" signatures.
Signatures come in three different formats:
- Inlined: the signed data and the signature are combined into a single file.
- Cleartext: the signature is appended to the data, using an ascii-armor representation.
- Detached: the signature is placed in a separate file.
Inlined and cleartext signatures can be verified by:
$ sq verify --message $FILE
or
$ sq verify --cleartext $FILE
sq
will detect if you chose the wrong option and will try to verify the $FILE
nonetheless.
Instead of providing the data to verify via a file, you can pipe it through STDIN
:
$ cat $FILE | sq verify --message
If the necessary certificate is locally available (from sq
's cert store), these commands return a success message:
$ sq verify $FILE
Authenticating B535B0D4736F809892B42F4A388344D1DEAA4483 (Alice (UNAUTHENTICATED)) using the web of trust:
B535B0D4736F809892B42F4A388344D1DEAA4483: <alice@example.com> is unauthenticated and may be an impersonation!
Fully authenticated (120 of 120) B535B0D4736F809892B42F4A388344D1DEAA4483, Alice
◯─┬ 7FDAD1C466501980F0765EE53B7051904AED91CF
│ └ (Local Trust Root)
│
│ certified the following binding on 2025‑01‑10
│
└─┬ B535B0D4736F809892B42F4A388344D1DEAA4483
└ Alice
Authenticated signature made by B535B0D4736F809892B42F4A388344D1DEAA4483 (Alice (authenticated))
1 authenticated signature.
If you get an error like this:
Can't authenticate signature allegedly made by
$SOME_FINGERPRINT: missing certificate.
Hint: Consider searching for the certificate using:
$ sq network search $SOME_FINGERPRINT
0 authenticated signatures, 1 uncheckable signature.
Error: Verification failed: could not authenticate any signatures
You are missing the certificate. sq
suggests a way to get the certificate from a key server, WKD, or DANE. Beware: this will get you the certificate of the key which created the signature—but there is no guarantee that this key is the right one. Double-check the displayed fingerprint to make sure that it is from the right certificate and not from an impostor.
If you get a result like this:
...
Authenticating $FINGERPRINT ($USER_ID (UNAUTHENTICATED)) using the
web of trust:
$FINGERPRINT: $USER_ID is unauthenticated and may be an impersonation!
...
Can't authenticate signature made by $FINGERPRINT
($USER_ID (UNAUTHENTICATED)): the certificate can't be authenticated.
Hint: After checking that $FINGERPRINT belongs
to $USER_ID (UNAUTHENTICATED), you can mark it as authenticated using:
$ sq pki link add --cert=$FINGERPRINT --userid=$USER_ID
0 authenticated signatures, 1 unauthenticated signature.
Error: Verification failed: could not authenticate any signatures
then you have the certificate locally available and the signature is valid. This, however, doesn't imply that the key which created this signature is the right one. There are several ways to continue from here:
- You can pass the fingerprint of a certificate, which just checks if the signature is made by the corresponding key, ignoring User IDs associated with the certificate:
$ sq verify --signer $FINGERPRINT $FILE
- You can authenticate (at least) one of the User IDs of the certificate by adding a link:
$ sq pki link add --cert=$FINGERPRINT --userid=$USER_ID
If you verify again, sq
will recognize the authentication and return success.
Detached signatures
Inlined and cleartext signatures are included in the signed data, thus changing its datatype. Before the data can be used again, this mix has to be untangled. Detached signatures are written to an extra file, which leaves the original data untouched. This way the data can be used immediately, allowing the user to skip the signature verification.
The file containing the detached signature has to be passed explicitly to sq
:
$ sq verify --signature-file $SIGNATURE_FILE $FILE
Time
If you want to verify a signature, but the corresponding certificate has expired, you can modify the concept of "now" for sq
. You can use --time
to tell sq
explicitly what time and date to assume as a reference when checking expiration dates. For example, you might want to check whether an encrypted email message was valid when it was received, rather than now:
$ sq verify --time 20120501 --message $FILE
This allows you to verify a signature as if today would be the first of may, 2012. The expiration time of the certificate is then matched against this date and not today.
Example: Download and verify Qubes OS
Qubes OS is a Linux distribution and—as is entirely normal—you can download an installation image from their website. To ensure the authenticity and integrity of the image, it is signed by the Qubes OS maintainers with a specific release key. As there are several releases, there are several release keys. To ease the adoption, each release key is certified by an additional key, the "Qubes Master Signing Key", functioning as a Certificate Authority.
To use the CA, first retrieve the "Qubes Master Signing Key" certificate:
$ sq network search 0x427F11FD0FAA4B080123F01CDDFA1A3E36879494
This example takes a fingerprint from the website of Qubes OS. If you want to install Qubes OS (and therefore download an untainted installation image), recheck the fingerprint!
Now declare the new certificate as a trusted introducer:
$ sq pki link authorize --depth 1 --cert 427F11FD0FAA4B080123F01CDDFA1A3E36879494 --regex 'Qubes OS Release ([0-9])+.([0-9])+ Signing Key' --all
Passing --depth 1
authorizes the certificate to only authenticate directly certified certificates. --regex
further limits the capabilities to only authorize certificates containing User IDs matching the regular expression.
With the CA certificate in place, the next step is to download the release key.
$ sq network search https://keys.qubes-os.org/keys/qubes-release-4.2-signing-key.asc
The URL is taken from the download page of the Qubes OS project—it might be different when you read this.
$ sq cert list
- 427F11FD0FAA4B080123F01CDDFA1A3E36879494
- created 2010-04-01 12:26:33 UTC
- [ ✓ ] Qubes Master Signing Key
- 9C884DF3F81064A569A4A9FAE022E58F8E34D89F
- created 2022-10-04 14:10:01 UTC
- [ ✓ ] Qubes OS Release 4.2 Signing Key
- C67DF04E3C358CA5A6BFF664A8C9447ECEE006F0
- [ ✓ ] Local Trust Root
Hint: To view why a user ID is considered valid, pass `--show-paths`.
Hint: To see more details about a certificate, for example C67DF04E3C358CA5A6BFF664A8C9447ECEE006F0, run:
$ sq inspect --cert=C67DF04E3C358CA5A6BFF664A8C9447ECEE006F0
7 bindings found.
Skipped 4 bindings, which could not be authenticated.
Pass `--gossip` to see the unauthenticated bindings.
Because the Qubes Master Signing Key
is marked as a trusted introducer, the Qubes OS Release 4.2 Signing Key
—which is directly certified by the master key (satisfying --depth 1
) and has a User ID matching the pattern from --regex
—is considered authentic.
Now fetch the installation image and the detached signature file (it's detached so that the image file can remain in its original format):
$ wget https://mirrors.edge.kernel.org/qubes/iso/Qubes-R4.2.3-x86_64.iso
$ wget https://mirrors.edge.kernel.org/qubes/iso/Qubes-R4.2.3-x86_64.iso.asc
The URLs are—again—taken from the Qubes OS download page.
Now verify the download:
$ sq verify --signature-file Qubes-R4.2.3-x86_64.iso.asc Qubes-R4.2.3-x86_64.iso
Authenticating 9C884DF3F81064A569A4A9FAE022E58F8E34D89F (Qubes OS Release 4.2 Signing Key (UNAUTHENTICATED)) using the web of trust:
Fully authenticated (120 of 120) 9C884DF3F81064A569A4A9FAE022E58F8E34D89F, Qubes OS Release 4.2 Signing Key
◯─┬ C67DF04E3C358CA5A6BFF664A8C9447ECEE006F0
│ └ (Local Trust Root)
│
│ certified the following certificate on 2025‑01‑14 as a meta-introducer (depth: unconstrained)
│
├─┬ 427F11FD0FAA4B080123F01CDDFA1A3E36879494
│ └ (Qubes Master Signing Key)
│
│ certified the following binding on 2023‑06‑03
│
└─┬ 9C884DF3F81064A569A4A9FAE022E58F8E34D89F
└ Qubes OS Release 4.2 Signing Key
Authenticated signature made by 9C884DF3F81064A569A4A9FAE022E58F8E34D89F (Qubes OS Release 4.2 Signing Key (authenticated))
1 authenticated signature.
The signature of the image file is good. This proves that the image is authentic and its integrity is confirmed.
Download
Once you have downloaded and authenticated the release key, sq
offers a shorter, more convenient subcommand to download and verify the image in one go:
$ sq download --url https://mirrors.edge.kernel.org/qubes/iso/Qubes-R4.2.3-x86_64.iso --signature-url https://mirrors.edge.kernel.org/qubes/iso/Qubes-R4.2.3-x86_64.iso.asc --signer-userid "Qubes OS Release 4.2 Signing Key" --output qubes_4.2.3.iso
Alleged signer 9C884DF3F81064A569A4A9FAE022E58F8E34D89F is good listed.
Finished downloading data. Authenticating data.
Authenticated signature made by 9C884DF3F81064A569A4A9FAE022E58F8E34D89F (Qubes OS Release 4.2 Signing Key (UNAUTHENTICATED))
1 authenticated signature.
Signing files and messages
Signing files and/or messages allows creating a signature for arbitrary data. There are two ways to store the newly created signature:
- by wrapping the file in an OpenPGP message structure which includes the signature
- by creating a detached signature within its own file, leaving the signed data untouched
The first option has the advantage that everything is in one file. The advantage of the second option is that the signed file doesn't change, so it can be used without unwrapping the OpenPGP message structure first.
Creating an inlined signature
$ sq sign --message --signer $FINGERPRINT $FILE
In this example a signature is created over the content of $FILE
file using the key designated by $FINGERPRINT
. Instead of using the data from a file, sq sign
can also take data from STDIN
.
$ echo "my message" | sq sign --message --signer $FINGERPRINT
-----BEGIN PGP MESSAGE-----
xA0DAAoWQeEtk/c7kG4Byw9iAAAAAABleGFtcGx0ZQrCvQQAFgoAbwWCZyt6wAkQ
QeEtk/c7kG5HFAAAAAAAHgAgc2FsdEBub3RhdGlvbnMuc2VxdW9pYS1wZ3Aub3Jn
72xkqs1135T2u5oIlngq51GwLmFm8jevOh4nssMT/WMWIQTlcn7y4+W/syeiLW1B
4S2T9zuQbgAA4VgBAK5XsyCTIA1VrZQYkKm7BpygYnco7K+IrWFR9ePczM3BAP9j
6V37oWwULdWG3vZsIweDNbjWfHeblOQzajAFjDEWDA==
=HkKe
-----END PGP MESSAGE-----
The output of this operation is printed to STDOUT
.
These examples generated an 'inline' signature; the output isn't human-readable. To generate a cleartext signature, use --cleartext
instead of --message
:
$ echo "my message" | sq sign --cleartext --signer $FINGERPRINT
-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA512
my message
-----BEGIN PGP SIGNATURE-----
wr0EARYKAG8FgmdiuRoJEA7sKm178RIgRxQAAAAAAB4AIHNhbHRAbm90YXRpb25z
LnNlcXVvaWEtcGdwLm9yZ/iaMdUKZbOpbMwwl+oThAXY3MMiAfXrjjKYsdoa+7M5
FiEE6QB8bZ+zNxhjUeC/DuwqbXvxEiAAAK3aAP96QhpFy782+306HDPMtaGOCNQq
fVag1Bsl0aGByI3r0wD/TPI5Md89V0ly+ixQ6SAUKgKONHEgkgaZ3sfUaCf4qAc=
=DZUO
-----END PGP SIGNATURE-----
Instead of using a key from the keystore (via --signer
), you can provide a key stored in a file with --signer-file $KEYFILE
.
Creating a detached signature
$ sq sign --signer $FINGERPRINT --signature-file $SIG_FILE $FILE
This example creates $SIG_FILE
containing the detached signature. It throws an error if $SIG_FILE
already exists; you can force it to overwrite it by adding the --overwrite
option.
Technical
This chapter describes more technical use cases.
Configuration
The behavior of sq
is configurable using configuration files. There are two separate sources of configuration values: the configuration for the cryptographic policy, and the configuration for sq
. The configuration for the cryptographic policy is used by the underlying Sequoia PGP and thus determines the behavior of sq
concerning cipher algorithms, hashes and the like (for details see below).
The sq
configuration file is in TOML format, and sets the selection of default keyservers, default expiration times for newly generated keys, default ciphers, etc. This configuration takes priority, and thus allows overriding settings from the cryptographic policy.
The cryptographic policy configuration is located in /etc/crypto-policies/back-ends/sequoia.config
. The configuration for sq
in ~/.config/sequoia/sq/config.toml
.
Accessing configuration settings
sq
has a subcommand config
to access configuration settings.
$ sq config get
Will read out the complete configuration of sq
, or specify a key to selectively retrieve the corresponding value:
$ sq config get key.generate.cipher-suite
key.generate.cipher-suite = "cv25519"
If you are interested in more than the plain value, and want some information on what a specific setting is used for, you can use sq config inspect
.
-
$ sq config inspect paths
This gives you paths to the different files or directories sq
uses: key and cert store, as well as configuration files.
-
$ sq config inspect network
Tells you which services
sq
will use if it's performing a network search. -
$ sq config inspect policy
Gives hints about the currently active cryptographic policy.
To create a new config file as a starting point for your setup, you can generate one based on your current settings using
$ sq config template
The generated configuration will be printed to STDOUT
or into a file if you specify --output $FILE
or redirect the output. Move this output to ~/.config/sequoia/sq/config.toml
, so that sq
can pick it up. This configuration also allows you to modify settings inherited from the cryptographic policy.
Cryptographic policy configuration
Sequoia PGP has a standard policy that defines which algorithms (ciphers, hash functions, etc.) are valid and available. This policy can be adjusted to cater for specific needs.
The adjustments are configured in a file (command line switches are not available), located by default in /etc/crypto-policies/back-ends/sequoia.config
. This location can be changed by setting the environment variable SEQUOIA_CRYPTO_POLICY
. A missing configuration file simply means "no adjustments" and the default policy applies.
These are global adjustments, effective for every user on the system. Per-user adjustments can be made in each user's respective configuration file.
Sequoia PGP comes with a sensible default cryptographic policy. There is no safety net when you modify its settings! You could introduce unsafe behavior or stop Sequoia PGP from working at all.
Format
The configuration file uses the TOML format, consisting of the following sections:
hash_algorithms
asymmetric_algorithms
symmetric_algorithms
aead_algorithms
packets
Missing or empty sections again mean "no adjustments".
A simple adjustment might look like this:
[hash_algorithms]
sha1 = "never"
This advises Sequoia PGP to never use or accept SHA-1 hashes. Signatures based on SHA-1 would be considered "invalid". Since signatures play a central role, this might also invalidate some certificates and keys.
Besides never
and always
, adjustments can have a date value. This specifies a cutoff time after which the adjustment would switch from always
to never
:
[hash_algorithms]
sha1 = 2010-01-01
The format of the configuration file allows using keys which are not (yet) defined. This way adjustment can be made for algorithms which will be included in the foreseeable future. To avoid parsing errors, these future keys have to be declared by using ignore_invalid
.
This example configures the nonexisting hash function "SHA-4" as "always valid" while declaring it as a future key, so that versions of Sequoia PGP which do not know about SHA-4 can still parse the configuration:
[hash_algorithms]
sha4 = "always"
ignore_invalid = ["sha4"]
Please note that ignore_invalid
has no influence on the crypto policy itself; it's only meant to support the parser.
If, for some reason, only a fixed set of algorithms should be considered valid, there is a way to prevent algorithms introduced by upgrades to the software to take effect. The "catch all" key is default_disposition
. The following example allows SHA256 and forbids everything else:
[hash_algorithms]
sha256 = "always"
default_disposition = "never"
Hash algorithms have properties which can be configured individually. second_preimage_resistance
and collision_resistance
address attacks on hash functions:
[hash_algorithms]
sha1.second_preimage_resistance = 2030-01-01
sha1.collision_resistance = 2022-01-01
The packets
section allows configurations for different versions of a packet type. The following example sets (different) cutoff times for signature packets in version 3 and 4, while unrestrictedly allows version 5 - since version 5 signatures are not yet available, ignore_invalid
is set for this key:
signature.v3 = 2017-01-01
signature.v4 = 2030-01-01
signature.v5 = "always"
signature.ignore_invalid = "v5"
Complete list of sections and keys
[hash_algorithms]
md5 = ...
sha1 = ...
ripemd160 = ...
sha256 = ...
sha384 = ...
sha512 = ...
sha224 = ...
[asymmetric_algorithms]
rsa1024 = ...
rsa2048 = ...
rsa3072 = ...
rsa4096 = ...
elgamal1024 = ...
elgamal2048 = ...
elgamal3072 = ...
elgamal4096 = ...
dsa1024 = ...
dsa2048 = ...
dsa3072 = ...
dsa4096 = ...
nistp256 = ...
nistp384 = ...
nistp521 = ...
brainpoolp256 = ...
brainpoolp512 = ...
cv25519 = ...
[symmetric_algorithms]
idea = ...
tripledes = ...
cast5 = ...
blowfish = ...
aes128 = ...
aes192 = ...
aes256 = ...
twofish = ...
camellia128 = ...
camellia192 = ...
camellia256 = ...
[aead_algorithms]
eax = ...
ocb = ...
[packets]
pkesk = ...
signature = ...
skesk = ...
onepasssig = ...
secretkey = ...
publickey = ...
secretsubkey = ...
compresseddata = ...
sed = ...
marker = ...
literal = ...
trust = ...
userid = ...
publicsubkey = ...
userattribute = ...
seip = ...
mdc = ...
aed = ...
Files
Directories
Sequoia PGP (and therefore sq
) stores its state—mainly keys and certificates—in a directory structure. The default locations are:
- the certificate store (short cert store):
$HOME/.local/share/pgp.cert.d
- the key store:
$HOME/.local/share/sequoia/keystore
- the revocation certificate store:
$HOME/.local/share/sequoia/revocation-certificates
These default locations can be changed by setting the environment variable SEQUOIA_HOME
. There are subtle changes in the directory structure when using SEQUOIA_HOME
:
- the certificate store is:
$SEQUOIA_HOME/data/pgp.cert.d
- the key store is:
$SEQUOIA_HOME/data/keystore
- the revocation certificate store is:
$SEQUOIA_HOME/data/revocation-certificates
If $SEQUOIA_HOME
is the same as $HOME
, then the default directory structure applies, as if SEQUOIA_HOME
is not set. If SEQUOIA_HOME
is set to none
, sq
will not use any key or cert store.
The location of the cert store can be overridden by setting PGP_CERT_D
or SEQUOIA_CERT_STORE
. SEQUOIA_CERT_STORE
has precedence over PGP_CERT_D
. Both override the implied setting from SEQUOIA_HOME
, even if it was set to none
.
The location of the key store can be overridden by setting SEQUOIA_KEY_STORE
, there is no second environment variable in this case. This will also override SEQUOIA_HOME
.
All these locations can also be specified on the command line, which will override the environment variables.
Keystore
The keystore contains keypairs, public and secret key material, together with User IDs and further metadata bundled together in files. These files are located in the subdirectory softkeys
- 'hard keys' are keys stored on specialized hardware like smart cards.
Certificate store
The certificate store contains all certificates imported via sq cert import
or fetched by sq network search
. It also contains the keys for the shadow CAs, the Local Trust Root
, and an SQLite database for cert lookup.
Revocation certificate store
Revocation certificates are created when keys are created. When a key is generated in the keystore, its corresponding revocation certificate is stored in the revocation certificate store.
This revocation certificate does not specify the reason for revocation (which is considered the same as 'compromised'). It's meant as a last resort, if the original key is lost and a more specific revocation cannot be created.
Backup
sq
(and Sequoia PGP) do not have in-memory stores or caches, so usable backups can be created by simply copying the files in the stores.
Upgrading a certification signature from SHA1 to SHA256
A certification signature is a way to store the assertion that a binding between a User ID (like an email address) and a public key is authentic. The certification signature is added to the certificate containing the binding. There are two types of certifications: self-signed ones, which signify that the certification aligns with the intents of the keyholder, and certifications made by others, signifying their confidence in the authenticity of the binding.
The first certification signature is added to a key when it's generated. It self-signs the key, creating a binding between its public key and its initial User IDs. Revocation certificates also contain a certification signature for verifying the authenticity of the revocation.
The certification signature is created by hashing the public key and the User ID, and then signing the resulting hash value. Ideally, the hash value for the combination of public key and User ID should be unique. Hash values, however, are of limited size, which implies information loss, and also that other input values could produce the same hash value. The consequence of these potential collisions is that a certification signature asserts the authenticity of an unlimited number of public key – User ID pairs. Obviously, this is not desired behavior.
One design criterion for a hash function used for the creation of certification signatures is that these collisions must be next to impossible to construct, making the abuse of such collisions infeasible in practice.
SHA1 is one such hash function. Over time, careful research has concluded that SHA1 is less robust against collision construction than initially thought. When combined with the inexorable expansion and availability of computing power, SHA1 became vulnerable, meaning the construction of a matching public key – User ID pair for a hash value became a realistic scenario. The remedy is to add a new signature (over the same content), using a hash calculated using a stronger hash function: SHA256. The old signature will not be removed, but its intention will now be confirmed in a way which is resistant to collisions.
In the following, we will show you how to use sq cert lint
and, due to a lack of awareness of outdated standards, how to use it with GnuPG.
Lint certificates of Sequoia's certificate store
Check for SHA1 signatures in certificates
sq cert lint
checks for certificates that uses SHA1 signatures. sq cert lint
takes the certificate(s) to examine as either a file parameter or from STDIN
. It also operates directly on the certificate store—so no need for a keyring, unless you would like to check the whole cert store, which we show later on. To check a specific certificate, use the command like this:
$ sq cert lint --cert $FINGERPRINT
Examined 1 certificate.
0 certificates are invalid and were not linted. (GOOD)
1 certificate was linted.
1 of the 1 certificates (100%) has at least one issue. (BAD)
0 of the linted certificates were revoked.
0 of the 0 certificates has revocation certificates that are weaker than the certificate and should be recreated. (GOOD)
0 of the linted certificates were expired.
1 of the non-revoked linted certificate has at least one non-revoked User ID:
1 has at least one User ID protected by SHA-1. (BAD)
1 has all User IDs protected by SHA-1. (BAD)
1 of the non-revoked linted certificates has at least one non-revoked, live subkey:
1 has at least one non-revoked, live subkey with a binding signature that uses SHA-1. (BAD)
0 of the non-revoked linted certificates have at least one non-revoked, live, signing-capable subkey:
0 certificates have at least one non-revoked, live, signing-capable subkey with a strong binding signature, but a backsig that uses SHA-1. (GOOD)
If you have access to the key, you can fix this now. Therefore, use the command sq cert lint
and add the --fix
option. This causes sq
to generate new certifications, using the SHA256 hash function, and update the certificate in the cert store:
$ sq cert lint --fix --cert $FINGERPRINT
Certificate B371C0977CFE0EBC is not valid under the standard policy: No binding signature at time 2025-03-27T08:25:00Z
Certificate B371C0977CFE0EBC contains a User ID (Alice Example <alice@example.com>) protected by SHA-1
Please enter the password to decrypt B371C0977CFE0EBC/B371C0977CFE0EBC, Alice Example <alice@example.com> (UNAUTHENTICATED):
Certificate B371C0977CFE0EBC, key 395B0E508A299D30 uses a SHA-1-protected binding signature.
Examined 1 certificate.
0 certificates are invalid and were not linted. (GOOD)
1 certificate was linted.
1 of the 1 certificates (100%) has at least one issue. (BAD)
0 of the linted certificates were revoked.
0 of the 0 certificates has revocation certificates that are weaker than the certificate and should be recreated. (GOOD)
0 of the linted certificates were expired.
1 of the non-revoked linted certificate has at least one non-revoked User ID:
1 has at least one User ID protected by SHA-1. (BAD)
1 has all User IDs protected by SHA-1. (BAD)
1 of the non-revoked linted certificates has at least one non-revoked, live subkey:
1 has at least one non-revoked, live subkey with a binding signature that uses SHA-1. (BAD)
0 of the non-revoked linted certificates have at least one non-revoked, live, signing-capable subkey:
0 certificates have at least one non-revoked, live, signing-capable subkey with a strong binding signature, but a backsig that uses SHA-1. (GOOD)
To check whether the certificate has been linted, we have to repeat the sq cert lint
command for this specific certificate, which you can see no longer shows an SHA1 signature:
$ sq cert lint --cert $FINGERPRINT
Examined 1 certificate.
0 certificates are invalid and were not linted. (GOOD)
1 certificate was linted.
0 of the 1 certificates (0%) have at least one issue. (GOOD)
0 of the linted certificates were revoked.
0 of the 0 certificates has revocation certificates that are weaker than the certificate and should be recreated. (GOOD)
0 of the linted certificates were expired.
1 of the non-revoked linted certificate has at least one non-revoked User ID:
0 have at least one User ID protected by SHA-1. (GOOD)
0 have all User IDs protected by SHA-1. (GOOD)
1 of the non-revoked linted certificates has at least one non-revoked, live subkey:
0 have at least one non-revoked, live subkey with a binding signature that uses SHA-1. (GOOD)
0 of the non-revoked linted certificates have at least one non-revoked, live, signing-capable subkey:
0 certificates have at least one non-revoked, live, signing-capable subkey with a strong binding signature, but a backsig that uses SHA-1. (GOOD)
To show more information, use sq packet dump
:
$ sq packet dump --cert $FINGERPRINT
Public-Key Packet, new CTB, 269 bytes
Version: 4
Creation time: 2025-02-17 14:32:54 UTC
Pk algo: RSA
Pk size: 2048 bits
Fingerprint: 17D714014EE9789B1152A408B371C0977CFE0EBC
KeyID: B371C0977CFE0EBC
User ID Packet, new CTB, 33 bytes
Value: Alice Example <alice@example.com>
Signature Packet, new CTB, 402 bytes
Version: 4
Type: PositiveCertification
Pk algo: RSA
Hash algo: SHA256
Hashed area:
Signature creation time: 2025-03-26 08:46:07 UTC (critical)
Symmetric algo preferences: AES256, AES192, AES128
Issuer: B371C0977CFE0EBC
Alice Example <alice@example.com> (UNAUTHENTICATED)
Notation: salt@notations.sequoia-pgp.org
00000000 5f 9e ba 5e 79 73 80 3c 34 69 18 6c a7 b4 9c 4f
00000010 e6 da c2 d8 f1 31 70 d7 0f a2 8d 09 a2 36 e9 b8
Hash preferences: SHA256, SHA512
Compression preferences: Zlib, BZip2, Zip
Keyserver preferences: no modify
Key flags: CS
Features: SEIPDv1
Issuer Fingerprint: 17D714014EE9789B1152A408B371C0977CFE0EBC
Alice Example <alice@example.com> (UNAUTHENTICATED)
Digest prefix: B3DB
Level: 0 (signature over data)
Signature Packet, new CTB, 335 bytes
Version: 4
Type: PositiveCertification
Pk algo: RSA
Hash algo: SHA1
Hashed area:
Signature creation time: 2025-02-17 14:32:54 UTC
Key flags: CS
Symmetric algo preferences: AES256, AES192, AES128, CAST5, TripleDES
Hash preferences: SHA256, SHA1, SHA384, SHA512, SHA224
Compression preferences: Zlib, BZip2, Zip
Features: SEIPDv1
Keyserver preferences: no modify
Unhashed area:
Issuer: B371C0977CFE0EBC
Alice Example <alice@example.com> (UNAUTHENTICATED)
Issuer Fingerprint: 17D714014EE9789B1152A408B371C0977CFE0EBC
Alice Example <alice@example.com> (UNAUTHENTICATED)
Digest prefix: A68C
Level: 0 (signature over data)
Public-Subkey Packet, new CTB, 269 bytes
Version: 4
Creation time: 2025-02-17 14:32:54 UTC
Pk algo: RSA
Pk size: 2048 bits
Fingerprint: 8CE181686CF93037B5971F22395B0E508A299D30
KeyID: 395B0E508A299D30
Signature Packet, new CTB, 382 bytes
Version: 4
Type: SubkeyBinding
Pk algo: RSA
Hash algo: SHA256
Hashed area:
Signature creation time: 2025-03-26 08:46:07 UTC (critical)
Issuer: B371C0977CFE0EBC
Alice Example <alice@example.com> (UNAUTHENTICATED)
Notation: salt@notations.sequoia-pgp.org
00000000 09 87 55 24 27 e4 93 20 d6 e3 94 e9 06 b8 31 a8
00000010 5a 5b 4f cb 5e 2a aa fe 57 37 d7 eb 48 50 58 0e
Key flags: EtEr
Issuer Fingerprint: 17D714014EE9789B1152A408B371C0977CFE0EBC
Alice Example <alice@example.com> (UNAUTHENTICATED)
Digest prefix: 6BC9
Level: 0 (signature over data)
Signature Packet, new CTB, 310 bytes
Version: 4
Type: SubkeyBinding
Pk algo: RSA
Hash algo: SHA1
Hashed area:
Signature creation time: 2025-02-17 14:32:54 UTC
Key flags: EtEr
Unhashed area:
Issuer: B371C0977CFE0EBC
Alice Example <alice@example.com> (UNAUTHENTICATED)
Issuer Fingerprint: 17D714014EE9789B1152A408B371C0977CFE0EBC
Alice Example <alice@example.com> (UNAUTHENTICATED)
Digest prefix: 40E8
Level: 0 (signature over data)
Checking all certificates
If you want to check all your certificates, even the ones you have received from others, to just know which ones are vulnerable (of course you can't update them without their secret key) they have to be exported and a keyring has to be created for sq lint
to handle them. First, here is how to export the certificates from the cert store:
$ sq cert export --all > keyring.pgp
Then you can check the exported keyring using the sq lint
command, which will not change anything:
$ sq cert lint --cert-file keyring.pgp
Certificate 9C2437DF50A1F904 is not valid under the standard policy: No binding signature at time 2025-03-27T08:11:04Z
Certificate 9C2437DF50A1F904 contains a User ID (Alice Example (GnuPG 1.4.18 Testkey) <alice@example.com>) protected by SHA-1
Certificate 9C2437DF50A1F904, key 6FBFA7A845431489 uses a SHA-1-protected binding signature.
[...]
Examined 644 certificates.
82 certificates are invalid and were not linted. (BAD)
562 certificates were linted.
175 of the 644 certificates (27%) have at least one issue. (BAD)
36 of the linted certificates were revoked.
1 of the 36 certificates has revocation certificates that are weaker than the certificate and should be recreated. (BAD)
276 of the linted certificates were expired.
249 of the non-revoked linted certificates have at least one non-revoked User ID:
91 have at least one User ID protected by SHA-1. (BAD)
89 have all User IDs protected by SHA-1. (BAD)
236 of the non-revoked linted certificates have at least one non-revoked, live subkey:
83 have at least one non-revoked, live subkey with a binding signature that uses SHA-1. (BAD)
74 of the non-revoked linted certificates have at least one non-revoked, live, signing-capable subkey:
0 certificates have at least one non-revoked, live, signing-capable subkey with a strong binding signature, but a backsig that uses SHA-1. (GOOD)
Error: 175 certificates have at least one issue
As you can see in this example, many invalid certificates may have accumulated over time. To resolve that, you could either ask the respective owners to update their certificates, or if the corresponding secret key material is available to you, fix the certificates yourself using the --fix
option described above.
sq cert lint
with certificates from gpg keyrings
Check for SHA1 signatures in certificates
Again, we use sq cert lint
to check for certificates that use SHA1 signatures. sq cert lint
takes the certificate(s) to examine either a file parameter or from STDIN
.
This example takes the certificates from gpg, using a keyring which contains just one vulnerable certificate.
$ gpg --export | sq cert lint
Certificate B371C0977CFE0EBC is not valid under the standard policy: No binding signature at time 2025-02-17T14:52:16Z
Certificate B371C0977CFE0EBC contains a User ID (Alice Example <alice@example.com>) protected by SHA-1
Certificate B371C0977CFE0EBC, key 395B0E508A299D30 uses a SHA-1-protected binding signature.
Examined 1 certificate.
0 certificates are invalid and were not linted. (GOOD)
1 certificate was linted.
1 of the 1 certificates (100%) has at least one issue. (BAD)
0 of the linted certificates were revoked.
0 of the 0 certificates has revocation certificates that are weaker than the certificate and should be recreated. (GOOD)
0 of the linted certificates were expired.
1 of the non-revoked linted certificate has at least one non-revoked User ID:
1 has at least one User ID protected by SHA-1. (BAD)
1 has all User IDs protected by SHA-1. (BAD)
1 of the non-revoked linted certificates has at least one non-revoked, live subkey:
1 has at least one non-revoked, live subkey with a binding signature that uses SHA-1. (BAD)
0 of the non-revoked linted certificates have at least one non-revoked, live, signing-capable subkey:
0 certificates have at least one non-revoked, live, signing-capable subkey with a strong binding signature, but a backsig that uses SHA-1. (GOOD)
Error: 1 certificate have at least one issue
To fix a certification signature, a secret key is needed.
Exporting a "secret key" actually exports the complete keypair—both the secret key material and the public certificate.
Passing --fix
to sq cert lint
causes sq
to generate new certifications using the SHA256 hash function. The result is written to stdout
and can be passed to sq
or, or back into gpg
for import.
$ gpg --export-secret-keys alice@example.com | sq cert lint --fix
Certificate B371C0977CFE0EBC is not valid under the standard policy: No binding signature at time 2025-02-17T14:56:10Z
Certificate B371C0977CFE0EBC contains a User ID (Alice Example <alice@example.com>) protected by SHA-1
Please enter the password to decrypt B371C0977CFE0EBC/B371C0977CFE0EBC Alice Example <alice@example.com> (UNAUTHENTICATED):
Certificate B371C0977CFE0EBC, key 395B0E508A299D30 uses a SHA-1-protected binding signature.
-----BEGIN PGP PRIVATE KEY BLOCK-----
xcMGBGezSJYBCACv2FM2TzZtv5LmV+CmKjRqrcuaexasLg6GxpoTHVN7gwSXDNGq
[...]
2DIgwY1O
=hUqr
-----END PGP PRIVATE KEY BLOCK-----
Examined 1 certificate.
0 certificates are invalid and were not linted. (GOOD)
1 certificate was linted.
1 of the 1 certificates (100%) has at least one issue. (BAD)
0 of the linted certificates were revoked.
0 of the 0 certificates has revocation certificates that are weaker than the certificate and should be recreated. (GOOD)
0 of the linted certificates were expired.
1 of the non-revoked linted certificate has at least one non-revoked User ID:
1 has at least one User ID protected by SHA-1. (BAD)
1 has all User IDs protected by SHA-1. (BAD)
1 of the non-revoked linted certificates has at least one non-revoked, live subkey:
1 has at least one non-revoked, live subkey with a binding signature that uses SHA-1. (BAD)
0 of the non-revoked linted certificates have at least one non-revoked, live, signing-capable subkey:
0 certificates have at least one non-revoked, live, signing-capable subkey with a strong binding signature, but a backsig that uses SHA-1. (GOOD)
The output of sq cert lint --fix
can be piped to sq key import
, or gpg --import
if you're using gpg
:
$ gpg --export-secret-keys alice@example.com | sq cert lint --fix | gpg --import
[...]
gpg: Total number processed: 1
gpg: new signatures: 2
gpg: secret keys read: 1
gpg: secret keys unchanged: 1
In this example, two new signatures were detected, and both used the improved hash algorithm.
We can now have a look at the cert again in sq
, expecting it to be linted:
$ gpg --export alice@example.com | sq cert lint
Examined 1 certificate.
0 certificates are invalid and were not linted. (GOOD)
1 certificate was linted.
0 of the 1 certificates (0%) have at least one issue. (GOOD)
0 of the linted certificates were revoked.
0 of the 0 certificates has revocation certificates that are weaker than the certificate and should be recreated. (GOOD)
0 of the linted certificates were expired.
1 of the non-revoked linted certificate has at least one non-revoked User ID:
0 have at least one User ID protected by SHA-1. (GOOD)
0 have all User IDs protected by SHA-1. (GOOD)
1 of the non-revoked linted certificates has at least one non-revoked, live subkey:
0 have at least one non-revoked, live subkey with a binding signature that uses SHA-1. (GOOD)
0 of the non-revoked linted certificates have at least one non-revoked, live, signing-capable subkey:
0 certificates have at least one non-revoked, live, signing-capable subkey with a strong binding signature, but a backsig that uses SHA-1. (GOOD)
All checks are good.
For a closer look, use sq packet dump
:
$ gpg --export alice@example.com | sq packet dump
Public-Key Packet, old CTB, 269 bytes
Version: 4
Creation time: 2025-02-17 14:32:54 UTC
Pk algo: RSA
Pk size: 2048 bits
Fingerprint: 17D714014EE9789B1152A408B371C0977CFE0EBC
KeyID: B371C0977CFE0EBC
User ID Packet, old CTB, 33 bytes
Value: Alice Example <alice@example.com>
Signature Packet, old CTB, 312 bytes
Version: 4
Type: PositiveCertification
Pk algo: RSA
Hash algo: SHA1
Hashed area:
Signature creation time: 2025-02-17 14:32:54 UTC
Key flags: CS
Symmetric algo preferences: AES256, AES192, AES128, CAST5, TripleDES
Hash preferences: SHA256, SHA1, SHA384, SHA512, SHA224
Compression preferences: Zlib, BZip2, Zip
Features: SEIPDv1
Keyserver preferences: no modify
Unhashed area:
Issuer: B371C0977CFE0EBC
Alice Example <alice@example.com> (UNAUTHENTICATED)
Digest prefix: A68C
Level: 0 (signature over data)
Signature Packet, old CTB, 402 bytes
Version: 4
Type: PositiveCertification
Pk algo: RSA
Hash algo: SHA256
Hashed area:
Signature creation time: 2025-02-17 14:59:11 UTC (critical)
Symmetric algo preferences: AES256, AES192, AES128
Issuer: B371C0977CFE0EBC
Alice Example <alice@example.com> (UNAUTHENTICATED)
Notation: salt@notations.sequoia-pgp.org
00000000 77 d1 f6 d5 be 0b 50 60 c2 02 e9 db 20 0e 38 37
00000010 04 7a 3d 06 58 fc 7c 16 de 66 86 d2 e1 b5 29 37
Hash preferences: SHA256, SHA512
Compression preferences: Zlib, BZip2, Zip
Keyserver preferences: no modify
Key flags: CS
Features: SEIPDv1
Issuer Fingerprint: 17D714014EE9789B1152A408B371C0977CFE0EBC
Alice Example <alice@example.com> (UNAUTHENTICATED)
Digest prefix: 7F53
Level: 0 (signature over data)
Public-Subkey Packet, old CTB, 269 bytes
Version: 4
Creation time: 2025-02-17 14:32:54 UTC
Pk algo: RSA
Pk size: 2048 bits
Fingerprint: 8CE181686CF93037B5971F22395B0E508A299D30
KeyID: 395B0E508A299D30
Signature Packet, old CTB, 287 bytes
Version: 4
Type: SubkeyBinding
Pk algo: RSA
Hash algo: SHA1
Hashed area:
Signature creation time: 2025-02-17 14:32:54 UTC
Key flags: EtEr
Unhashed area:
Issuer: B371C0977CFE0EBC
Alice Example <alice@example.com> (UNAUTHENTICATED)
Digest prefix: 40E8
Level: 0 (signature over data)
Signature Packet, old CTB, 382 bytes
Version: 4
Type: SubkeyBinding
Pk algo: RSA
Hash algo: SHA256
Hashed area:
Signature creation time: 2025-02-17 14:59:11 UTC (critical)
Issuer: B371C0977CFE0EBC
Alice Example <alice@example.com> (UNAUTHENTICATED)
Notation: salt@notations.sequoia-pgp.org
00000000 1d f1 d2 67 0e 98 6a 8c f9 ab 62 c0 44 fc cf 17
00000010 41 5e 64 9d 63 5a 75 d2 13 82 73 3b 08 7d 5c ec
Key flags: EtEr
Issuer Fingerprint: 17D714014EE9789B1152A408B371C0977CFE0EBC
Alice Example <alice@example.com> (UNAUTHENTICATED)
Digest prefix: 94F9
Level: 0 (signature over data)
Note that the old SHA1 certifications are still present within the certificate, but the new SHA256 certifications of the same entities will be used in preference to them.
Fixing a revocation certificate
First, let's look into a revocation certificate to examine the hash algorithm used. In this example alice.rev
contains the revocation certificate. Use sq packet dump
to inspect it more closely:
$ sq packet dump alice.rev
Signature Packet, old CTB, 293 bytes
Version: 4
Type: KeyRevocation
Pk algo: RSA
Hash algo: SHA1
Hashed area:
Signature creation time: 2025-02-17 15:10:37 UTC
Reason for revocation: Key is retired and no longer used, Retire
Unhashed area:
Issuer: B371C0977CFE0EBC
Alice Example <alice@example.com> (UNAUTHENTICATED)
Digest prefix: E7F3
Level: 0 (signature over data)
Hash algo
tells us the hash algorithm used, SHA1 in this case.
sq cert lint
is not meant to fix this issue; Instead, create a new revocation certificate.
The generation of a revocation certificate only works with keys containing SHA256 certifications. See above for how to fix keys in case they are missing.
$ gpg --export-secret-key alice@example.com | sq key userid revoke --cert-file - --userid "Alice Example <alice@example.com>" --reason "retired" --message "This key is retired" --output alice_new.rev
Waiting for OpenPGP certificates on stdin...
Please enter the password to decrypt B371C0977CFE0EBC/B371C0977CFE0EBC Alice Example <alice@example.com> (UNAUTHENTICATED):
$ sq packet dump alice_new.rev
Public-Key Packet, new CTB, 269 bytes
Version: 4
Creation time: 2025-02-17 14:32:54 UTC
Pk algo: RSA
Pk size: 2048 bits
Fingerprint: 17D714014EE9789B1152A408B371C0977CFE0EBC
KeyID: B371C0977CFE0EBC
User ID Packet, new CTB, 33 bytes
Value: Alice Example <alice@example.com>
Signature Packet, new CTB, 401 bytes
Version: 4
Type: CertificationRevocation
Pk algo: RSA
Hash algo: SHA512
Hashed area:
Signature creation time: 2025-02-17 15:15:34 UTC (critical)
Issuer: B371C0977CFE0EBC
Alice Example <alice@example.com> (UNAUTHENTICATED)
Notation: salt@notations.sequoia-pgp.org
00000000 3f 86 99 1e cb 7a 33 0d 39 f0 da 7c 99 00 be 22
00000010 42 04 f9 d5 83 a9 b0 36 33 1c 88 07 34 2d 0d 18
Reason for revocation: User ID information is no longer valid, This key is retired
Issuer Fingerprint: 17D714014EE9789B1152A408B371C0977CFE0EBC
Alice Example <alice@example.com> (UNAUTHENTICATED)
Digest prefix: D216
Level: 0 (signature over data)
Inspect key, certificates, messages and the like
Sometimes you want a closer look at the objects sq
works with, for instance to troubleshoot a failing operation. sq
offers two levels of detail: inspect
and the subcommand hierarchy under sq packet
.
The workhorse of these two is sq inspect
, which displays a human-readable extract of the object passed to it. The most general use of this command is:
$ sq inspect FILE
FILE
can be a certificate, a key, a message—any file, even a non-OpenPGP one:
$ sq inspect /etc/fstab
/etc/fstab: No OpenPGP data.
The output of a certificate looks like this:
$ sq inspect bob.cert
bob.cert: OpenPGP Certificate.
Fingerprint: 265BA2AB62FFF0B67AF62A70A9FE49218A6A88B0
Public-key algo: EdDSA
Public-key size: 256 bits
Creation time: 2024-10-28 11:31:56 UTC
Expiration time: 2027-10-29 04:58:17 UTC (creation time + 2years 11months 30days 9h 16m 45s)
Key flags: certification
Subkey: 8600DB2A7FB8BFE5DAAA922884B61A3ABEACD605
Public-key algo: EdDSA
Public-key size: 256 bits
Creation time: 2024-10-28 11:31:56 UTC
Expiration time: 2027-10-29 04:58:17 UTC (creation time + 2years 11months 30days 9h 16m 45s)
Key flags: authentication
Subkey: 60F64238FDD763AFF76677928A880E8E21CC6C3E
Public-key algo: EdDSA
Public-key size: 256 bits
Creation time: 2024-10-28 11:31:56 UTC
Expiration time: 2027-10-29 04:58:17 UTC (creation time + 2years 11months 30days 9h 16m 45s)
Key flags: signing
Subkey: 41E5C9C3AC983EA9CC04649A9754F1AB759E21AF
Public-key algo: ECDH
Public-key size: 256 bits
Creation time: 2024-10-28 11:31:56 UTC
Expiration time: 2027-10-29 04:58:17 UTC (creation time + 2years 11months 30days 9h 16m 45s)
Key flags: transport encryption, data-at-rest encryption
UserID: <bob@example.com>
Certifications: 1, use --certifications to list
UserID: Bob
If FILE
is omitted, sq inspect
reads from STDIN
.
$ echo "hello" | sq encrypt --for-email alice@example.com | sq inspect
-: Encrypted OpenPGP Message.
Recipient: 70481B0CCFC64D03
In this example, we created an encrypted message on the fly. The cleartext message ("hello") is passed through sq encrypt
, and then forwarded to sq inspect
.
A revocation certificate looks like this:
$ sq inspect bob.rev
bob.rev: Revocation Certificate.
Fingerprint: 7C4A15AD3C51181E43B534DE120862007D362AF2
Revoked:
- No reason specified
On: 2024-10-28 11:36:16 UTC
Message: Unspecified
Invalid: No binding signature at time 2024-10-28T13:25:00Z
Public-key algo: EdDSA
Public-key size: 256 bits
Creation time: 2024-10-28 11:36:16 UTC
You can experiment with
sq inspect
- as it only reads data, it will not cause any damage.
Data from within the certificate store is also accessible:
$ sq inspect --cert 7C4A15AD3C51181E43B534DE120862007D362AF2
OpenPGP Certificate.
[...]
When inspecting a certificate, its certifications are hidden. You can display certifications by passing the --certifications
option:
$ sq inspect --cert 7C4A15AD3C51181E43B534DE120862007D362AF2 --certifications
OpenPGP Certificate.
Fingerprint: 7C4A15AD3C51181E43B534DE120862007D362AF2
Public-key algo: EdDSA
Public-key size: 256 bits
Creation time: 2024-10-28 11:36:16 UTC
Expiration time: 2027-10-29 05:02:37 UTC (creation time + 2years 11months 30days 9h 16m 45s)
Key flags: certification
Subkey: F753DAA5EE1633D6B6B7071F4ED3187E3CE8EC04
Public-key algo: EdDSA
Public-key size: 256 bits
Creation time: 2024-10-28 11:36:16 UTC
Expiration time: 2027-10-29 05:02:37 UTC (creation time + 2years 11months 30days 9h 16m 45s)
Key flags: signing
Subkey: DF676EC1ADA16D84DF6B197881FE2BA966469B1E
Public-key algo: EdDSA
Public-key size: 256 bits
Creation time: 2024-10-28 11:36:16 UTC
Expiration time: 2027-10-29 05:02:37 UTC (creation time + 2years 11months 30days 9h 16m 45s)
Key flags: authentication
Subkey: 3B5DE910F20C2D9A4989D15E90E3343A16CA864D
Public-key algo: ECDH
Public-key size: 256 bits
Creation time: 2024-10-28 11:36:16 UTC
Expiration time: 2027-10-29 05:02:37 UTC (creation time + 2years 11months 30days 9h 16m 45s)
Key flags: transport encryption, data-at-rest encryption
UserID: <bob@example.com>
Certification: Creation time: 2024-10-28 11:38:35 UTC
Expiration time: 2029-10-28 16:42:30 UTC (after 5 years)
Trust depth: 1
Trust amount: 120
Regular expression: "<[^>]+[@.]example\\.com>$"
Alleged certifier: 188C014A41FD5FF4D83A35A0A879D7033A9B6293
<alice@example.com> (authenticated)
Hash algorithm: SHA512
Certification: Creation time: 2024-10-28 11:36:25 UTC
Trust depth: 255
Trust amount: 120
Alleged certifier: B9C899C70BE20C10D794E924288ADA34F36309B1
Local Trust Root (authenticated)
Hash algorithm: SHA512
Note: Certifications have NOT been verified!
Packet dump
An even deeper dive is available. You can list the packets comprising an OpenPGP artifact (message, certificate, signature, etc). In this example, we generate an encrypted message and display its packet structure:
$ echo "hello" | sq encrypt --for-email bob@example.com | sq packet dump
Public-Key Encrypted Session Key Packet, new CTB, 94 bytes
Version: 3
Recipient: 90E3343A16CA864D
Pk algo: ECDH
Public-Key Encrypted Session Key Packet, new CTB, 94 bytes
Version: 3
Recipient: 90E3343A16CA864D
Pk algo: ECDH
Sym. Encrypted and Integrity Protected Data Packet, new CTB, 55 bytes
│ Version: 1
│ Session key: E2A9BB6A3A94127C240346792AE53806A722B45BCB8DEB1A249F8E43405F5D9C
│ Symmetric algo: AES-256
│ Decryption successful
│
├── Literal Data Packet, new CTB, 12 bytes
│ Format: Binary data
│ Content: "hello\n"
│
└── Modification Detection Code Packet, new CTB, 20 bytes
Digest: EA9382CBE41EB2A2F1B40E9E6C3529873B67FE8F
Computed digest: EA9382CBE41EB2A2F1B40E9E6C3529873B67FE8F
The output of sq packet dump
shows the *wire format—*the sequence and content of the different packets composing an OpenPGP artifact.
Objects from the certificate store are also available:
$ sq packet dump --cert 7C4A15AD3C51181E43B534DE120862007D362AF2
Like sq inspect
, sq packet dump
only reads data, so it's safe to experiment with.
Hardware keys/ Smart cards
Hardware keys or smart cards are small computers. They come as USB sticks, in credit card format, or as a SIM card (and probably other form factors). Their main purpose is to protect secret key material. Secret key material can be written to and stored on these devices, but never read. Instead, these devices can be used to sign or decrypt data, usually after supplying some kind of authentication (like a PIN). That way, secret key material can be used without disclosing it.
Sequoia PGP (and therefore sq
) uses key and cert stores to manage their respective objects. These stores delegate requests to backends. One of these backends is gpg-agent
. As long as gpg-agent
can use your OpenPGP hardware token, sq
can do so as well.
If you point the environment variable
SEQUOIA_HOME
to a location other thandefault
,gpg-agent
support is disabled.
Overview
For the whole picture—aside from sq
, gpg-agent
and the hardware token—there are two further relevant components: scdaemon
and pcscd
. Both components implement access to the hardware token.
scdaemon
is part of the GnuPG ecosystem and gpg-agent
is hardwired to use it. pcscd
is a more generic solution and is used by practically every software stack other than GnuPG.
If scdaemon
and pcscd
are used simultaneously, there has to be coordination between them, as they access the same shared hardware resource. For that, scdaemon
has to be configured to use pcscd
for hardware access, instead of connecting to it directly. The picture looks like this:
sq <-> gpg-agent <-> scdaemon <-> pcscd <-> hardware
^
|
v
pin-entry
The pin-entry
service is used by gpg-agent
to ask for a passphrase or PIN from the user.
This construction is brittle, as scdaemon
expects exclusive control of the hardware. With pcscd
in the middle, this exclusivity is not given, and any intervening access endangers scdaemon
s expectations of the state of a connection.
You can disable pcscd
and let scdaemon
talk directly to the hardware, but then you might lose access to additional capabilities the hardware might offer (like two-factor authentication).
Configuration
To configure scdaemon
to use pcscd
for hardware access, make sure that the following two lines are in ~/.gnupg/scdaemon.conf
:
disable-ccid
pcsc-shared
If you want to have scdaemon
talk directly to the hardware, disable pcscd
and make sure that the two lines above are removed or commented out.
Keyrings
Public keys, encrypted messages and other OpenPGP artifacts consist of a series of packets in a specific sequence. Certificates, for instance, start with a Public-Key packet, followed by a string of packets which contain the actual content of the certificate (User IDs, subkeys, etc.), mixed with Signature packets which bind components to the primary key.
A stream of these packets can contain multiple artifacts. If such a stream consists only of certificates (with or without secret key material), it is called a keyring.
Keyrings are a way to store a collection of certificates in a single file.
Creating a keyring
A keyring can be created by exporting certificates from the cert store into a file:
$ sq cert export --cert $FPR_A --cert $FPR_B --cert $FPR_C ... > $FILE
Likewise sq key export ...
will also create a keyring, but one containing keys.
Listing the certificates in a keyring
A summary of certificates in a keyring can be listed with keyring list
:
$ sq keyring list example_keyring.pgp
0. 159F7D1D498EF2BFC489D13FB5A86AAD248300C8 Bob
1. 722E3D4101B82C0AE80A3EF07A512226FF0D14E7 Alice
2. 8B19816EE960EEBBD09A1256CF755B2FB77C6171 Dave
3. CA0CB6126046DE2E91770FDBBBC18569610E3ACF Carol
Extracting certificates from a keyring
Certificates can be extracted from a keyring by "filtering" it. sq keyring filter ...
takes a keyring as input and produces a new keyring on output containing the certificates which match the filter.
$ sq keyring filter --experimental --userid Alice example_keyring.pgp
-----BEGIN PGP PUBLIC KEY BLOCK-----
xjMEZ7Wz3hYJKwYBBAHaRw8BAQdAuhfXxD0ewpB0BrkDpMH5YhmobPkp/eGpJRxu
[...]
=adZu
-----END PGP PUBLIC KEY BLOCK-----
Please note that you have to pass --experimental
as a parameter to the command, as its API isn't stable yet and might change in the future.
Filters can be combined:
$ sq keyring filter --experimental --userid Alice --userid Bob example_keyring.pgp
sq keyring filter
builds the union of all supplied filters; the result contains all certificates that match at least one of the filters.
You can filter by
--userid
--name
--email
--domain
— matches User IDs containing an email address.--cert
— matches on the fingerprint of the primary key.--key
— like--cert
, but also matches on subkeys.
The filtering doesn't change the certificates themselves. If you want the filter to also modify the components of a certificate - dropping user ids for instance - you can pass --prune-certs
to the command.
Consider a certificate containing User IDs from different domains:
$ sq key generate --email alice@example.com --email alice@foo.org --own-key --without-password
[...]
Fingerprint: 49294AF69CF035A5F1FD45FAB96A1FE8D78692A1
[...]
UserID: <alice@example.com>
Certifications: 1, use --certifications to list
UserID: <alice@foo.org>
Certifications: 1, use --certifications to list
[...]
$ sq cert export --cert 49294AF69CF035A5F1FD45FAB96A1FE8D78692A1 | sq keyring filter --experimental --domain example.com --prune-certs | sq inspect
-: OpenPGP Certificate.
Fingerprint: 49294AF69CF035A5F1FD45FAB96A1FE8D78692A1
Public-key algo: EdDSA
Public-key size: 256 bits
Creation time: 2025-05-12 20:00:42 UTC
Expiration time: 2028-05-12 13:27:03 UTC (creation time + 2years 11months 30days 9h 16m 45s)
Key flags: certification
Subkey: C5B009B5025C3F10C748EB7864374DFEB4FEF8CC
Public-key algo: EdDSA
Public-key size: 256 bits
Creation time: 2025-05-12 20:00:42 UTC
Expiration time: 2028-05-12 13:27:03 UTC (creation time + 2years 11months 30days 9h 16m 45s)
Key flags: authentication
Subkey: 5EE2DC667CC6B3740D7030284A85C1D90AEC4E15
Public-key algo: EdDSA
Public-key size: 256 bits
Creation time: 2025-05-12 20:00:42 UTC
Expiration time: 2028-05-12 13:27:03 UTC (creation time + 2years 11months 30days 9h 16m 45s)
Key flags: signing
Subkey: C7F5F97A82B48A40530B08F305AE78D605641B6F
Public-key algo: ECDH
Public-key size: 256 bits
Creation time: 2025-05-12 20:00:42 UTC
Expiration time: 2028-05-12 13:27:03 UTC (creation time + 2years 11months 30days 9h 16m 45s)
Key flags: transport encryption, data-at-rest encryption
UserID: <alice@example.com>
The User ID alice@foo.org
didn't match the domain filter and thus does not appear in the output of the command.
To build the intersection of several filters (i.e. logical AND
rather than OR
), chain multiple invocations of sq keyring filter
:
$ sq keyring filter --experimental --domain example.com example_keyring.pgp | sq keyring filter --experimental --userid Alice
sq keyring filter
manipulates keyrings directly — it doesn't care about authenticity at all! If you want to use this command in a security sensitive context, make sure to take extra measures to assert authenticity.
Split and merge
A keyring can be split up into individual certificates by:
$ sq keyring split $FILE
Taking the above keyring:
$ mkdir out
$ cd out
$ sq keyring split ../example_keyring.pgp
$ ls
example_keyring.pgp-0-159F7D1D498EF2BFC489D13FB5A86AAD248300C8-bob@example.com
example_keyring.pgp-1-722E3D4101B82C0AE80A3EF07A512226FF0D14E7-alice@example.com
example_keyring.pgp-2-8B19816EE960EEBBD09A1256CF755B2FB77C6171-dave@example.com
example_keyring.pgp-3-CA0CB6126046DE2E91770FDBBBC18569610E3ACF-carol@example.com
$
This operation can be reversed using sq keyring merge ...
:
$ sq keyring merge *
-----BEGIN PGP PUBLIC KEY BLOCK-----
xjMEZ7Wz3hYJKwYBBAHaRw8BAQdAuhfXxD0ewpB0BrkDpMH5YhmobPkp/eGpJRxu
[...]
=MuLv
-----END PGP PUBLIC KEY BLOCK-----
or any other selection of files:
$ sq keyring merge *alice* *dave* | sq keyring list
0. 722E3D4101B82C0AE80A3EF07A512226FF0D14E7 Alice
1. 8B19816EE960EEBBD09A1256CF755B2FB77C6171 Dave
sqv
- a verifying tool
sqv
is a standalone tool for verifying signatures. Its main use case is for verifying the integrity and authenticity of downloaded files.
Installation
Linux
Install sqv
by using your Linux distribution's package manager.
Debian
sqv
has been part of the Debian distribution since "bookworm" (Debian 12). Up-to-date versions are part of "trixie" (Debian 13) and later.
$ sudo apt install sqv
macOS
There is a package for sqv
in homebrew:
$ brew install sequoia-sqv
Windows
The easiest way to get sqv
on Windows is via the Windows Subsystem for Linux (WSL). By default, this installs Ubuntu Linux, which is based on Debian, so after installing WSL, follow the instructions for Debian above.
Install from source
sqv
is written in Rust. To compile it, you will need a recent Rust toolchain. Your Linux distribution very likely already has the relevant packages. If you don't want to use it or can't (because it's too old, or you are not using Linux), there is also the option to get the toolchain from the Rust project directly. See the rust tools documentation for further information.
Install the dependencies (Debian 12 "bookworm" / Ubuntu 24.04):
$ sudo apt install clang nettle-dev pkg-config libssl-dev capnproto libsqlite3-dev
Install the dependencies (macOS):
If you are using homebrew, install the dependencies with:
% brew install pkgconfig nettle openssl capnp
Install using cargo
After installing the dependencies, sqv
can be installed using cargo. Cargo will take care of downloading the source code and its dependencies (only the crates). Cargo will then compile and install the resulting binary.
$ cargo install --locked sequoia-sqv
Usage
sqv
is stateless—it doesn't use the certificate store or the key store. Everything needed for verification has to be passed via the command line. sqv
does, however, use the configured cryptographic policy. Similar to sq
, sqv
supports the --policy-as-of
parameter to change the reference time of the policy compliance test.
$ sqv --signature-file Qubes-R4.2.3-x86_64.iso.asc Qubes-R4.2.3-x86_64.iso --keyring qubes-release-4.2-signing-key.asc
9C884DF3F81064A569A4A9FAE022E58F8E34D89F
This example verifies a file called Qubes-R4.2.3-x86_64.iso
, using the detached signature in Qubes-R4.2.3-x86_64.iso.asc
, the certificate for the signature is in qubes-release-4.2-signing-key.asc
.
sqv
returns exit code 0
on success; anything else signifies a failure. Its output contains the fingerprints of the certificates for which verification succeeded.
Further conditions can be added to the signature check:
--not-after
checks that the signature was made before the given date.--not-before
checks that the signature was made after the given date.
The keyring passed via --keyring
can contain more than one certificate, so you can use a collection of trusted certificates. If you want to ensure that several signatures are valid at once (for example, if you need to corroborate a file's integrity from several independent sources), use --signatures
together with the number of necessary valid signatures required to pass the test.
$ sqv --keyring my_trusted_certs.asc --signatures 3 --signature-file ...
This example only succeeds if three valid signatures made with certificates from my_trusted_certs.asc
are found.
Besides detached signatures, sqv
can also verify inline signed and cleartext-signed messages.
$ sqv --keyring my_trusted_certs.asc --message $FILE --output $OUTPUT_FILE
or
$ sqv --keyring my_trusted_certs.asc --cleartext $FILE --output $OUTPUT_FILE
When using --message
or --cleartext
you must specify an output file, which will contain the original message without a signature on success.
If sqv
reports Error: Malformed Message: Malformed OpenPGP message
, then you might have passed an encrypted message to sqv
. The usual procedure when creating signed and encrypted messages is to first sign and then encrypt both the message and the signature. As sqv
doesn't try to decrypt a message, the signature is thus invisible to sqv
.
Background
This chapter discusses some of the underlying concepts of OpenPGP and public key cryptography in general.
It may be useful to read this chapter to fine-tune your mental model of what is happening under the hood of OpenPGP.
Characteristics of a secure system
A communication system has to provide certain guarantees to be considered secure. While there are certainly many characteristics that are desirable in a communication system, the requirements for a secure system can be summarized as confidentiality, integrity, and authenticity. All three of these characteristics have to hold; if one of them is weak or even missing, the whole system cannot be considered secure.
Security has to be distinguished from clandestinity. A secure system is not required to hide the fact that communication is taking place or to obfuscate its participants. That would constitute a different requirement on a communication system.
These requirements also do not include mechanisms to contain the consequences of a breach. Perfect forward secrecy, future secrecy, plausible deniability are all methods to limit the effects of a compromise, how much an attacker gains by knowing the encryption secret (the key) at some point in time. For a system to be called secure, it's not necessary to implement these measures.
Although this definition of security might seem very limited, and real world applications will certainly implement many of the other characteristics mentioned, when reasoning about a system, it helps to have clear-cut definitions, and doesn't include neighboring concerns.
Confidentiality
Confidentiality means that only the intended recipients of a message are able to read it. Everybody else should only see an indecipherable mass of seemingly random characters. This is where encryption algorithms are used. For an in-depth discussion of encryption, see the background text on public key cryptography. Encryption traditionally cares only about the content of a message. Sender, receiver, time of sending, size of the message – all the metainformation about the communication itself is not addressed by encryption.
This ignores the fact that the mere fact of communication occurring between two users might allow an educated guess of the content.
Integrity
Integrity ensures that messages reach their intended receivers unmodified. Any modification done to a message while in transit should be immediately obvious.
This looks like it's already achieved by encryption, because a successful modification requires the knowledge of the encryption key. Without that key, any modification would likely corrupt the message. If the message was text, the damage might be obvious, but if it was binary, the damage might even represent valid content. Another attack would be to simply append an old, intercepted message to a new one. Depending on how the encryption works in detail, this might go unnoticed since the encryption key would be the right one.
To decouple the guarantee of integrity from the applied encryption – the guarantee of confidentiality – actual systems use checksums of the content to detect modifications.
Authenticity
Authenticity is about the certainty that a message actually comes from its alleged sender. Impersonation should be impossible or obvious if it's attempted. More generally, authenticity protects against man-in-the-middle attacks. Or put the other way, man-in-the-middle attacks are basically attempts to subvert the methods of guaranteeing authenticity within a communication system.
This is usually implemented by adding a piece of data to the communication which only the alleged sender can create, but everyone else can validate.
Open source and reproducibility
While open source and reproducibility are not part of the requirements for a secure system, for all practical purposes, it's better to be able to validate the promises of a software vendor than be forced to blindly believe them.
Being open source doesn't, of course, imply that a validation (aka code audit) has taken place, that the software upholds its promises, and is free of bugs. In contrast to closed-source software (which shares the same problems), there is at least the possibility.
Keys, subkeys, and certificates
A key in the context of OpenPGP is actually a dataset containing User IDs, key material (both public and secret), metadata like the expiration date, preferences, certifications, signatures, and subkeys – to name a few. This dataset is not fixed; parts of it will (likely) be modified over time as new certifications might be added, User IDs might change, and the expiration might be extended.
A key is needed for operations that only the keyholder should be able to do, like decrypting or signing.
A certificate is the public representation of a key. It contains the same objects as the key except for the secret key material. It is used for operations everyone should be able to do, like encryption or signature verification.
The objects within a key (and a certificate) can be signed using the secret key material of that key. This signifies that the keyholder either added the object themselves, or is content with it (like third-party certifications). Keys, and specifically certificates, can also hold unsigned objects like additional User IDs or unapproved certifications.
Keys (and certificates) can and usually have subkeys. These are keys that are bound to another key (called the primary key) by a subkey binding signature.
Subkeys were added to OpenPGP to solve a few shortcomings:
- Subkeys introduce new key material to a key to prevent the use of the same key material for potentially conflicting operations. Both decrypting and signing are technically an encryption using the private key material. Signing an encrypted message might therefore inadvertently decrypt the message. OpenPGP prevents this as signatures are never created using the original data, but a checksum of it. Still, this is uncomfortably close and open to implementation mistakes. Using different keypairs for encryption/decryption and signing/verifying eliminates this problem.
- Subkeys are grouped under a primary key, which represents the bundle. Hence, designating this bundle is done via the fingerprint of the primary key. This allows rotation or revocation of subkeys without affecting existing certifications, as the primary key doesn't change. The subkey binding signature is used to extend the certification of the primary key to its subkeys.
- Subkeys have flags which indicate their usage (signing, encryption, etc.). OpenPGP allows having more than one subkey with the same flags. Having two subkeys flagged for encryption in a certificate will encrypt a message for both subkeys.
- By careful arrangement, the primary key of this bundle can be kept offline, as its secret key material is then only needed for updating metadata (User IDs, expiration) and subkey bindings. Day-to-day usage can then be completely delegated to subkeys.
Subkeys do not have User IDs, as these are associated with the primary key, but they can have an expiration date.
There is no limit to the number of subkeys a key can have. When generating a new key, sq
creates three subkeys with it. One for encryption, one for signing, and one for authentication. The primary key is used to certify the authenticity of the subkeys. As subkeys have markings (flags) for their use case, an application (like sq
) can choose the right subkey for each operation. In some ways, a key acts like a little CA, with the primary key acting as a certificate authority, certifying the subkeys.
You can peek into the internals of a key using:
$ sq key generate --own-key --userid alice --output - --rev-cert /dev/null --overwrite | sq packet dump
This example generates a key with a User ID of "alice", it specifies --output
, so that the generated key is not added to the key store. Using --output -
redirects the new key to STDOUT
, and piping the output into sq packet dump
displays its expanded contents. --rev-cert
and --overwrite
are needed as a side effect of --output
- in this example the result is discarded. --own-key
authorizes the new key to introduce certificates - you can try --shared-key
instead to see an unauthorized key.
Public key cryptography
Encryption
Every now and then you might want to have a private word with someone. Private in the sense that you can control who is following the conversation, excluding unwanted listeners, but not necessarily hiding that the conversation is taking place. Technically speaking, this setting forms a secure channel. If you don't have control or can't even estimate the number of people who might be privy to a conversation, this conversation uses an insecure channel.
People learn early in life how to establish a secure channel, for example, by whispering or changing the location. Both methods, however, are difficult to achieve on the internet. Apart from edge cases, the internet consists only of insecure channels - security was not part of the design criteria when the internet was first conceptualized. Consequently, a secure channel over the internet has to be built on top of insecure channels. This can be done by using a "language", which can only be understood by the intended participants. This – in its broadest sense – is encryption.
Technically, it's not exactly a new language which is used, but a transformation that renders a message unreadable (encryption), and a receiver can reverse the process to recover the original message (decryption). Naïve schemes use methods like changing the alphabet and reordering the sequence of characters. Stronger schemes use an algorithm and a secret key to achieve the same result. The algorithm itself is not secret. Certainly, keeping the algorithm secret makes an attack harder, but comes with drawbacks. Current algorithms are public and thoroughly scrutinized; their publication does not help an attacker.
Probably the biggest advantage of a publicly available algorithm is that it can be reused for different secure channels, as its protective character lies completely within its secret key. Choosing a new secret gives you a new secure channel without sacrificing the protection of either the new channel or ones that already exist.
Symmetric encryption
As its name suggests, symmetric encryption uses the same secret key for both encryption and decryption. Decryption is simply done by repeating the steps of the encryption in reverse order, undoing what encryption did to the original text. The constraints for designing an algorithm for symmetric encryption are usually not dependent on the capabilities of CPUs, which means that contemporary algorithms are optimized for performance—symmetric encryption is fast.
Symmetric encryption is, however, not well suited for establishing a secure channel. This is not due to weak algorithms, but the simple fact that sender and receiver, and nobody else, must have the same secret key. This implies that for the exchange of that key, a secure channel is needed—a chicken-and-egg problem.
Symmetric encryption is used where either a secret does not need to be shared (hard disk encryption, for instance), or where other effective ways exist for the exchange.
Asymmetric encryption
Asymmetric encryption solves the key distribution problem, but its design criteria are radically different from those of symmetric encryption.
Asymmetric encryption uses mathematical functions, where the inverse of that function is infeasible to calculate in any meaningful time frame. As an example: it's easy to multiply two prime numbers to get a single result, but it's hard (effectively impossible or at least impractical) to reverse this operation, finding those same prime factors if you only have the resulting number. Thus, decryption cannot be performed as it would be with symmetric encryption - reversing the encryption steps would be too costly, even if you know the key.
Instead, asymmetric encryption uses functions which allow for a trapdoor, a way to calculate the inverse of that function by different means. This is done by not reversing encryption, but by encrypting a second time with a different key. There must be a mathematical relationship between the key used for encryption and the one used for decryption, so keys for asymmetric encryption are constructed in pairs. These keys are interchangeable – whichever key you used for encryption, you need the other one of the pair for decryption.
For a complete cycle — cleartext -> encrypted text -> cleartext
— both keys are needed. Knowing only one of them is not enough, so it doesn't compromise the security of this mechanism if one of them is exposed. This is probably the most difficult part of asymmetric encryption to understand and rely on.
Given that one key of the pair is publicly known, it can be used for the cleartext -> encrypted text
step. Anyone in possession of the counterpart key can complete the cycle: encrypted text -> cleartext
. So you'd better keep that key private!
In OpenPGP, the pair (both keys) is included in the "key", whereas the public part of the pair goes into the "certificate". The certificate can then be disseminated.
In practical terms, asymmetric encryption is very slow, so it's not suitable for high-throughput, low-latency protocols that are commonly needed for web protocols, but it's very well suited to encrypting small things like symmetric keys.
Hybrid encryption
Just like TLS and SSH, OpenPGP uses symmetric and asymmetric encryption together, combining the speed of symmetric encryption with the key distribution capabilities of asymmetric encryption.
A message is first symmetrically encrypted, using a random, unique key, which then is asymmetrically encrypted using each receiver's public key, and appended to the message. A receiver has to identify the right key, asymmetrically decrypt it using their own private key, and use it to symmetrically decrypt the message. Besides speed, this scheme has the advantage that each additional receiver only adds a little to the resulting size of the encrypted message.
Signatures
Signatures are used to signify authenticity. The alleged creator of a message adds something to that message which only they can create, but everyone else can verify. This is done by encrypting the checksum of the message with the private part of the key pair, effectively exactly the same process as encryption, but with the keys swapped. Since the public part of the pair is – well – public, anyone can decrypt that checksum and see if it is indeed the checksum of the message. Assuming the private key really is private, this is a robust proof of authenticity.
Signatures also provide an assurance that the message has not been altered since the creation of the signature. Otherwise, the message would have a checksum that differs from the encrypted one in the signature. To modify the message and adjust the checksum within the signature, the private key would be needed to re-sign the message.
Beyond messages, signatures are also used to allow the verification of the authenticity and integrity of downloaded files. The mechanism is the same.
While it does guarantee authenticity and integrity, a signature does not guarantee that a signed file doesn't contain malicious content or is free of bugs or is even fit for a specific purpose.
As signatures can be created by anyone, it's vital to verify that it was created by the right private key, and not one belonging to an impostor. How to do this is explained in the chapter on Authenticating certificates.
User IDs
OpenPGP knows User IDs; they are the human-readable content of certificates (and keys) and are meant as representations of the keyholder, for instance, in the form of an email address. Technically, User IDs are just sequences of printable characters. Their content depends on the intended use case. To retrieve a certificate for an email address, you can search for certificates that contain a User ID matching the address.
<alice@example.com>
User IDs are not limited to email addresses; they can also contain a name:
Alice Example
or a combination of both:
Alice Example <alice@example.com>
As email addresses have to be in angle brackets, they are identifiable. Other use cases may have different conventions.
Instead of specifying an email User ID as
--userid "<alice@example.com>"
, you can use--email "alice@example.com"
;sq
takes care of the angle brackets.
In public key cryptography, User IDs are not needed. Encryption, decryption, signing, and verification do not use the information supplied in a User ID. User IDs are meant as markers to allow for a key retrieval or human identification without having to resort to fingerprints. As such, they are included in keys and certificates—their integrity is ensured by signatures also contained in the key or certificate. There is no limit to the number of User IDs a key or certificate can contain.
User IDs can be added to a key at any time; they don't have to be all present at the time of a key's generation.
User IDs are claims—anyone can create a key containing <alice@example.com>
as a User ID and publish its certificate. How to validate the authenticity—the veracity and trustworthiness of the claim—is described in Authenticating certificates.
Removing a User ID from a key is complex. While it's technically possible to strip a User ID from a key, once the corresponding certificate has been distributed, it's impossible to recall them. The solution is to revoke the User ID, and the record of its revocation will stay in the key along with a revocation certificate. The revocation will also be included in certificates generated from that key, giving software processing these certificates a hint as to how to handle that User ID (such as ignoring it).
Web of Trust
There are relationships between keys, certificates, signatures, etc. which can be computed. Given a file, a detached signature and a certificate, one can calculate whether the signature was made over the file using the key corresponding to the certificate. No other (outside) information, and no further context is needed to validate this relationship.
Cryptographic keys and certificates are rarely (if at all) used without context. A context which comes to mind would be email encryption. This context introduces a new set of entities, namely email addresses. There is no way to derive the key from the email address alone – there is no computable relationship. For that, a mapping is needed, binding an email address to a key (and vice versa).
By definition, this mapping is arbitrary and not computable – otherwise it wouldn't be needed. Technically, any email address can be bound to any key. In that sense, a binding is no more than a claim.
This introduces the problem of authenticity: is the claimed binding correct? Is the holder of the key also the owner of that email address?
As this problem cannot be solved computationally, it has to be addressed by other means: human intervention, also known as "work."
Authenticity can only be established by humans.
This is not a good requirement, since humans tend to avoid work. This leads to keys being used without proper authentication, signatures not being checked, and so on.
If human work cannot be eliminated from that process, it makes sense to use the result of that work in the most efficient way.
Probably the first optimization of this process which comes to mind is the avoidance of repeated work. The results of work (of authenticating a binding) must be captured in a persistent form that can be stored and reused.
OpenPGP realizes this by creating a signature, where the person doing the work signs the authenticated binding. Next time a key lookup is done, the signature will be recognized, and the binding will be taken as authentic, a process that happens without human intervention.
The signature as a result of work can be seen as a product. This product can be passed on to others, so that they too can benefit from the work it embodies.
This approach is not limited to OpenPGP. x509 operates along the same lines, where some authority attests authenticity and spreads this attestation.
This gives rise to a new problem: product quality. The signature gives no clue as to how diligently the authentication was carried out, if at all.
There is no good solution to this problem. Unless repeating the authentication and comparing the result, one can only rely on the reputation of the individual (or institution) which made the attestation.
OpenPGP supports this reputation model by formalizing a way to attest to good reputation - good work. This is done by assigning a trust depth when certifying the authenticity of a certificate / User ID binding. A trust depth of one, for instance, means that the attestation of authenticity not only covers the actual binding, but also attestations made by the certificate of that binding—the attestation spreads out by one "hop". Higher levels of trust depth simply increase the number of hops an attestation covers.
Accompanying the spread of a reputation, it's also possible to quantify it. Instead of attesting to only a good reputation (or not), OpenPGP allows assigning a value between 0 and 120, expressing the amount of trust in an attestation made by a certain keyholder. Such an attestation would then be viewed as "partially authenticated". These values can accumulate; if several independent sources express only limited trust in a reputation, the overall amount of trust might qualify as "good enough" (for a given use case).
So far, a reputation can be characterized by its spread and its (assumed) quality — the diligence put into the product. This can be further tuned by limiting attestations to a certain range of User IDs. That way only the attestations matching a supplied regular expression would be considered. A typical use case would be a certification authority for an institution, where only the attestations for User IDs from that institution (having a specific email domain) would benefit from the assigned spread and trust amount. This doesn't stop a CA from attesting to User IDs from other domains, but those attestations would be ignored when calculating the overall degree of trust.
Attestations are bundled with the corresponding certificates and can be circulated the same way the original certificate circulated. The way a certificate circulates might give hints to its authenticity. This is usually done by using a communication channel which involves control over a certain resource linked to the User ID part of a binding. Using DANE, for instance, implies control over the DNS zone file of the email domain. WKD implies control over a webserver answering on a domain linked to the mail domain. Other proofs of control might also give clues to the authenticity of a binding.
So far, this situation does not yet form a web of trust — it's more a soup of partially entangled certificates. In some sense, this soup is the result of the collective work of OpenPGP users; it's a kind of commons.
This provides a glimpse of how others judge the authenticity of bindings, which certificates belong to a person of good reputation, and so on. But these are judgments made by other people. To benefit from the work contained in this soup, one has to start using the products. This is done by attesting authenticity and trust depth to certificates from this soup — obviously not arbitrarily, but after checking. These certificates become personal trust anchors (or introducers) and are the starting point of a web of trust.
A web of trust is the result of personal choices, and thus differs between different users. This is in stark contrast to x509, where trust anchors are defined by institutions — web browser vendors, for instance.
Certificate Authorities
Imagine an NGO which wants to attest that the certificates of its staff members are authentic, that they indeed work for this NGO, and that the User IDs of these certificates are the right ones, are actual representations of staff members. In other words, the NGO wants to establish an authority which can be queried about the validity of a certificate.
While there are many ways to do so, these attestations should be processable by an algorithm—human involvement should not be necessary. Additionally, the authority shouldn't be localized, to avoid a single point of failure. A server on the internet which can vouch for uploaded certificates is therefore not a solution for this recommendation.
A solution would be to create an additional key whose sole purpose is to certify the staff members' certificates by adding its signature to them. An interested user could then search for this signature in a certificate to verify its validity.
However, given that anyone can create a key with the same User ID as the special signing key above and then sign certificates with it, this moves the problem rather than solving it. Instead of authenticating a staff member's certificate, you have to verify the authenticity of the signing key. This, however, introduces a beneficial indirection: members of staff can leave, new ones can enter, but as the signing key stays the same, no further authentications are necessary for an outside user.
The signing key plays the role of a Certificate Authority (CA).
Viewed from the outside, such a setting of a CA key and signed certificates is just a bundle of entangled certificates. This bundle becomes useful when the CA key (or more precisely its certificate) is used as a trust introducer.
Everyone can create a CA; it's not limited to established institutions. That said, the term CA usually denotes some degree of institutionalization.
The certificate of a CA can itself be certified by other keys, either by other CAs, or by individuals.
And that's it: a Certificate Authority is just a key like any other, but used in a specific way.
Shadow CAs
The certificate of a CA key has to be publicly available to serve its role.
The principle of delegation, the reliance on the authenticity of a binding to a designated key, can be used locally too. This is what Sequoia PGP (and therefore sq
) does.
When first invoked, sq
creates a Local Trust Root
, that is a key located in the local cert store which certifies other certificates.
A certificate — or more precisely its User ID — is considered authenticated if there is a path from the Local Trust Root
to this User ID of the certificate with a trust amount of 100% (or 120 out of 120).
sq
further creates more (intermediate) CAs on demand. Their role is to provide a record of a certificate's provenance, and an amount of trust derived from that provenance.
For example, if you fetch the certificate of alice@example.com
from the keyserver keys.openpgp.org
, sq
will create two internal CAs: One named Public Directories
and one named Downloaded from keys.openpgp.org
. It will then create a trust path Local Trust Root -> Public Directories -> Downloaded from keys.openpgp.org -> alice@example.com
.
Along that path a trust amount is calculated: the internal CA Public Directories
for instance is certified with a trust amount of 40/120. This amount creates an upper limit for the whole path; other CAs in that path can further reduce that amount, but cannot increase it. Downloaded from keys.openpgp.org
is certified by Public Directories
with a trust amount of 1/120. The complete path from Local Trust Root
to alice@example.com
has therefore a combined trust amount of 1/120.
To increase this value, you could change the certifications of the internal CAs by creating new ones or (the recommended way) by creating a new path between Local Trust Root
and alice@example.com
by using sq pki link add
– linking alice@example.com
and Local Trust Root
directly.
Besides calculating a trust amount, the path also shows where a certificate originates from and when it was included in the certificate store, as certifications have a creation date.
These internal CAs are called Shadow CAs.
Using subkeys
Subkeys are independent key pairs associated with the primary key. There are differences between Sequoia PGP and other OpenPGP implementations in how keys are structured by default when they are generated. sq
generates a primary key for certification and separate subkeys for authentication, signing, and encryption, and it's possible to generate more subkeys if needed. For more technical information, see chapter Keys and certificates.
The primary key should be the most protected one and therefore should be stored offline where no one can have access to it.
For this reason, the best practice is not to publish the primary certificate. Instead, it makes sense to only publish certificates created from subkeys, to avoid other people using the certificate of the primary key. Again, this is not a security consideration, but if people use the primary certificate, it will become difficult to keep the primary key offline.
In this scenario, subkeys are the ones to use for frequent work; it's enough to publish the certificates of the respective subkeys. It makes sense to rotate them more often by revoking them faster and letting them expire earlier than the primary key. The primary key can be used to revoke a subkey if necessary.
To prevent losing data if an encryption subkey is lost, it's vital to keep a backup of it in a safe place.
In case of a signing key, it is not as important or even not recommended to have a backup. This is simply to minimize the risk of key theft by avoiding storing it in two different places. If the key is lost, functionality will not be limited, signatures made previously with that key will still remain valid, and for future signatures you can generate a new subkey using the primary key.
As an aside, in some countries, there may be even more of a need to separate keys for encryption and signing in terms of key disclosure laws. For example, in the UK and in France it can be construed as a crime not to decrypt and/or provide a decryption key to law enforcement agencies if required, even without a court order. In these cases, the rest of the key material should remain safe; using separate subkeys for each function maximizes that protection.
Key expiration
OpenPGP keys may include an expiration date. This date is included when a certificate of this key is generated. The expiration date within a certificate gives the software processing the cert a hint on how to handle it. Email clients, for instance, might refuse to use an expired certificate to encrypt a message. Signature-verifying software might display a warning, but continue to function.
The expiration date is optional. It's possible to create (or update) keys that never expire. The expiration date does not imply that the certificate becomes "insecure" after that date. It's just a method to enforce an update.
There are different reasons for, and effects of, setting an expiration date for certificates. Whether setting an expiry date represents a security improvement depends on the threat models concerned. If a certificate has an expiry date, you can always extend its validity so long as you have access to the key.
If you lose your key without there being an attack and still have your revocation file stored somewhere safe, you can revoke the key and certificate without having to wait for it to expire. If you don't have a revocation file, you can at least let it expire by itself.
If someone steals your key, they would be able to use it. Only a revocation file would help to avert the damage here as the attacker could also manipulate the expiration date on certificates that they issue.
It can make sense to set different expiration dates for subkeys with different purposes. If only a specific subkey is lost or stolen, it can be revoked independently of the primary key, which will remain valid.
Another reason to set an expiration dates which is often mentioned is that it forces people to refresh their certificate stores or pubrings regularly. In summary, one can say that storing the revocation file somewhere safe that's separate from the key is more important than setting an expiration date. Nevertheless, setting an expiration date could limit possible damage.
Fast lanes
Here you will find various short how-to guides on the most important functions of sq
, along with links to the corresponding detailed main text.
How to generate a key
To generate a key with sq
:
$ sq key generate --own-key --name "Alice Example" --email alice@example.com
To generate a v6 key, use the --profile
option:
$ sq key generate --profile rfc9580 --own-key --name "Alice Example" --email alice@example.com
Read more about generating keys.
Adding subkeys
How to generate an additional subkey
To generate and add a subkey to an existing certificate use sq subkey add
:
$ sq key subkey add --cert $FINGERPRINT --can-sign --without-password
This example limits the abilities of the subkey to signing, and opts out of password protection.
How to bind an existing subkey to another certificate
$ sq key subkey bind --key $KEY_TO_BE_BOUND_FINGERPRINT --cert $FINGERPRINT
Read more about adding subkeys.
How to rotate a key
Rotate a key by copying existing capabilities and issued certifications to a new key using sq key rotate
:
$ sq key rotate --cert $FINGERPRINT
Replaying certifications
To only recreate the certifications made by the old key without generating a new key, use sq pki vouch replay
:
$ sq pki vouch replay --source $SOURCE_FPR --target $TARGET_FPR
Read more about rotating keys.
How to import and export a key
Import a key into the keystore
$ sq key import $KEYFILE
Export a key from the keystore into a file
$ sq key export --cert $FINGERPRINT > $KEYFILE
Read more about importing and exporting keys.
How to manage keyrings
Create a keyring by exporting certificates from the cert store
$ sq cert export --cert $FPR_A --cert $FPR_B --cert $FPR_C ... > $FILE
List the certificates in a keyring
$ sq keyring list example_keyring.pgp
0. 159F7D1D498EF2BFC489D13FB5A86AAD248300C8 Bob
1. 722E3D4101B82C0AE80A3EF07A512226FF0D14E7 Alice
2. 8B19816EE960EEBBD09A1256CF755B2FB77C6171 Dave
[...]
Extract certificates from a keyring
$ sq keyring filter --experimental --userid Alice example_keyring.pgp
-----BEGIN PGP PUBLIC KEY BLOCK-----
xjMEZ7Wz3hYJKwYBBAHaRw8BAQdAuhfXxD0ewpB0BrkDpMH5YhmobPkp/eGpJRxu
[...]
=adZu
-----END PGP PUBLIC KEY BLOCK-----
Split up a keyring into its certificates
$ sq keyring split $FILE
Merge a split keyring
$ sq keyring merge *
-----BEGIN PGP PUBLIC KEY BLOCK-----
xjMEZ7Wz3hYJKwYBBAHaRw8BAQdAuhfXxD0ewpB0BrkDpMH5YhmobPkp/eGpJRxu
[...]
=MuLv
-----END PGP PUBLIC KEY BLOCK-----
Read more about how to manage keyrings.
How to encrypt a file
To encrypt a file, use sq encrypt
, providing the name of the file to encrypt and an email address which is binded to the key you control:
$ sq encrypt --for-email alice@example.com message.txt
To encrypt with another one's certificate, you need to specify an email address which is binded to the key which is signing the file:
$ sq encrypt --for-email bob@example.com --signer-email=alice@example.com message.txt
Read more about how to encrypt and decrypt files.
How to decrypt a file
To decrypt a file, use sq decrypt
, providing the name of the encrypted file and implicitly using a key in the keystore:
$ sq decrypt message.pgp
Read more about how to encrypt and decrypt files.
How to sign a file or message
To sign a file with an inlined signature:
$ sq sign --signer $FINGERPRINT $FILE
To separate the signature from the file, make a detached signature as follows:
$ sq sign --signer $FINGERPRINT --signature-file $FILE
Read more about signing files.
How to verify a file or a detached signature
Inlined and cleartext signatures
Inlined and cleartext signatures can be verified with:
$ sq verify --message $FILE
or
$ sq verify --cleartext $FILE
If the certificate is available, but the signature could not be authenticated, you can either pass the fingerprint of a certificate (aussuming the authenticity of that cert):
$ sq verify --signer $FINGERPRINT $FILE
or authenticate (at least) one of the User IDs of the certificate by adding a link:
$ sq pki link add --cert=$FINGERPRINT --userid=$USER_ID
Then verify again.
Detached signatures
Detached signatures can be verified by explicitly passing the file containing the detached signature:
$ sq verify --signature-file $SIGNATURE_FILE $FILE
Read more about verifying signatures.
How to import and export a certificate
A certificate which is stored in a file can be imported into the certificate store as follows:
$ sq cert import $FILE
To export a certificate from the cert store:
$ sq cert export $QUERY
To export an invalid certificate (for example, one which uses an SHA1 signature), you can pass --policy-as-of
, using a date which pre-dates the deprecation date of said function:
$ sq cert export --cert-email alice@example.com --policy-as-of 20070101
To export all certificates, including the invalid ones, use this query:
$ sq cert export --all
Read more about importing and exporting certificates.
How to publish a certificate to servers
Publish to keyservers
To publish a certificate to the preconfigured list of keyservers, you can use the sq network keyserver publish
subcommand as follows:
$ sq network keyserver publish --cert $FINGERPRINT
Publish to WKD
To generate a file and the directory structure to a local location from where it can be uploaded to a webserver like WKD, you can use the 'sq network wkd generate' subcommand. In this example /tmp/foo is the directory everything will be generated into, the information 'sequoia-pgp.org' excludes User IDs from certificates with other domain data from the key store 'certs.cert':
$ sq network wkd generate /tmp/foo sequoia-pgp.org certs.cert
Publish to DANE
To publish a certificate by DNS / DANE, create a TXT record containing the certificate:
$ sq network dane generate sequoia-pgp.org certs.cert
Then add the TXT
record to your DNS.
Read more about keyservers.
How to retrieve a certificate
To retrieve a certificate from a keyserver, use the sq network search $PATTERN
subcommand:
$ sq network search alice@example.org
Read more about searching for a certificate on the internet.
How to revoke a certificate
To revoke a certificate, use sq key revoke
and specify a <REASON>
and a <MESSAGE>
:
$ sq key revoke --cert $FINGERPRINT --reason superseded --message 'there will be a new one'
Here is the equivalent to revoke a subkey:
$ sq key subkey revoke --cert $PRIMARY_KEY_FPR --key $SUBKEY_FPR --reason retired --message 'not used'
To revoke a User ID from a certificate, add the userid
subcommand:
$ sq key userid revoke --cert $FINGERPRINT --userid 'alice' --reason retired --message 'testing_purposes'
Read more about revocation.
How to certify a User ID for a certificate
To create an exportable certification for a binding between a User ID and a certificate, use sq pki vouch add
. In this example, Alice certifies that Bob controls $FPR_BOB and bob@example.com:
$ sq pki vouch add --certifier $FPR_ALICE --cert $FPR_BOB --email=bob@example.com
A certification to mark a certificate as a trusted introducer is done like this:
$ sq pki vouch authorize --certifier $ALICE --cert $FPR_BOB --domain=example.com --email bob@example.com
An updated certificate should then be sent to the certificate holder, who should approve and publish it:
$ sq inspect bob.cert --certifications
$ sq cert import bob.cert
$ sq key approvals update --all --cert $FPR_BOB
Read more about certifications.
How to mark a certificate as a trusted introducer
To mark a certificate as an exportable trust introducer (a certificate authority) for the domain example.com
with a preconfigured trust depth of 1, use sq pki vouch authorize
as follows:
$ sq pki vouch authorize --certifier $FPR_CERTIFIER --cert $FPR_TO_BE_CERTIFIED --domain=example.com
Read more about how to certify and authorize certificates.
How to approve a certification
To approve a certification which has been received by a third party, a keyholder can approve it by using the following command:
$ sq key approvals update --all --cert $FP_KEYHOLDER
Read more about how to certify and authorize certificates.
Recipes
This chapter contains a loose, incomplete, and hopefully growing collection of guides on how to use sq
and related tools to solve everyday problems.
Using OpenPGP keys with ssh
ssh
, the command line client for Secure Shell, uses public key cryptography as one method of authentication. For that, an ssh key pair has to be generated and the public part of that pair has to be installed on the server as an authorized key. ssh
can also use OpenPGP keys for this purpose.
On the server side, nothing changes; the public part of the key has to be configured as an authorized key.
On the client side:
ssh
uses anssh-agent
to manage its secrets.- There can be more than one active
ssh-agent
.ssh
can be told to use a specific agent through theSSH_AUTH_SOCK
environment variable. - An agent which can use OpenPGP keys is
gpg-agent
.gpg-agent
has to be configured to:- start the
ssh-agent
subsystem - use a specific set of OpenPGP keys in the
ssh-agent
context
- start the
gpg-agent
needs agnupg
-compatible backend to access the actual keys.chameleon
provides this integration forsq
's keystore.
Preparation
Prepare the key
To be used for ssh authentication, a key must have a subkey marked for authentication. Use sq inspect
to identify the subkey:
$ sq inspect --cert 8140AA1A97177805FD263466718AC099BAFDC830
OpenPGP Certificate.
Fingerprint: 8140AA1A97177805FD263466718AC099BAFDC830
Public-key algo: EdDSA
Public-key size: 256 bits
Creation time: 2025-03-19 13:29:31 UTC
Expiration time: 2028-03-19 06:55:52 UTC (creation time + 2years 11months 30days 9h 16m 45s)
Key flags: certification
Subkey: EA495531497101582BFCB0F947E2D1A287003BD9
Public-key algo: EdDSA
Public-key size: 256 bits
Creation time: 2025-03-19 13:29:31 UTC
Expiration time: 2028-03-19 06:55:52 UTC (creation time + 2years 11months 30days 9h 16m 45s)
Key flags: signing
Subkey: A07809FFE01CD7091B9CB6D40B3E57A87C4AFF46
Public-key algo: EdDSA
Public-key size: 256 bits
Creation time: 2025-03-19 13:29:31 UTC
Expiration time: 2028-03-19 06:55:52 UTC (creation time + 2years 11months 30days 9h 16m 45s)
Key flags: authentication
Subkey: DEE6688DA0336013F5E96062812861395DA7428C
Public-key algo: ECDH
Public-key size: 256 bits
Creation time: 2025-03-19 13:29:31 UTC
Expiration time: 2028-03-19 06:55:52 UTC (creation time + 2years 11months 30days 9h 16m 45s)
Key flags: transport encryption, data-at-rest encryption
UserID: <alice@example.com>
Certifications: 2, use --certifications to list
In this example the second subkey listed (Subkey: A07809FFE01CD7091B9CB6D40B3E57A87C4AFF46
) has a key flag authentication
– that is the right subkey to use.
There is no need for a key to have an authenticating subkey as OpenPGP doesn't use these types of keys, but if your key is missing such a subkey, you can add one with:
$ sq key subkey add --cert $FINGERPRINT --can-authenticate
where $FINGERPRINT
is the fingerprint of the primary key you want to add the subkey to.
Likewise, you can revoke old authenticating subkeys. You can even have several such subkeys in your key - see chapter Adding subkeys.
Prepare the gpg-agent
Ensure that gpg-agent
activates its ssh-agent
subsystem. The configuration gpg-agent.conf
can be found in ~/.gnupg
and has to contain the line:
enable-ssh-support
When started with this option, gpg-agent
listens on a UNIX socket for commands related to its ssh-agent
functionality. The socket can be found using:
$ gpgconf -L
The output should contain the line:
agent-ssh-socket:/home/user/.gnupg/S.gpg-agent.ssh
(your output might look a little different – look for agent-ssh-socket
).
From the example above, /home/user/.gnupg/S.gpg-agent.ssh
is the important bit: this is the value that SSH_AUTH_SOCK
needs to have in order to use gpg-agent
as an ssh-agent
.
Register the key
gpg-agent
needs to know which keys to use when authenticating an ssh session. For that purpose, add the keygrip of that key to ~/.gnupg/sshcontrol
on a line of its own.
To get the keygrip, use chameleon
– this example assumes that you have installed the chameleon
as gpg-sq
.
$ gpg-sq -k --with-keygrip --with-subkey-fingerprints 8140AA1A97177805FD263466718AC099BAFDC830
pub ed25519 2025-03-19 [C] [expires: 2028-03-19]
8140AA1A97177805FD263466718AC099BAFDC830
Keygrip = C7EC53D3635F42437C3C73DE6B34F4577F3FF346
uid [ full ] <alice@example.com>
sub ed25519 2025-03-19 [S] [expires: 2028-03-19]
EA495531497101582BFCB0F947E2D1A287003BD9
Keygrip = 55044CA1ED6232F784CFFF13589801F7027B8175
sub ed25519 2025-03-19 [A] [expires: 2028-03-19]
A07809FFE01CD7091B9CB6D40B3E57A87C4AFF46
Keygrip = FE39C76C0F879E840C1EE9E8E655694C73516437
sub cv25519 2025-03-19 [E] [expires: 2028-03-19]
DEE6688DA0336013F5E96062812861395DA7428C
Keygrip = 27244A6CC8B77ABE34BD54557241744157DC30C9
Again, the second subkey listed is the authentication key, identified by the [A]
flag and the matching fingerprint. Add the keygrip FE39C76C0F879E840C1EE9E8E655694C73516437
to ~/.gnupg/sshcontrol
to use it.
Alternatively you can add keys to the ssh-agent
subsystem with:
$ gpg-connect-agent 'keyattr FE39C76C0F879E840C1EE9E8E655694C73516437 Use-for-ssh: true' /bye
and remove them with:
$ gpg-connect-agent 'keyattr FE39C76C0F879E840C1EE9E8E655694C73516437 Use-for-ssh: false' /bye
Use gpg-sq
The gpg-agent
uses a binary named gpg
to access stored keys. As the gpg
implementation provided by g10code doesn't know about Sequoia PGP's keystore, that binary has to be exchanged for chameleon
, which does. There are many ways to achieve this. One way is to create a symlink and add the directory where the symlink was created in as the first component in your PATH
environment variable:
$ mkdir $HOME/bin
$ ln -s /usr/bin/gpg-sq $HOME/bin/gpg
Adjust the paths accordingly.
Modify $PATH
(for instance in your .bashrc
):
export PATH="$HOME/bin:$PATH"
In a new shell (or after source .bashrc
), which gpg
should point to $HOME/bin/gpg
; check it with:
$ gpg --version
gpg (GnuPG-compatible Sequoia Chameleon) 2.2.40
Sequoia gpg Chameleon 0.13.1
sequoia-openpgp 2.0.0
Copyright (C) 2024 Sequoia PGP
...
Usage
First check whether ssh
can see the OpenPGP key:
$ SSH_AUTH_SOCK=/home/user/.gnupg/S.gpg-agent.ssh ssh-add -L
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIBvWSboin0MoLpdliKGTDEMx9zMplSbjfacaWxDeqqKq (none)
This gives you the public part of the ssh key pair, which has to be deposited as authenticated key in ~/.ssh/authenticated_keys
on the remote machine.
If you do not see your key in this list, chances are that gpg-agent
is still using g10code's gpg
.
Use
$ SSH_AUTH_SOCK=/home/user/.gnupg/S.gpg-agent.ssh ssh user@remote-host.tld
to connect to the remote server with your OpenPGP key. For convenience, you can set and export SSH_AUTH_SOCK
in your .bashrc
(or equivalent) so that you don't need to include it on the command line every time.
Troubleshooting
Here you will find a collection of suggested solutions for possible issues or unexpected outcomes that can occur when using sq
.
Troubleshooting authenticity issues
Can't find a specific certificate in my cert store
If you use sq cert list
—even with the option --cert-email=<EMAIL>
—you may find yourself in a situation where you don't get what your looking for. Some certificates may not be displayed because they are either unauthenticated or 'unusable'. 'Unusable' means either 'expired', 'revoked', or not conforming to the configured policy (like using deprecated hash algorithms). If so, there will be a hint at the end of the output like this:
$ sq cert list
[...]
226 bindings found.
Skipped 128 bindings, which are unusable.
Skipped 29 bindings, which could not be authenticated.
Pass `--gossip` to see the unauthenticated bindings.
Solutions
As proposed in the hint message, certificates with unauthenticated bindings can be found by using the same command with the--gossip
option:
$ sq cert list --gossip
[...]
226 bindings found.
Skipped 128 bindings, which are unusable.
Similarly, unusable bindings that are found but not shown can be displayed by using the option --unusable
in combination with --gossip
, because these are also considered as 'unauthenticated'.
$ sq cert list --gossip --unusable
A certificate is unauthenticated
If a binding between a User ID and a certificate is unauthenticated, sq
will refuse to use the certificate, for example, to encrypt a file with your own certificate or another one if you access it via the cert store (which is recommended).
Solution
To turn an unauthenticated certificate into an authenticated one, you could—after checking its correctness—mark a binding as authenticated by using the command sq pki link add
like this:
$ sq pki link add --cert=EB28F26E2739A4870ECC47726F0073F60FD0CBF0 --email=alice@example.org
Alternatively, you could use sq pki vouch add
to certify the binding with a trust root like so:
$ sq pki vouch add --certifier=$MY_FINGERPRINT --cert=$ALICES_FINGERPRINT --email=alice@example.org
If you are absolutely sure about the authenticity of a certificate's binding, it's also possible to use the certificate directly from a file instead from the cert store:
$ sq verify --signer-file alice.cert file.txt
My certificate has expired
If a certificate has expired, it is not usable. In this example, we try to encrypt a file with our own expired certificate, but the same would apply to encrypting a file with an expired third-party certificate:
$ sq encrypt --for=$FINGERPRINT --signer=$FINGERPRINT file.txt
Error: $FINGERPRINT was not considered because
it is: not alive
because: Found no suitable key on $FINGERPRINT
Now you can have a closer look at the certificate by using sq inspect
to see what is meant by 'not alive':
$ sq inspect --cert $FINGERPRINT
OpenPGP Certificate.
Fingerprint: xxx
Invalid: The primary key is not live: Expired on 2023-01-09T16:52:48Z
[...]
In this case, you could either use another—valid—certificate or make the old key valid again (e.g. by extending its expiry). This also works if the key has already expired.
$ sq key expire --expiration 1y --cert $FINGERPRINT
Hint: Imported updated cert into the cert store. To make the update effective, it has to be published so that others can find it, for example using:
$ sq network keyserver publish --cert=$FINGERPRINT
In case you tried to encrypt with someone else's expired certificate, you could search for a new valid certificate for that fingerprint:
$ sq network search $FINGERPRINT
A certificate has been revoked
If a certificate has been revoked, first have a look at the revocation certificate message, which will (in the best case) include a reason for the revocation along with a follow-up certificate including a fingerprint. If so, you could then search for this specific certificate:
$ sq network search $FINGERPRINT
If there was no announcement, you could still search for a new certificate using, for example, an email address, keeping in mind that the search results might include false or inauthentic results:
$ sq network search alice@example.org
Certificate uses deprecated hash algorithms like SHA1
If you have a certificate for which you have access to the secret key material but is considered 'unusable' because it uses an SHA1 signature, you could update it by using the command sq cert lint
, described further in linting certificates.
sq
is rejecting my SHA1-protected file
When verifying signatures, sq
might reject a signature and return an error, even if the correct certificate is available. One reason for this behavior concerns how the validity of a certificate is determined.
A certificate is considered invalid, if its internal binding signatures are only made with deprecated hash algorithms (like SHA1). This can be fixed with sq cert lint
, but only for certificates where the secret key material is available, as new signatures have to be made. sq cert lint
therefore cannot fix other people's certificates.
The best way to deal with a situation like this is to get the keyholder to update their certificate.
If it's not feasible to get the keyholder of the certificate in question to update their certificate, there is one way to persuade sq
to perform the verification anyway.
Which algorithms are considered deprecated is configured in the cryptographic policy. You can display the configuration with:
$ sq config get policy.hash_algorithms.sha1
policy.hash_algorithms.sha1.collision_resistance = 2013-02-01
policy.hash_algorithms.sha1.second_preimage_resistance = 2023-02-01
In the case of SHA1, there are different cut-off dates depending on how the algorithm is being used. These dates define when the use of said algorithm switches from valid to deprecated for each of these applications.
You can edit the dates in the configuration file to get sq
to accept SHA1 based signatures. The easier (and less intrusive) way is to use the --policy-as-of
switch:
$ sq verify --policy-as-of 20130131 --message msg.pgp
This temporarily modifies the reference time of the policy compliance check; choose a date which predates the cut-off date to work around the restriction.
Using
--policy-as-of
also affects other signature checks, like certifications. This has implications on the authenticitysq
assigns to a certificate.
This also applies to encryption, for example, if you need to encrypt a message for someone only holding an invalid certificate. In the same way as for verification, you can work around the restriction on SHA1 by using the --policy-as-of
option in combination with sq encrypt
:
$ sq --policy-as-of 20130201 encrypt --for=$FINGERPRINT_BOB --signer-email=alice@example.org msg.txt
Acknowledgements
Sequoia PGP is an implementation of the OpenPGP standard. As such, it is a successor to the venerable PGP, developed by Phil Zimmermann, reflecting his commitment to human rights and the peace movement. Our first thanks goes to Phil. PGP provided strong encryption for everyone, kick-starting an ecosystem of privacy protecting software many have made contributions to. Thanks go out to them as well.
Team
Of course, this book would not exist without the Sequoia team. That's why we would like to thank the main developers Neal H. Walfield and Justus Winter, as well as the current team members Devan, the project's devop, Fabio (decthorpe
), the Fedora Linux sq
package maintainer, Holger Levsen, Debian package maintainer for sq
together with dkg
and Alexander Kjäll.
In addition, there are many other people who contributed to the project. Some of them can be found here.
Documentation
This documentation is written by Franziska Schmidtke and Malte Meiboom. A special thanks goes to @Synchro (Marcus Bointon) from Common Caretakers for copy editing the book.
Funding
The Sovereign Tech Fund funded the project in 2024; without them, this documentation would probably not exist.