About this guide

This guide is about sq, the command line interface to Sequoia PGP. Here you will find an overview of the most common use cases and commands. There are also chapters about encryption in general as well as underlying concepts of Sequoia PGP in particular.

Very specific examples are presented only occasionally, if you can't find anything about a specific case here, please take a look at the respective help- or man page of the command or subcommand. The same applies if some details are out of date. This book is under constant revision and we try to keep up with the fast development of sq. 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
  • clear text messages: .txt
  • detached signatures: .sig
  • revocation certificates: .rev

Type settings

We present “typescripts” of command line use like this:

$ sq version
sq 1.3.0
using sequoia-openpgp 2.0.0
with cryptographic backend Nettle 3.8 (Cv448: true, OCB: false)

The first line is the shell command. The $ represents the shell prompt: the dollar sign is traditional for Unix, but it’s likely that your actual prompt is different. The rest of the line is the command you write to invoke a command. The rest of the typescript is the output of the command. A typescript may contain multiple commands, and are all identified by a leading dollar sign.

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 other applications can integrate to use OpenPGP functionality without reinventing the wheel. sq is a command line application using Sequoia PGP in such a way.

sq aims to expose a useful set of OpenPGP functionality for common tasks: encryption and decryption, signing and verifying, key and certificate management, certification and the like.

OpenPGP is a complex and elaborated standard, addressing issues of privacy and authenticity. As such it's not easy to digest, sq delivers an opinionated approach, making OpenPGP accessible and (hopefully) easy to use without sacrificing the goals of OpenPGP.

sq aims to safe by default, that doesn't mean that you cannot do stupid or dangerous things with sq, but the easy, default way of using sq is the safe way.

Sequoia PGP implements RFC9580 (published in 2024, also known as v6) since version 2.0.0, which is used by sq since version 1.3.0.

Design and specialties

sq (and Sequoia PGP for that matter) uses a specific terminology when addressing keys (public and private) - please make sure to 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.

sq doesn't use keyrings (by default), there are stores. While the key store operates similar 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 these kind of questions 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 first glimpse (and avoids typing long option names or subcommands).

sq is stateful. It operates on its stores and updates them according to the used operations. 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 rational of this renaming see chapter Background: Keys and certificates.

Stores instead of keyrings

Some popular OpenPGP softwares 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. 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 certificated by a trust introducer.

Trust introducer

A trust introducer is a person or institution which is considered as trustworthy (and competent), so that certifications done by the trust introducer are taken over. Trust introducers can be limited in scope,

  • so that only specific certifications are considered,
  • in outreach - can a trust introducer introduce other entities as introducer?
  • and trustworthiness, which effects 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 in 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. You have to authenticate the User IDs in it beforehand - declaring a "trust relationship".

These relationships are managed within the PKI. You can think of it as your local Web of Trust.

Structure of the interface

The functionality of sq is split into categories. Related functionality is located in the same category, sometimes even in subcategories.

The top level as orientation:

  • 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:

$ sq command <TAB><TAB>

shows 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

Please note that the hints pick up the results of the command and include them appropriately - in this case 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, allowing a = in between.

Installing sq

Linux

Install sq by using the package management of your linux distribution.

Debian

sq is part of the debian distribution since bookworm (Debian 12). Up to date versions are part of trixie (Debian 13) and following.

$ sudo apt install sq

Fedora

sq is part of Fedora 40 and following. To install sq, you can use yum:

$ sudo yum install sequoia-sq

MacOSX

There is a package for sq from homebrew:

$ brew install sequoia-sq

Windows

Start by getting a decent OS from https://www.debian.org.

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. Look here for more information: https://www.rust-lang.org/tools/install

Install the dependencies (bookworm, Debian 12/ 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, you can install the dependencies by

% 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). Then, cargo will compile and install the resulting binary.

$ cargo install --locked sequoia-sq

If you are using this method, you get the latest release of sq installed. If you are interested in HEAD (the current state in 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 result can by 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, simply pull the latest changes from the repository and repeat the build. Change into the project directory and:

$ git pull
$ cargo install --locked --path .

Confidentiality

This chapter deals with subjects related to confidentiality. It contains the generation of keys and maintaining certificates. It's also about using the key material for encrypting and decryption files as well as publication and revocation of certificates.

Confidentiality - Quick start

Here you find a quick walk through on the topics of key generation and typical usages of certificates.

Generating a key

To generate a key with sq, you can use the following command:

$ 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, you can use 'sq encrypt' as it is shown here:

$ sq encrypt --for-email alice@example.com message.txt

To decrypt a file using a key in the keystore, you can use 'sq decrypt' as follows:

$ sq decrypt message.pgp

Key generation

Generate a key

You can generate a key with different parameters and for different purposes. The default settings include an expiration of 3 years and elliptic curves as cryptographic algorithm. All of them can be modified.

Just a quick note at this point: 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.

Using 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 chapter 'File structure and backup' for more information ) - as long as you don't state otherwise.

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 to have separate User IDs for name and email address, which allows them to be certified or being revoked separately.

$ sq key generate --own-key --name alice --email alice@example.com 

This example generates a key with the two User IDs alice 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 chapter '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

To show it with an example:

$ sq key generate --own-key --name alice --email alice@example.com  --cipher-suite rsa4k
Opting out password protection

The default settings for generating a key include password protection for the key material, which is queried after entering the sq key generate command. In order to create a key without password protection, you have to specify the --without-password option.

$ sq key generate --own-key --name alice --email alice@example.com --without-password 
Choose expiration time

The default setting is an expiration time of 3 years. To define a different one, you can either choose any ISO 8601 string or a custom duration using N[ymwds] (both with a possible reference time using --time). You can also change the expiration date at a later point to prolong or shorten the period of time. See chapter '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 --email alice@example.com --expiration 2y

Note: All of the key generating options showed above (and more) can be combined as follows:

$ sq key generate --own-key --name alice --email alice@example.com --expiration 2y --cipher-suite rsa4k
Generating v6 keys

Version 6 (v6) keys are introduced with 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 already created key material, such as updating the expiration time or adding a User ID. Others like for example the cipher suite are set once and can't be updated later.

Adding a User ID

If you want to add a User ID like an additional name and/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

Note that if you accidentally specified and published a wrong 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, you can use the sq key expire subcommand:

$ sq key expire 2y --cert $FINGERPRINT

Note: This command does not change the associated subkeys.

If you instead or additionally want to change the expiration of a subkey, choose the sq key subkey expire subcommand as follows. 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 never --key $SUBKEY_FINGERPRINT --cert $FINGERPRINT

Rotating a certificate

If you want to replace a certificate with a new one and take over the capabilities, you can use sq key rotate. The new certificate will then contain the same self-signed User IDs and both, the old and the new one, will cross certify each other as unconstrained trust introducers. Also the certifications that where made by the old certificate will be replayed by the new cert.

The new certificate will be marked in the same way as the old one as 'own key' or 'shared key'. Per default, its expiration will be set to three years. If the old primary key was marked to have certain capabilities, this will not be recreated for the new primary key in any case. 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. There will be generated a revocation certificate which indicates the retirement of the old certificate in defaultly 182 days. The idea is to have a decent amount of time for the transition period. Of course this is also changeable, as shown in the example below.

Here you can see the result of applying the command on an existing certificate. First, we have a look into 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 supershort retire 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 have a look at the original certificate again, we see that is is revoked yet:

$ 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 operations 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 by:

$ 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 - which take the corresponding certificate from a file instead of the certificate store.

sq pki vouch replay checks if 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 (now) expired, revoked or invalid certificates are skipped.

Retracting a key

A key has a certain lifespan, outside of which it is no longer valid. This either can be the case when you don't want to extend the expired key because you no longer want to use it, or if you want to terminate the expiration time prematurely. In a more severe case, you have to end it because you lost the key or it has been compromised.

Since you not only use the key yourself, but in most cases also have 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 invalidity, to prevent future usage such as encrypting data with that certificate. Otherwise people would 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.

Note: The expiration of the certificate does not affect the secret key material, encrypted data can still be decrypted.

Hard and soft revocation

Revocation is done by creating a revocation certificate and publishing this 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 would add it to the origin certificate and 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 disseminate your updated certificate. If your certificate is not published on keyservers, but only passed by lets say email, you could either simply send the revocation certificate itself to somebody to import it (importing will automatically add it to the certificate) or you could send the already revoked certificate.

There are two different ways a revocation can be looked at. They concern the validity of signatures and certifications created by the revoked key. If the key got compromised, meaning an attacker got control over the secret key material, new signatures/certifications could be created by the attacker (and probably even dated back in time). This implies that signatures and certificates made by a compromised key cannot be trusted (even if they have been made before the compromise).

But keys do not have to be compromised to be revoked, keys can also be retired or superseded - the secret key material in this case would still be under the sole control of the keyholder. Signatures and certifications keep their validity.

So there are two classes of revocations. The revocation in case of a compromise that invalidates signatures and certifications is call "hard". The other - the retirement case - is called "soft".

The revocation certificate which is generated at creation time of the key is an emergency measure in case you lost your key. That's unrelated to any possible breach. This certificate has been stored in the $HOME/.local/share/sequoia/revocation-certificates (unless stated otherwise as described below) and it is tagged as 'unspecified' in the revocation certificate, which cannot be changed later on. If you still have access to your key (even compromised), you can (and should) generate a more meaningful revocation certificate. Besides the revocation reason this also includes - if intended - an announcement of a new certificate's fingerprint, using the 'message' option.

In both cases you have the possibility to write the revocation certificate to a file. In the case of the emergency revocation certificate the option '--rev-cert' followed by a file name has to be provided while 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 not available anymore. When creating a soft revocation certificate, for example in order to have a more specified one prepared for future usage, you would have to use the '--output' option to write that revocation certificate to a file instead of importing it directly into the keystore, which would happen, if you don't use this option. In that case your key would automatically be revoked by sq. 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 by itself 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 the command sq key revoke.

In the following example --cert requires the fingerprint of the cert to be revoked. The last two options <REASON>, where to choose from compromised, superseded, retired or unspecified, and <MESSAGE> are mandatory.

$ sq key revoke --cert $FINGERPRINT --reason retired --message 'quit my job'

This command does not give any output. If you have a look into the certificate using the origin fingerprint, you will see that is is marked as revoked. The revocation certificate has been created and at the same time imported into the keystore and applied on the key, so that the inspection shows the certificate which is marked as revoked:

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 usage, 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 change the key, it's still not revoked! To use this revocation cert, import it:

$ sq cert import $FILE

then the key is revoked.

Publishing

After creating the revocation certificate, you should publish the revoked certificate on the keyservers:

$ sq network keyserver publish --cert $FINGERPRINT

As described in the beginning of this chapter, you can also publish the revocation certificate itself if you saved it in a file:

$ sq network keyserver publish FILE 

Publishing is dealt in this chapter

Revoke a User ID

It's also possible to revoke a User ID such as a name or an email address using the subcommand userid. 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 more specific use cases in which someone wants to keep the certificate valid, but the secret key material should be deleted.

If you for example work in stateless mode and manage keys only in files, the way to extract the certificate from a key would be to delete the secret key material from that file( in case you want to use both of them afterwards, it's necessary to make a copy of the key beforehand). A second use case could arise during the process of a (sub)key rotation. It would improve confidentiality to delete the secret key material for the sake of not being able to become infiltrated at some point in the future, which would otherwise effect previous sent and stored messages.

For this cases, sq offers the command sq key delete to delete the secret key material from a key, what remains is the certificate:

$ sq key delete --cert $FINGERPRINT

Also possible is to provide the User ID that includes a specific email address, which should be done carefully if there exist more than one key containing this User ID:

$ sq key delete --cert-email alice@example.org

Key import and export

Import a key

To import a key from a file into the keystore, you just have to use the following command:

$ sq key import $KEYFILE

$KEYFILE can contain more than one key, sq key import will import all 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 another software like Thunderbird, this is the suitable command:

$ sq key export --cert $FINGERPRINT > $KEYFILE

or

$ sq key export --cert $FINGERPRINT --output $KEYFILE

You can also export a special subkey instead of the complete material of a key. For this you can use sq key subkey export:

$ 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 it is. If you want 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 selector (as fingerprints are self-authenticating) or --for-file.

By default sq encrypt signs encrypted messaged. 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 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 the file is missing in parameter list, the message is taken 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 chapter configuration.

Using v6 (RFC9580)

When encrypting, the used parameters are taken from the certificate - this includes the format of the encryption container: if the certificate is of version v4, sq will use a corresponding container. However if there is no guidance - for instance when just using --with-password - sq can be directed to use a specific encryption container format by passing --profile.

The values of --profile can be rfc4880, which uses a v4 container, or rfc9580, which uses a v6 container.

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 provides the possibility of generating additional subkeys for specified purposes and adding it to an existing certificate. The sq key subkey add command will generate and add a new subkey to the certificate both in once.

In contrast to generating 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.

The determination of the expiration date is also slightly different. While a primary key - unless otherwise specified - gets assigned a default value of 3 years, the 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 the usage of the sq key subkey add command:

$ sq key subkey add --cert $FINGERPRINT --can-encrypt 'transport'

Note: Other values for --can-encrypt are 'storage' and 'universal', the latter being the default.

To see the result, just have a look at the certificate to which the subkey was added. It will be listed together will all other subkeys.

$ sq inspect --cert $FINGERPRINT`

Subkeys cannot be removed for 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 chapter on deleting a key for more details.

Binding a subkey to another certificate

In addition to the option of generating new subkeys and attaching them to a certificate, you can bind an existing subkey (or key) to another certificate than the original one. This can be the need, 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.

Here is how it is done:

$ sq key subkey bind --key $KEY_TO_BE_BINDED_FINGERPRINT --cert $FINGERPRINT
 

Generating keys for special purposes

Per default sq generates a key that is usable for certification as well as for signing, authentication and encryption. Nevertheless, there is also the option to generate keys with limited functionalities. Depending on what purpose the key is supposed to serve, you have to either opt out the respective other features or specify the feature in more detail. There is also the possibility to generate subkeys for specified purposes and add it to the existing primary key like shown above.

The following example shows how to generate a key for certification only. Further options, also 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. Creating and verifying certifications and signature, revocation of certificates, publish and retrieving and other aspects of managing certificates.

Quick start

This is an overview over operations concerning authenticity. It is by no means complete, but might give you a first orientation.

Getting a certificate from the internet

$ sq network search $QUERY

Search default keyservers, WKDs and DNS servers for certificates with User IDs or fingerprints matching $QUERY.

Publish a certificate

$ sq network keyserver publish --cert $FINGERPRINT

Publish the certificate with fingerprint $FINGERPRINT on the default keyservers.

Authenticate a certificate

sq only uses authenticated certificates. That means that certificates can only be designated 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 use 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 effected. Use:

$ sq pki link add --cert $FINGERPRINT --all

as a convenience to authenticate all User IDs in a certificate in one go.

Retract authentication from a certificate

To reverse an authentication use:

$ sq pki link retract --cert $FINGERPRINT --userid $USERID

or the following to retract any associated links of a certificate:

$ 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

Certifying 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 of 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, use:

$ sq verify --signature-file $DETACHED_SIGNATURE_FILE $FILE

To verify a signed message use:

$ sq verify $FILE

Please note that for a successful verification the signing certificate has to be authenticated.

Sign files and messages

$ sq sign --signer $FINGERPRINT $FILE

Sign a file using the key with fingerprint $FINGERPRINT.

Sign with detached signature

$ sq sign --signature-file $DETACHED_SIGNATURE_FILE --signer $FINGERPRINT $FILE

Authenticating certificates

In case you ever want to send an encrypted email to someone, you will need public key material to do the encryption. Public key material is part of a certificate, 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 an email address), identifying the right certificate should be easy.

User IDs however are just claims. Claims which need to be verified before it's advisable to use any public key material. This process of verification is called authentication. This process however is blurry - how much "evidence" do I need to rely on the claimed binding between a User ID and the public key material?

In case the intended receiver of my email created and published a certificate, there exists 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 to identify a certificate with certainty.

This shifts the problem from identifying the right certificate to identifying the right fingerprint, fingerprints however 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 resemblance with a User ID.

Identifying a certificate - the waterproof way

Assuming that you are certain to have the right fingerprint - lets name it $FPR. Retrieving the corresponding certificate can be done with:

$ sq network search $FPR

This downloads the certificate from sources in the internet and stores it in the local certificate store. More details on retrieving can be found here.

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 what is left to do is to verify if this certificate contains the wanted 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 by

$ sq key list $USER_ID

Identifying a certificate - softer approaches

The scenario in the preceding chapter 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 in the internet

Usually circumstances are less optimal. But, however you got the fingerprint (or certificate), this happened in a context which can be taken into account - used as evidence.

If you got a fingerprint printed on a slip of paper handed over by the alleged creator of the corresponding key, there is high evidence that you got the right fingerprint. Same could be said if you got the certificate itself on a usb stick.

Things become less reliable if the fingerprint reached you in an email, especially if that email is not signed. If that email came as response to a request ("Please send me your certificate ..."), it adds to its trustworthiness. If you even know the sender personally, this might be another plus.

Some keyservers require an attestation. They send an email to each User ID in a certificate which looks like an email address, containing a link. Clicking that link leads to sending a specific request back to the server, which is taken as a confirmation of the authenticity of the User ID - certificate binding. If you get a certificate from such a server, you can take this into account.

Some websites publish alongside their contact email address the corresponding fingerprint or even the certificate. 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 be taken as evidence of authenticity.

However, all the above examples (and there are many more) are circumstantial. They reflect social relationships, assumptions on integrity and diligence and probably more.

On top of this: Not every email is send in a life or death (or prison) situation. Many of them are quiet mundane, for some it might be even acceptable to send them unencrypted.

All of this makes trade offs a viable option. It affects the amount of evidence one wants to see before relying on a certificate. These trade offs are probably the main way how "authentication" is handled.

Using the result

Once you are willing to rely on a certificate, you have 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 by

$ sq pki link add --cert $FPR --userid $USER_ID

Alternatively to --userid, also --email would work. If instead you specify --all, all bindings in the designated certificate will be added.

This command does several things:

It's adding a signature to the certificate. This signature indicates that you consider the binding between the certificate and the User ID authentic. Future invocations of sq (or any other software using Sequoia PGP) will recognize this signature and the implied authenticity. You can see the effect by:

$ 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 certificate - User ID binding. Using sq pki link add like above the trust depth will be 0, which means that the implied authenticity by the signature does not "spread out" - it's limited to exactly this binding.

A trust depth of 1 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 - so there are 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 correct 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 it.

If Alice would use a trust depth of one, then not only Bob's certificate is considered authenticated, but also Carol's, because Carol's certificate has a certification made with Bob's certificate. By assigning a trust depth of one, Alice marks Bob as a trust introducer - his certifications are now as good as if Alice would have done the certifications herself.

Carol certified Dave's certificate, from the perspective of Alice 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. Her certification of Dave's certificate would then be taken into account by Alice.

To visualize the effect of trust depth:

trust-deptheffected certificates
0Bob
1Bob, Carol
2Bob, Carol, Dave

Please note that Alice is assigning 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. Actually they don't have to be in Alice's local certificate store, they don't even have to exist at the time of the certification. They have to be locally available, when Alice calculates the authenticity of the certificates.

The 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 at the command line.

$ sq pki link add --cert $FPR --all

Links created by sq pki link add will not expire, this can be changed by using --expiration DATE - DATE can either by in ISO 8601 format (2025-01-01) or a time interval 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 - 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 by:

$ 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.

Import and export of certificates

Certificates can be disseminated in many possible ways: as 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 by:

$ sq cert import $FILE

This will try to import the certificate inside $FILE. If $FILE is skipped, sq cert import expects the certificate via STDIN:

$ cat $FILE | sq cert import

Importing certificates from a file has no influence on any implied authenticity. It's included into the certificate store as such. Use sq pki link add to attest authenticity.

Certificates can be exported from the cert store via

$ sq cert export $QUERY

with $QUERY consisting 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, sq cert export will then export any valid certificate matching any of these queries. Invalid certificates are for instance certificates using outdated hash functions in signatures, those are excluded. You can work around this restriction by passing --policy-as-of using a date which predates 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

Search a certificate in the internet

Certificates are necessary for encryption or the verification of signatures. The users of certificates are usually not the ones who create them, so there emerges the problem of how to make certificates available to interested users. Since certificates in wire format are just plain files, they can be published on webservers or send as email attachments, but these are individual solutions which differ for each certificate.

There are solutions which are more generic - 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 to search 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 4 different types of these servers:

  • SKS servers: These are "first generation" servers with unrestricted publishing.
  • Keyservers which require a confirmation: An email is send to each User ID in the certificate that resembles an email address containing a link which has to be clicked to acknowledge the upload. keys.openpgp.org works this way.
  • Web Key Directories (WKD): The certificate is stored on a webserver and its URL is constructed from the User ID. As a side effect WKD cannot be queried for fingerprints. WKD implies the control of a webserver related to the mail domain of the User ID.
  • DNS-based Authentication of Named Entities (DANE): Although initially not meant 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 not only stores 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). The values add up, certificates found on different servers gain authenticity. However, there is a cap of 40/120, which cannot be surpassed by this mechanism, thus preventing fully authenticated certificates generated simply by flooding the servers. This trust signature concept comes from the RFC 9580 standard, which replaced RFC 4880, 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, sks.pod01.fleetstreetops.com)
  • tries to download a certificate via WKD
  • and queries the DNS for a DANE entry

In this example sq network search found exactly one certificate, which gets stored in the local certificate storage.

To keep track of the origin, Sequoia PGP creates intermediate certificate authorities, which certify the received certificate. This is basically 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 is found on two different keyservers, which makes 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 need to have their protocol specified (hkps://).

Using the --output $FILE option prevents sq from importing the certificate into the cert store, but outputs to the supplied $FILE 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 form of an email address, but it can also be a fingerprint.

$ sq network search EE99C48D11A4BE940569C4B3919EA6F609043A04 

will fetch Alice's certificate from the example above.

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 these certificates, as they might be usable or even better fitting.

Please keep in mind 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 type of queries: WKD for instance can only be queried for User IDs in the form of email addresses.

Authenticating

sq network search will download fitting certificates into the cert store. In this state the certificates are of limited use, as they do not (yet) contain authenticated User ID - certificate bindings. The certificate is treated as unauthenticated. To change this, use sq pki link add to add authentication to bindings. For details see chapter 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

Publish a certificate

Publishing a certificate is a bit more hands-on than retrieving them. sq can directly publish to keyservers, for other methods of dissemination sq can generate the necessary data.

The simplest way to publish to keyservers is:

$ sq network keyserver publish --cert EE99C48D11A4BE940569C4B3919EA6F609043A04

or more general

$ 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 (keys.openpgp.org, mail-api.proton.me, keys.mailvelope.com, keyserver.ubuntu.com, 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. Please note that --server expects the parameter to specify the protocol (e.g. hkps://).

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 (actually there are two schemes). That corresponds to an accordingly configured webserver which delivers the certificates upon request.

This requires the generation of files and directories complying with the scheme. sq can help by generating the required 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. From the cert store only certificates with User IDs in domain example.com are 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' (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 into the WKD directory structure can be selected using the following designators:

  • --cert $FINGERPRINT to use a certificate by 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 which can be found inside the cert store by skipping 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.

$ 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 has to 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 cert 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 by:

$ sq cert list $PATTERN

In this case, $PATTERN is just a string (in UTF-8). Each certificate which contains a User ID containing this string will be displayed.

$ 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

This is an example where $PATTERN contains an incomplete email address, which matches one certificate.

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 directly certified the binding of bob@example.com with its certificate, when this certification was made and the assigned trust depth.

Finding certificates in the local cert store

There are further commands sq implements, which 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 differences 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. There are --userid and --email as designators available.
  • sq pki authenticate takes a certificate and a User ID and checks if there is a binding between these in the cert store, which is authenticated.

These three commands can also be used on unauthentic bindings, if you pass --gossip.

$ sq pki lookup --email alice@example.com --gossip

Modifying a certificate

If you want to modify or add a User ID or update expiration dates, you have to 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 modifying your key, you have to export the certificate again. It will now contain the modifications applied to the key and can be published in the usual way.

Along the same line, certificates cannot be revoked, this also has to 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. The self signature is used to show that the signed component in a certificate is aligned with the intention 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 an important information for others 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 use the email address to identify the binding which should be certified (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 like above has a lifetime of 5 years, after that period the certification is considered as 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 disseminated - this however is not recommended. Instead send the new certificate to the respective keyholder, so that he/she can approve your attestation, basically signing your certification. See more details after the following paragraph.

Approving a certification

This sounds complicated, but it prevents attackers (or trolls) to simply add loads 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 newly certified certificate, a keyholder can check that certification by saving it into a file and then inspect, import and subsequently approve 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 in case 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.

For getting a list a 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

For the other direction - Given a certificate, list those certificates which 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 certifiers trust in the certifications made by the trusted introducer / the CA to be valid. Such a certification as 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 before mentioned certification will restrict its effect to the passed 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 unique for a given amount of data - there are no "stand-alone" signatures.

Signatures come in 3 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.
  • 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

In case the necessary certificate is locally available, 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

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 proposes 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 - there is no guaranty 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 the following:

...
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 into 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 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 is 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.

$ 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 usual - you can download an installation image from their website. To ensure the authenticity and integrity of the image, it is signed by the Cubes 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 utilize 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 trust 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 having a 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 trust 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 get the installation image and the detached signature:

$ 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 - from the download page of Qubes OS.

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 is a proof that the image is authentic and its integrity is confirmed.

Download

Once you downloaded and authenticated the release key, there is a shorter, more convenient subcommand sq offers:

$ 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 means creating a signature over data. There are two ways to store the newly created signature:

  • by wrapping the file in an OpenPGP message structure which includes the signature
  • by creating a detached signature within its own file, leaving the signed data untouched

The first option has the advantage that everything is in one file. The advantage of the second option is that the signed file doesn't change, so that 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 clear text signature, exchange --message with --cleartext.

$ 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), a file containing a key can be used: --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 existed before. You can add --overwrite in this case.

Technical

This chapter describes more technical use cases.

Configuration

The behavior of sq is configurable using configuration files. There is the configuration for the cryptographic policy and the configuration for sq. The configuration for the cryptographic policy is used by Sequoia PGP and thus determines the behavior of sq concerning cipher algorithms, hashes and the like (for details see below).

There is a second file configuring sq: Selection of default keyservers, default expiration times for newly generated keys, default ciphers, etc. Additionally this configuration allows to override the settings for 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.

Getting 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, but want a hint 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 to tune your setup, you can generate one based on your current setting by

$ sq config template

The generated configuration will be printed to STDOUT or into a file if you specify --output $FILE. Move this output to ~/.config/sequoia/sq/config.toml, so that sq can pick it up. This configuration allows you to also modify the cryptographic policy.

Cryptographic Policy Configuration

Sequoia PGP has a StandardPolicy that defines which algorithms (ciphers, hash functions, etc.) are valid and available. This policy can be adjusted to care for specific needs.

The adjustments are configured in a file (command line switches are not available), by default located 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. Individualized adjustments can be made in the user 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. It consists 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 to use keys which are not (yet) defined. This way adjustment can be made for algorithms which will be included in a 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 equals $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 a 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 revocation certificate gets stored in the revocation certificate store.

This revocation certificate is unspecific on the reason for revocation (which equals to '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, which means that creating a backup of the files in the stores is sufficient.

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 basically two types of certifications: self-signed ones, which signify that the certification aligns with the intends 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 selfsigns 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 subsequently 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 an information loss – there exist other input values, which produce the same hash value. The consequence of these collisions is that a certification signature asserts authenticity of an unlimited amount of public key - User ID pairs. Obviously this is not intended.

One design criteria for a hash function used for the creation of certification signatures is that these collisions are next to impossible to construct, making the abuse of a collision infeasible in practice.

SHA1 is one of such hash functions. Over the time careful research concluded that SHA1 was less robust against collision construction than initially thought. Combined with the expanding availability of computing power, SHA1 became vulnerable, meaning the construction of a fitting 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 by a stronger hash function: SHA256. The old signature will not be removed, its intention however is now confirmed in a way which is robust against collisions.

In the following, we will show you how to use sq cert lint and, due to a lack of awareness of outdated standards, also how to use it with GnuPG.

Lint certificates of Sequoia's certificate store

Check for SHA1 signatures in certificates

We use sq cert lint to check certificates that uses SHA1 signatures. sq cert lint takes the certificate(s) to examine either a file parameter or from stdin. It also operates directly on the certificate store - so no need for a keyring, except 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 by passing the --fix option. This causes sq to generate new certifications, using SHA256 as 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:

$ 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)

If we want to get more information, we can also 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 from others, to just know which ones are vulnerable, - which you of course can't update without the secret key material - 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 keyring by 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, many invalid certificates may have accumulated over time. Now you could either nudge other people to update their certificate or, if the corresponding secret key material is availabe for you, you could fix it like shown 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 keypair - the secret key material and the public certificate.

Passing --fix to sq cert lint causes sq to generate new certifications using SHA256 as hash function. The result is written to stdout and can be passed to sq or 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 using gpg like

$ 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

Two new signatures are detected, these are the ones with the improved hash algorithm.

We now have a look at the cert again, 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, sq packet dump can be used.

$ 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 within the certificate.

Fixing a revocation certificate

First, lets 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 have a closer look.

$ 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 the used algorithm, SHA1 in this case.

sq cert lint is not meant to fix this issue. Instead simply create a new revocation certificate.

Note: The generation of a revocation certificate only works with keys containing SHA256 certifications. See above how 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 on the objects sq works with, for instance if you want to troubleshoot a failing operation. sq offers two levels of details: inspect and the subcommand hierarchy under sq packet.

The workhorse of these two is sq inspect. It shows a human readable extract of the object passed to it. The most general application is:

$ sq inspect FILE

FILE can be a certificate, a key, a message - it can be 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 create an encrypted message on the fly. The message in clear ("hello") is passed through sq encrypt and then forwarded to sq inspect.

A revocation certificate looks like:

$ 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 certificates, certifications of this certificate are hidden. You can display certifications by passing --certifications to sq inspect.

$ 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

There is an even deeper dive possible. You can list the packets an OpenPGP artifact (message, certificate, signature, etc) is comprised of.

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 save to experiment with it.

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 requested to sign or decrypt data, usually after supplying some kind of authentication (like a PIN). That way secret key material can be used without disclosure.

Sequoia PGP (and therefore sq) uses key- and cert stores to manage the respected 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.

Please note that if you point the environment variable SEQUOIA_HOME to a different location than default, gpg-agent support is disabled.

Keyrings

Public keys, encrypted messages and other OpenPGP artifacts consist of packets with 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, expiration date, subkeys, etc.), intermingled with Signature Packets which bind everything together.

A stream of these packets can contain multiple artifacts. If such a stream consists only of certificates (with or without secret key material), such a stream 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:

$ sq cert export --cert $FPR_A --cert $FPR_B --cert $FPR_C ... > $FILE

Likewise sq key export ... will also create a keyring.

Listing the certificates in a keyring

The summary of certificates in a keyring can be listed by:

$ 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 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

You can filter by

  • --userid
  • --name
  • --email
  • --domain - --domain applies to User IDs containing an email address.
  • --cert or --key - --key matches certificates containing secret key material.

sq keyring filter builds the union set of all supplied filters - the result contains all certificates which match at least one of the filters.

The selection can be reversed by passing --prune-certs. The result will then contain all certificates not matching at least one of the filters.

$ sq keyring filter --experimental --userid Alice --prune-certs example_keyring.pgp

Taken the above example keyring, the result will contain the certificates of Bob, Carol and Dave, but not Alice.

If you want to build the intersection of several filters, you have to chain sq keyring filter:

$ sq keyring filter --experimental --domain example.com example_keyring | sq keyring filter --experimental --userid Alice

Split and merge

A keyring can be split up into its 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 the verification of signatures over data. Its main use case is the verification of downloaded files.

Installation

Linux

Install sqv by using the package management of your linux distribution.

Debian

sqv is part of the debian distribution since bookworm (Debian 12). Up to date versions are part of trixie (Debian 13) and following.

$ sudo apt install sqv

MacOSX

There is a package for sqv from homebrew:

$ brew install sequoia-sqv

Using cargo

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. Look here for more information: https://www.rust-lang.org/tools/install

Install the dependencies (bookworm, Debian 12/ 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, you can install the dependencies by

% brew install pkgconfig nettle openssl capnp

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). Then, cargo will 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 necessary for verification has to be passed via the command line. sqv however uses 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 Qubes-R4.2.3-x86_64.iso, using the detached signature in Qubes-R4.2.3-x86_64.iso.asc, the certificate of the signature is in qubes-release-4.2-signing-key.asc.

sqv returns exit code 0 on success, everything else signifies a failure. Its output contains the fingerprints of the certificates for which a successful verification was possible.

The conditions for the signature check can be further restricted, use:

  • --not-after to pass a date which produces a failure, if the signature was made at a later point
  • --not-before to pass a date to ensure that the signature was made after that time.

The keyring passed via --keyring can contain more than one certificate, so you can use a collection of trusted certificates. In case you want to ensure several valid signatures at once, use --signatures together with the number of necessary valid signatures to pass the test.

$ sqv --keyring my_trusted_certs.asc --signatures 3 --signature-file ...

This example only succeeds if 3 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 an output file has to be given, which will contain the original message without 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 invisible to sqv.

Background

This chapter discusses some of underlying concepts of OpenPGP and public key cryptography in general.

It is 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 called secure. While there are certainly a lot of characteristics which are wishful in a communication system, the requirements for a secure system can be summarized as confidentiality, integrity and authenticity. These three 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.

Also not included are mechanisms to contain the consequences of a breach. Perfect forward secrecy, future secrecy, plausible deniability are methods to limit the effect of a compromise - how much an attacker gains by knowing the encryption secret (the key) at some point in time. For a system to by called secure it's not required to implement them.

Although this definition of security might seem very limited and real world application will certainly also implement the other mentioned characteristics, it helps reasoning about a system if the definition is clear cut and doesn't include neighboring concerns.

Confidentiality

Confidentiality means that only the intended receivers of a message are able to read it. Everybody else should only see an undecipherable lump 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 about the content of a message. Sender, receiver, time of sending, size of the message - all the meta information about the communication itself is not addressed by encryption.

This ignores the fact that the mere fact of communication 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 a modification would corrupt the message. If the message was a text, the damage would be obvious - if it was a 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 guaranty of integrity from the applied encryption - the guaranty 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 a man-in-the-middle attack. Or spelled the other way around: man-in-the-middle attacks are basically attempts to subvert the methods of guarantying authenticity within a communication system.

This is usually realized 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 of course doesn't imply that a validation (aka code audit) has taken place, that the software uphold 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, lots of 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 prolonged.

A key is needed for operations 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 with the exception of 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 herself 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 which are bound to another key (called primary key) by a subkey binding signature.

Subkeys were added to OpenPGP to solve a couple of shortcomings:

  • Subkeys introduce new key material to a key, to prevent the usage 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 to rotate or revoke subkeys without sacrificing existing certifications as the primary key doesn't change. The subkey binding signature is used to extend the certification of the primary key to it's subkeys.
  • Subkeys have flags which indicate their usage (signing, encryption, etc.). OpenPGP allows to have 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 offlined, as it's 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, 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 3 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 is a little CA with the primary key acting as a certificate authority, certifying the subkeys.

You can have a peek into a key by:

$ 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, so that sq packet dump can display its 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 necessary hiding that the conversation is taking place. Technically speaking, this setting composes a secure channel. If you don't have control or cannot even estimate the number of people which can follow a conversation, this conversation uses an insecure channel.

People learn early in life how the establish a secure channel - for instance by whispering or changing the location. Both methods however are difficult to achieve in the internet. Apart from edge cases, the internet consists only of insecure channels - security was no design criteria, when the internet was first conceptualized. A secure channel over the internet has to be build on top of insecure channel(s). 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 new notation. The usual schemes contain methods like changing the alphabet and reordering the sequence of characters. Concrete schemes use an algorithm and a secret key to specify this notation. The algorithm itself is not secret. Certainly, keeping the algorithm secret makes an attack harder, but comes with drawbacks. Current algorithms are thoroughly scrutinized, so that their publication does not help an attacker.

Probably the biggest advantage with publicly available algorithms is that it can be reused for different secure channels, as its protective character lies completely within the secret key. Choosing a new secret gives you a new channel without sacrificing the protection of either the new channel or already existing ones.

Symmetric encryption

Symmetric encryption uses the same secret key for encryption and decryption, decryption is simply done by repeating the steps of the encryption, but in reverse order - undoing what encryption did to the original text. The constrictions for designing an algorithm for symmetric encryption are usually not imposed by capabilities of CPUs, which means that contemporary algorithms are optimized for performance - symmetric encryption is fast.

Symmetric encryption however is not well suited for establishing a secure channel. This is not due to weak algorithms, but merely the fact that sender and receiver have to have the same secret key - and nobody else. This implies that for the exchange of that key a secure channel is needed - a hen 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. Its design criteria however are radically different from 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, but it's hard to reverse this operation, finding the prime factors, if you only have the resulting number. Thus decryption cannot be achieved as it would be with symmetric encryption - reversing the encryption steps would be to 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. Obviously there has to be a mathematical relationship between the key used for encryption and the one used for decryption. In fact, keys for asymmetric encryption are constructed as pairs. And: they are interchangeable - no matter which key you used for encryption, you need the other one of the pair to decrypt.

For a complete cycle - clear text -> encrypted text -> clear text - both keys are necessary. Knowing only one of them is not enough, so it doesn't compromise the security of this mechanism if one of them is publicly known. 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 clear text -> encrypted text step. Anyone who is in possession of the other key of the pair can complete the cycle: encrypted text -> clear text. So you 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.

Hybrid encryption

OpenPGP uses both, symmetric and asymmetric encryption. It combines the speed of symmetric encryption, using the key distribution capabilities of asymmetric encryption.

A message is first symmetrically encrypted, using a random, unique key, which then is asymmetrically encrypted for each receiver of the message and appended to it. A receiver has to identify the right key, asymmetrically decrypt it and use it to symmetrically decrypt the message. Besides speed, this scheme has the advantage that each additional receiver only adds 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 she 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. 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 to really be private, this is a proof of authenticity.

Signatures also provide an assurance that the message is not altered after the creation of the signature. Otherwise the message would have a checksum different to the encrypted one of the signature. To modify the message and adjust the checksum within the signature, the private key would be necessary.

Beyond messages, signatures are also used to allow the verification of the authenticity and integrity of downloaded files. The mechanism is the same.

Besides authenticity and integrity a signature does not guaranty that a signed file doesn't contain malicious content or is free of bugs or even fits for a specific propose.

As signatures can be created by anyone, it's vital to ensure that it was created by the right private key - and not the one of an impostor. How to do this is explained in chapter 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 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 a certain email address, the certificate contains a User ID having the said email address.

<alice@example.com>

User IDs are not limited to email addresses, they can consist of 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.

sq allows a shortcut. 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 are not using the information supplied in a User ID. User IDs are meant as markers to allow for a key retrieval or identification without having to resort to fingerprints. As such they are included in keys and certificates - their untaintedness is ensured by signatures also contained in the key or certificate. There is no limit in 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 soundness of the claim - is described in chapter 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 got disseminated, it's impossible to call that back. Then a User ID has to be revoked, it will stay in the key with a revocation certificate added. The revocation will also be part of certificates generated from that key, giving the software processing these certificates a hint on how to handle that User ID (such as ignoring it).

Web of Trust

There are relationships between keys, certificates, signatures, etc. which are computable. Given a file, a detached signature and a certificate, one can calculate if the signature is made over the file using the key corresponding to the certificate. There is no other (outside) information, no context 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, which binds 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 bind 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 owner of that email address? As this problem cannot be computably solved, 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 repeating work. For this the results of work (of authenticating a binding) has to be captured in a persistent form which 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 - which is 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 inside it.

This approach is not limited to OpenPGP, x509 operates along the same idea where some authority attests authenticity and spreads this attestation.

This gives rise to a new problem: product quality. The signature gives no clue on how diligent the authentication was performed, 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 good reputation - good work. This is done by assigning a trust depth while 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 one "hop". Higher levels of trust depth simply increase the number of hops an attestation covers.

Accompanying the outreach of a reputation, there is also the possibility to quantify it. Instead of attesting only a good reputation (or not), OpenPGP allows to assign a value between 0 and 120, expressing the trust in an attestation made by a certain keyholder. Such an attestation would then be viewed as "partially authenticated". The values can add up, if independent sources only express limited trust in a reputation, so that the result might qualify as "good enough" (for a certain use case).

So far a reputation can be characterized by its outreach and its (assumed) quality - the diligence put into the product. This can be further tuned by limiting the 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 of an institution, where only the attestations for User IDs from that institution (having a specific mail domain) would benefit from the assigned outreach and trust amount. This doesn't stop a CA from attesting User IDs from other domains, but those attestations would be ignored.

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 on 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 mail domain. WKD implies control over a webserver answering on a domain linked to the mail domain. Other proofs of control might also give clues on the authenticity of a binding.

So far this situation is not yet a web of trust - it's more a soup of partially entangled certificates. In some sense this soup is result of the collective work of OpenPGP users, it's kind of a commons.

It gives you a glimpse on how others judge the authenticity of bindings, which certificates belong to a person with good reputation and so on. But these are judgments of 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 choice thus differs between different users. This is in stark contrast to x509 where the 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, the 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 in the internet which judges 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, the problem just got shifted. 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 to the staff can leave, new ones can enter - 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 precise: its certificate) is used as a trust introducer.

Everyone can create a CA, it's not limited to established institutions. But usually the term CA denotes some degree of institutionalization.

The certificate of a CA can itself be certified by other keys - either by (other) CAs or 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 precise: 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 a record of the provenience of a certificate and a trust amount derived from that provenience.

As an 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 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.

Besides calculating a trust amount, the path also shows from where a certificate originates and when it was included in the cert store, as certifications have a creation date.

These internal CAs are called Shadow CAs.

Using subkeys

Subkeys are key pairs, but 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 respectively subkeys for authentication, signing and encryption, even though it's possible to generate more subkeys in case of need. Fire more technical information see chapter Keys and certificates.

The primary key should be the most protected one and therefore stored offline where no one can have access to it. For this reason, the best practice would be not to publish the primary certificate. Instead it makes sense only to publish certificates of subkeys, to avoid that other people use the cert 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 scenarios subkeys are the ones to use for frequent work. Then you just need to publish the certificates of the respective subkeys. It makes sense to rotate them more often by revoking them faster and let them expire earlier than the primary key. The primary key can be used to revoke a subkey if it's needed.

To prevent losing data if an encryption subkey gets lost, it's necessary to have 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 file. This is simply to minimize the risk of key theft by avoiding storing it in two different places. In case of loss the functionality will not be limited, signatures made previously with that key still remain valid and for future signatures you can easily generate a new one using the primary key.

Just a site note, in some countries there is even more 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 hand out a decryption key to law enforcement agencies if recommended, even without a court order. In this cases the rest of the key material should remain safe.

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 reject using 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, so that they 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. To answer the question whether it is an improval of security to set an expiration date, we think about possible threadmodels someone could face. Starting with a certificate expiring, you can always extend the certificate validity as long as you have access to the key.

If you lose your key due to hardware loss or damage without there being an attack and still have your revocation file stored at another place, you can simply revoke the key and the certificate, and don't have to wait for the expiration. If you don't have a revocation file, you can at least let it expire by itself.

If someone steels your key, the person would be able to compromise the key. Only a revocation file would help to avert the damage here too since the expiration date could also be manipulated by the attacker.

It can make sense to set different expiration dates for subkeys with different purposes. In the case that only a specific subkey gets lost or stolen, the primary key 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 on a regular basis. In summary, one can say that more important than setting an expiration date is to store the revocation file separately from the key in another location. Nevertheless in some cases setting an expiration date could limit a possible damage.

Fast lanes

Here you will find various short how-to guides on important functions of sq. The detailed main text respectively tutorial is linked in each document.

How to generate a key

To generate a key with sq, enter the following command in your command line:

$ sq key generate --own-key --name alice --email alice@example.com 

To generate a v6 key, use the --profile option:

$ sq key generate --profile rfc9580  --own-key --name alice --email alice@example.com

To learn about the possibilities and options around generating a key see chapter 'Key generation'.

Additional subkeys

How to generate an additional subkey

To generate and add an additional subkey to an existing certificate, see the following command, which exemplarily specifies the capability of signing and opts out the password protection:

$ sq key subkey add --cert $FINGERPRINT --can-sign  --without-password  

How to bind an existing subkey to another certificate

$ sq key subkey bind --key $KEY_TO_BE_BINDED_FINGERPRINT --cert $FINGERPRINT
 

To learn about the capabilities and other specifications of adding a subkey see chapter 'Adding a subkey'.

How to rotate a key

To rotate a key by copying existing capabilities and issued certifications on a new one, you can use sq key rotate like this:

$ sq key rotate --cert $FINGERPRINT

Replaying certifications

To only recreate the certifications made by the old key without generating a new key you can use sq pki vouch replay as shown here:

$ sq pki vouch replay --source $SOURCE_FPR --target $TARGET_FPR

Detailed information about this topic can be found here.

How to import and export a key

Import a key into keystore

$ sq key import $KEYFILE

Export a key from the keystore into a file

$ sq key export --cert $FINGERPRINT > $KEYFILE

See chapter 'Import and export of a key' for more detailed information.

How to manage keyrings

Creates a keyring by exporting certificates from the cert store:

$ sq cert export --cert $FPR_A --cert $FPR_B --cert $FPR_C ... > $FILE

Lists the certificates in a keyring:

$ sq keyring list example_keyring.pgp
0. 159F7D1D498EF2BFC489D13FB5A86AAD248300C8 Bob
1. 722E3D4101B82C0AE80A3EF07A512226FF0D14E7 Alice
2. 8B19816EE960EEBBD09A1256CF755B2FB77C6171 Dave
[...]

Extracts 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-----

Splits up a keyring into its certificates:

$ sq keyring split $FILE

Merges a split keyring:

$ sq keyring merge *
-----BEGIN PGP PUBLIC KEY BLOCK-----

xjMEZ7Wz3hYJKwYBBAHaRw8BAQdAuhfXxD0ewpB0BrkDpMH5YhmobPkp/eGpJRxu
[...]
=MuLv
-----END PGP PUBLIC KEY BLOCK-----

More on managing keyrings.

How to encrypt a file

To encrypt a file with a certificate, use sq encrypt as it is shown here:

$ sq encrypt --for-email alice@example.com message.txt

Encrypting a message and adding a signature works as follows:

sq encrypt --signer $FINGERPRINT --for-email alice@example.com

See the complete instructions here: Chapter '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

See also: Chapter 'Encrypt and decrypt files'

How to sign a file or message

To sign a file with an inlined signature, use the following command:

$ 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

More information is available here: Chapter 'Sign files'

How to verify a file or a detached signature

Inlined and cleartext signatures

Inlined and cleartext signatures can be verified by:

$ 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 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

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

See more options and a detailed example in Chapter 'Verify 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 (which uses SHA1 signatures, for example), you can pass --policy-as-of, using a date which predates 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, you can also use this query:.

$ sq cert export --all

More on this topic you will find here.

How to publish a certificate to servers

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

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

DANE

To publish a certificate by DNS / DANE, create a TXT record:

$ sq network dane generate sequoia-pgp.org certs.cert

See chapter 'Keyservers' for more options about this topics.

How to retrieve a certificate

To retrieve a certificate from a keyserver, use the sq network search $PATTERN subcommand for example as follows:

$ sq network search alice@example.org

To get more information like customizing the list of keyservers etc., see chapter 'Search a certificate in 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 subcommand userid:

$ sq key userid revoke --cert $FINGERPRINT --userid 'alice' --reason retired --message 'testing_purposes'

More about revocation: Chapter '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 certify. Note that the updated certificate should then be sent to the certificate holder to be approved and published.

$ sq pki vouch  certify --certifier $FP_CERTIFIER --cert $FP_TO_BE_CERTIFIED --email=bob@example.org

More information can be found here: Chapter 'Certifications'

How to mark a certificate as a trusted introducer

To mark a certificate as an exportable trust introducer (respectively 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

More information can be found here: Chapter '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

Find more information here: Chapter 'Certify and authorize certificates'.

Recipes

This chapter contains a loose, incomplete and hopefully growing collection of guides on how to use sq and kin to solve certain 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, a ssh key pair has to be generated and the public part of that pair has to be configured at the server side as an authorized key. ssh can also use OpenPGP keys for that purpose. A bird's eye view of the configuration looks like:

On the server side nothing changes. The public part of the key has to be configured as authorized key.

On the client side:

  • ssh uses an ssh-agent to manage its secrets, here an ssh-agent has to be used which can facilitate OpenPGP keys.
  • There can be more than one active ssh-agent, ssh can be told to use a specific agent by setting the SSH_AUTH_SOCK environment variable accordingly.
  • 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. Using chameleon for that purpose allows to integrate the keystore of sq.

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 subkey to use.

There is no necessity for a key to have an authenticating subkey, OpenPGP functionality doesn't use these type of keys. If your key misses such a subkey, you can add one by:

$ sq key subkey add --cert $FINGERPRINT --can-authenticate

With $FINGERPRINT being 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 to a unix socket for commands related to its ssh-agent functionality. The socket can be found by:

$ gpgconf -L

The output has to 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 SSH_AUTH_SOCK has to have in order to use gpg-agent as ssh-agent.

Register the key

gpg-agent has to know which keys to use when authenticating a 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 the chameleon - this example assumes that you 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 - see flag [A] and the matching fingerprint. Add the keygrip FE39C76C0F879E840C1EE9E8E655694C73516437 to ~/.gnupg/sshcontrol to use it.

Alternatively you can add keys to the ssh-agent subsystem by:

$ 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 provided by g10code doesn't know about the keystore from Sequoia PGP, that binary has to be exchanged for chameleon. 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 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. You can check by

$ 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 if ssh sees 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 still uses g10codes 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).

Troubleshooting

Here you will find a collection of suggested solutions for possible issues or unexpected outcomes that can occur when using sq.

Troubleshooting regarding 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 sometimes 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, the certificates with unauthenticated bindings can by found by using the just used command followed by the--gossip option:

$ sq cert list --gossip
[...]
226 bindings found.
Skipped 128 bindings, which are unusable.

The unusable bindings respectively certificates which are found but not shown can be displayed by using the option --unusable in combination with the option --gossip, because they 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 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 file instead from the cert store. Here is an example on how to apply:

$ sq verify --signer-file alice.cert file.txt                                                                                                   

My certificate is expired

If a certificate is expired, it is obviously also not usable. Here is an example where we try to encrypt a file with our own expired certificate, but the same also applies to messages that shall be encrypted with a 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 into the certificate by using the command 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. 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 a certificate from someone else, you could search for new valid certificate:

$ sq network search $FINGERPRINT

A certificate is revoked

If a certificate is revoked, it is suggested to first have a look into the revocation certificate message, which will in the best case announce a follow-up certificate including a fingerprint. If so, you could 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 there might be included false results:

$ sq network search alice@example.org

Certificate uses deprecated hash algorithms like SHA1

Finally, if you have a certificate for which you have access to the secret key material and that is considered as 'unusable' because it uses SHA1 signatures, you could update it by using the command sq cert lint. For this operation you can find the full instructions here.

sq is rejecting my SHA1-protected file

When verifying signatures, sq might reject a signature and returns an error, even if the correct certificate is available. One reason for this behavior is located in sqs evaluation of the validity of a certificate.

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 peoples certificates.

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 nonetheless.

Which algorithms are considered deprecated is configure in the cryptographic policy. You can read out 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 case of SHA1 there are different applications of the algorithm with different cut-off dates. This date defines when the usage of said algorithm switches from valid to deprecated.

You can edit the dates in the configuration file to get sq to accept SHA1 based signatures. The easier (and less intruding) 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. Just choose a date which predates the cut-off date.

Please note that using --policy-as-of also effects other signature checks, like certifications. This has implications on the authenticity sq assigns to a certificate.

The best way to deal with a situation like this is to get the keyholder to update their certificate.

If this however is not possible and you for example need to encrypt a message for someone only holding an invalid certificate, you could also use the --policy-as-of switch in combination with the command sq encrypt as follows:

$ sq --policy-as-of 20130201 encrypt --for=$FINGERPRINT_BOB --signer-email=alice@example.org msg.txt

Acknowledgments

Sequoia PGP is an implementation of the OpenPGP standard. As such it is a successor of 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 or have done so in the past. Some of them can be found here.

Documentation

This documentation is written by Franziska Schmidtke and Malte Meiboom.

Funding

The Sovereign Tech Fund funded the project in 2024 - without them, this documentation would probably not exist.