|
|
|
@ -1,6 +1,6 @@
|
|
|
|
|
# Protecting code integrity with PGP
|
|
|
|
|
|
|
|
|
|
Updated: 2017-12-15
|
|
|
|
|
Updated: 2017-12-14
|
|
|
|
|
|
|
|
|
|
*Status: CURRENT, BETA*
|
|
|
|
|
|
|
|
|
@ -9,8 +9,9 @@ Updated: 2017-12-15
|
|
|
|
|
This document is aimed at developers working on free software projects. It
|
|
|
|
|
covers the following topics:
|
|
|
|
|
|
|
|
|
|
1. PGP key best practices
|
|
|
|
|
2. Introduction to PGP and Git
|
|
|
|
|
1. PGP basics and best practices
|
|
|
|
|
2. How to use PGP with Git
|
|
|
|
|
3. How to protect your developer accounts
|
|
|
|
|
|
|
|
|
|
We use the term "Free" as in "Freedom," but this guide can also be used for
|
|
|
|
|
developing non-free or source-available ("Open Source") software. If you write
|
|
|
|
@ -98,7 +99,8 @@ For encryption, PGP uses the public key of the owner to create a message that
|
|
|
|
|
is only decryptable using the owner's private key:
|
|
|
|
|
|
|
|
|
|
1. the sender generates a random encryption key ("session key")
|
|
|
|
|
2. the sender encrypts the contents using the session key
|
|
|
|
|
2. the sender encrypts the contents using that session key (using a symmetric
|
|
|
|
|
cipher)
|
|
|
|
|
3. the sender encrypts the session key using the recipient's _public_ PGP key
|
|
|
|
|
4. the sender sends both the encrypted contents and the encrypted session key
|
|
|
|
|
to the recipient
|
|
|
|
@ -162,10 +164,10 @@ not to an impostor (Eve). In PGP, this certainty is called "key validity:"
|
|
|
|
|
- **Validity: full** -- means we are pretty sure this key belongs to Alice
|
|
|
|
|
- **Validity: marginal** -- means we are *somewhat* sure this key belongs to
|
|
|
|
|
Alice
|
|
|
|
|
- **Validity: uknown** -- means there is no assurance at all that this key
|
|
|
|
|
- **Validity: unknown** -- means there is no assurance at all that this key
|
|
|
|
|
belongs to Alice
|
|
|
|
|
|
|
|
|
|
#### Web of Trust (WoT) vs. Trust on First Use (TOFU)
|
|
|
|
|
#### Web of Trust (WOT) vs. Trust on First Use (TOFU)
|
|
|
|
|
|
|
|
|
|
PGP incorporates a trust delegation mechanism known as the "Web of Trust." At
|
|
|
|
|
its core, this is an attempt to replace the need for centralized Certification
|
|
|
|
@ -406,7 +408,7 @@ The command is (put the full key fingerprint instead of `[fpr]`):
|
|
|
|
|
|
|
|
|
|
$ gpg --quick-add-uid [fpr] 'Alice Engineer <allie@example.net>'
|
|
|
|
|
|
|
|
|
|
You can review the IDs you've already added using:
|
|
|
|
|
You can review the UIDs you've already added using:
|
|
|
|
|
|
|
|
|
|
$ gpg --list-key [fpr] | grep ^uid
|
|
|
|
|
|
|
|
|
@ -434,8 +436,8 @@ be using for day-to-day work. We create 2048-bit keys because a lot of
|
|
|
|
|
specialized hardware (we'll discuss this further) does not handle larger keys,
|
|
|
|
|
but also for pragmatic reasons. If we ever find ourselves in a world where
|
|
|
|
|
2048-bit RSA keys are not considered good enough, it will be because of
|
|
|
|
|
fundamental problems with the RSA protocol and longer 4096-bit keys will not
|
|
|
|
|
make much difference.
|
|
|
|
|
fundamental breakthroughs in computing or mathematics and therefore longer
|
|
|
|
|
4096-bit keys will not make much difference.
|
|
|
|
|
|
|
|
|
|
#### Create the subkeys
|
|
|
|
|
|
|
|
|
@ -445,7 +447,7 @@ To create the subkeys, run:
|
|
|
|
|
$ gpg --quick-add-key [fpr] rsa2048 sign
|
|
|
|
|
|
|
|
|
|
You can also create the Authentication key, which will allow you to use your
|
|
|
|
|
PGP key for ssh purposes (covered in other guides):
|
|
|
|
|
PGP key for ssh purposes:
|
|
|
|
|
|
|
|
|
|
$ gpg --quick-add-key [fpr] rsa2048 auth
|
|
|
|
|
|
|
|
|
@ -467,8 +469,8 @@ you've created, as this just litters keyservers with useless data.)
|
|
|
|
|
|
|
|
|
|
$ gpg --send-key [fpr]
|
|
|
|
|
|
|
|
|
|
If this command does not succeed, you can try specifying a keyserver on a port
|
|
|
|
|
that is most likely to work:
|
|
|
|
|
If this command does not succeed, you can try specifying the keyserver on a
|
|
|
|
|
port that is most likely to work:
|
|
|
|
|
|
|
|
|
|
$ gpg --keyserver hkp://pgp.mit.edu:80 --send-key [fpr]
|
|
|
|
|
|
|
|
|
@ -486,7 +488,7 @@ marks them as revoked -- making them stand out even more.
|
|
|
|
|
|
|
|
|
|
That said, if you participate in software development on a public project, all
|
|
|
|
|
of the above information is already public record, and therefore making it
|
|
|
|
|
additionally available via a keyserver record does not result in net loss in
|
|
|
|
|
additionally available via keyservers does not result in a net loss in
|
|
|
|
|
privacy.
|
|
|
|
|
|
|
|
|
|
##### Upload your public key to GitHub
|
|
|
|
@ -496,14 +498,15 @@ your key following the instructions they have provided:
|
|
|
|
|
|
|
|
|
|
- [Adding a PGP key to your GitHub account](https://help.github.com/articles/adding-a-new-gpg-key-to-your-github-account/)
|
|
|
|
|
|
|
|
|
|
To generate the public key file to paste in, just run:
|
|
|
|
|
To generate the public key output suitable to paste in, just run:
|
|
|
|
|
|
|
|
|
|
$ gpg --export --armor [fpr]
|
|
|
|
|
|
|
|
|
|
#### Set up a refresh cronjob
|
|
|
|
|
|
|
|
|
|
You will need to regularly refresh the public keys on your keyring in order to
|
|
|
|
|
get the latest changes on other people's keys.
|
|
|
|
|
get the latest changes on other people's keys. You can set up a cronjob to do
|
|
|
|
|
that:
|
|
|
|
|
|
|
|
|
|
$ crontab -e
|
|
|
|
|
|
|
|
|
@ -531,9 +534,10 @@ accidentally leaked. Private keys are tasty targets for malicious actors -- we
|
|
|
|
|
know this from several successful malware attacks that scanned users' home
|
|
|
|
|
directories and uploaded any private key content found there.
|
|
|
|
|
|
|
|
|
|
It would be very damaging to a developer to have their PGP keys stolen -- in
|
|
|
|
|
the Free Software world this is often tantamount to identity theft. Removing
|
|
|
|
|
private keys from your home directory helps protect you from such events.
|
|
|
|
|
It would be very damaging for any developer to have their PGP keys stolen --
|
|
|
|
|
in the Free Software world this is often tantamount to identity theft.
|
|
|
|
|
Removing private keys from your home directory helps protect you from such
|
|
|
|
|
events.
|
|
|
|
|
|
|
|
|
|
#### Back up your GnuPG directory
|
|
|
|
|
|
|
|
|
@ -571,8 +575,8 @@ You should now test to make sure it still works:
|
|
|
|
|
If you don't get any errors, then you should be good to go. Unmount the USB
|
|
|
|
|
drive, distinctly label it so you don't blow it away next time you need to use
|
|
|
|
|
a random USB drive, and put in a safe place -- but not too far away, because
|
|
|
|
|
you'll need to use it every now and again for things like adding identities,
|
|
|
|
|
extra subkeys, or adding signatures to other people's keys.
|
|
|
|
|
you'll need to use it every now and again for things like editing identities,
|
|
|
|
|
adding or revoking subkeys, or signing other people's keys.
|
|
|
|
|
|
|
|
|
|
#### Remove the master key
|
|
|
|
|
|
|
|
|
@ -734,7 +738,7 @@ To configure your smartcard, you will need to use the GnuPG menu system, as
|
|
|
|
|
there are no convenient command-line switches:
|
|
|
|
|
|
|
|
|
|
$ gpg --card-edit
|
|
|
|
|
...
|
|
|
|
|
[...omitted...]
|
|
|
|
|
gpg/card> admin
|
|
|
|
|
Admin commands are allowed
|
|
|
|
|
gpg/card> passwd
|
|
|
|
@ -752,7 +756,7 @@ information about your smartcard should you lose it).
|
|
|
|
|
#### Moving the subkeys to your smartcard
|
|
|
|
|
|
|
|
|
|
Exit the card menu (using "q") and save all changes. Next, let's move your
|
|
|
|
|
subkeys onto the smartcard. You will need both your key passphrase and the
|
|
|
|
|
subkeys onto the smartcard. You will need both your PGP key passphrase and the
|
|
|
|
|
admin PIN of the card for most operations. Remember, that `[fpr]` stands for
|
|
|
|
|
the full 40-character fingerprint of your key.
|
|
|
|
|
|
|
|
|
@ -794,7 +798,7 @@ The output should be subtly different:
|
|
|
|
|
[ultimate] (2) Alice Engineer <allie@example.net>
|
|
|
|
|
|
|
|
|
|
Notice the `*` that is next to the `ssb` line corresponding to the key -- it
|
|
|
|
|
indicates that the key is currently "selected". It works as a toggle, meaning
|
|
|
|
|
indicates that the key is currently "selected." It works as a toggle, meaning
|
|
|
|
|
that if you type `key 1` again, the `*` will disappear and the key will not be
|
|
|
|
|
selected any more.
|
|
|
|
|
|
|
|
|
@ -806,7 +810,7 @@ Now, let's move that key onto the smartcard:
|
|
|
|
|
Your selection? 2
|
|
|
|
|
|
|
|
|
|
Since it's our **[E]** key, it makes sense to put it into the Encryption slot.
|
|
|
|
|
When you submit your selection, you will be prompted first for your key
|
|
|
|
|
When you submit your selection, you will be prompted first for your PGP key
|
|
|
|
|
passphrase, and then for the admin PIN. If the command returns without an
|
|
|
|
|
error, your key has been moved.
|
|
|
|
|
|
|
|
|
@ -831,7 +835,7 @@ making sure first to unselect `key 2`. Once you're done, choose "q":
|
|
|
|
|
gpg> q
|
|
|
|
|
Save changes? (y/N) y
|
|
|
|
|
|
|
|
|
|
Saving the changes will remove the keys you moved to the card from your home
|
|
|
|
|
Saving the changes will delete the keys you moved to the card from your home
|
|
|
|
|
directory (but it's okay, because we have them in our backups should we need
|
|
|
|
|
to do this again for a replacement smartcard).
|
|
|
|
|
|
|
|
|
@ -848,8 +852,8 @@ the output:
|
|
|
|
|
ssb> rsa2048 2017-12-06 [E]
|
|
|
|
|
ssb> rsa2048 2017-12-06 [S]
|
|
|
|
|
|
|
|
|
|
The `>` in the `ssb>` output indicates that the subkey is only available on a
|
|
|
|
|
smartcard. If you go back into your secret keys directory and look at the
|
|
|
|
|
The `>` in the `ssb>` output indicates that the subkey is only available on
|
|
|
|
|
the smartcard. If you go back into your secret keys directory and look at the
|
|
|
|
|
contents there, you will notice that the `.key` files there have been replaced
|
|
|
|
|
with stubs:
|
|
|
|
|
|
|
|
|
@ -875,7 +879,7 @@ your digital developer identity!
|
|
|
|
|
|
|
|
|
|
### Other common GnuPG operations
|
|
|
|
|
|
|
|
|
|
Here is a quick reference for various common operations you'll need to do with
|
|
|
|
|
Here is a quick reference for some common operations you'll need to do with
|
|
|
|
|
your PGP key.
|
|
|
|
|
|
|
|
|
|
In all of the below commands, the `[fpr]` is your key fingerprint.
|
|
|
|
@ -892,7 +896,8 @@ directory and tell GnuPG to use that as its home:
|
|
|
|
|
$ gpg --list-secret-keys
|
|
|
|
|
|
|
|
|
|
You want to make sure that you see `sec` and not `sec#` in the output (the `#`
|
|
|
|
|
means the key is not available).
|
|
|
|
|
means the key is not available and you're still using your regular home
|
|
|
|
|
directory location).
|
|
|
|
|
|
|
|
|
|
##### Updating your regular GnuPG working directory
|
|
|
|
|
|
|
|
|
@ -908,12 +913,12 @@ The master key we created has the default expiration date of 2 years from the
|
|
|
|
|
date of creation. This is done both for security reasons and to make obsolete
|
|
|
|
|
keys eventually disappear from keyservers.
|
|
|
|
|
|
|
|
|
|
To extend the expiry date on your key by another year, just run:
|
|
|
|
|
To extend the expiration on your key by a year from current date, just run:
|
|
|
|
|
|
|
|
|
|
$ gpg --quick-set-expire [fpr] 1y
|
|
|
|
|
|
|
|
|
|
You can also use a specific date if that is easier to remember (e.g. your
|
|
|
|
|
birthday, Cinco de Mayo, or Canada Day):
|
|
|
|
|
birthday, January 1st, or Canada Day):
|
|
|
|
|
|
|
|
|
|
$ gpg --quick-set-expire [fpr] 2020-07-01
|
|
|
|
|
|
|
|
|
@ -967,13 +972,13 @@ for trees and blobs that have changed with each commit, so as not to
|
|
|
|
|
re-checksum the entire tree unnecessarily if only a small part of it was
|
|
|
|
|
touched.
|
|
|
|
|
|
|
|
|
|
Then it calculates and stores the checksum of the toplevel directory, which
|
|
|
|
|
will inevitably be different if any part of the repository has changed.
|
|
|
|
|
Then it calculates and stores the checksum of the toplevel tree, which will
|
|
|
|
|
inevitably be different if any part of the repository has changed.
|
|
|
|
|
|
|
|
|
|
##### Commit hashes
|
|
|
|
|
|
|
|
|
|
Once the tree hash has been created, git will calculate the commit hash, which
|
|
|
|
|
will list the following information about the repository and the change being
|
|
|
|
|
will include the following information about the repository and the change being
|
|
|
|
|
made:
|
|
|
|
|
|
|
|
|
|
- the checksum hash of the tree
|
|
|
|
@ -1011,13 +1016,13 @@ effectively assure you of the following:
|
|
|
|
|
- what the state of their repository was at the time of signing:
|
|
|
|
|
- the tag includes the hash of the commit
|
|
|
|
|
- the commit hash includes the hash of the toplevel tree
|
|
|
|
|
- which includes hashes of all files and subtrees
|
|
|
|
|
- which includes hashes of all files, contents, and subtrees
|
|
|
|
|
- it also includes all information about authorship
|
|
|
|
|
- including exact times when changes were made
|
|
|
|
|
|
|
|
|
|
When you clone a git repository and verify a signed tag, that gives you
|
|
|
|
|
assurances that _all contents in the repository, including all of its
|
|
|
|
|
history, are exactly the same as the contents of the repository on the
|
|
|
|
|
cryptographic assurance that _all contents in the repository, including all of
|
|
|
|
|
its history, are exactly the same as the contents of the repository on the
|
|
|
|
|
developer's computer at the time of signing_.
|
|
|
|
|
|
|
|
|
|
#### Signed commits
|
|
|
|
@ -1036,7 +1041,7 @@ to be enabled on the server receiving the push before it does anything useful.
|
|
|
|
|
As we saw above, PGP-signing a git object gives verifiable information about
|
|
|
|
|
the developer's git tree, but not about their *intent* for that tree.
|
|
|
|
|
|
|
|
|
|
For example, you can be working on an experimental branch in your repository
|
|
|
|
|
For example, you can be working on an experimental branch in your own git fork
|
|
|
|
|
trying out a promising cool feature, but after you submit your work for
|
|
|
|
|
review, someone finds a nasty bug in your code. Since your commits are
|
|
|
|
|
properly signed, someone can take the branch containing your nasty bug and
|
|
|
|
@ -1045,15 +1050,15 @@ in production. Since the commit is properly signed with your key, everything
|
|
|
|
|
looks legitimate and your reputation is questioned when the bug is discovered.
|
|
|
|
|
|
|
|
|
|
Ability to enforce PGP-signatures during `git push` was added in order to
|
|
|
|
|
enforce the *intent* of the commit, and not merely certify what the commit is.
|
|
|
|
|
enforce the *intent* of the commit, and not merely certify its contents.
|
|
|
|
|
|
|
|
|
|
#### Configure git to use your PGP key
|
|
|
|
|
|
|
|
|
|
If you only have one secret key in your keyring, then you don't really need to
|
|
|
|
|
do anything extra, as it becomes your default key.
|
|
|
|
|
|
|
|
|
|
However, if you happen to have multiple keys, you can tell git which key
|
|
|
|
|
should be used (`[fpr]` is the fingerprint of your key):
|
|
|
|
|
However, if you happen to have multiple secret keys, you can tell git which
|
|
|
|
|
key should be used (`[fpr]` is the fingerprint of your key):
|
|
|
|
|
|
|
|
|
|
$ git config --global user.signingKey [fpr]
|
|
|
|
|
|
|
|
|
@ -1070,13 +1075,13 @@ To create a signed tag, simply pass the `-s` switch to the tag command:
|
|
|
|
|
|
|
|
|
|
Our recommendation is to always sign git tags, as this allows other developers
|
|
|
|
|
to ensure that the git repository they are working with has not been
|
|
|
|
|
maliciously altered (e.g. to introduce backdoors).
|
|
|
|
|
maliciously altered (e.g. in order to introduce backdoors).
|
|
|
|
|
|
|
|
|
|
##### How to verify signed tags
|
|
|
|
|
|
|
|
|
|
To verify a signed tag, simply pass the `-v` switch to the tag command:
|
|
|
|
|
To verify a signed tag, simply use the `verify-tag` command:
|
|
|
|
|
|
|
|
|
|
$ git tag -v [tagname]
|
|
|
|
|
$ git verify-tag [tagname]
|
|
|
|
|
|
|
|
|
|
If you are verifying someone else's git tag, then you will need to import
|
|
|
|
|
their PGP key. Please refer to the "Trusted Team communication" document in
|
|
|
|
@ -1119,7 +1124,7 @@ incorporate them into your workflow. Many projects use signed commits as a
|
|
|
|
|
sort of "Committed-by:" line equivalent that records code provenance -- the
|
|
|
|
|
signatures are rarely verified by others except when tracking down project
|
|
|
|
|
history. In a sense, signed commits are used for "tamper evidence," and not to
|
|
|
|
|
"tamper-proof" the repository.
|
|
|
|
|
"tamper-proof" the git workflow.
|
|
|
|
|
|
|
|
|
|
To create a signed commit, you just need to pass the `-S` flag to the `git
|
|
|
|
|
commit` command (it's capital `-S` due to collision with another flag):
|
|
|
|
@ -1139,7 +1144,7 @@ To verify a single commit you can use `verify-commit`:
|
|
|
|
|
You can also look at repository logs and request that all commit signatures
|
|
|
|
|
are verified and shown:
|
|
|
|
|
|
|
|
|
|
$ git log --pretty=short --show-signatures
|
|
|
|
|
$ git log --pretty=short --show-signature
|
|
|
|
|
|
|
|
|
|
##### Verifying commits during git merge
|
|
|
|
|
|
|
|
|
@ -1152,7 +1157,7 @@ the `-S` flag):
|
|
|
|
|
Note, that the merge will fail if there is even one commit that is not signed
|
|
|
|
|
or does not pass verification. As it is often the case, technology is the easy
|
|
|
|
|
part -- the human side of the equation is what makes adopting strict commit
|
|
|
|
|
signing difficult.
|
|
|
|
|
signing for your project difficult.
|
|
|
|
|
|
|
|
|
|
##### If your project uses mailing lists for patch management
|
|
|
|
|
|
|
|
|
@ -1171,13 +1176,13 @@ You can tell git to always sign commits:
|
|
|
|
|
|
|
|
|
|
git config --global commit.gpgSign true
|
|
|
|
|
|
|
|
|
|
Or you can train your muscle memory to always pass the -S flag to all git
|
|
|
|
|
commit operations.
|
|
|
|
|
Or you can train your muscle memory to always pass the `-S` flag to all `git
|
|
|
|
|
commit` operations (this includes `--amend`).
|
|
|
|
|
|
|
|
|
|
#### Configure gpg-agent options
|
|
|
|
|
|
|
|
|
|
The GnuPG agent is a helper tool that will start automatically whenever you
|
|
|
|
|
use the `gpg` command and run on the background with the purpose of caching
|
|
|
|
|
use the `gpg` command and run in the background with the purpose of caching
|
|
|
|
|
the private key passphrase. This way you only have to unlock your key once to
|
|
|
|
|
use it repeatedly (very handy if you need to sign a bunch of git operations in
|
|
|
|
|
an automated script without having to continuously retype your passphrase).
|
|
|
|
@ -1215,23 +1220,23 @@ Then, add this to your `.bashrc`:
|
|
|
|
|
|
|
|
|
|
export SSH_AUTH_SOCK=$(gpgconf --list-dirs agent-ssh-socket)
|
|
|
|
|
|
|
|
|
|
You will need to kill existing `gpg-agent` sessions and start a new login
|
|
|
|
|
session:
|
|
|
|
|
You will need to kill the existing `gpg-agent` process and start a new login
|
|
|
|
|
session for the changes to take effect:
|
|
|
|
|
|
|
|
|
|
$ killall gpg-agent
|
|
|
|
|
$ bash
|
|
|
|
|
$ ssh-add -L
|
|
|
|
|
|
|
|
|
|
The last command should list the SSH representation of your PGP Auth key (the
|
|
|
|
|
comment should say `cardno:XXXXXXX` at the end to indicate it's coming from
|
|
|
|
|
comment should say `cardno:XXXXXXXX` at the end to indicate it's coming from
|
|
|
|
|
the smartcard).
|
|
|
|
|
|
|
|
|
|
To enable key-based logins with ssh, just add the above output to
|
|
|
|
|
To enable key-based logins with ssh, just add the `ssh-add -L` output to
|
|
|
|
|
`~/.ssh/authorized_keys` on remote systems you log in to. Congratulations,
|
|
|
|
|
you've just made your ssh credentials extremely difficult to steal.
|
|
|
|
|
|
|
|
|
|
As a bonus, you can get other people's PGP-based ssh keys from public
|
|
|
|
|
keyservers, should you need to grant them ssh-based access to anything:
|
|
|
|
|
keyservers, should you need to grant them ssh access to anything:
|
|
|
|
|
|
|
|
|
|
$ gpg --export-ssh-key [keyid]
|
|
|
|
|
|
|
|
|
@ -1277,19 +1282,19 @@ The most widely known mechanisms for 2-factor authentication are:
|
|
|
|
|
SMS-based verification is easiest to configure, but has the following
|
|
|
|
|
important downsides: it is useless in areas without signal (e.g. most building
|
|
|
|
|
basements), and can be defeated if the attacker is able to intercept or divert
|
|
|
|
|
SMS messages.
|
|
|
|
|
SMS messages, for example by cloning your SIM card.
|
|
|
|
|
|
|
|
|
|
TOTP-based multi-factor authentication offers more protection than SMS, but
|
|
|
|
|
has important scaling downsides (there are only so many tokens you can add to
|
|
|
|
|
your smartphone app before finding the correct one becomes a hurdle). Plus,
|
|
|
|
|
your smartphone app before finding the correct one becomes unwieldy). Plus,
|
|
|
|
|
there's no avoiding the fact that your secret key ends up stored on the
|
|
|
|
|
smartphone itself -- which is a complex, globally connected device that may or
|
|
|
|
|
may not have been receiving timely security patches from the manufacturer.
|
|
|
|
|
|
|
|
|
|
Most importantly, neither TOTP nor SMS methods protect you from phishing
|
|
|
|
|
attacks -- if the phisher is able to obtain both your account password and
|
|
|
|
|
the 2-factor token, they can replay them on the legitimate site and gain
|
|
|
|
|
access to your account.
|
|
|
|
|
attacks -- if the phisher is able to steal both your account password and the
|
|
|
|
|
2-factor token, they can replay them on the legitimate site and gain access to
|
|
|
|
|
your account.
|
|
|
|
|
|
|
|
|
|
[Fido U2F](https://en.wikipedia.org/wiki/Universal_2nd_Factor) is a standard
|
|
|
|
|
developed specifically to provide a mechanism for 2-factor authentication
|
|
|
|
@ -1331,7 +1336,7 @@ where this functionality should be enabled are:
|
|
|
|
|
#### Configure TOTP failover, if possible
|
|
|
|
|
|
|
|
|
|
Many sites will allow you to configure multiple 2-factor mechanisms, and the
|
|
|
|
|
recommended option is:
|
|
|
|
|
recommended setup is:
|
|
|
|
|
|
|
|
|
|
- U2F token as the primary mechanism
|
|
|
|
|
- TOTP phone app as the secondary mechanism
|
|
|
|
|