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)

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, like file(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:

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 under encrypt.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 under sign.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-passwordsq 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 are storage and universal, 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:

  1. It adds a signature to the certificate.
  2. This signature indicates that you consider the binding between the certificate and the User ID to be authentic.
  3. 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-depthTrusted certificates
0Bob
1Bob, Carol
2Bob, 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:

  1. SKS servers: These are "first generation" servers with unrestricted publishing.
  2. 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.
  3. 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.
  4. 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, and sks.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 like sq cert list $FPR does. The difference is that sq 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, like 2024-07-02 or a time interval like 3y (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:

  1. by wrapping the file in an OpenPGP message structure which includes the signature
  2. 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 than default, 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 scdaemons 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 an ssh-agent to manage its secrets.
  • There can be more than one active ssh-agent. ssh can be told to use a specific agent through the SSH_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
  • gpg-agent needs a gnupg-compatible backend to access the actual keys. chameleon provides this integration for sq'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 authenticity sq 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.