├── .github └── CODEOWNERS ├── .gitignore ├── LICENSE ├── README.md ├── SECURITY.md ├── bin └── gpg-sign-notify ├── docs ├── optional.md └── troubleshooting.md ├── env.sh ├── expects ├── expect-arch.sh ├── expect-macos.sh └── expect-ubuntu.sh ├── git.sh ├── gpg.sh ├── import.sh ├── lib ├── git_conf.sh ├── gpg_agent_conf.sh ├── gpg_conf.sh ├── install.sh ├── notifications.sh ├── scdaemon.sh ├── ssh_conf.sh └── tree.sh ├── realname-and-email.sh ├── reset.sh └── ssh.sh /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @DataDog/corp-it-security 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.gpg.pub.asc 2 | *.gpg.pub.bin 3 | *.pub 4 | *.code-workspace 5 | *.sw[a-z] 6 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Trishank K Kuppusamy 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ⚠️ Repository Deprecated ⚠️ 2 | Notice: This code repository is no longer maintained or updated. The content and code are provided as-is, and may no longer be relevant or functional. 3 | 4 | For Datadog employee, see the "commit signing setup guide" in Confluence instead_ 5 | 6 | # YubiKey at Datadog 7 | 8 | - [Summary](#summary) 9 | - [Estimated burden and prerequisites](#estimated-burden-and-prerequisites) 10 | - [U2F](#u2f) 11 | - [GPG](#gpg) 12 | - [git](#git) 13 | - [SSH](#ssh) 14 | - [Reset](#reset) 15 | - [Troubleshooting](#troubleshooting) 16 | - [Optional](#optional) 17 | - [References](#references) 18 | 19 | ## Summary 20 | 21 | GPG is useful for authenticating yourself over SSH and / or GPG-signing your 22 | git commits / tags. However, without hardware like the 23 | [YubiKey](https://www.yubico.com/products/yubikey-hardware/), you would 24 | typically keep your GPG private subkeys in "plain view" on your machine, even 25 | if encrypted. That is, attackers who personally target 26 | [[1](https://www.kennethreitz.org/essays/on-cybersecurity-and-being-targeted), 27 | [2](https://bitcoingold.org/critical-warning-nov-26/), 28 | [3](https://panic.com/blog/stolen-source-code/), 29 | [4](https://www.fox-it.com/en/insights/blogs/blog/fox-hit-cyber-attack/)] you 30 | can compromise your machine can exfiltrate your (encrypted) private key, and 31 | your passphrase, in order to pretend to be you. 32 | 33 | Instead, this setup lets you store your private subkeys on your YubiKey. 34 | Actually, it gives you much stronger guarantees: you *cannot* authenticate over 35 | SSH and / or sign GPG commits / tags *without*: (1) your YubiKey plugged in and 36 | operational, (2) your YubiKey PIN, and (3) touching your YubiKey. So, even if 37 | there is malware trying to get you to sign, encrypt, or authenticate something, 38 | you would almost certainly notice, because your YubiKey will flash, asking for 39 | your attention. (There is the "[time of check to time of 40 | use](https://en.wikipedia.org/wiki/Time_of_check_to_time_of_use)" issue, 41 | but that is out of our scope.) 42 | 43 | ## Estimated burden and prerequisites 44 | 45 | About 2-3 hours. 15 minutes could save you 15% or more on cybersecurity 46 | insurance. 47 | 48 | You will need macOS with [Homebrew](https://brew.sh/) / Ubuntu / Archlinux, a password manager, and a 49 | [YubiKey 5](https://www.yubico.com/products/yubikey-hardware/). 50 | 51 | ## U2F 52 | 53 | **STRONGLY recommended:** configure U2F for 54 | [GitHub](https://help.github.com/articles/configuring-two-factor-authentication/#configuring-two-factor-authentication-using-fido-u2f) 55 | and 56 | [Google](https://support.yubico.com/hc/en-us/articles/360013717460-Using-Your-YubiKey-with-Google). 57 | 58 | ## GPG 59 | 60 | **Please read and follow all of the instructions carefully.** 61 | 62 | ```bash 63 | $ ./gpg.sh 64 | ``` 65 | 66 | (Protip: set `TEMPDIR=1` when preparing YubiKey for someone else to avoid 67 | polluting your default GPG homedir.) 68 | 69 | ## git 70 | 71 | **STRONGLY RECOMMENDED:** signing your git commits and tags. 72 | 73 | You **must** first set up [GPG](#gpg). 74 | 75 | Then, to sign git commits and tags for a _particular_ repository: 76 | 77 | ```bash 78 | $ ./git.sh /path/to/git/repository 79 | ``` 80 | 81 | Or, to sign git commits and tags for _all_ repositories: 82 | 83 | ```bash 84 | $ ./git.sh 85 | ``` 86 | 87 | ## SSH 88 | 89 | **NOT recommended** for most users. This script sets up your YubiKey as the holder of your SSH key, 90 | helping to prevent it from being leaked or stolen. The script will take control of `ssh-agent`, so 91 | it's not particularly compatible with other SSH keys - you should only run this if you intend to use 92 | this as your only SSH key on the machine you're using. 93 | 94 | With this setup, you'll need to enter a PIN to unlock the key every 24 hours and then physically touch the 95 | key when it blinks (i.e. every time you SSH or push/pull Git). If you don't touch the key, the request will 96 | timeout and you'll get an unhelpful message. 97 | 98 | This is compatible with usage on remote machines over SSH 99 | (it will set up agent forwarding to use the key remotely; touch is required on each action). 100 | 101 | You **must** have first set up [GPG](#gpg). Then: 102 | 103 | ```bash 104 | $ ./ssh.sh 105 | ``` 106 | 107 | ## Reset 108 | 109 | If you need to reset YubiKeys, you may use the following script. The script looks for every plugged YubiKey, 110 | and shows a menu to reset one specific key, or all of them. 111 | **Please read and follow all of the instructions carefully. YOU WILL NOT BE ABLE TO RETRIEVE KEYS/DATA FROM THE YUBIKEY AFTER COMPLETION.** 112 | 113 | ```bash 114 | $ ./reset.sh 115 | ``` 116 | 117 | ## Troubleshooting 118 | 119 | Go [here](docs/troubleshooting.md) for troubleshooting common issues such as unblocking a blocked card, error when pulling or pushing with git over SSH, and rebasing with git. 120 | 121 | ## Optional 122 | 123 | Go [here](docs/optional.md) for support on optional bits such as configuring a computer to use an already configured YubiKey, signing for different git repositories with different keys, Keybase, VMware Fusion, and Docker Content Trust. 124 | 125 | ## References 126 | 127 | 1. [YubiKey Handbook](https://ruimarinho.gitbooks.io/yubikey-handbook/content/openpgp/) 128 | 129 | 2. [A Git Horror Story: Repository Integrity With Signed Commits](https://mikegerwitz.com/papers/git-horror-story) 130 | 131 | 3. [Welp, there go my Git signatures](http://karl.kornel.us/2017/10/welp-there-go-my-git-signatures/) 132 | 133 | 4. [[Bitcoin-development] PSA: Please sign your git commits](https://lists.linuxfoundation.org/pipermail/bitcoin-dev/2014-May/005877.html) 134 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # Security Policy 2 | 3 | ## Reporting a Vulnerability 4 | 5 | If you believe you’ve discovered a bug in Datadog’s security, 6 | please get in touch at security@datadoghq.com and we will get back to you within 24 hours, 7 | and usually earlier. 8 | Our [PGP key](https://www.datadoghq.com/8869756E.asc.txt) is available for download in case you need to encrypt communications with us. 9 | We request that you not publicly disclose the issue until we have had a chance to address it. 10 | -------------------------------------------------------------------------------- /bin/gpg-sign-notify: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Notifies the user that they need to do something if gpg-sign takes longer than 4 | # 5 seconds 5 | 6 | # The explicit redirection of stdin is required, otherwise (in the absence of 7 | # job control) bash will bind stdin of the background process to /dev/null, 8 | # resulting in a bad signature/bad verification for good signatures. 9 | gpg "$@" = 4 thing, and the default bash on MacOS is 3, so we prioritise 17 | # compatibility. 18 | 19 | notify() { 20 | local title="$1"; shift; 21 | local body="$1"; shift; 22 | 23 | %%NOTIFICATION_NOTIFY%% 24 | } 25 | 26 | reap() { 27 | # Cancel the trap, otherwise it will run again when another child exits 28 | trap "" SIGCHLD 29 | 30 | if kill -0 $gpg_pid 2>/dev/null; then 31 | # GPG is still running; we assume it's waiting for input 32 | notify "Git wants to sign a commit!" "Touch your YubiKey after submitting the User PIN" 33 | # Make sure we exit with the gpg status 34 | wait $gpg_pid 35 | else 36 | # GPG is not running; kill the sleep process if it's still there and exit 37 | kill $sleep_pid 38 | wait $gpg_pid 39 | exit_status=$? 40 | exit $exit_status 41 | fi 42 | } 43 | 44 | # Run reap as soon as _any_ child exits 45 | trap reap SIGCHLD 46 | 47 | # Wait for all children to exit 48 | wait 49 | -------------------------------------------------------------------------------- /docs/optional.md: -------------------------------------------------------------------------------- 1 | # Optional 2 | 3 | - [Configure another computer to use a configured YubiKey](#configure-another-computer-to-use-a-configured-yubikey) 4 | - [Signing for different git repositories with different keys](#signing-for-different-git-repositories-with-different-keys) 5 | - [Keybase](#keybase) 6 | - [VMware Fusion](#vmware-fusion) 7 | - [Docker Content Trust](#docker-content-trust) 8 | 9 | ## Configure another computer to use a configured YubiKey 10 | 11 | You don't need to do anything extra if you have not set up GPG and SSH to your use YubiKey. 12 | 13 | Otherwise, you need to: 14 | 15 | On your previous computer: 16 | 1. Get the Yubikey GPG key ID by running `gpg --list-keys`, in the following example the key ID is `4E09860E71D948019BD426D5D099A306DBECDF1B` 17 | ![image](https://user-images.githubusercontent.com/4062883/148108677-ab3a04b4-8ef6-4ba0-b78e-ec9c127857e3.png) 18 | 2. Get a copy of your Yubikey GPG public key (this might have been backed up in your password manager) by running `gpg --export --armor key_id > /path/to/pubkey.asc`, so in our example it will be `gpg --export --armor 4E09860E71D948019BD426D5D099A306DBECDF1B > pubkey.asc`. 19 | 3. (Optional) Write your copy of your GPG public key stored in your password manager to disk if not already there (e.g., to `/path/to/pubkey.asc`). 20 | 21 | On the new computer: 22 | 1. Get the pubkey.asc file on the disk by downloading it 23 | 2. Run [`./import.sh -p /path/to/pubkey.asc -i key_id`](../import.sh). In our example, `./import.sh -p ~/pubkey.asc -i 4E09860E71D948019BD426D5D099A306DBECDF1B` 24 | 3. You will be prompted several times: 25 | 1. To install dependencies (required), type yes, and press enter 26 | 2. To configure the Yubikey GPG key for commit signing (or not), type yes or no, and press enter 27 | 3. To use the Yubikey GPG key for SSH connections (or not), type yes or no, and press enter 28 | 29 | ## Signing for different git repositories with different keys 30 | 31 | The script can setup your Git installation so that all your commits and tags 32 | will be signed by default with the key contained in the YubiKey. We 33 | **strongly** recommend that you turn on this option. If you have done so, 34 | please stop reading here. 35 | 36 | Otherwise, one reason for declining this option may be that you wish to sign 37 | for different repositories with different keys. There are a few ways to handle 38 | this. Perhaps the simplest is to let the script assign the YubiKey to all git 39 | repositories, and then use `git config --local` to override `user.signingkey` 40 | for different repositories. 41 | 42 | Alternatively, let us say you use your personal key for open source projects, 43 | and the one in the YubiKey for Datadog proprietary code. One possible 44 | solution is to setup git aliases. First, make sure signing is turned on 45 | globally: 46 | 47 | ```sh 48 | git config --global commit.gpgsign true 49 | git config --global tag.forceSignAnnotated true 50 | ``` 51 | 52 | Then you can tell git to use a specific key by default, depending on which one 53 | is the one you use the most: 54 | 55 | ```sh 56 | git config --global user.signingkey 57 | ``` 58 | 59 | You can alias the `commit` command to override the default key and use another 60 | one to sign that specific commit: 61 | 62 | ```sh 63 | git config --global alias.dd-commit '-c user.signingkey= commit' 64 | git config --global alias.dd-tag '-c user.signingkey= tag' 65 | ``` 66 | 67 | With this setup, every time you do `git commit` or `git tag`, the default key 68 | will be used while `git dd-commit` and `git dd-tag` will use the one in the 69 | YubiKey. 70 | 71 | ## Keybase 72 | 73 | Optional: verify public key on Keybase. You can now do this using the 74 | command-line option, with only `curl` and `gpg`, and without installing any 75 | Keybase app, or uploading an encrypted copy of your private key. For example, 76 | see this [profile](https://keybase.io/trishankdatadog). 77 | 78 | If you have the [Keybase application](https://keybase.io/docs/the_app/install_macos) 79 | installed, you can import your YubiKey public key like this: 80 | 81 | ```bash 82 | $ keybase pgp select 83 | 84 | # If you already have a primary Keybase public key, use the --multi flag to import another 85 | $ keybase pgp select --multi 86 | ``` 87 | 88 | See `keybase pgp help select` for more detail. 89 | 90 | ## VMware Fusion 91 | 92 | Optional: using YubiKey inside GNU/Linux running on VMware Fusion. 93 | 94 | 1. Shut down your VM, find its .vmx file, edit the file to the [add the 95 | following 96 | line](https://www.symantec.com/connect/blogs/enabling-hid-devices-such-usb-keyboards-barcode-scanners-vmware), 97 | and then reboot it: `usb.generic.allowHID = "TRUE"` 98 | 99 | 2. Connect your YubiKey to the VM once you have booted and logged in. 100 | 101 | 3. Install libraries for smart card: 102 | 103 | 1. Ubuntu 17.10: `apt install scdaemon` 104 | 105 | 2. Fedora 27: `dnf install pcsc-lite pcsc-lite-ccid` 106 | 107 | 4. Import your public key (see Step 13). 108 | 109 | 5. Set ultimate trust for your key (see Step 20). 110 | 111 | 6. Configure GPG (see Step 22). 112 | 113 | 7. Test the keys (see Step 23). On Fedora, make sure to replace `gpg` with 114 | `gpg2`. 115 | 116 | 8. Use the absolutely terrible kludge in Table 1 to make SSH work. 117 | 118 | 9. Spawn a new shell, and test GitHub SSH (see Step 26). 119 | 120 | 10. Test Git signing (see Step 28). On Fedora, make sure to replace `gpg` with 121 | `gpg2`: `git config --global gpg.program gpg2` 122 | 123 | ```sh 124 | # gpg-ssh hack 125 | gpg-connect-agent killagent /bye 126 | eval $(gpg-agent --daemon --enable-ssh-support --sh) 127 | ssh-add -l 128 | ``` 129 | 130 | **Table 1**: Add these lines to `~/.bashrc`. 131 | 132 | ## Docker Content Trust 133 | 134 | Optional: using YubiKey to store the root role key for Docker Notary. 135 | 136 | 1. Assumption: you are running all of the following under [Fedora 137 | 27](#vmware-fusion). 138 | 139 | 2. Install prerequisites: `dnf install golang yubico-piv-tool` 140 | 141 | 3. Set [GOPATH](https://golang.org/doc/code.html#GOPATH) (make sure to update 142 | PATH too), and spawn a new `bash` shell. 143 | 144 | 4. Check out the Notary source code: `go get 145 | github.com/theupdateframework/notary` 146 | 147 | 5. Patch source code to [point to correct location of shared library on 148 | Fedora](https://github.com/theupdateframework/notary/pull/1286). 149 | 150 | 1. `cd ~/go/src/go get github.com/theupdateframework/notary` 151 | 152 | 2. `git pull https://github.com/trishankatdatadog/notary.git trishank_kuppusamy/fedora-pkcs11` 153 | 154 | 6. [Build and install](https://github.com/theupdateframework/notary/pull/1285) 155 | the Notary client: `go install -tags pkcs11 156 | github.com/theupdateframework/notary/cmd/notary` 157 | 158 | 7. Add the lines in Table 2 to your `bash` profile, and spawn a new shell. 159 | 160 | 8. Try listing keys (there should be no signing keys as yet): 161 | 162 | 1. `dockernotary key list -D` 163 | 164 | 2. If you see the line `"DEBU[0000] Initialized PKCS11 library 165 | /usr/lib64/libykcs11.so.1 and started HSM session"`, then we are in 166 | business. 167 | 168 | 3. Otherwise, if you see the line `"DEBU[0000] No yubikey found, using 169 | alternative key storage: found library /usr/lib64/libykcs11.so.1, but 170 | initialize error pkcs11: 0x6: CKR_FUNCTION_FAILED"`, then you probably 171 | need to `gpgconf --kill scdaemon` ([see this 172 | issue](https://github.com/theupdateframework/notary/issues/1006)), 173 | and try again. 174 | 175 | 9. Generate the root role key ([can be reused across multiple Docker 176 | repositories](https://github.com/theupdateframework/notary/blame/a41821feaf59a28c1d8f78799300d26f8bdf8b0d/docs/best_practices.md#L91-L95)), 177 | and export it to both YubiKey, and keep a copy on disk: 178 | 179 | 1. Choose a strong passphrase. 180 | 181 | 2. `dockernotary key generate -D` 182 | 183 | 3. Commit passphrase to memory and / or offline storage. 184 | 185 | 4. Try listing keys again, you should now see a copy of the same private 186 | key in two places (disk, and YubiKey). 187 | 188 | 5. Backup private key in `~/.docker/trust/private/KEYID.key` unto offline, 189 | encrypted, long-term storage. 190 | 191 | 6. [Securely 192 | delete](https://www.gnu.org/software/coreutils/manual/html_node/shred-invocation.html) 193 | this private key on disk. 194 | 195 | 7. Now if you list the keys again, you should see the private key only on 196 | YubiKey. 197 | 198 | 10. Link the yubikey library so that the prebuilt docker client can find it: 199 | `sudo ln -s /usr/lib64/libykcs11.so.1 /usr/local/lib/libykcs11.so` 200 | 201 | 11. Later, when you want Docker to use the root role key on your YubiKey: 202 | 203 | 1. When you push an image, you may have to kill `scdaemon` (in a separate 204 | shell) right after Docker pushes, but right before Docker uses the root 205 | role key on your YubiKey, and generates a new targets key for the 206 | repository. 207 | 208 | 2. Use `docker -D` to find out exactly when to do this. 209 | 210 | 3. This is annoying, but it works. 211 | 212 | ```sh 213 | # docker notary stuff 214 | alias dockernotary="notary -s https://notary.docker.io -d ~/.docker/trust" 215 | # always be using content trust 216 | export DOCKER_CONTENT_TRUST=1 217 | ``` 218 | 219 | **Table 2**: Add these lines to `~/.bashrc`. 220 | -------------------------------------------------------------------------------- /docs/troubleshooting.md: -------------------------------------------------------------------------------- 1 | # Troubleshooting 2 | 3 | - [Blocked card](#blocked-card) 4 | - [Error with git pull/fetch or when using SSH](#error-with-git-pullfetch-or-when-using-ssh) 5 | - [git rebase](#git-rebase) 6 | - [No PyUSB backend detected](#no-pyusb-backend-detected) 7 | - [Bad substitution](#bad-substitution) 8 | - [Operation-not-supported-by-device-error](#operation-not-supported-by-device-error) 9 | 10 | ## Blocked card 11 | 12 | If you are blocked out of using GPG because you entered your PIN wrong too 13 | many times (3x by default), **don’t panic**: just [follow the 14 | instructions](https://github.com/ruimarinho/yubikey-handbook/blob/master/openpgp/troubleshooting/gpg-failed-to-sign-the-data.md) 15 | here. Make sure you enter your **Admin PIN** correctly within 3x, otherwise 16 | your current keys are blocked, and you must reset your YubiKey to use new keys. 17 | 18 | ## Error with git pull/fetch or when using SSH 19 | 20 | If you try to ssh or git pull/fetch and you have the following error: 21 | ``` 22 | sign_and_send_pubkey: signing failed: agent refused operation 23 | ``` 24 | You are probably mistyping your PIN. To verify it, you can: 25 | ``` 26 | gpg --card-edit 27 | gpg/card> verify 28 | ... 29 | PIN retry counter : 3 0 3 # if it is the right PIN 30 | PIN retry counter : 2 0 3 # if it is a wrong PIN 31 | ... 32 | ``` 33 | If your PIN is wrong, try 123456, which is the default PIN. 34 | If it still fails, reset your PIN: 35 | ``` 36 | gpg --card-edit 37 | gpg/card> admin 38 | gpg/card> passwd 39 | gpg: OpenPGP card no. D2760001240102010006055532110000 detected 40 | 41 | Your selection? 1 42 | PIN changed. 43 | 44 | 1 - change PIN 45 | 2 - unblock PIN 46 | 3 - change Admin PIN 47 | 4 - set the Reset Code 48 | Q - quit 49 | 50 | Your selection? q 51 | ``` 52 | 53 | ## git rebase 54 | 55 | If you are using the FIPS model, you can perform signing operations for 15 56 | seconds after touching your YubiKey before having to touch it again. When 57 | running a large git rebase, you may have to touch your YubiKey multiple times. 58 | If the rebase seems to hang and the YubiKey flashes, it means you need to touch 59 | it again. 60 | 61 | If you are still having issues when rebasing, you might consider using 62 | the `--no-gpg-sign` flag as a [workaround](https://github.com/DataDog/yubikey/issues/19). 63 | 64 | ## No PyUSB backend detected 65 | 66 | If you see the following error while running `./gpg.sh`: 67 | 68 | ``` 69 | Usage: ykman [OPTIONS] COMMAND [ARGS]... 70 | Try "ykman -h" for help. 71 | 72 | Error: No PyUSB backend detected! 73 | ``` 74 | 75 | Hit CTRL-C to exit the script (if the script has not already exited) and [reinstall](https://github.com/Yubico/yubikey-manager/issues/185#issuecomment-446379356) `libsub`, then try again: `brew reinstall libusb` 76 | 77 | ## Bad substitution 78 | 79 | If you see the following error while running `./gpg.sh`: 80 | 81 | ``` 82 | OS detected is macos 83 | Is it correct ? (y|N) Y 84 | env.sh: line 34: ${OS,,}: bad substitution 85 | ``` 86 | 87 | Run `brew install bash`. The script is using a feature not that is not supported by the old macOS bash. 88 | 89 | ## Operation not supported by device error 90 | 91 | This manifests as PIN Entry dialog prompting to insert the card in a perpetual loop. 92 | 93 | You may also see: 94 | 95 | ```shell 96 | gpg --card-status 97 | gpg: selecting card failed: Operation not supported by device 98 | gpg: OpenPGP card not available: Operation not supported by device 99 | ``` 100 | 101 | Run [./lib/scdaemon.sh](../lib/scdaemon.sh). 102 | 103 | ## GPG error on maOS M1 104 | 105 | If you see the following running gpg related scripts: 106 | ``` 107 | gpg 108 | dyld[23790]: Library not loaded: /opt/homebrew/opt/libgpg-error/lib/libgpg-error.0.dylib 109 | Referenced from: /opt/homebrew/Cellar/gnupg/2.3.4/bin/gpg 110 | Reason: tried: '/opt/homebrew/opt/libgpg-error/lib/libgpg-error.0.dylib' (mach-o file, but is an incompatible architecture (have 'x86_64', need 'arm64e')), '/usr/local/lib/libgpg-error.0.dylib' (no such file), '/usr/lib/libgpg-error.0.dylib' (no such file), '/opt/homebrew/Cellar/libgpg-error/1.44/lib/libgpg-error.0.dylib' (mach-o file, but is an incompatible architecture (have 'x86_64', need 'arm64e')), '/usr/local/lib/libgpg-error.0.dylib' (no such file), '/usr/lib/libgpg-error.0.dylib' (no such file) 111 | [1] 23790 abort gpg 112 | ``` 113 | 114 | Use `brew reinstall` to reinstall all of the GPG dependencies as well as GPG itself. 115 | -------------------------------------------------------------------------------- /env.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | function which_flavour { 4 | if [[ -f /etc/os-release ]]; then 5 | detected="$(grep '^ID=' /etc/os-release | cut -d= -f2)" 6 | fi 7 | echo "$detected" 8 | } 9 | 10 | case "$OSTYPE" in 11 | darwin*) 12 | OS='macos' 13 | ;; 14 | linux*) 15 | OS=$(which_flavour) 16 | ;; 17 | *) 18 | OS="not detected" 19 | ;; 20 | esac 21 | 22 | echo "OS detected is $OS" 23 | 24 | case $(echo "$OS" | tr "[:upper:]" "[:lower:]") in 25 | macos) 26 | PKG_MANAGER="brew" 27 | PKG_MANAGER_ENV="" 28 | PKG_MANAGER_INSTALL="install" 29 | PKG_MANAGER_UPDATE="update" 30 | PKG_MANAGER_UPGRADE="upgrade" 31 | PKG_CHECK="brew" 32 | PKG_CHECK_ARGS="list" 33 | HOMEBREW_PREFIX=$(brew --prefix) 34 | HOMEBREW_BIN=$HOMEBREW_PREFIX/bin 35 | USER_BIN_DIR=${HOME}/.local/bin 36 | GIT=$HOMEBREW_BIN/git 37 | GPG=$HOMEBREW_BIN/gpg 38 | GPG_AGENT=$HOMEBREW_BIN/gpg-agent 39 | GPGCONF=$HOMEBREW_BIN/gpgconf 40 | YKMAN=$HOMEBREW_BIN/ykman 41 | CLIP="pbcopy" 42 | CLIP_ARGS="" 43 | PINENTRY_SETUP="${HOMEBREW_BIN}/pinentry-tty" 44 | PINENTRY="${HOMEBREW_BIN}/pinentry-mac" 45 | OPEN="open" 46 | DEPS=( 47 | "expect" 48 | "git" 49 | "gpg" 50 | "pinentry-mac" 51 | "ykman" 52 | ) 53 | set +e 54 | read -r -d '' NOTIFICATION_NOTIFY << EOF 55 | osascript -e 'display notification "'"\$body"'" with title "'"\$title"'"' 56 | EOF 57 | set -e 58 | NOTIFICATION_SCRIPT_PATH="${USER_BIN_DIR}/yubinotif" 59 | SCDAEMON_CONF="disable-ccid\nreader-port \"$(pcsctest <<< 01 | grep 'Reader 01' | awk -F ': ' '{print $2}' | head -n1)\"" 60 | export HOMEBREW_NO_AUTO_UPDATE=1 61 | ;; 62 | ubuntu|debian) 63 | PKG_MANAGER="apt" 64 | PKG_MANAGER_ENV="sudo" 65 | PKG_MANAGER_INSTALL="install" 66 | PKG_MANAGER_UPDATE="update" 67 | PKG_MANAGER_UPGRADE="install" 68 | PKG_CHECK="apt" 69 | PKG_CHECK_ARGS="show" 70 | BIN_PATH="/usr/bin" 71 | USER_BIN_DIR=${HOME}/.local/bin 72 | GIT="${BIN_PATH}/git" 73 | GPG="${BIN_PATH}/gpg" 74 | GPG_AGENT="${BIN_PATH}/gpg-agent" 75 | GPGCONF="${BIN_PATH}/gpgconf" 76 | YKMAN="${BIN_PATH}/ykman" 77 | CLIP="${BIN_PATH}/xclip" 78 | CLIP_ARGS="-selection clipboard -i" 79 | PINENTRY_SETUP="/usr/bin/pinentry-tty" 80 | PINENTRY="/usr/bin/pinentry-gnome3" 81 | OPEN="xdg-open" 82 | DEPS=( 83 | "expect" 84 | "git" 85 | "gpg" 86 | "pinentry-tty" 87 | "python3" 88 | "scdaemon" 89 | "yubikey-manager" 90 | "xclip" 91 | ) 92 | set +e 93 | read -r -d '' NOTIFICATION_NOTIFY << EOF 94 | notify-send "\$title" "\$body" 95 | EOF 96 | set -e 97 | NOTIFICATION_SCRIPT_PATH="${USER_BIN_DIR}/yubinotif" 98 | SCDAEMON_CONF="" 99 | if ! grep -rqE '^deb http://ppa.launchpad.net/yubico/stable/ubuntu' /etc/apt/sources.list.d/*.list; then 100 | sudo apt-add-repository ppa:yubico/stable 101 | fi 102 | ;; 103 | arch) 104 | PKG_MANAGER="pacman" 105 | PKG_MANAGER_ENV="sudo" 106 | PKG_MANAGER_INSTALL="-S" 107 | PKG_MANAGER_UPDATE="-Sy" 108 | PKG_MANAGER_UPGRADE="-S" 109 | PKG_CHECK="pacman" 110 | PKG_CHECK_ARGS="-Qi" 111 | BIN_PATH="/usr/bin" 112 | USER_BIN_DIR=${HOME}/.local/bin 113 | GIT="${BIN_PATH}/git" 114 | GPG="${BIN_PATH}/gpg" 115 | GPG_AGENT="${BIN_PATH}/gpg-agent" 116 | GPGCONF="${BIN_PATH}/gpgconf" 117 | YKMAN="${BIN_PATH}/ykman" 118 | CLIP="${BIN_PATH}/xclip" 119 | CLIP_ARGS="-selection clipboard -i" 120 | PINENTRY_SETUP="/usr/bin/pinentry" 121 | PINENTRY="/usr/bin/pinentry" 122 | OPEN="xdg-open" 123 | # shellcheck disable=SC2034 124 | DEPS=( 125 | "expect" 126 | "gnupg" 127 | "pinentry" 128 | "git" 129 | "yubikey-manager" 130 | "xclip" 131 | "pcsclite" 132 | ) 133 | set +e 134 | read -r -d '' NOTIFICATION_NOTIFY << EOF 135 | notify-send "\$title" "\$body" 136 | EOF 137 | set -e 138 | NOTIFICATION_SCRIPT_PATH="${USER_BIN_DIR}/yubinotif" 139 | # shellcheck disable=SC2034 140 | SCDAEMON_CONF="" 141 | ;; 142 | *) 143 | echo "Sorry, your OS is not supported" 144 | exit 1 145 | esac 146 | 147 | # Use Homebrew binaries. 148 | export PKG_MANAGER 149 | export PKG_MANAGER_ENV 150 | export PKG_MANAGER_INSTALL 151 | export PKG_MANAGER_UPDATE 152 | export PKG_MANAGER_UPGRADE 153 | export PKG_CHECK 154 | export PKG_CHECK_ARGS 155 | export GIT 156 | export GPG 157 | export GPG_AGENT 158 | export GPGCONF 159 | export YKMAN 160 | export CLIP 161 | export CLIP_ARGS 162 | export OPEN 163 | export PINENTRY_SETUP 164 | export PINENTRY 165 | export NOTIFICATION_SCRIPT_PATH 166 | export USER_BIN_DIR 167 | 168 | # Colors galore. 169 | BOLD=$(tput bold) 170 | export BOLD 171 | RED=$(tput setaf 1) 172 | export RED 173 | GREEN=$(tput setaf 2) 174 | export GREEN 175 | YELLOW=$(tput setaf 3) 176 | export YELLOW 177 | BLUE=$(tput setaf 4) 178 | export BLUE 179 | MAGENTA=$(tput setaf 5) 180 | export MAGENTA 181 | RESET=$(tput sgr0) # Reset text 182 | export RESET 183 | 184 | # SSH. 185 | export SSH_ENV="$HOME/.ssh/environment" 186 | 187 | # Folders and files. 188 | export DEFAULT_GPG_HOMEDIR=$HOME/.gnupg 189 | export DEFAULT_GPG_AGENT_CONF=$DEFAULT_GPG_HOMEDIR/gpg-agent.conf 190 | export DEFAULT_GPG_CONF=$DEFAULT_GPG_HOMEDIR/gpg.conf 191 | export DEFAULT_GPG_SCDAEMON_CONF=${DEFAULT_GPG_HOMEDIR}/scdaemon.conf 192 | 193 | # Functions. 194 | 195 | # Backup configuration in default GPG homedir, if it exists. 196 | function backup_conf { 197 | local conf 198 | local conf_backup 199 | conf="$1" 200 | 201 | if [[ -e "$conf" ]] 202 | then 203 | conf_backup=$conf.$(date +%s) 204 | if [[ -e $conf_backup ]] 205 | then 206 | echo "Unlikely for $conf_backup to exist!" 207 | exit 4 208 | else 209 | echo "Backing up $conf to $conf_backup" 210 | mv "$conf" "$conf_backup" 211 | fi 212 | else 213 | echo "$conf doesn't exist" 214 | fi 215 | } 216 | 217 | # Get the GPG keyid using the given homedir. 218 | function get_keyid { 219 | $GPG --homedir="$1" --card-status | grep 'Signature key' | cut -f2 -d: | tr -d ' ' 220 | } 221 | 222 | function vercomp { 223 | if [[ $1 == "$2" ]] 224 | then 225 | return 0 226 | fi 227 | local IFS=. 228 | # shellcheck disable=SC2206 229 | local i ver1=($1) ver2=($2) 230 | # fill empty fields in ver1 with zeros 231 | for ((i=${#ver1[@]}; i<${#ver2[@]}; i++)) 232 | do 233 | ver1[i]=0 234 | done 235 | for ((i=0; i<${#ver1[@]}; i++)) 236 | do 237 | if [[ -z ${ver2[i]} ]] 238 | then 239 | # fill empty fields in ver2 with zeros 240 | ver2[i]=0 241 | fi 242 | if ((10#${ver1[i]} > 10#${ver2[i]})) 243 | then 244 | return 1 245 | fi 246 | if ((10#${ver1[i]} < 10#${ver2[i]})) 247 | then 248 | return 2 249 | fi 250 | done 251 | return 0 252 | } 253 | 254 | function join { local IFS="$1"; shift; echo "$*"; } 255 | 256 | # https://stackoverflow.com/a/44348249 257 | function install_or_upgrade { 258 | local pkg 259 | pkg="$1" 260 | if "$PKG_CHECK" "$PKG_CHECK_ARGS" "$pkg" >/dev/null; then 261 | eval "$PKG_MANAGER_ENV" "$PKG_MANAGER" "$PKG_MANAGER_UPGRADE" "$pkg" 262 | else 263 | eval "$PKG_MANAGER_ENV" "$PKG_MANAGER" "$PKG_MANAGER_INSTALL" "$pkg" 264 | fi 265 | } 266 | 267 | function check_presence { 268 | local pkg 269 | pkg="$1" 270 | if ! "$PKG_CHECK" "$PKG_CHECK_ARGS" "$pkg" >/dev/null 2>&1; then 271 | echo "$pkg is missing, please install it" 272 | return 1 273 | fi 274 | } 275 | -------------------------------------------------------------------------------- /expects/expect-arch.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env expect 2 | # 3 | # This Expect script was generated by autoexpect on Wed Aug 1 17:10:59 2018 4 | # Expect and autoexpect were both written by Don Libes, NIST. 5 | # 6 | # Note that autoexpect does not guarantee a working script. It 7 | # necessarily has to guess about certain things. Two reasons a script 8 | # might fail are: 9 | # 10 | # 1) timing - A surprising number of programs (rn, ksh, zsh, telnet, 11 | # etc.) and devices discard or ignore keystrokes that arrive "too 12 | # quickly" after prompts. If you find your new script hanging up at 13 | # one spot, try adding a short sleep just before the previous send. 14 | # Setting "force_conservative" to 1 (see below) makes Expect do this 15 | # automatically - pausing briefly before sending each character. This 16 | # pacifies every program I know of. The -c flag makes the script do 17 | # this in the first place. The -C flag allows you to define a 18 | # character to toggle this mode off and on. 19 | 20 | set force_conservative 1 ;# set to 1 to force conservative mode even if 21 | ;# script was not run conservatively originally 22 | if {$force_conservative} { 23 | set send_slow {1 .1} 24 | proc send {ignore arg} { 25 | sleep .1 26 | exp_send -s -- $arg 27 | } 28 | } 29 | 30 | # 31 | # 2) differing output - Some programs produce different output each time 32 | # they run. The "date" command is an obvious example. Another is 33 | # ftp, if it produces throughput statistics at the end of a file 34 | # transfer. If this causes a problem, delete these patterns or replace 35 | # them with wildcards. An alternative is to use the -p flag (for 36 | # "prompt") which makes Expect only look for the last line of output 37 | # (i.e., the prompt). The -P flag allows you to define a character to 38 | # toggle this mode off and on. 39 | # 40 | # Read the man page for more info. 41 | # 42 | # -Don 43 | 44 | set timeout -1 45 | match_max 100000 46 | 47 | # https://stackoverflow.com/a/17060172 48 | set TOUCH_POLICY [lindex $argv 0]; 49 | set ADMIN_PIN [lindex $argv 1]; 50 | set GPG_HOMEDIR [lindex $argv 2]; 51 | set USER_PIN [lindex $argv 3]; 52 | set KEY_LENGTH [lindex $argv 4]; 53 | set REALNAME [lindex $argv 5]; 54 | set EMAIL [lindex $argv 6]; 55 | set COMMENT [lindex $argv 7]; 56 | 57 | # Turn off OTP. 58 | send_user "Turning off YubiKey OTP:\n" 59 | spawn ykman config mode "FIDO+CCID" 60 | expect { 61 | "Mode is already FIDO+CCID, nothing to do..." { 62 | expect eof 63 | } 64 | 65 | ": " { 66 | send -- "y\r" 67 | expect eof 68 | } 69 | } 70 | 71 | # Set up User and Admin PINs, and then generate keys on card. 72 | 73 | send_user "Now generating your GPG keys on the YubiKey itself.\n" 74 | spawn gpg --homedir=$GPG_HOMEDIR --card-edit 75 | 76 | expect -exact "gpg/card> " 77 | send -- "admin\r" 78 | 79 | # https://developers.yubico.com/PGP/Card_edit.html 80 | 81 | expect -exact "gpg/card> " 82 | send -- "passwd\r" 83 | 84 | # Change User PIN 85 | expect -exact "Your selection? " 86 | send -- "1\r" 87 | 88 | # Default User PIN 89 | expect -exact "PIN: " 90 | send -- "123456\r" 91 | 92 | # New User PIN 93 | expect -exact "PIN: " 94 | send -- "$USER_PIN\r" 95 | 96 | # Repeat new User PIN 97 | expect -exact "PIN: " 98 | send -- "$USER_PIN\r" 99 | 100 | # Change Admin PIN 101 | expect -exact "Your selection? " 102 | send -- "3\r" 103 | 104 | # Default Admin PIN 105 | expect -exact "Admin PIN: " 106 | send -- "12345678\r" 107 | 108 | # New Admin PIN 109 | expect -exact "Admin PIN: " 110 | send -- "$ADMIN_PIN\r" 111 | 112 | # Repeat new Admin PIN 113 | expect -exact "Admin PIN: " 114 | send -- "$ADMIN_PIN\r" 115 | 116 | # Get out of passwd menu 117 | expect -exact "Your selection? " 118 | send -- "q\r" 119 | 120 | # Set desired key attributes. 121 | 122 | expect -exact "gpg/card> " 123 | send -- "key-attr\r" 124 | 125 | # Signature key. 126 | expect -exact "Your selection? " 127 | # RSA 128 | send -- "1\r" 129 | 130 | expect "What keysize do you want? (*) " 131 | send -- "$KEY_LENGTH\r" 132 | 133 | # Send new Admin PIN 134 | expect -exact "Admin PIN: " 135 | send -- "$ADMIN_PIN\r" 136 | 137 | # Encryption key. 138 | expect -exact "Your selection? " 139 | # RSA 140 | send -- "1\r" 141 | 142 | expect "What keysize do you want? (*) " 143 | send -- "$KEY_LENGTH\r" 144 | 145 | # Send new Admin PIN 146 | expect -exact "Admin PIN: " 147 | send -- "$ADMIN_PIN\r" 148 | 149 | # Authentication key. 150 | expect -exact "Your selection? " 151 | # RSA 152 | send -- "1\r" 153 | 154 | expect "What keysize do you want? (*) " 155 | send -- "$KEY_LENGTH\r" 156 | 157 | # Send new Admin PIN 158 | expect -exact "Admin PIN: " 159 | send -- "$ADMIN_PIN\r" 160 | 161 | # Time to generate. 162 | 163 | expect -exact "gpg/card> " 164 | send -- "generate\r" 165 | 166 | expect -exact "Make off-card backup of encryption key? (Y/n) " 167 | send -- "n\r" 168 | 169 | # Send new User PIN 170 | expect -exact "PIN: " 171 | send -- "$USER_PIN\r" 172 | 173 | expect -exact "Key is valid for? (0) " 174 | send -- "10y\r" 175 | 176 | expect -exact "Is this correct? (y/N) " 177 | send -- "y\r" 178 | 179 | expect -exact "Real name: " 180 | send -- "$REALNAME\r" 181 | 182 | expect -exact "E-mail address: " 183 | send -- "$EMAIL\r" 184 | 185 | expect -exact "Comment: " 186 | send -- "$COMMENT\r" 187 | 188 | expect -exact "Change (N)ame, (C)omment, (E)-mail or (O)kay/(Q)uit? " 189 | send -- "O\r" 190 | 191 | # Send new Admin PIN 192 | expect -exact "Admin PIN: " 193 | send -- "$ADMIN_PIN\r" 194 | 195 | send_user "\nNow generating keys on card, lights will be flashing, this will take a few minutes, please wait...\n" 196 | 197 | # Send new User PIN 198 | expect { 199 | "PIN: " { 200 | send -- "$USER_PIN\r" 201 | expect -exact "gpg/card> " 202 | send -- "quit\r" 203 | } 204 | "gpg/card> " { send -- "quit\r" } 205 | } 206 | 207 | expect eof 208 | 209 | # Turn on touch for SIGNATURES. 210 | 211 | send_user "Now requiring you to touch your YubiKey to sign any message.\n" 212 | spawn ykman openpgp keys set-touch sig $TOUCH_POLICY 213 | 214 | expect -exact "Enter Admin PIN: " 215 | stty -echo 216 | send -- "$ADMIN_PIN\r" 217 | 218 | expect -exact "Set touch policy of SIG key to $TOUCH_POLICY? \[y/N\]: " 219 | send -- "y\r" 220 | expect eof 221 | 222 | # Turn on touch for AUTHENTICATION. 223 | 224 | send_user "Now requiring you to touch your YubiKey to authenticate SSH.\n" 225 | spawn ykman openpgp keys set-touch aut on 226 | 227 | expect -exact "Enter Admin PIN: " 228 | stty -echo 229 | send -- "$ADMIN_PIN\r" 230 | 231 | expect -exact "Set touch policy of AUT key to on? \[y/N\]: " 232 | send -- "y\r" 233 | expect eof 234 | 235 | # Turn on touch for ENCRYPTION. 236 | 237 | send_user "Now requiring you to touch your YubiKey to encrypt any message.\n" 238 | spawn ykman openpgp keys set-touch enc on 239 | 240 | expect -exact "Enter Admin PIN: " 241 | stty -echo 242 | send -- "$ADMIN_PIN\r" 243 | 244 | expect -exact "Set touch policy of ENC key to on? \[y/N\]: " 245 | send -- "y\r" 246 | expect eof 247 | 248 | # Touch for ATTESTATION works only for Yubico firmware >= 5.2.3. 249 | # https://support.yubico.com/support/solutions/articles/15000027139-yubikey-5-2-3-enhancements-to-openpgp-3-4-support 250 | -------------------------------------------------------------------------------- /expects/expect-macos.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/expect 2 | # 3 | # This Expect script was generated by autoexpect on Wed Aug 1 17:10:59 2018 4 | # Expect and autoexpect were both written by Don Libes, NIST. 5 | # 6 | # Note that autoexpect does not guarantee a working script. It 7 | # necessarily has to guess about certain things. Two reasons a script 8 | # might fail are: 9 | # 10 | # 1) timing - A surprising number of programs (rn, ksh, zsh, telnet, 11 | # etc.) and devices discard or ignore keystrokes that arrive "too 12 | # quickly" after prompts. If you find your new script hanging up at 13 | # one spot, try adding a short sleep just before the previous send. 14 | # Setting "force_conservative" to 1 (see below) makes Expect do this 15 | # automatically - pausing briefly before sending each character. This 16 | # pacifies every program I know of. The -c flag makes the script do 17 | # this in the first place. The -C flag allows you to define a 18 | # character to toggle this mode off and on. 19 | 20 | set force_conservative 1 ;# set to 1 to force conservative mode even if 21 | ;# script was not run conservatively originally 22 | if {$force_conservative} { 23 | set send_slow {1 .1} 24 | proc send {ignore arg} { 25 | sleep .1 26 | exp_send -s -- $arg 27 | } 28 | } 29 | 30 | # 31 | # 2) differing output - Some programs produce different output each time 32 | # they run. The "date" command is an obvious example. Another is 33 | # ftp, if it produces throughput statistics at the end of a file 34 | # transfer. If this causes a problem, delete these patterns or replace 35 | # them with wildcards. An alternative is to use the -p flag (for 36 | # "prompt") which makes Expect only look for the last line of output 37 | # (i.e., the prompt). The -P flag allows you to define a character to 38 | # toggle this mode off and on. 39 | # 40 | # Read the man page for more info. 41 | # 42 | # -Don 43 | 44 | set timeout -1 45 | match_max 100000 46 | 47 | # https://stackoverflow.com/a/17060172 48 | set TOUCH_POLICY [lindex $argv 0]; 49 | set ADMIN_PIN [lindex $argv 1]; 50 | set GPG_HOMEDIR [lindex $argv 2]; 51 | set USER_PIN [lindex $argv 3]; 52 | set KEY_LENGTH [lindex $argv 4]; 53 | set REALNAME [lindex $argv 5]; 54 | set EMAIL [lindex $argv 6]; 55 | set COMMENT [lindex $argv 7]; 56 | 57 | # Turn off OTP. 58 | send_user "Turning off YubiKey OTP:\n" 59 | spawn ykman config mode "FIDO+CCID" 60 | expect { 61 | "Mode is already FIDO+CCID, nothing to do..." { 62 | expect eof 63 | } 64 | 65 | ": " { 66 | send -- "y\r" 67 | expect eof 68 | } 69 | } 70 | 71 | # Set up User and Admin PINs, and then generate keys on card. 72 | 73 | send_user "Now generating your GPG keys on the YubiKey itself.\n" 74 | spawn gpg --homedir=$GPG_HOMEDIR --card-edit 75 | 76 | expect -exact "gpg/card> " 77 | send -- "admin\r" 78 | 79 | # https://developers.yubico.com/PGP/Card_edit.html 80 | 81 | expect -exact "gpg/card> " 82 | send -- "passwd\r" 83 | 84 | # Change User PIN 85 | expect -exact "Your selection? " 86 | send -- "1\r" 87 | 88 | # Default User PIN 89 | expect -exact "PIN: " 90 | send -- "123456\r" 91 | 92 | # New User PIN 93 | expect -exact "PIN: " 94 | send -- "$USER_PIN\r" 95 | 96 | # Repeat new User PIN 97 | expect -exact "PIN: " 98 | send -- "$USER_PIN\r" 99 | 100 | # Change Admin PIN 101 | expect -exact "Your selection? " 102 | send -- "3\r" 103 | 104 | # Default Admin PIN 105 | expect -exact "Admin PIN: " 106 | send -- "12345678\r" 107 | 108 | # New Admin PIN 109 | expect -exact "Admin PIN: " 110 | send -- "$ADMIN_PIN\r" 111 | 112 | # Repeat new Admin PIN 113 | expect -exact "Admin PIN: " 114 | send -- "$ADMIN_PIN\r" 115 | 116 | # Get out of passwd menu 117 | expect -exact "Your selection? " 118 | send -- "q\r" 119 | 120 | # Set desired key attributes. 121 | 122 | expect -exact "gpg/card> " 123 | send -- "key-attr\r" 124 | 125 | # Signature key. 126 | expect -exact "Your selection? " 127 | # RSA 128 | send -- "1\r" 129 | 130 | expect "What keysize do you want? (*) " 131 | send -- "$KEY_LENGTH\r" 132 | 133 | # Send new Admin PIN 134 | expect -exact "Admin PIN: " 135 | send -- "$ADMIN_PIN\r" 136 | 137 | # Encryption key. 138 | expect -exact "Your selection? " 139 | # RSA 140 | send -- "1\r" 141 | 142 | expect "What keysize do you want? (*) " 143 | send -- "$KEY_LENGTH\r" 144 | 145 | # Send new Admin PIN 146 | expect -exact "Admin PIN: " 147 | send -- "$ADMIN_PIN\r" 148 | 149 | # Authentication key. 150 | expect -exact "Your selection? " 151 | # RSA 152 | send -- "1\r" 153 | 154 | expect "What keysize do you want? (*) " 155 | send -- "$KEY_LENGTH\r" 156 | 157 | # Send new Admin PIN 158 | expect -exact "Admin PIN: " 159 | send -- "$ADMIN_PIN\r" 160 | 161 | # Time to generate. 162 | 163 | expect -exact "gpg/card> " 164 | send -- "generate\r" 165 | 166 | expect -exact "Make off-card backup of encryption key? (Y/n) " 167 | send -- "n\r" 168 | 169 | # Send new User PIN 170 | expect -exact "PIN: " 171 | send -- "$USER_PIN\r" 172 | 173 | expect -exact "Key is valid for? (0) " 174 | send -- "10y\r" 175 | 176 | expect -exact "Is this correct? (y/N) " 177 | send -- "y\r" 178 | 179 | expect -exact "Real name: " 180 | send -- "$REALNAME\r" 181 | 182 | expect -exact "Email address: " 183 | send -- "$EMAIL\r" 184 | 185 | expect -exact "Comment: " 186 | send -- "$COMMENT\r" 187 | 188 | expect -exact "Change (N)ame, (C)omment, (E)mail or (O)kay/(Q)uit? " 189 | send -- "O\r" 190 | 191 | # Send new Admin PIN 192 | expect -exact "Admin PIN: " 193 | send -- "$ADMIN_PIN\r" 194 | 195 | send_user "\nNow generating keys on card, lights will be flashing, this will take a few minutes, please wait...\n" 196 | 197 | # Send new User PIN 198 | expect { 199 | "PIN: " { 200 | send -- "$USER_PIN\r" 201 | expect -exact "gpg/card> " 202 | send -- "quit\r" 203 | } 204 | "gpg/card> " { send -- "quit\r" } 205 | } 206 | 207 | expect eof 208 | 209 | # Turn on touch for SIGNATURES. 210 | 211 | send_user "Now requiring you to touch your YubiKey to sign any message.\n" 212 | spawn ykman openpgp keys set-touch sig $TOUCH_POLICY 213 | 214 | expect -exact "Enter Admin PIN: " 215 | stty -echo 216 | send -- "$ADMIN_PIN\r" 217 | 218 | expect -exact "Set touch policy of SIG key to $TOUCH_POLICY? \[y/N\]: " 219 | send -- "y\r" 220 | expect eof 221 | 222 | # Turn on touch for AUTHENTICATION. 223 | 224 | send_user "Now requiring you to touch your YubiKey to authenticate SSH.\n" 225 | spawn ykman openpgp keys set-touch aut on 226 | 227 | expect -exact "Enter Admin PIN: " 228 | stty -echo 229 | send -- "$ADMIN_PIN\r" 230 | 231 | expect -exact "Set touch policy of AUT key to on? \[y/N\]: " 232 | send -- "y\r" 233 | expect eof 234 | 235 | # Turn on touch for ENCRYPTION. 236 | 237 | send_user "Now requiring you to touch your YubiKey to encrypt any message.\n" 238 | spawn ykman openpgp keys set-touch enc on 239 | 240 | expect -exact "Enter Admin PIN: " 241 | stty -echo 242 | send -- "$ADMIN_PIN\r" 243 | 244 | expect -exact "Set touch policy of DEC key to on? \[y/N\]: " 245 | send -- "y\r" 246 | expect eof 247 | 248 | # Touch for ATTESTATION works only for Yubico firmware >= 5.2.3. 249 | # https://support.yubico.com/support/solutions/articles/15000027139-yubikey-5-2-3-enhancements-to-openpgp-3-4-support 250 | -------------------------------------------------------------------------------- /expects/expect-ubuntu.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env expect 2 | # 3 | # This Expect script was generated by autoexpect on Wed Aug 1 17:10:59 2018 4 | # Expect and autoexpect were both written by Don Libes, NIST. 5 | # 6 | # Note that autoexpect does not guarantee a working script. It 7 | # necessarily has to guess about certain things. Two reasons a script 8 | # might fail are: 9 | # 10 | # 1) timing - A surprising number of programs (rn, ksh, zsh, telnet, 11 | # etc.) and devices discard or ignore keystrokes that arrive "too 12 | # quickly" after prompts. If you find your new script hanging up at 13 | # one spot, try adding a short sleep just before the previous send. 14 | # Setting "force_conservative" to 1 (see below) makes Expect do this 15 | # automatically - pausing briefly before sending each character. This 16 | # pacifies every program I know of. The -c flag makes the script do 17 | # this in the first place. The -C flag allows you to define a 18 | # character to toggle this mode off and on. 19 | 20 | set force_conservative 1 ;# set to 1 to force conservative mode even if 21 | ;# script was not run conservatively originally 22 | if {$force_conservative} { 23 | set send_slow {1 .1} 24 | proc send {ignore arg} { 25 | sleep .1 26 | exp_send -s -- $arg 27 | } 28 | } 29 | 30 | # 31 | # 2) differing output - Some programs produce different output each time 32 | # they run. The "date" command is an obvious example. Another is 33 | # ftp, if it produces throughput statistics at the end of a file 34 | # transfer. If this causes a problem, delete these patterns or replace 35 | # them with wildcards. An alternative is to use the -p flag (for 36 | # "prompt") which makes Expect only look for the last line of output 37 | # (i.e., the prompt). The -P flag allows you to define a character to 38 | # toggle this mode off and on. 39 | # 40 | # Read the man page for more info. 41 | # 42 | # -Don 43 | 44 | set timeout -1 45 | match_max 100000 46 | 47 | # https://stackoverflow.com/a/17060172 48 | set TOUCH_POLICY [lindex $argv 0]; 49 | set ADMIN_PIN [lindex $argv 1]; 50 | set GPG_HOMEDIR [lindex $argv 2]; 51 | set USER_PIN [lindex $argv 3]; 52 | set KEY_LENGTH [lindex $argv 4]; 53 | set REALNAME [lindex $argv 5]; 54 | set EMAIL [lindex $argv 6]; 55 | set COMMENT [lindex $argv 7]; 56 | 57 | # Turn off OTP. 58 | send_user "Turning off YubiKey OTP:\n" 59 | spawn ykman config mode "FIDO+CCID" 60 | expect { 61 | "Mode is already FIDO+CCID, nothing to do..." { 62 | expect eof 63 | } 64 | 65 | ": " { 66 | send -- "y\r" 67 | expect eof 68 | } 69 | } 70 | 71 | # Set up User and Admin PINs, and then generate keys on card. 72 | 73 | send_user "Now generating your GPG keys on the YubiKey itself.\n" 74 | spawn gpg --homedir=$GPG_HOMEDIR --card-edit 75 | 76 | expect -exact "gpg/card> " 77 | send -- "admin\r" 78 | 79 | # https://developers.yubico.com/PGP/Card_edit.html 80 | 81 | expect -exact "gpg/card> " 82 | send -- "passwd\r" 83 | 84 | # Change User PIN 85 | expect -exact "Your selection? " 86 | send -- "1\r" 87 | 88 | # Default User PIN 89 | expect -exact "PIN: " 90 | send -- "123456\r" 91 | 92 | # New User PIN 93 | expect -exact "PIN: " 94 | send -- "$USER_PIN\r" 95 | 96 | # Repeat new User PIN 97 | expect -exact "PIN: " 98 | send -- "$USER_PIN\r" 99 | 100 | # Change Admin PIN 101 | expect -exact "Your selection? " 102 | send -- "3\r" 103 | 104 | # Default Admin PIN 105 | expect -exact "Admin PIN: " 106 | send -- "12345678\r" 107 | 108 | # New Admin PIN 109 | expect -exact "Admin PIN: " 110 | send -- "$ADMIN_PIN\r" 111 | 112 | # Repeat new Admin PIN 113 | expect -exact "Admin PIN: " 114 | send -- "$ADMIN_PIN\r" 115 | 116 | # Get out of passwd menu 117 | expect -exact "Your selection? " 118 | send -- "q\r" 119 | 120 | # Set desired key attributes. 121 | 122 | expect -exact "gpg/card> " 123 | send -- "key-attr\r" 124 | 125 | # Signature key. 126 | expect -exact "Your selection? " 127 | # RSA 128 | send -- "1\r" 129 | 130 | expect "What keysize do you want? (*) " 131 | send -- "$KEY_LENGTH\r" 132 | 133 | # Send new Admin PIN 134 | expect -exact "Admin PIN: " 135 | send -- "$ADMIN_PIN\r" 136 | 137 | # Encryption key. 138 | expect -exact "Your selection? " 139 | # RSA 140 | send -- "1\r" 141 | 142 | expect "What keysize do you want? (*) " 143 | send -- "$KEY_LENGTH\r" 144 | 145 | # Send new Admin PIN 146 | expect -exact "Admin PIN: " 147 | send -- "$ADMIN_PIN\r" 148 | 149 | # Authentication key. 150 | expect -exact "Your selection? " 151 | # RSA 152 | send -- "1\r" 153 | 154 | expect "What keysize do you want? (*) " 155 | send -- "$KEY_LENGTH\r" 156 | 157 | # Send new Admin PIN 158 | expect -exact "Admin PIN: " 159 | send -- "$ADMIN_PIN\r" 160 | 161 | # Time to generate. 162 | 163 | expect -exact "gpg/card> " 164 | send -- "generate\r" 165 | 166 | expect -exact "Make off-card backup of encryption key? (Y/n) " 167 | send -- "n\r" 168 | 169 | # Send new User PIN 170 | expect -exact "PIN: " 171 | send -- "$USER_PIN\r" 172 | 173 | expect -exact "Key is valid for? (0) " 174 | send -- "10y\r" 175 | 176 | expect -exact "Is this correct? (y/N) " 177 | send -- "y\r" 178 | 179 | expect -exact "Real name: " 180 | send -- "$REALNAME\r" 181 | 182 | expect -exact "Email address: " 183 | send -- "$EMAIL\r" 184 | 185 | expect -exact "Comment: " 186 | send -- "$COMMENT\r" 187 | 188 | expect -exact "Change (N)ame, (C)omment, (E)mail or (O)kay/(Q)uit? " 189 | send -- "O\r" 190 | 191 | # Send new Admin PIN 192 | expect -exact "Admin PIN: " 193 | send -- "$ADMIN_PIN\r" 194 | 195 | send_user "\nNow generating keys on card, lights will be flashing, this will take a few minutes, please wait...\n" 196 | 197 | # Send new User PIN 198 | expect { 199 | "PIN: " { 200 | send -- "$USER_PIN\r" 201 | expect -exact "gpg/card> " 202 | send -- "quit\r" 203 | } 204 | "gpg/card> " { send -- "quit\r" } 205 | } 206 | 207 | expect eof 208 | 209 | # Turn on touch for SIGNATURES. 210 | 211 | send_user "Now requiring you to touch your YubiKey to sign any message.\n" 212 | spawn ykman openpgp keys set-touch sig $TOUCH_POLICY 213 | 214 | expect -exact "Enter Admin PIN: " 215 | stty -echo 216 | send -- "$ADMIN_PIN\r" 217 | 218 | expect -exact "Set touch policy of SIG key to $TOUCH_POLICY? \[y/N\]: " 219 | send -- "y\r" 220 | expect eof 221 | 222 | # Turn on touch for AUTHENTICATION. 223 | 224 | send_user "Now requiring you to touch your YubiKey to authenticate SSH.\n" 225 | spawn ykman openpgp keys set-touch aut on 226 | 227 | expect -exact "Enter Admin PIN: " 228 | stty -echo 229 | send -- "$ADMIN_PIN\r" 230 | 231 | expect -exact "Set touch policy of AUT key to on? \[y/N\]: " 232 | send -- "y\r" 233 | expect eof 234 | 235 | # Turn on touch for ENCRYPTION. 236 | 237 | send_user "Now requiring you to touch your YubiKey to encrypt any message.\n" 238 | spawn ykman openpgp keys set-touch enc on 239 | 240 | expect -exact "Enter Admin PIN: " 241 | stty -echo 242 | send -- "$ADMIN_PIN\r" 243 | 244 | expect -exact "Set touch policy of DEC key to on? \[y/N\]: " 245 | send -- "y\r" 246 | expect eof 247 | 248 | # Touch for ATTESTATION works only for Yubico firmware >= 5.2.3. 249 | # https://support.yubico.com/support/solutions/articles/15000027139-yubikey-5-2-3-enhancements-to-openpgp-3-4-support 250 | -------------------------------------------------------------------------------- /git.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Stop on error. 4 | set -e 5 | 6 | source env.sh 7 | source realname-and-email.sh 8 | 9 | # Determine whether to set globally or locally. 10 | OLD_PWD="$(pwd)" 11 | if [[ -z "$1" ]] 12 | then 13 | echo "Signing git commits & tags ${GREEN}${BOLD}GLOBALLY${RESET}" 14 | SCOPE="--global" 15 | else 16 | echo "Signing git commits & tags ${GREEN}${BOLD}LOCALLY${RESET}: $1" 17 | SCOPE="--local" 18 | cd "$1" 19 | fi 20 | 21 | 22 | source "$OLD_PWD"/lib/git_conf.sh 23 | cd "$OLD_PWD" 24 | 25 | # If scope local, only configure git, and don't try to push the key 26 | # to github and set up the notifications 27 | if [[ "${SCOPE}" == "--local" ]]; then 28 | exit 0 29 | fi 30 | 31 | # Export GPG public key to GitHub. 32 | echo "Exporting your GPG public key to GitHub." 33 | $GPG --armor --export "$KEYID" | $CLIP $CLIP_ARGS 34 | echo "It has been copied to your clipboard." 35 | echo "${YELLOW}You may now add it to GitHub: https://github.com/settings/gpg/new${RESET}" 36 | echo "${GREEN}Opening GitHub...${RESET}" 37 | $OPEN "https://github.com/settings/gpg/new" 38 | echo 39 | 40 | # Turn on notifications. 41 | source lib/notifications.sh $SCOPE 42 | echo "${GREEN}Enjoy signing your git commits with your YubiKey!${RESET}" 43 | -------------------------------------------------------------------------------- /gpg.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Stop on error. 4 | set -e 5 | 6 | # shellcheck disable=SC1091 7 | source env.sh 8 | source lib/install.sh 9 | 10 | # Get full name and email address. 11 | # shellcheck disable=SC1091 12 | source realname-and-email.sh 13 | 14 | # Get comment to distinguish between keys. 15 | COMMENT="GPG on YubiKey for Datadog" 16 | echo "${YELLOW}What is a comment you would like to use to distinguish this key?" 17 | read -rp "Comment (press Enter to accept '$COMMENT'): ${RESET}" input 18 | COMMENT=${input:-$COMMENT} 19 | echo 20 | 21 | # Generate some information for the user. 22 | USER_PIN=$(python3 -S -c "import random; print(random.SystemRandom().randrange(10**7,10**8))") 23 | ADMIN_PIN=$(python3 -S -c "import random; print(random.SystemRandom().randrange(10**7,10**8))") 24 | SERIAL=$($YKMAN info | grep 'Serial number:' | cut -f2 -d: | tr -d ' ') 25 | 26 | # Set some parameters based on whether FIPS key or not. 27 | DEVICE_TYPE=$($YKMAN info | grep 'Device type:' | cut -f2 -d: | awk '{$1=$1;print}') 28 | echo "YubiKey device type: $DEVICE_TYPE" 29 | if [[ "$DEVICE_TYPE" == *"YubiKey"*"FIPS"* ]]; then 30 | echo "Which appears to be a FIPS key" 31 | YUBIKEY_FIPS=true 32 | # YubiKey FIPS supports at most RSA-3072 on-card key generation, which should 33 | # be good until at least 2030 according to NIST: 34 | # https://csrc.nist.gov/CSRC/media/projects/cryptographic-module-validation-program/documents/security-policies/140sp3204.pdf 35 | # https://www.keylength.com/en/compare/ 36 | KEY_LENGTH=3072 37 | else 38 | echo "Which does not appear to be a FIPS key" 39 | YUBIKEY_FIPS=false 40 | KEY_LENGTH=4096 41 | fi 42 | # Activate cache policy if possible (when firmware version equal or superior to 5.2.3 43 | # https://github.com/Yubico/yubikey-manager/issues/277#issuecomment-529805540 44 | FIRMWARE_VERSION=$($YKMAN info | grep 'Firmware version:' | cut -f2 -d: | awk '{$1=$1;print}') 45 | set +e 46 | vercomp "$FIRMWARE_VERSION" 5.2.3 47 | if [[ "$?" -eq 2 ]] || [[ "$YUBIKEY_FIPS" == "true" ]]; then 48 | echo "Setting touch policy to on" 49 | TOUCH_POLICY=on 50 | else 51 | echo "Setting touch policy to cached" 52 | TOUCH_POLICY=cached 53 | fi 54 | set -e 55 | echo 56 | 57 | source lib/tree.sh 58 | 59 | # Update scdaemon.conf 60 | source lib/scdaemon.sh 61 | 62 | # Show card information to user so they can be sure they are wiping right key 63 | # NOTE: explicitly check against default GPG homedir to make sure we are not wiping something critical... 64 | echo "YubiKey status:" 65 | # shellcheck disable=SC2153 66 | $GPG --card-status 67 | echo 68 | 69 | # Reset YubiKey openPGP applet 70 | echo "${YELLOW}RESETTING THE OPENGPG APPLET ON YOUR YUBIKEY!!!" 71 | $YKMAN openpgp reset 72 | echo "${RESET}" 73 | 74 | # Whatever our GPG homedir, we replace pinentry-curses with pinentry-tty, so that we can automate entering User and Admin PINs. 75 | GPG_AGENT_CONF=$GPG_HOMEDIR/gpg-agent.conf 76 | cat << EOF > "$GPG_AGENT_CONF" 77 | pinentry-program $PINENTRY_SETUP 78 | EOF 79 | 80 | source lib/gpg_conf.sh 81 | # force locale to prevent expect script from breaking on non-english systems. 82 | old_locale="${LC_ALL}" 83 | export LC_ALL=en_US.UTF-8 84 | 85 | # drive yubikey setup 86 | # but right before, kill all GPG daemons to make sure things work reliably 87 | $GPGCONF --homedir="$GPG_HOMEDIR" --kill all 88 | # The script failed to run when GPG_TTY != '' so we ensure it's empty before the expect script. 89 | # https://gnupg.org/documentation/manuals/gnupg/Common-Problems.html 90 | GPG_TTY="" ./expects/"expect-${OS}.sh" "$TOUCH_POLICY" "$ADMIN_PIN" "$GPG_HOMEDIR" "$USER_PIN" "$KEY_LENGTH" "$REALNAME" "$EMAIL" "$COMMENT" 91 | echo 92 | 93 | # restore initial locale value 94 | export LC_ALL="${old_locale}" 95 | 96 | source lib/gpg_agent_conf.sh 97 | 98 | # restart GPG daemons to pick up pinentry-mac 99 | $GPGCONF --kill all 100 | 101 | echo "There are two important random numbers for the YubiKey you MUST keep safely." 102 | echo "See https://developers.yubico.com/yubikey-piv-manager/PIN_and_Management_Key.html" 103 | echo 104 | 105 | echo "The first number is the User PIN." 106 | echo "The User PIN is used during normal operation to authorize an action such as issuing a new GPG signature." 107 | echo "${GREEN}" 108 | echo "***********************************************************" 109 | echo "New User PIN: $USER_PIN" 110 | echo "***********************************************************" 111 | echo "${RESET}" 112 | echo "${YELLOW}Please save this new User PIN (copied to clipboard) immediately in your password manager.${RESET}" 113 | echo "$USER_PIN" | $CLIP $CLIP_ARGS 114 | read -rp "${YELLOW}Have you done this?${RESET}" 115 | echo "${YELLOW}Please also associate it with this YubiKey serial number (copied to clipboard): ${SERIAL}${RESET}" 116 | echo "$SERIAL" | $CLIP $CLIP_ARGS 117 | read -rp "${YELLOW}Have you done this? ${RESET}" 118 | echo 119 | 120 | echo "The second number is the Admin PIN." 121 | echo "The Admin PIN can be used to reset the PIN if it is ever lost or becomes blocked after the maximum number of incorrect attempts." 122 | echo "${GREEN}" 123 | echo "***********************************************************" 124 | echo "New Admin PIN: $ADMIN_PIN" 125 | echo "***********************************************************" 126 | echo "${RESET}" 127 | echo "${YELLOW}Please save this new Admin PIN (copied to clipboard) immediately in your password manager.${RESET}" 128 | echo "$ADMIN_PIN" | $CLIP $CLIP_ARGS 129 | read -rp "${YELLOW}Have you done this? ${RESET}" 130 | echo "${YELLOW}Please also associate it with this YubiKey serial number (copied to clipboard): ${SERIAL}${RESET}" 131 | echo "$SERIAL" | $CLIP $CLIP_ARGS 132 | read -rp "${YELLOW}Have you done this?${RESET}" 133 | echo 134 | 135 | # Export GPG public key. 136 | KEYID=$(get_keyid "$GPG_HOMEDIR") 137 | BIN_GPG_PUBKEY=$KEYID.gpg.pub.bin 138 | ASC_GPG_PUBKEY=$KEYID.gpg.pub.asc 139 | echo "${GREEN}Exporting your binary GPG public key to $(pwd)/${BIN_GPG_PUBKEY}${RESET}" 140 | $GPG --homedir="$GPG_HOMEDIR" --export "$KEYID" > "$BIN_GPG_PUBKEY" 141 | echo "${GREEN}Exporting your ASCII-armored GPG public key to $(pwd)/${ASC_GPG_PUBKEY}${RESET}" 142 | $GPG --homedir="$GPG_HOMEDIR" --armor --export "$KEYID" > "$ASC_GPG_PUBKEY" 143 | echo "$ASC_GPG_PUBKEY" | $CLIP $CLIP_ARGS 144 | echo "${YELLOW}Please save a copy in your password manager.${RESET}" 145 | read -rp "${YELLOW}Have you done this? ${RESET}" 146 | echo "There is NO off-card backup of your private / secret keys." 147 | echo "So, if your YubiKey is damaged, lost, or stolen, then you must rotate your GPG keys out-of-band." 148 | echo "You would also no longer be able to decrypt messages encrypted for this GPG key." 149 | echo 150 | 151 | # Ask user to save revocation certificate before deleting it. 152 | REVOCATION_CERT=$GPG_HOMEDIR/openpgp-revocs.d/$KEYID.rev 153 | echo "$REVOCATION_CERT" | $CLIP $CLIP_ARGS 154 | echo "${GREEN}Your revocation certificate is at ${REVOCATION_CERT}${RESET}" 155 | echo "It has been copied to your clipboard." 156 | echo "${YELLOW}Please save a copy in your password manager before we delete it off disk.${RESET}" 157 | read -rp "${YELLOW}Have you done this? ${RESET}" 158 | rm "$REVOCATION_CERT" 159 | echo "Great. Deleted this revocation certificate from disk." 160 | # NOTE: EMPTY clipboard after this. 161 | $CLIP $CLIP_ARGS < /dev/null 162 | echo 163 | 164 | # Final reminders. 165 | echo "Finally, remember that your keys will not ${GREEN}expire until 10 years from now.${RESET}" 166 | echo "You will need to ${GREEN}${BOLD}enter your User PIN (once a day)${RESET}, and ${GREEN}${BOLD}touch your YubiKey${RESET} in order to sign any message with this GPG key." 167 | if [[ "$TOUCH_POLICY" == "on" ]]; then 168 | echo "${YELLOW}You may wish to pass the --no-gpg-sign flag to git rebase.${RESET}" 169 | else 170 | echo "Touch is cached for 15s on sign operations." 171 | fi 172 | echo "Enjoy using your YubiKey at Datadog!" 173 | -------------------------------------------------------------------------------- /import.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Stop on error. 4 | set -e 5 | 6 | 7 | function usage () 8 | { 9 | cat << EOF 10 | Usage : $0 [options] [--] 11 | 12 | Options: 13 | -h|--help OPTIONAL Display this message 14 | -p|--public REQUIRED Path to your public key on disk 15 | -i|--id REQUIRED Key ID you are importing 16 | EOF 17 | 18 | } 19 | 20 | if [[ $# -lt 1 ]]; then 21 | echo -e "Missing arguments\n" 22 | usage 23 | exit 1 24 | fi 25 | 26 | while [[ $# -gt 0 ]]; do 27 | case $1 in 28 | -h|--help) 29 | usage 30 | exit 0 31 | ;; 32 | -p|--public) 33 | publickey_path="$2" 34 | shift 2 35 | ;; 36 | -i|--id) 37 | keyid="$2" 38 | shift 2 39 | ;; 40 | *) 41 | echo -e "ERROR: An unknown flag was passed: ${1}\n" 42 | usage 43 | ;; 44 | esac 45 | done 46 | if [[ -z "$publickey_path" ]] || [[ -z "$keyid" ]]; then 47 | usage 48 | exit 1 49 | fi 50 | if [[ ! -f "$publickey_path" ]] \ 51 | || [[ "$(head -n 1 "$publickey_path")" != "-----BEGIN PGP PUBLIC KEY BLOCK-----" ]]; then 52 | echo "Public key $publickey_path is not GPG public key, exiting" 53 | exit 1 54 | fi 55 | 56 | # shellcheck disable=SC1091 57 | source env.sh 58 | source lib/install.sh 59 | source lib/tree.sh 60 | source lib/gpg_conf.sh 61 | source lib/gpg_agent_conf.sh 62 | 63 | # Configure scdaemon. 64 | source lib/scdaemon.sh 65 | echo "YubiKey status:" 66 | # https://security.stackexchange.com/questions/108190/export-secret-key-after-yubikey-is-plugged-in 67 | $GPG --card-status 68 | echo 69 | 70 | # Import the GPG public key. 71 | echo "Importing your GPG public key..." 72 | $GPG --import "$publickey_path" 73 | echo 74 | echo -e "5\ny\n" | $GPG --no-tty --command-fd 0 --edit-key "$keyid" trust 75 | 76 | read -rp "${YELLOW}Do you also want to use GPG on your YubiKey to sign git commits? (y/n)${RESET}" answer 77 | case "$answer" in 78 | yes|YES|y|Y|Yes) 79 | echo "Configuring git to use GPG signing subkey..." 80 | export SCOPE="--global" 81 | source lib/git_conf.sh 82 | source lib/notifications.sh $SCOPE 83 | ;; 84 | *) 85 | echo "Skipping signing git commits." 86 | esac 87 | echo 88 | 89 | # Authenticating over SSH with their GPG authentication subkey OTOH is a different story. 90 | read -rp "${YELLOW}Do you also want to use GPG on your YubiKey to authenticate over SSH? (y/n)${RESET}" answer 91 | case "$answer" in 92 | yes|YES|y|Y|Yes) 93 | echo "Configuring SSH to use GPG authentication subkey..." 94 | source lib/ssh_conf.sh 95 | ;; 96 | *) 97 | echo "Skipping using GPG authentication subkey for SSH." 98 | esac 99 | echo 100 | 101 | echo "All done! Enjoy reusing your YubiKey on your new computer." 102 | -------------------------------------------------------------------------------- /lib/git_conf.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Set git name and email. 4 | echo "Setting your git-config user.name..." 5 | $GIT config $SCOPE user.name "$REALNAME" 6 | echo "Setting your git-config user.email..." 7 | $GIT config $SCOPE user.email "$EMAIL" 8 | 9 | # Ask user whether all git commits and tags should be signed. 10 | KEYID=$(get_keyid "$DEFAULT_GPG_HOMEDIR") 11 | echo "Setting git to use this GPG key." 12 | echo "Also, turning on signing of all commits and tags by default." 13 | # Tell git to use this GPG key. 14 | $GIT config $SCOPE user.signingkey "$KEYID" 15 | # Also, turn on signing commits and tags by default. 16 | $GIT config $SCOPE commit.gpgsign true 17 | $GIT config $SCOPE tag.forceSignAnnotated true 18 | echo 19 | -------------------------------------------------------------------------------- /lib/gpg_agent_conf.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Overwrite default GPG agent configuration with our own. 4 | # We want to replace the pinentry-tty with the pinentry-mac. 5 | cat << EOF > "$DEFAULT_GPG_AGENT_CONF" 6 | # https://www.gnupg.org/documentation/manuals/gnupg/Agent-Options.html 7 | pinentry-program $PINENTRY 8 | # For usability while balancing security, cache User PIN for at most a day. 9 | default-cache-ttl 86400 10 | max-cache-ttl 86400 11 | EOF 12 | 13 | -------------------------------------------------------------------------------- /lib/gpg_conf.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Backup GPG configuration in default GPG homedir, if it exists. 4 | backup_conf "$DEFAULT_GPG_CONF" 5 | 6 | # https://csrc.nist.rip/groups/STM/cmvp/documents/140-1/140crt/140crt1130.pdf 7 | # https://nvlpubs.nist.gov/nistpubs/SpecialPublications/NIST.SP.800-131Ar2.pdf 8 | # Specify crypto algorithms that will be used 9 | # Note: the goal is to favor algorithms: 10 | # - without known vulnerabilties 11 | # - with a long key and block sizes 12 | GPG_CONF=$GPG_HOMEDIR/gpg.conf 13 | cat << EOF > "$GPG_CONF" 14 | disable-pubkey-algo ELG 15 | disable-pubkey-algo DSA 16 | disable-cipher-algo 3DES 17 | disable-cipher-algo BLOWFISH 18 | disable-cipher-algo CAMELLIA256 19 | disable-cipher-algo CAMELLIA128 20 | disable-cipher-algo CAMELLIA192 21 | disable-cipher-algo CAST5 22 | disable-cipher-algo IDEA 23 | disable-cipher-algo TWOFISH 24 | personal-cipher-preferences AES256 AES192 AES 25 | personal-digest-preferences SHA512 SHA384 SHA256 SHA224 26 | personal-compress-preferences BZIP2 ZLIB ZIP Uncompressed 27 | default-preference-list AES256 AES192 AES SHA512 SHA384 SHA256 SHA224 BZIP2 ZLIB ZIP Uncompressed 28 | EOF 29 | -------------------------------------------------------------------------------- /lib/install.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Install required dependencies. 4 | echo "${YELLOW}You need to have $(join ',' "${DEPS[@]}") installed on your device." 5 | read -rp "Do you want us to install them for you ? (y/n)${RESET}" answer 6 | case "$answer" in 7 | yes|YES|y|Y|Yes) 8 | # install required tools 9 | echo "Installing or upgrading required tools..." 10 | eval "$PKG_MANAGER_ENV" "$PKG_MANAGER" "$PKG_MANAGER_UPDATE" 11 | for pkg in "${DEPS[@]}"; do 12 | install_or_upgrade "$pkg" 13 | done 14 | ;; 15 | *) 16 | echo "Skipping install or upgrade of required tools" 17 | for pkg in "${DEPS[@]}"; do 18 | check_presence "$pkg" 19 | done 20 | ;; 21 | esac 22 | echo 23 | 24 | -------------------------------------------------------------------------------- /lib/notifications.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Look if the file is sourced or directly called. if not source env.sh 4 | # https://stackoverflow.com/a/2684300 5 | [[ ${BASH_SOURCE[0]} != "$0" ]] || source env.sh 6 | 7 | # Honour git.sh setting or set to --global by default 8 | # Besides you can specify --local when directly called 9 | SCOPE=${1:---global} 10 | echo "Turning on notifications for signing git commits & tags ${GREEN}${BOLD}${SCOPE//--/}ly${RESET}" 11 | 12 | set -e 13 | 14 | # Create a bin directory where user has write access 15 | mkdir -p "$USER_BIN_DIR" 16 | 17 | echo "Deploying the notifications script" 18 | sed -E -e 's/%%NOTIFICATION_NOTIFY%%/'"$NOTIFICATION_NOTIFY"'/' bin/gpg-sign-notify > "$NOTIFICATION_SCRIPT_PATH" 19 | chmod u+x "$NOTIFICATION_SCRIPT_PATH" 20 | echo "${GREEN}The notifications script has been deployed${RESET}" 21 | 22 | $GIT config "$SCOPE" --add gpg.program "$NOTIFICATION_SCRIPT_PATH" 23 | echo "${GREEN}Notifications have been set up in git${RESET}" 24 | -------------------------------------------------------------------------------- /lib/scdaemon.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Stop on error. 4 | set -e 5 | 6 | # shellcheck disable=SC1091 7 | [[ ${BASH_SOURCE[0]} != "$0" ]] || source env.sh 8 | 9 | echo "${GREEN}Generating scdaemon.conf.${RESET}" 10 | 11 | # Sometimes on macOS, a gpg update make the yubikey detection flaky or completely impossible 12 | # So we enforce the scdaemon.conf configuration to detect the YubiKey as it is on macOS only 13 | # cf env.sh 14 | # https://gpgtools.tenderapp.com/discussions/problems/58454-after-updating-to-gpgtools-20171-yubikey-no-longer-functions-properly-both-in-mail-gpg2-card-edit/page/1 15 | if [[ -n "$SCDAEMON_CONF" ]]; then 16 | backup_conf "$DEFAULT_GPG_SCDAEMON_CONF" 17 | echo -e "$SCDAEMON_CONF" > "$DEFAULT_GPG_SCDAEMON_CONF" 18 | fi 19 | 20 | $GPGCONF --kill all 21 | -------------------------------------------------------------------------------- /lib/ssh_conf.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | function configure_shell { 4 | case $(basename "$SHELL") in 5 | bash) 6 | config_file="${HOME}/.bashrc" 7 | ;; 8 | zsh) 9 | config_file="${HOME}/.zshrc" 10 | ;; 11 | fish) 12 | config_file="${HOME}/.config/fish/config.fish" 13 | ;; 14 | *) 15 | config_file="${HOME}/.profile" 16 | ;; 17 | esac 18 | 19 | config_file_basename=$(basename "$config_file") 20 | echo "$config_file_basename detected" 21 | if [[ "$config_file_basename" == "config.fish" ]]; then 22 | RC_SSH_CONF="set -gx SSH_AUTH_SOCK \$($GPGCONF --list-dirs agent-ssh-socket)" 23 | else 24 | RC_SSH_CONF="export SSH_AUTH_SOCK=\$($GPGCONF --list-dirs agent-ssh-socket)" 25 | fi 26 | if ! grep -q "gpg-agent.ssh" "$config_file"; then 27 | echo "$RC_SSH_CONF" >> "$config_file" 28 | fi 29 | eval "$RC_SSH_CONF" 30 | if [[ "$SSH_AUTH_SOCK" != "$($GPGCONF --list-dirs agent-ssh-socket)" ]]; then 31 | echo "Failed to configure SSH_AUTH_SOCK in $config_file" 32 | exit 1 33 | fi 34 | } 35 | 36 | if ! grep -q "enable-ssh-support" "$DEFAULT_GPG_AGENT_CONF"; then 37 | # enable ssh support 38 | echo "enable-ssh-support" >> "$DEFAULT_GPG_AGENT_CONF" 39 | fi 40 | # NOTE: Kill existing SSH and GPG agents, and start GPG agent manually (with SSH 41 | # support added above) to maximize odds of picking up SSH key. 42 | killall ssh-agent || echo "ssh-agent was not running." 43 | $GPGCONF --kill all 44 | # put set +e before trying running the gpg agent as it can be already running according to the OS and return != 0 45 | set +e 46 | $GPG_AGENT --daemon 47 | set -e 48 | if [[ -f "$SSH_ENV" ]]; then 49 | rm -f "$SSH_ENV" 50 | fi 51 | 52 | configure_shell 53 | -------------------------------------------------------------------------------- /lib/tree.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Figure out whether we need to write GPG keys to a tempdir. 4 | # This is useful when you need to generate keys for someone else w/o adding to your own keystore. 5 | if [[ -z "$TEMPDIR" ]] 6 | then 7 | GPG_HOMEDIR=$DEFAULT_GPG_HOMEDIR 8 | echo "Using *default* GPG homedir: $GPG_HOMEDIR" 9 | else 10 | GPG_HOMEDIR=$(mktemp -d) 11 | echo "Using *temp* GPG homedir: $GPG_HOMEDIR" 12 | fi 13 | echo 14 | 15 | # Create default directories. 16 | mkdir -p "$GPG_HOMEDIR" 17 | chmod 700 "$GPG_HOMEDIR" 18 | 19 | # Backup GPG agent configuration in default GPG homedir, if it exists. 20 | backup_conf "$DEFAULT_GPG_AGENT_CONF" 21 | -------------------------------------------------------------------------------- /realname-and-email.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Stop on error. 4 | set -e 5 | 6 | # Get some information from the user. 7 | 8 | # 1. Real name. 9 | REALNAME=$($GIT config --global --default '' --get user.name) 10 | echo "${YELLOW}What is the real name you use on GitHub?" 11 | read -rp "Real name (press Enter to accept '$REALNAME')${RESET}: " input 12 | 13 | if [[ -z $REALNAME ]] 14 | then 15 | if [[ -z $input ]] 16 | then 17 | echo "${RED}No name given!${RESET}" 18 | exit 1 19 | else 20 | REALNAME=$input 21 | echo "Using given input: $REALNAME" 22 | fi 23 | else 24 | if [[ -z $input ]] 25 | then 26 | echo "Using given user.name: $REALNAME" 27 | else 28 | REALNAME=$input 29 | echo "Using given input: $REALNAME" 30 | fi 31 | fi 32 | 33 | REALNAME_LEN=${#REALNAME} 34 | if [[ $REALNAME_LEN -lt 5 ]] 35 | then 36 | echo "${RED}Real name has $REALNAME_LEN < 5 characters!${RESET}" 37 | exit 2 38 | fi 39 | 40 | echo 41 | 42 | # 2. Email address. 43 | EMAIL=$($GIT config --global --default '' --get user.email) 44 | echo "${YELLOW}What is an email address you have registered with GitHub?" 45 | read -rp "Email (press Enter to accept '$EMAIL'): ${RESET}" input 46 | 47 | if [[ -z $EMAIL ]] 48 | then 49 | if [[ -z $input ]] 50 | then 51 | echo "${RED}No email given!${RESET}" 52 | exit 3 53 | else 54 | EMAIL=$input 55 | echo "Using given input: $EMAIL" 56 | fi 57 | else 58 | if [[ -z $input ]] 59 | then 60 | echo "Using given user.email: $EMAIL" 61 | else 62 | EMAIL=$input 63 | echo "Using given input: $EMAIL" 64 | fi 65 | fi 66 | 67 | echo 68 | -------------------------------------------------------------------------------- /reset.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | source env.sh 3 | 4 | shopt -s extglob 5 | 6 | confirm() { 7 | local msg 8 | 9 | msg="$1" 10 | 11 | echo "$msg" 12 | read -rn 3 answer 13 | case $answer in 14 | Yes|yes|Y|y|YES) 15 | echo 16 | return 0 17 | ;; 18 | *) 19 | echo "Cancelling" 20 | return 1 21 | esac 22 | } 23 | 24 | reset_device() { 25 | local serial 26 | serial="$1" 27 | 28 | for i in $(seq 1 2); do 29 | if ! $YKMAN --device "${serial}" otp delete "$i" -f >/dev/null 2>&1; then 30 | echo "Warning the slot $i didn't contain OTP configuration or an error happened" 31 | fi 32 | done 33 | $YKMAN --device "${serial}" oath reset -f 34 | $YKMAN --device "${serial}" openpgp reset -f 35 | $YKMAN --device "${serial}" piv reset -f 36 | $YKMAN --device "${serial}" fido reset 37 | } 38 | 39 | yubikeys=$($YKMAN list --serials) 40 | select serial in all $yubikeys cancel; do 41 | echo "You chose $serial" 42 | case $serial in 43 | all) 44 | confirm "Are you sure you want to reset $yubikeys ? yes/no" || exit 0 45 | for yubikey in $yubikeys; do 46 | echo "Reset $yubikey" 47 | reset_device "$yubikey" 48 | done 49 | break 50 | ;; 51 | cancel) 52 | echo "Cancelled" 53 | break 54 | ;; 55 | # https://www.linuxjournal.com/content/bash-extended-globbing 56 | +([0-9])) 57 | confirm "Are you sure you want to reset $serial ? yes/no" || exit 0 58 | echo "Reset $serial" 59 | reset_device "$serial" 60 | break 61 | ;; 62 | *) 63 | echo "Unexpected error, exiting" 64 | exit 1 65 | ;; 66 | esac 67 | done 68 | 69 | 70 | -------------------------------------------------------------------------------- /ssh.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Stop on error. 4 | set -e 5 | 6 | source env.sh 7 | 8 | # Sets gpg-agent for SSH_AUTH_SOCK in the user's profile & environment 9 | echo "${GREEN}Setting the GPG agent for SSH_AUTH_SOCK to handle SSH actions${RESET}" 10 | echo "${YELLOW}This includes git actions like pushing and pulling from Github${RESET}" 11 | echo "${YELLOW}If this breaks your workflow, edit your profile to remove it${RESET}" 12 | echo "${YELLOW}And please open a ticket to let us know: https://github.com/DataDog/yubikey${RESET}" 13 | source lib/ssh_conf.sh 14 | 15 | # Export SSH key derived from GPG authentication subkey. 16 | KEYID=$(get_keyid "$DEFAULT_GPG_HOMEDIR") 17 | SSH_PUBKEY=$KEYID.ssh.pub 18 | echo "${YELLOW}Exporting your SSH public key to ${SSH_PUBKEY}${RESET}" 19 | ssh-add -L | grep -iF 'cardno' > "$SSH_PUBKEY" 20 | cat "$SSH_PUBKEY" | $CLIP $CLIP_ARGS 21 | echo "It has also been copied to your clipboard." 22 | echo "${YELLOW}You may now add it to GitHub: https://github.com/settings/ssh/new${RESET}" 23 | echo "${GREEN}Opening GitHub...${RESET}" 24 | $OPEN "https://github.com/settings/ssh/new" 25 | echo "${YELLOW}Please save a copy in your password manager.${RESET}" 26 | read -rp "${YELLOW}Have you done this? ${RESET}" 27 | echo "Great." 28 | echo 29 | echo "You will need to ${GREEN}${BOLD}enter your PIN (once a day)${RESET}, and ${GREEN}${BOLD}touch your YubiKey everytime${RESET} in order to use SSH." 30 | echo 31 | echo "Enjoy authenticating over SSH with your YubiKey at Datadog!" 32 | --------------------------------------------------------------------------------