├── .gitignore ├── LICENCE ├── Makefile ├── README.md ├── config-sample ├── copy-secrets-to-usb.sh ├── copy-to-transfer-usb.sh ├── export-public-keys-and-shadows.sh ├── genkey-signing+encryption.conf-sample ├── genkey-signing.conf-sample ├── gpg.conf-sample └── yubikey-reset.txt /.gitignore: -------------------------------------------------------------------------------- 1 | /20*_signing 2 | /20*_signing+encryption 3 | -------------------------------------------------------------------------------- /LICENCE: -------------------------------------------------------------------------------- 1 | Copyright 2017 Jinn Koriech 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of 4 | this software and associated documentation files (the "Software"), to deal in 5 | the Software without restriction, including without limitation the rights to 6 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 7 | of the Software, and to permit persons to whom the Software is furnished to do 8 | so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | SOFTWARE. 20 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # vim:set ts=2 sw=2 sts=2 noexpandtab: 2 | 3 | .PHONY: help 4 | help: 5 | @echo "Usage: [GPGHOME=/path/to/gnupghome] (g)make [TARGET]" 6 | @echo "Where:" 7 | @echo " GPGHOME the path to a folder for new keyrings, or containing existing keyrings" 8 | @echo " will be created if it doesn't exist, and will contain subfolders for" 9 | @echo " each smartcard/yubikey detected" 10 | @echo 11 | @echo "Targets:" 12 | @echo " default create a soft master key and encryption subkey, then" 13 | @echo " initialise two smartcards with signing and auth subkeys" 14 | @echo " and the soft encryption key." 15 | @echo 16 | @echo " softkeys (default) create a full set of keys: master, soft signing" 17 | @echo " subkey, soft encryption subkey, soft auth key." 18 | @echo 19 | @echo " master create a master key used to sign subkeys." 20 | @echo " signing a soft subkey used for signing only" 21 | @echo " encryption a soft subkey used for encryption only" 22 | @echo " auth a soft subkey used for authentication only" 23 | @echo 24 | @echo " id_rsa.pub extract the SSH public key from the authentication key, placing the" 25 | @echo " resulting file at \$$(GPGHOME)/DATA-transferred/id_rsa-\$${SSH_KEY}.pub" 26 | @echo 27 | @echo " import-secrets delete the master key and subkeys, then re-import the master key" 28 | @echo " and the encryption subkey" 29 | @echo 30 | @echo " show Display secret key information for temporary working path," 31 | @echo " and card status." 32 | @echo 33 | @echo " remove-master Backup and remove the private master key from the working keychain" 34 | @echo 35 | @echo " import-ssb Import secret subkeys in FILE and link to SmartCard, requires" 36 | @echo " that you set SHADOWS=/path/to/DATA-transfered/subkey-shadows.asc" 37 | @echo 38 | @echo " test Perform a sign and encrypt, and a decrypt and verify to confirm" 39 | @echo " all is functioning as expected." 40 | @echo 41 | @echo " reset-yubikey Reset the GPG applet on a yubikey. WARNING: this will" 42 | @echo " irrevicoable wipe your smartcard." 43 | @echo 44 | @echo " clean Clean up previously created GNUPGHOME paths" 45 | @echo 46 | @echo "Requirements:" 47 | @echo " Only GPG >= 2.1 is supported." 48 | 49 | KEYSET := $(MAKECMDGOALS) 50 | ifeq ($(findstring signing, $(MAKECMDGOALS)),signing) 51 | KEYSET := sign 52 | KEYTAG := S 53 | else ifeq ($(findstring encryption, $(MAKECMDGOALS)),encryption) 54 | KEYSET := encr 55 | KEYTAG := E 56 | else ifeq ($(findstring auth, $(MAKECMDGOALS)),auth) 57 | KEYSET := auth 58 | KEYTAG := A 59 | else 60 | KEYSET := UNSET 61 | KEYTAG := UNSET 62 | endif 63 | 64 | # Use the GPGHOME environment variable to define a fixed gnupg home folder. 65 | GPGHOME ?= GPGHOME 66 | GPGBIN ?= gpg 67 | CARDNO := $(shell $(GPGBIN) --no-keyring --card-status 2>/dev/null | awk -F: '/^Serial/ {gsub("[ ]+", "", $$2); print $$2}') 68 | 69 | ifeq ($(GPGHOME),GPGHOME) 70 | ifeq ($(CARDNO),) 71 | GNUPGHOME := $(shell /bin/date '+%F_%H%M')/gnupg 72 | else 73 | GNUPGHOME := $(shell /bin/date '+%F_%H%M')/gnupg-$(CARDNO) 74 | endif 75 | else 76 | ifeq ($(CARDNO),) 77 | GNUPGHOME := $(GPGHOME)/gnupg 78 | else 79 | GNUPGHOME := $(GPGHOME)/gnupg-$(CARDNO) 80 | endif 81 | endif 82 | 83 | GPGCMD ?= $(GPGBIN) --no-default-keyring --homedir $(GNUPGHOME) 84 | GPG_VERSION_MAJOR = $(shell $(GPGCMD) --version 2>&1 | awk '/^gpg.*[0-9\.]+$$/ {print $$3}' | grep -Eo '^[0-9]') 85 | GPG_VERSION_MINOR = $(shell $(GPGCMD) --version 2>&1 | awk '/^gpg.*[0-9\.]+$$/ {print $$3}' | sed -e 's/^[0-9]\.//' -e 's/\.[0-9]*$$//') 86 | 87 | ### 88 | # Public goals 89 | ### 90 | .PHONY: default 91 | default: master sckey $(GPGHOME)/DATA-transferred/subkey-shadows.asc id_rsa.pub test 92 | @echo "🔑 🔑 🔑 🔑 🔑 🔑 🔑 🔑 🔑 🔑 🔑 🔑 🔑 🔑 🔑 🔑 🔑 🔑 🔑 🔑 🔑 🔑 🔑 🔑 🔑 🔑 🔑 🔑 🔑 🔑 🔑 🔑 🔑 🔑 🔑 🔑 🔑 " 93 | @echo "Your keys have been generated." 94 | @echo 95 | @echo "To prepare another smartcard/yubikey with subkeys off this master key do the following:" 96 | @echo 97 | @echo " 1. '$(MAKE) import-secrets sckey'" 98 | @echo " 2. Follow the below instructions to backup up all public and private components" 99 | @echo 100 | @echo "💾 💾 💾 💾 💾 💾 💾 💾 💾 💾 💾 💾 💾 💾 💾 💾 💾 💾 💾 💾 💾 💾 💾 💾 💾 💾 💾 💾 💾 💾 💾 💾 💾 💾 💾 💾 💾 " 101 | @echo "You'll should now back up $(GNUPGHOME) to an encrypted SLC USB stick or" 102 | @echo "two. Remember this USB stick will contain your original master key," 103 | @echo "revocation certificates, and original soft subkeys, such as your" 104 | @echo "encryption key." 105 | @echo 106 | @echo "🖨 🖨 🖨 🖨 🖨 🖨 🖨 🖨 🖨 🖨 🖨 🖨 🖨 🖨 🖨 🖨 🖨 🖨 🖨 🖨 🖨 🖨 🖨 🖨 🖨 🖨 🖨 🖨 🖨 🖨 🖨 🖨 🖨 🖨 🖨 🖨 🖨 " 107 | @echo "You can also print out your key backups and store them in a safe" 108 | @echo "place. The files are at:" 109 | @echo " - $(GPGHOME)/DATA-airgapped/paperkey-master.txt" 110 | @echo " - $(GPGHOME)/DATA-airgapped/paperkey-subkey-*.txt" 111 | @echo 112 | @echo "⏳ ⏳ ⏳ ⏳ ⏳ ⏳ ⏳ ⏳ ⏳ ⏳ ⏳ ⏳ ⏳ ⏳ ⏳ ⏳ ⏳ ⏳ ⏳ ⏳ ⏳ ⏳ ⏳ ⏳ ⏳ ⏳ ⏳ ⏳ ⏳ ⏳ ⏳ ⏳ ⏳ ⏳ ⏳ ⏳ ⏳ " 113 | @echo "Your keys, both master and subkeys, are set to expire. To extend their" 114 | @echo "validity you'll need to use the master key." 115 | @echo 116 | @echo "🔐 🔐 🔐 🔐 🔐 🔐 🔐 🔐 🔐 🔐 🔐 🔐 🔐 🔐 🔐 🔐 🔐 🔐 🔐 🔐 🔐 🔐 🔐 🔐 🔐 🔐 🔐 🔐 🔐 🔐 🔐 🔐 🔐 🔐 🔐 🔐 🔐 " 117 | @echo "Finally, you'll want to transfer the following files to your regular" 118 | @echo "device and import the subkeys into your regular ~/.gnupg keychain." 119 | @echo " - $(GPGHOME)/DATA-transferred/subkey-shadows.asc" 120 | @echo " - $(GPGHOME)/DATA-transferred/id_rsa.pub" 121 | @echo 122 | 123 | .PHONY: softkeys 124 | softkeys: master $(GPGHOME)/DATA-airgapped/paperkey-subkey-sign.txt $(GPGHOME)/DATA-airgapped/paperkey-subkey-auth.txt 125 | $(MAKE) signing 126 | $(MAKE) encryption 127 | $(MAKE) auth 128 | 129 | .PHONY: master 130 | master: $(GPGHOME)/DATA-airgapped/paperkey-master.txt 131 | $(MAKE) encryption 132 | 133 | .PHONY: signing 134 | signing: $(GPGHOME)/DATA-airgapped/subkey-sign.asc 135 | 136 | .PHONY: encryption 137 | encryption: $(GPGHOME)/DATA-airgapped/subkey-encr.asc $(GPGHOME)/DATA-airgapped/paperkey-subkey-encr.txt 138 | 139 | .PHONY: auth 140 | auth: id_rsa.pub 141 | 142 | # reset 143 | reset-yubikey: 144 | @echo "Are you sure you want to reset the yubikey GPG applet? This is a destructive" 145 | @echo "action and not reversible." 146 | @bash -c "read -r -p \"Type 'reset-yubikey' if you're sure you want to continue: \" ANS; \ 147 | if [ \$$ANS = 'reset-yubikey' ]; then \ 148 | gpg-connect-agent -r yubikey-reset.txt; \ 149 | else \ 150 | echo 'You entered '\$$ANS', so the card was NOT reset.'; \ 151 | fi" 152 | 153 | # clean 154 | .PHONY: clean 155 | clean: 156 | @if [ -d "$(GPGHOME)" ]; then \ 157 | echo "Will remove following path: $(GPGHOME)"; \ 158 | echo "Continue (y/N)?"; \ 159 | read -r REPLY; \ 160 | if [ $${REPLY} = 'y' ]; then \ 161 | rm -rf "$(GPGHOME)"; \ 162 | echo "Done."; \ 163 | else \ 164 | echo "Aborted."; \ 165 | fi \ 166 | fi 167 | @if [ $$(ls -1d $$(/bin/date '+%F_')* 2>/dev/null | wc -l) -eq 0 ]; then \ 168 | echo "No auto-generated folders to clean."; \ 169 | else \ 170 | @echo "Will remove following paths:"; \ 171 | RMPATHS=$$(/bin/date '+%F_%H'); \ 172 | ls -1d $${RMPATHS}*; \ 173 | echo "Continue (y/N)?" \ 174 | read -r REPLY; \ 175 | if [ $${REPLY} = 'y' ]; then \ 176 | rm -rf "$${RMPATHS}*"; \ 177 | echo "Done."; \ 178 | else \ 179 | echo "Aborted."; \ 180 | fi \ 181 | fi 182 | 183 | .PHONY: show 184 | show: 185 | cd $(GNUPGHOME) && \ 186 | $(GPGCMD) --list-secret-keys 187 | $(GPGBIN) --card-status 188 | 189 | ### 190 | # master key 191 | ### 192 | $(GNUPGHOME)/private-keys-v1.d: $(GNUPGHOME)/gpg.conf $(GPGHOME)/DATA-airgapped/config 193 | cd $(GNUPGHOME) && \ 194 | . $(GPGHOME)/DATA-airgapped/config && \ 195 | $(GPGCMD) --quick-generate-key "$${GPG_USER_ID}" "$${MASTER_ALGO}" cert "$${MASTER_VALIDITY}" || : 196 | 197 | $(GPGHOME)/DATA-transferred/keyid-master.txt: $(GNUPGHOME)/private-keys-v1.d 198 | cd $(GNUPGHOME) && \ 199 | $(GPGCMD) --list-secret-keys | awk '/^sec/ {gsub("(rsa|elg|dsa|ecdh|ecdsa|eddsa)*[0-9]+R?/", "", $$2); print $$2}' > $(GPGHOME)/DATA-transferred/keyid-master.txt 200 | 201 | $(GPGHOME)/DATA-transferred/keyfp-master.txt: $(GPGHOME)/DATA-transferred/keyid-master.txt 202 | cd $(GNUPGHOME) && \ 203 | $(GPGCMD) --list-secret-keys | awk -F= '/Key fingerprint/ {gsub(" ", "", $$2); print $$2}' > $(GPGHOME)/DATA-transferred/keyfp-master.txt 204 | 205 | $(GPGHOME)/DATA-airgapped/key-master.asc: $(GPGHOME)/DATA-transferred/keyfp-master.txt 206 | cd $(GNUPGHOME) \ 207 | && $(GPGCMD) --armor --output $(GPGHOME)/DATA-airgapped/key-master.asc --export-secret-keys $(shell head -n1 $(GPGHOME)/DATA-transferred/keyid-master.txt) \ 208 | && $(GPGCMD) --armor --output $(GPGHOME)/DATA-transferred/key-master.pub --export $(shell head -n1 $(GPGHOME)/DATA-transferred/keyid-master.txt) 209 | 210 | $(GPGHOME)/DATA-airgapped/paperkey-master.txt: $(GPGHOME)/DATA-airgapped/key-master.asc 211 | cd $(GNUPGHOME) \ 212 | && $(GPGCMD) --export-secret-keys | paperkey -o $(GPGHOME)/DATA-airgapped/paperkey-master.txt 213 | 214 | ### 215 | # subkey 216 | ### 217 | $(GPGHOME)/DATA-transferred/subkeyid-$(KEYSET).txt: $(GNUPGHOME)/gpg.conf $(GPGHOME)/DATA-airgapped/config 218 | cd $(GNUPGHOME) \ 219 | && . $(GPGHOME)/DATA-airgapped/config \ 220 | && $(GPGCMD) --quick-add-key "$(shell head -n1 $(GPGHOME)/DATA-transferred/keyfp-master.txt)" "$${SUBKEY_ALGO}" "$(KEYSET)" "$${SUBKEY_VALIDITY}" 221 | cd $(GNUPGHOME)\ 222 | && $(GPGCMD) --list-secret-keys | awk '/^ssb.*\[$(KEYTAG)\]/ {gsub("(rsa|elg|dsa|ecdh|ecdsa|eddsa|ed)*[0-9]+R?/", "", $$2); print $$2}' | head -n1 > $(GPGHOME)/DATA-transferred/subkeyid-$(KEYSET).txt 223 | 224 | 225 | $(GPGHOME)/DATA-airgapped/subkey-$(KEYSET).asc: $(GPGHOME)/DATA-transferred/subkeyid-$(KEYSET).txt 226 | cd $(GNUPGHOME) \ 227 | && $(GPGCMD) --armor --output $(GPGHOME)/DATA-airgapped/subkey-$(KEYSET).asc --export-secret-subkeys "$(shell head -n1 $(GPGHOME)/DATA-transferred/subkeyid-$(KEYSET).txt)" \ 228 | && $(GPGCMD) --armor --output $(GPGHOME)/DATA-transferred/subkey-$(KEYSET).pub --export "$(shell head -n1 $(GPGHOME)/DATA-transferred/subkeyid-$(KEYSET).txt)" 229 | 230 | $(GPGHOME)/DATA-airgapped/paperkey-subkey-$(KEYSET).txt: $(GPGHOME)/DATA-airgapped/subkey-$(KEYSET).asc 231 | cd $(GNUPGHOME) \ 232 | && $(GPGCMD) --export-secret-subkeys "$(shell head -n1 $(GPGHOME)/DATA-transferred/subkeyid-$(KEYSET).txt)" | paperkey -o $(GPGHOME)/DATA-airgapped/paperkey-subkey-$(KEYSET).txt 233 | 234 | .PHONY: id_rsa.pub 235 | id_rsa.pub: 236 | cd $(GNUPGHOME) && \ 237 | $(GPGCMD) --list-secret-keys | awk '/\[A\]/ {gsub(".*/", "", $$2); print $$2}' | while read -r SSH_KEY; do \ 238 | if [ ! -r $(GPGHOME)/DATA-transferred/id_rsa-$${SSH_KEY}.pub ]; then \ 239 | $(GPGCMD) --export-ssh-key "$${SSH_KEY}" > $(GPGHOME)/DATA-transferred/id_rsa-$${SSH_KEY}.pub; \ 240 | fi; \ 241 | done 242 | 243 | ### 244 | # perform smartcard actions 245 | ### 246 | .PHONY: sckey 247 | sckey: cardedit id_rsa.pub $(GPGHOME)/DATA-transferred/subkey-shadows.asc 248 | 249 | .PHONY: cardedit 250 | cardedit: $(GPGHOME)/DATA-transferred/keyid-master.txt 251 | @echo "===================================================================================" 252 | @echo 253 | @echo "Unfortunately GnuPG 2.1 doesn't yet support automation of the SmartCard functions" 254 | @echo "so you'll have to carry out the following steps manually." 255 | @echo 256 | @echo "To create signing and and authentication keys directly on the SmartCard you'll" 257 | @echo "need the following commands:" 258 | @echo 259 | @echo " 1. 'toggle' to enter admin mode" 260 | @echo 261 | @echo " 2. 'key 1' to select the encryption subkey designated by 'usage: E'. Make sure" 262 | @echo " select the correct subkey, it may be 'key 3' for example." 263 | @echo " 3. 'keytocard' to move the selected encryption key to the card. Follow the" 264 | @echo " instructions to complete this step. When done you should see 'ssb>' next to" 265 | @echo " the key indicating you have a stub locally" 266 | @echo 267 | @echo " 4. 'addcardkey' and follow the instructions to generate a signing key on the card" 268 | @echo " 5. Repeat previous step to create an auth subkey" 269 | @echo 270 | @echo " 6. 'quit' to leave the edit-key mode." 271 | @echo 272 | @echo "If you want to prepare a second SmartCard with the same encryption key you'll" 273 | @echo "have to repeat this step with '$(MAKE) import-secrets sckey' to re-import the encryption" 274 | @echo "key and put it on the second SmartCard." 275 | @echo 276 | @echo "===================================================================================" 277 | cd $(GNUPGHOME) && \ 278 | $(GPGCMD) --edit-key "$(shell head -n1 $(GPGHOME)/DATA-transferred/keyid-master.txt)" || : 279 | # There seems to be a bug in at least GPG-2.1.23 that results in occasional 280 | # non-zero exit codes when finishing the --edit-key interaction, so we explicitly 281 | # return true at this point. 282 | 283 | ### 284 | # import encryption key following moving it to a SmartCard 285 | ### 286 | .PHONY: import-secrets 287 | import-secrets: $(GNUPGHOME)/gpg.conf 288 | gpgconf --kill gpg-agent scdaemon 289 | cd $(GNUPGHOME) && \ 290 | $(GPGCMD) --batch --delete-secret-and-public-keys "$(shell head -n1 $(GPGHOME)/DATA-transferred/keyid-master.txt)" || :; \ 291 | $(GPGCMD) --import $(GPGHOME)/DATA-airgapped/key-master.asc && \ 292 | $(GPGCMD) --import $(GPGHOME)/DATA-airgapped/subkey-encr.asc 293 | 294 | ### 295 | # remove master key 296 | ### 297 | .PHONY: remove-master 298 | remove-master: $(GPGHOME)/DATA-transferred/subkey-shadows.asc 299 | cd $(GNUPGHOME) && \ 300 | $(GPGCMD) --delete-secret-keys $(shell head -n1 $(GPGHOME)/DATA-transferred/keyid-master.txt) && \ 301 | $(GPGCMD) --import $(GPGHOME)/DATA-transferred/subkey-shadows.asc 302 | 303 | ### 304 | # import secret subkeys & link to smartcard 305 | ### 306 | 307 | .PHONY: $(GPGHOME)/DATA-transferred/subkey-shadows.asc 308 | $(GPGHOME)/DATA-transferred/subkey-shadows.asc: 309 | cd $(GNUPGHOME) && \ 310 | CARDNO=$(shell $(GPGCMD) --card-status | awk -F: '/^Serial/ {gsub("[ ]+", "", $$2); print $$2}') && \ 311 | $(GPGCMD) --export-secret-subkeys --armor > $(GPGHOME)/DATA-transferred/subkey-shadows-$${CARDNO}.asc 312 | $(GPGCMD) --export --armor > $(GPGHOME)/DATA-transferred/subkey-shadows-$${CARDNO}.pub 313 | 314 | .PHONY: import-ssb 315 | import-ssb: 316 | @if [ -z "$(SHADOWS)" ]; then \ 317 | echo "ERROR: You must set SHADOWS=/path/to/DATA-transfered/subkey-shadows.asc for this to work."; \ 318 | exit 1; \ 319 | fi 320 | gpgconf --kill gpg-agent scdaemon 321 | $(GPGBIN) --import $(SHADOWS) 322 | $(GPGBIN) --card-status 323 | 324 | ### 325 | # test 326 | ### 327 | .PHONY: test 328 | test: 329 | gpgconf --kill gpg-agent scdaemon 330 | @echo '%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%' 331 | @echo 'Testing that we can encrypt & sign, then decrypt and validate a message.' 332 | @cd $(GNUPGHOME) && \ 333 | { echo '🔐🔐🔐🔐🔐🔐🔐🔐🔐🔐🔐🔐🔐🔐🔐🔐🔐🔐🔐🔐🔐🔐🔐🔐🔐🔐🔐🔐🔐🔐🔐🔐🔐🔐🔐🔐🔐🔐🔐🔐🔐🔐🔐🔐🔐🔐🔐'; \ 334 | echo 'If you can read this the encryption and decryption have worked!'; \ 335 | echo '🎉🎉🎉🎉🎉🎉🎉🎉🎉🎉🎉🎉🎉🎉🎉🎉🎉🎉🎉🎉🎉🎉🎉🎉🎉🎉🎉🎉🎉🎉🎉🎉🎉🎉🎉🎉🎉🎉🎉🎉🎉🎉🎉🎉🎉🎉🎉'; \ 336 | } | \ 337 | $(GPGCMD) --encrypt --sign -a -r $(shell head -n1 $(GPGHOME)/DATA-transferred/keyfp-master.txt) | \ 338 | $(GPGCMD) --decrypt || echo 'FAILED. Check that you have a working pinentry program configured' 339 | @echo '%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%' 340 | 341 | ### 342 | # Common, config and other phonies 343 | ### 344 | $(GPGHOME)/DATA-transferred/.keep: 345 | mkdir -m 0700 -p $(GPGHOME)/DATA-transferred 346 | touch "$(GPGHOME)/DATA-transferred/.keep" 347 | 348 | $(GPGHOME)/DATA-airgapped/.keep: $(GPGHOME)/DATA-transferred/.keep 349 | mkdir -m 0700 -p $(GPGHOME)/DATA-airgapped 350 | touch "$(GPGHOME)/DATA-airgapped/.keep" 351 | 352 | $(GNUPGHOME): 353 | gpgconf --kill gpg-agent scdaemon 354 | mkdir -m 0700 -p $(GNUPGHOME) 355 | 356 | $(GPGHOME)/DATA-airgapped/gpg-version: $(GNUPGHOME) $(GPGHOME)/DATA-airgapped/.keep 357 | @if [ $(GPG_VERSION_MAJOR) -ge 2 ] && [ $(GPG_VERSION_MINOR) -ge 1 ]; then \ 358 | echo "Found GnuPG $(GPG_VERSION_MAJOR).$(GPG_VERSION_MINOR)"; \ 359 | $(GPGBIN) --version > $(GPGHOME)/DATA-airgapped/gpg-version 2>&1; \ 360 | else \ 361 | echo "GnuPG >= 2.1 is required, found GnuPG $(GPG_VERSION_MAJOR).$(GPG_VERSION_MINOR)"; \ 362 | exit 1; \ 363 | fi 364 | 365 | $(GNUPGHOME)/gpg-agent.conf: 366 | @if [ $(shell uname) == 'FreeBSD' ] && [ ! -r $(GNUPGHOME)/gpg-agent.conf ]; then \ 367 | for PINENTRY in pinentry-gnome3 pinentry-gtk; do \ 368 | if [ -x /usr/local/bin/$${PINENTRY} ]; then \ 369 | echo "pinentry-program /usr/local/bin/$${PINENTRY}" > $(GNUPGHOME)/gpg-agent.conf; \ 370 | break; \ 371 | elif [ -x /usr/bin/$${PINENTRY} ]; then \ 372 | echo "pinentry-program /usr/local/bin/$${PINENTRY}" > $(GNUPGHOME)/gpg-agent.conf; \ 373 | break; \ 374 | fi; \ 375 | done; \ 376 | echo "default-cache-ttl 3600" >> $(GNUPGHOME)/gpg-agent.conf; \ 377 | echo "enable-extended-key-format" >> $(GNUPGHOME)/gpg-agent.conf; \ 378 | fi 379 | 380 | $(GNUPGHOME)/gpg.conf: $(GPGHOME)/DATA-airgapped/gpg-version $(GNUPGHOME)/gpg-agent.conf 381 | cp gpg.conf-sample $(GNUPGHOME)/gpg.conf 382 | 383 | $(GPGHOME)/DATA-airgapped/config: $(GPGHOME)/DATA-airgapped/gpg-version 384 | if [ ! -f $(GPGHOME)/DATA-airgapped/config ]; then \ 385 | cp config-sample $(GPGHOME)/DATA-airgapped/config; \ 386 | $(EDITOR) $(GPGHOME)/DATA-airgapped/config; \ 387 | fi 388 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Automate GPG keyset creation, optionally on Yubikey(s) and/or SmartCard(s) 2 | 3 | TL;DR: jump to the [Instructions for creating an airgapped key set with two 4 | yubikeys](#scenario1) 5 | 6 | ## Contents 7 | 8 | - [Overview](#overview) 9 | - [What to expect](#what-to-expect) 10 | - [Requirements](#requirements) 11 | - [Usage](#usage) 12 | - [Scenario 1: Instructions for creating an airgapped key set with two yubikeys](#scenario1) 13 | - [Scenario 2: Instructions for creating an airgapped soft key set](#scenario2) 14 | - [Key practices followed in this repository](#key-practices) 15 | - [Using ZFS and EncFS](#zfs) 16 | - [Expired Key Renewals](#renewal) 17 | - [To do](#to-do) 18 | - [Known issues](#known-issues) 19 | 20 | ## Overview 21 | 22 | There are numerous well written guides that describe how to manually generate 23 | a GPG key set, both for soft keys and for Yubikeys or other PGP/GPG 24 | SmartCards. The aim of this work is to automate the GPG commands so you can 25 | generate and create your key set without having to become a GPG veteran. 26 | 27 | There are a couple of scenarios currently supported: 28 | 29 | 1. An offline soft master key, and subkeys stored on one or more smartcards or 30 | yubikeys. 31 | 2. An offline soft master key, and soft subkeys that can be imported into your 32 | main devices. 33 | 3. An offline soft master key, and several sets of soft subkeys, with a common 34 | encryption key to be used across devices. 35 | 36 | You can, of course, choose not to keep your master key offline or airgapped. 37 | 38 | Additionally you can keep your master key on a smartcard to aide with sub key 39 | renewals. If you have a spare smartcard or are willing to purchase one, this 40 | is highly recommended as it massively simplifies renewals and certification of 41 | other keys. See the [instructions on the wiki](https://github.com/jinnko/gpg-smartcard-automation/wiki#using-a-separate-smartcard-for-the-master-certification-key) for how to do this. 42 | 43 | ### What to expect 44 | 45 | The goal is to have a structure as follows for your day to day use. The 46 | master secret key is: 47 | 48 | - generated on an airgapped device 49 | - only stored in cold storage 50 | - only capable of certifying subkeys 51 | 52 | All the private sub keys are either soft keys in your regular ~/.gnupg 53 | keyring, or are stored on a Yubikey or other PGP/GPG smartcard. 54 | 55 | sec# rsa4096 2017-06-26 [C] [expires: 2022-06-25] 56 | D49147668E74D68C108A83DB53E708995F9835CD 57 | uid [ultimate] Real Name (https://keybase.io/userid) 58 | ssb> rsa2048 2017-06-26 [E] [expires: 2019-06-26] 59 | ssb> rsa4096 2017-06-26 [S] [expires: 2019-06-26] 60 | ssb> rsa4096 2017-06-26 [A] [expires: 2019-06-26] 61 | ssb# rsa2048 2017-09-09 [S] [expires: 2019-06-26] 62 | ssb# rsa2048 2017-09-09 [A] [expires: 2019-06-26] 63 | 64 | In the above example we can make the following observations: 65 | 66 | - `#` in `sec#` indicates the private key part of the master key is not 67 | available locally 68 | - `>` in `ssb>` indicates the private key for each subkey is stored on 69 | a PGP/GPG SmartCard or Yubikey. 70 | - `#` in `ssb#` indicates the public key for these subkeys is available. This 71 | would usually be the case if you have the public keys for an additional 72 | smartcard in your keychain. 73 | - `[C]` indicates the key is only capable of certification 74 | - signing (`[S]`), encryption (`[E]`) and authentication (`[A]`) are all 75 | achieved by the corresponding individual subkeys 76 | - The encryption key is 2048 bit so that it can be shared between multiple 77 | smartcards or yubikeys, among which the lowest common support is 2048 bit 78 | keys. If your smartcards support higher sizes you can use the size you 79 | need. 80 | - The signing and authentication keys are 4096 bit as they're generated on the 81 | card at the maximum supported bits for that specific card. 82 | 83 | ## Requirements 84 | 85 | 1. GnuPG >= 2.1 86 | 2. GNU Make 87 | 3. [Paperkey](http://www.jabberwocky.com/software/paperkey/) 88 | 89 | ### Recommended / optional extras 90 | 91 | - Air gapped device. Either a system that is not online, or make use of 92 | a live system image, with all connectivity disabled, possibly after 93 | downloading further requirements. 94 | - One or two PGP/GPG smartcards, such as Yubikeys. 95 | - At least two USB thumb drives to keep your master key safe. You may want to 96 | consider using SLC sticks, and/or using [ZFSonLinux](http://zfsonlinux.org/) 97 | / [O3X](https://openzfsonosx.org/) to keep the keys safe from bitrot and 98 | device failure. For a live OS image, [GhostBSD](http://ghostbsd.org/) works 99 | well, though it didn't boot on my 2013 Macbook Pro, so I used another old PC 100 | laptop. 101 | - An additional USB thumb drive to transfer subkey shadows if using a 102 | SmartCard, or private subkeys if not using a smartcard, and public keys to 103 | your regular device. 104 | - Access to a printer for creating your hard copies of your master key. 105 | 106 | ## Usage 107 | 108 | Use the built-in help for additional targets and usage. 109 | 110 | gmake help 111 | 112 | ## Scenario 1: Instructions for creating an airgapped key set with two yubikeys 113 | 114 | These steps will take you through an example scenario. Adjust as needed. 115 | 116 | ### Checklist 117 | 118 | 1. Two Yubikeys, one is a Neo (max 2048bit keys), and the other a Nano (max 119 | 4096bit keys). 120 | 2. Four USB sticks: 121 | 1. First a USB stick for the live OS image. 122 | 2. Two that will store your secrets offline and should never be used in 123 | a networked device. Ideally these are industrial quality, possibly 124 | SLC. 125 | 3. One for transferring data across the air gap. 126 | 3. GhostBSD live image. At the time of writing, the GhostBSD 11 beta images 127 | worked well, supports ZFS, and has up to date GPG packages. You could also 128 | use any linux live system, such as [Tails](https://tails.boum.org), however 129 | be aware you won't get ZFS with that. See the [Cold storage of the secret 130 | keys and revocation certificates](#cold-storage) section below for why ZFS 131 | would be of value. 132 | 4. Passwords you'll be using to protect your keys, and if you choose to use 133 | EncFS, then also for the encrypted mount. 134 | 5. Printer for the paper keys. 135 | 136 | ### Preparation 137 | 138 | 1. Prepare the USB drives for the OS, storing secrets, and for transferring 139 | keys. If your primary workstation is Linux or Mac and you want to use ZFS, 140 | prepare the sticks before moving to the air gapped system. 141 | 142 | 1. First, prepare your live OS image following the instructions for the 143 | live system you've chosen. 144 | 145 | 2. At least two USB thumb drives for storing your secrets. We'll 146 | refer to these as the *secure USB sticks* as they should ideally only 147 | be used on an air-gapped device. See the section below on [Creating 148 | ZFS pools](#zfs) if that's how you'll be proceeding. 149 | 150 | 3. One USB thumb drive for transferring data between workstation and the 151 | air-gapped device. We'll refer to this as the *transfer USB stick*. 152 | It will contain a copy of this repository, GPG public keys and shadow 153 | keys. No secrets will be stored on this, and any data on here can be 154 | recreated from the *secure USB sticks*. See the section below on 155 | [Creating ZFS pools](#zfs) if that's how you'll be proceeding. 156 | 157 | 3. Clone this repository to the *transfer USB stick*. 158 | 159 | ### Generate the keys 160 | 161 | These steps will prepare two SmartCards with secret subkeys, paperkey backups 162 | that should be printed, and a revocation certificate in case the master key is 163 | damaged or compromised. 164 | 165 | 1. Boot up the air-gapped system. 166 | 167 | 2. Get the live OS network connected, then get all the packages you'll need to 168 | complete this task, and finally disable networking. For GhostBSD these are 169 | the steps: 170 | 171 | 1. Establish a network connection, whether wired or wireless, using the 172 | GUI. 173 | 2. In a terminal, get the necessary packages: 174 | 175 | sudo pkg install gnupg paperkey fusefs-encfs gmake 176 | 177 | 3. Turn off networking: 178 | 179 | sudo service netif stop 180 | 181 | 3. Insert the *transfer USB stick*, mount it, and copy the 182 | gpg-smartcard-automation repository off the USB stick. I recommend *not* 183 | working directly from the stick as I've experienced odd behaviours with the 184 | filesystems when doing this. 185 | 186 | mkdir ~ghostbsd/zpools 187 | sudo zpool import -R ~ghostbsd/zpools airgap-transfers 188 | sudo zpool scrub airgap-transfers 189 | cp -a ~/zpools/airgap-transfers/gpg-smartcard-automation ~/ 190 | sudo zpool export airgap-transfers 191 | 192 | 4. Create your keyset: 193 | 194 | 1. Prepare your working environment 195 | 196 | setenv GPGHOME $HOME/gpg-20170903 197 | cd ~/gpg-smartcard-automation 198 | 199 | 2. Insert the first yubikey. If you've not used the key before, or if 200 | you're recently reset it (try `gmake reset-yubikey`), then the default 201 | admin PIN is `12345678`, and regular PIN is `123456`. Remember to change 202 | the default PIN as soon as possible (hint: `gpg --card-edit`). 203 | 204 | gmake default 205 | 206 | You'll be prompted for your password many times. Follow the 207 | instructions in the output to complete all the parts of this step. 208 | 209 | 3. If you'll be using a second yubikey, insert it now. The tooling will 210 | prepare another GPG homedir and keychain to work around a [GnuPG 211 | limitation that will be fixed by T2291](https://dev.gnupg.org/T2291). 212 | 213 | gmake import-secrets sckey 214 | 215 | You can skip this step if you're only preparing a single Yubikey. 216 | 217 | 4. Copy data to USB sticks. A series of helper scripts make this easy for 218 | you. 219 | 220 | 1. Insert first *secure USB stick*, then 221 | 222 | ./copy-secrets-to-usb.sh $GPGHOME secure-1 223 | 224 | In this case, `secure-1` is the pool name for this USB stick. This 225 | scripts assume you're using ZFS and EncFS. 226 | 227 | 2. Repeat step 1 for additional *secure USB sticks*, adjusting the pool 228 | name as appropriate. 229 | 230 | 3. Insert the *transfer USB stick* to copy the data destined for your 231 | main device. As before, the second argument is the ZFS pool name. 232 | In this case there's no EncFS as there's nothing to be hidden here. 233 | 234 | ./copy-to-transfer-usb.sh $GPGHOME airgap-transfers 235 | 236 | 5. Print the paper master key. Ideally this will be via a USB connected 237 | printer rather than something wireless or networked. The file to print 238 | is: 239 | 240 | $(GPGHOME)/DATA-airgapped/paperkey-master.txt 241 | 242 | At this point you're done with the air gapped live OS work! 243 | 244 | 5. Import your new keys to your main devices. 245 | 246 | 1. Insert the *transfer USB stick* into your main device and copy the 247 | `$GPGHOME` path across to your system. For example 248 | 249 | cp -a /Volumes/airgap-transfers/gpg-20170903 ~/ownCloud/ 250 | 251 | 2. If you prepared multiple Yubikeys, some files will be suffixed with the 252 | Yubikey serial number, for example 253 | `$GPGHOME/DATA-transferred/subkey-shadows-03821903.asc`. Choose the one 254 | you want to import and pull it in. 255 | 256 | gpg --import < ~/ownCloud/gpg-20170903/DATA-transferred/subkey-shadows-03821903.asc 257 | 258 | 3. Create the link between the imported shadows keys and your Yubikey: 259 | 260 | gpg --card-status 261 | 262 | 4. If you want to use your other Yubikey with your mobile you'll need to 263 | import the other `subkey-shadows-*.asc` file into something like 264 | OpenKeyChain (for Android), then register your Yubikey with the app. 265 | 266 | ### What next 267 | 268 | You may want to do a few further things to strengthen your key and web of 269 | trust: 270 | 271 | - Sign your new key with your old key. 272 | 273 | gpg --local-user 0xOLD_KEY_ID --sign-key NEW_KEY_ID 274 | 275 | - Pull in the public keys for your second set of keys. 276 | 277 | gpg --import /path/to/DATA-transferred/key-master.pub 278 | gpg --import /path/to/DATA-transferred/subkey*.pub 279 | 280 | - Upload your new public key to https://keybase.io 281 | 282 | ## Scenario 2: Instructions for creating an airgapped soft key set 283 | 284 | If you don't have a smartcard, you can generate only soft keys, with the 285 | master key kept in cold storage, and your subkeys available to your every day 286 | devices. The benefit of this is that if your subkeys are compromised for any 287 | reason, your web of trust doesn't break down due to the trust being anchored 288 | to your master key. 289 | 290 | The main difference here is that in step 4.2 of [the first 291 | scenario](#scenario1), rather than running `gmake default`, you need to `gmake 292 | softkeys`. There are some further considerations for copying your secrets 293 | across to the your main device, and those steps will be detailed soon. Short 294 | explanation is that you'll likely want to use an EncFS on your *transfer USB 295 | stick* to keep those secrets less exposed. 296 | 297 | ## Key practices followed in this repository 298 | 299 | ### Offline master key 300 | 301 | The master key is a soft key only capable of certification and should not be 302 | stored on a device used daily. It's a good idea to keep this safe on an 303 | encrypted USB device, and even better to have a print out of it, as is 304 | prepared by paperkey. 305 | 306 | The generated master key backups are stored at: 307 | 308 | - $(GPGHOME)/DATA-airgapped/key-master.asc 309 | - $(GPGHOME)/DATA-airgapped/paperkey-master.txt 310 | 311 | ### Separate signing, encryption and authentication sub keys 312 | 313 | When using Yubikeys or PGP/GPG smartcards, we want to have different signing 314 | and authentication keys on each smartcard so that in the event one is lost we 315 | only need to revoke that single key set of that token, plus the shared 316 | encryption key. 317 | 318 | The use of a shared encryption key is primarily for convenience: to have the 319 | same encryption key used on both smartcards making it easy for others to send 320 | us encrypted messages and not require them to have any knoweldge of our 321 | PGP/GPG key structure. 322 | 323 | For these reasons we generate the encryption key as a soft key that is 324 | transferred to the smartcard, while we generate the signing and authentication 325 | keys directly on the cards. 326 | 327 | If you don't have a smartcard then you can generate all the keys as soft keys 328 | using [scenario 2](#scenario2). 329 | 330 | Storing and keeping the soft keys requires a more prudence than when 331 | using a smartcard. 332 | 333 | ### Cold storage of the secret keys and revocation certificates 334 | 335 | Ideally all your key material will be generated on an air gapped device, and 336 | even better on a live OS so there's no persistence. If you use an ephemeral 337 | system for this, such as [Tails](https://tails.boum.org) or 338 | [GhostBSD](https://ghostbsd.org), you'll need to keep your master key and 339 | revocation certificates on a couple of USB thumb drives. 340 | 341 | You should consider using SLC (Single Layer Cell) USB sticks as they should be 342 | more resilient, and using a filesystem capable of bitrot detection and 343 | recovery, such as ZFS. 344 | 345 | You should also print out the paperkey representations of all private key 346 | material as paper generally lasts a lot longer than USB thumb drives under 347 | normal conditions. 348 | 349 | ## Using ZFS and EncFS 350 | 351 | Using ZFS offers some benefits: 352 | 353 | - Interoperability between unix and macOs. On Linux this is availabe from the 354 | [ZFSonLinux](http://zfsonlinux.org/) project, and on macOS use 355 | [O3X](https://openzfsonosx.org/). BSDs generally come with ZFS by default. 356 | 357 | - Support for multiple copies of the data on a single drive. If your USB 358 | thumbdrive were to experience bitflips/bitrot ZFS will return the correct 359 | data silently, though there are some caveates. Details below. 360 | 361 | Using EncFS on top of ZFS has the benefit of interoperability between unix 362 | and macOS. 363 | 364 | ### Creating ZFS pools 365 | 366 | This section isn't comprehensive as there's enough documentation online, 367 | however here's a quick start to creating a pool with multipe data copies on 368 | a single stick: 369 | 370 | 1. Determine the device for your USB stick 371 | 372 | diskutil list 373 | 374 | Look for the device that corresponds to the characteristics of your stick 375 | and take node of the `/dev/diskX` title. 376 | 377 | 2. Create the new zpool. This is a destructive operation on the USB stick, so 378 | be sure you know what you're doing. 379 | 380 | sudo zpool create -f -o ashift=12 -O copies=3 -O normalization=formD airgap-transfers /dev/diskX 381 | 382 | This will create a volume with 3 copies of your data. To detect bitrot 383 | you'll need to perform scrubs of the disk as ZFS doesn't do this during 384 | normal operation. See 385 | [ZFSonLinux issue 1256](https://github.com/zfsonlinux/zfs/issues/1256) 386 | for more details. 387 | 388 | ### Mounting and unmounting ZFS volumes 389 | 390 | On macOS, if you have O3X installed, when you insert a ZFS USB stick it should 391 | get mounted automatically. If not, the follwing is what you need: 392 | 393 | sudo zpool import POOL_NAME 394 | 395 | You can get a list of available pools by omiting the `POOL_NAME`. 396 | 397 | Before you remove a USB stick from macOS you must *export* it: 398 | 399 | sudo zpool export POOL_NAME 400 | 401 | 402 | ## To doExpired key renewals 403 | 404 | If you're managing multiple smartcards you'll need to follow these steps for each smartcard. 405 | 406 | 1. Ensure you're working from a clean slate 407 | 408 | gpgconf --kill gpg-agent scdaemon 409 | ps -ef | grep gpg 410 | 411 | 2. Set up your working environment 412 | 413 | export GNUPGHOME=/path/to/gpg/conf/path 414 | 415 | 3. Get the primary key ID 416 | 417 | gpg --card-status 418 | 419 | 4. Start GPG key editing 420 | 421 | gpg --edit-key $PRIMARY_KEY 422 | 423 | 5. Select the keys you want to update 424 | 425 | gpg> key 1 426 | gpg> key 4 427 | gpg> key 5 428 | 429 | 6. Update expiry on the keys 430 | 431 | gpg> expire 432 | ... 433 | 434 | 7. Save and exit 435 | 436 | gpg> save 437 | 438 | 8. Export the new subkey shadows 439 | 440 | for key in $subkey1 $subkey2 $subkey3; do 441 | gpg --export-secret-subkeys --export-options export-minimal --armor $key > $key.shadow 442 | done 443 | 444 | Note that the inspected exports will appear to contain the primary key, however GnuPG 445 | essentially supports this `--export-secret-subkeys` as an extension to the PGP standard, and 446 | the primary key in these exports has been rendered unusable. 447 | 448 | 9. Export the new public keys 449 | 450 | gpg --export --export-options export-minimal --armor $PRIMARY_KEY > $GNUPGHOME/keys.pub 451 | 452 | 10. Transfer the exported `*.shadow` files to your main systems and `gpg --import` the ones 453 | relevant to the smartcard you use on that device. 454 | 455 | ## To do 456 | 457 | - Manage key expiry extention. As of GPG 2.1 only master key expiry can be 458 | easily manipulated with --quick-set-expire. 459 | - Add tests to validate all resulting artefacts are as expected, for example 460 | the exported ASCII armored private key and subkeys are well structured and 461 | encrypted, and the SSH public key is exported correctly. 462 | - When GnuPG supports automation of card key generation this will need to be 463 | implemented. 464 | - For some reason a Makefile seemed a good idea at the outset due to the fact 465 | we're creating many files and it should handle idempotence for us. The code 466 | has probably grown beyond that and would benefit from a rewrite in something 467 | more suitable. 468 | 469 | ## Using Raspbian as an airgapped system 470 | 471 | With a Raspberry Pi I wasn't able to get a working ZFS set up, probably because the Pi I'm using 472 | is 32bit, but it could also be due to limited memory. 473 | 474 | ### Quick start 475 | 476 | - Raspberry Pi (tested on a Pi Zero) 477 | - Raspbian (tested with Debian Stretch) 478 | - Install the base system to an SD card 479 | - Install essential packages 480 | 481 | apt-get update 482 | apt-get install paperkey scdaemon 483 | 484 | - You may want to use LUKS encrypted volume, in which case you'll need: 485 | 486 | apt-get install cryptsetup 487 | 488 | - Update & upgrade the system 489 | 490 | apt-get update 491 | apt-get upgrade 492 | 493 | - Copy this repository on to the Raspberry Pi 494 | 495 | ## Known issues 496 | 497 | ### GPG max key size 498 | 499 | GPG has a hard max key size of 4096bit. Apparently the brew version has upped 500 | this to 8192bit. You can try that if you desire, however keep in mind that 501 | most SmartCards only support 2048bit, while a few now support 4096bit. 502 | 503 | ### gpg-agent socket path length limit 504 | 505 | As of GnuPG 2.1 the key handling logic has been entirely moved out of the old 506 | `gpg` binary and into `gpg-agent`. As a result `gpg-agent` will be invoked 507 | automatically as needed, and therefore the `gpg-agent` socket will always be 508 | used for communication between the `gpg` command line interface and the 509 | running `gpg-agent`. Unfortunately sockets on Unix systems have a hard limit 510 | of 108 characters in their path, including macOS. This is not a limitation of 511 | GnuPG but rather of the underlying OS, and `gpg` may fail slightly cryptically 512 | if the path is longer than 108 characters. 513 | 514 | ### GPG only supports one card pointer per shadow/stub key 515 | 516 | This is a [GnuPG limitation that will be fixed by T2291](https://dev.gnupg.org/T2291). 517 | Unfortunately this didn't make it into the GnuPG-2.2. We work around this limitation 518 | by generating seaprate $GNUPGHOME folders for each smartcard being used. You then 519 | only import the main one you would use day to day on a given device, but if the 520 | need arises you're still able to switch to using the other smartcard by deleting 521 | your key shadows/stubs (hint: `gpg --delete-secret-and-public-keys 0xKEY_ID`), then 522 | re-importing the other shadows/stubs and registering them with the `gpg --card-status` 523 | command. 524 | -------------------------------------------------------------------------------- /config-sample: -------------------------------------------------------------------------------- 1 | # 2 | # This configuration will be used when generating your keys 3 | # 4 | 5 | ### 6 | # User ID 7 | ### 8 | GPG_REAL_NAME="Real Name" 9 | GPG_COMMENT="https://keybase.io/userid" 10 | GPG_EMAIL="email@addr.ess" 11 | 12 | # Use the appropriate GPG_USER_ID line below as needed depending on which 13 | # combination of above values is set. 14 | 15 | #GPG_USER_ID="${GPG_EMAIL}" 16 | #GPG_USER_ID="${GPG_REAL_NAME} <${GPG_EMAIL}>" 17 | GPG_USER_ID="${GPG_REAL_NAME} (${GPG_COMMENT}) <${GPG_EMAIL}>" 18 | 19 | ### 20 | # Key parameters 21 | ### 22 | MASTER_ALGO="rsa4096" 23 | MASTER_VALIDITY="5y" 24 | 25 | SUBKEY_ALGO="rsa2048" 26 | SUBKEY_VALIDITY="3m" 27 | -------------------------------------------------------------------------------- /copy-secrets-to-usb.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | GPGHOME="${1%\/}" 4 | POOL="$2" 5 | 6 | usage() { 7 | echo "Usage: $(basename $0) GPGHOME ZFS_POOL" 8 | echo " GPGHOME Path to the GPG working directory" 9 | echo " ZFS_POOL The pool name to work with" 10 | exit 1 11 | } 12 | [ -n "$GPGHOME" ] || usage 13 | [ -n "$POOL" ] || usage 14 | 15 | set -eux 16 | 17 | [ -r ${HOME}/encfs ] || mkdir ${HOME}/encfs 18 | 19 | sudo zpool import -R ${HOME}/zpools $POOL 20 | sudo zpool scrub $POOL 21 | sudo encfs --public ${HOME}/zpools/$POOL ${HOME}/encfs 22 | sudo chown -R ${USER}: ${HOME}/encfs 23 | rsync -ca --progress --stats --exclude="S.gpg-agent*" --exclude="S.scdaemon" $GPGHOME ${HOME}/encfs/ 24 | rsync -ca --delete-before --progress --stats ${HOME}/gpg-smartcard-automation ~/zpools/${POOL}/ 25 | sudo umount ${HOME}/encfs 26 | sudo zpool scrub $POOL 27 | sudo zpool export ${POOL} 28 | -------------------------------------------------------------------------------- /copy-to-transfer-usb.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -x 4 | 5 | GPGHOME="${1%\/}" 6 | POOL="$2" 7 | 8 | usage() { 9 | echo "Usage: $(basename $0) GPGHOME ZFS_POOL" 10 | echo " GPGHOME Path to the GPG working directory" 11 | echo " ZFS_POOL The pool name to work with" 12 | exit 1 13 | } 14 | [ -n "$GPGHOME" ] || usage 15 | [ -n "$POOL" ] || usage 16 | 17 | set -eu 18 | 19 | GPGDIR=$(basename ${GPGHOME}) 20 | 21 | sudo zpool import -R ${HOME}/zpools ${POOL} 22 | sudo zpool scrub ${POOL} 23 | 24 | sudo chown -R ${USER}: ${HOME}/zpools/${POOL} 25 | rsync -ca --progress --stats --exclude="S.gpg-agent*" --exclude="S.scdaemon" ${GPGHOME}/DATA-transferred ${HOME}/zpools/${POOL}/${GPGDIR} 26 | rsync -ca --delete-before --progress --stats ${HOME}/gpg-smartcard-automation ${HOME}/zpools/${POOL}/ 27 | 28 | sudo zpool scrub ${POOL} 29 | sudo zpool export ${POOL} 30 | -------------------------------------------------------------------------------- /export-public-keys-and-shadows.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -eE 4 | set -x 5 | 6 | PRIMARY_KEY=$(cat DATA-transferred/keyid-master.txt) 7 | 8 | for gnupg in gnupg-0{4,7}*; do 9 | gpgconf --kill gpg-agent scdaemon 10 | export GNUPGHOME=$(readlink -f "$gnupg") 11 | CARD_ID=${gnupg/gnupg-/} 12 | 13 | # Export public keys 14 | gpg --export --export-options export-minimal --armor \ 15 | "$PRIMARY_KEY" \ 16 | > "DATA-transferred/public-$CARD_ID-$(date -I).asc" 17 | 18 | # Export subkey shadows 19 | gpg --export-secret-subkeys --export-options export-minimal --armor \ 20 | > "DATA-transferred/subkey-shadows-$CARD_ID-$(date -I).asc" 21 | done 22 | 23 | gpgconf --kill gpg-agent scdaemon 24 | unset GNUPGHOME 25 | -------------------------------------------------------------------------------- /genkey-signing+encryption.conf-sample: -------------------------------------------------------------------------------- 1 | # This is the config file that will be used to generate your private keys. 2 | # Documentation on this file can be found at: 3 | # https://www.gnupg.org/documentation/manuals/gnupg-devel/Unattended-GPG-key-generation.html 4 | 5 | # Key-Type must come first. Only edit if you know what you're doing. 6 | Key-Type: RSA 7 | 8 | # BEGIN MAIN EDITABLE SECTION 9 | Name-Real: NAME 10 | Name-Comment: COMMENT 11 | Name-Email: EMAIL 12 | Key-Length: 2048 13 | Subkey-Length: 2048 14 | Expire-Date: 730 15 | # END MAIN EDITABLE SECTION 16 | 17 | # Other settings to get the right type of keys generated 18 | Key-Usage: sign 19 | Subkey-Type: RSA 20 | Subkey-Usage: encrypt 21 | Preferences: SHA512 SHA384 SHA256 SHA224 AES256 AES192 AES CAST5 ZLIB BZIP2 ZIP Uncompressed 22 | 23 | # FINISH 24 | %commit 25 | %echo done 26 | -------------------------------------------------------------------------------- /genkey-signing.conf-sample: -------------------------------------------------------------------------------- 1 | # This is the config file that will be used to generate your private keys. 2 | # Documentation on this file can be found at: 3 | # https://www.gnupg.org/documentation/manuals/gnupg-devel/Unattended-GPG-key-generation.html 4 | 5 | # Key-Type must come first. Only edit if you know what you're doing. 6 | Key-Type: RSA 7 | 8 | # BEGIN MAIN EDITABLE SECTION 9 | Name-Real: NAME 10 | Name-Comment: COMMENT 11 | Name-Email: EMAIL 12 | Key-Length: 2048 13 | Expire-Date: 730 14 | # END MAIN EDITABLE SECTION 15 | 16 | # Other settings to get the right type of keys generated 17 | Key-Usage: sign 18 | Preferences: SHA512 SHA384 SHA256 SHA224 AES256 AES192 AES CAST5 ZLIB BZIP2 ZIP Uncompressed 19 | 20 | # FINISH 21 | %commit 22 | %echo done 23 | -------------------------------------------------------------------------------- /gpg.conf-sample: -------------------------------------------------------------------------------- 1 | use-agent 2 | personal-cipher-preferences AES256 AES192 AES CAST5 3 | personal-digest-preferences SHA512 SHA384 SHA256 SHA224 4 | default-preference-list SHA512 SHA384 SHA256 SHA224 AES256 AES192 AES CAST5 ZLIB BZIP2 ZIP Uncompressed 5 | digest-algo SHA512 6 | cipher-algo AES256 7 | cert-digest-algo SHA512 8 | s2k-digest-algo SHA512 9 | s2k-cipher-algo AES256 10 | charset utf-8 11 | fixed-list-mode 12 | no-comments 13 | no-emit-version 14 | keyid-format 0xlong 15 | list-options show-uid-validity 16 | verify-options show-uid-validity 17 | with-fingerprint 18 | with-subkey-fingerprint 19 | -------------------------------------------------------------------------------- /yubikey-reset.txt: -------------------------------------------------------------------------------- 1 | /hex 2 | scd serialno 3 | scd apdu 00 20 00 81 08 40 40 40 40 40 40 40 40 4 | scd apdu 00 20 00 81 08 40 40 40 40 40 40 40 40 5 | scd apdu 00 20 00 81 08 40 40 40 40 40 40 40 40 6 | scd apdu 00 20 00 81 08 40 40 40 40 40 40 40 40 7 | scd apdu 00 20 00 83 08 40 40 40 40 40 40 40 40 8 | scd apdu 00 20 00 83 08 40 40 40 40 40 40 40 40 9 | scd apdu 00 20 00 83 08 40 40 40 40 40 40 40 40 10 | scd apdu 00 20 00 83 08 40 40 40 40 40 40 40 40 11 | scd apdu 00 e6 00 00 12 | scd apdu 00 44 00 00 13 | /echo Card has been successfully reset. 14 | /bye 15 | --------------------------------------------------------------------------------