aboutsummaryrefslogtreecommitdiffstats
path: root/modules/security/gpg/gpg-backup.sh
blob: 41c7524e4a8b623097664a4450a3c93d708a7cc2 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
#!/usr/bin/env bash
# Export all GPG keys and revocation certificates to an encrypted backup
# file, or decrypt and import them back from one.

set -euo pipefail

info() { printf '\033[1;34m==>\033[0m %s\n' "$*"; }
warn() { printf '\033[1;33m==> warning:\033[0m %s\n' "$*" >&2; }
error() { printf '\033[1;31m==> error:\033[0m %s\n' "$*" >&2; }
die() {
  error "$*"
  exit 1
}

WORKDIR="$(mktemp -d)"

cleanup() {
  if [[ -d $WORKDIR ]]; then
    find "$WORKDIR" -type f -exec shred -u -z {} \; 2>/dev/null || true
    rm -rf "$WORKDIR"
  fi
}
trap cleanup EXIT

usage() {
  echo "Usage: $0 <export|import> <filename>"
  echo ""
  echo "Commands:"
  echo "  export    Export all GPG keys to an encrypted file"
  echo "  import    Decrypt and import keys from a backup file"
  exit 1
}

do_export() {
  local outfile="$1"

  if [[ -e $outfile ]]; then
    die "Refusing to overwrite existing file: $outfile"
  fi

  info "Exporting public keys..."
  gpg --export --armor >"$WORKDIR/public-keys.asc"

  info "Exporting secret keys..."
  gpg --export-secret-keys --armor >"$WORKDIR/secret-keys.asc"

  info "Exporting secret subkeys (if any)..."
  gpg --export-secret-subkeys --armor >"$WORKDIR/secret-subkeys.asc" || true

  info "Exporting owner trust database..."
  gpg --export-ownertrust >"$WORKDIR/ownertrust.txt"

  info "Exporting revocation certificates..."
  mkdir -p "$WORKDIR/revocation-certs"
  if [[ -d "$HOME/.gnupg/openpgp-revocs.d" ]]; then
    cp "$HOME"/.gnupg/openpgp-revocs.d/*.rev "$WORKDIR/revocation-certs/" 2>/dev/null || true
  fi

  info "Bundling everything into a single archive..."
  tar -C "$WORKDIR" -cf "$WORKDIR/gpg-full-backup.tar" \
    public-keys.asc \
    secret-keys.asc \
    secret-subkeys.asc \
    ownertrust.txt \
    revocation-certs

  info "Encrypting with GPG (AES256, SHA512, max S2K iteration count)..."
  echo "  You will be prompted for a passphrase — use a strong one."
  gpg --symmetric \
    --cipher-algo AES256 \
    --digest-algo SHA512 \
    --s2k-mode 3 \
    --s2k-digest-algo SHA512 \
    --s2k-count 65011712 \
    --output "$outfile" \
    "$WORKDIR/gpg-full-backup.tar"

  info "Verifying: attempting decryption to confirm it works..."
  if gpg --decrypt "$outfile" >"$WORKDIR/verify.tar" 2>/dev/null; then
    if cmp -s "$WORKDIR/gpg-full-backup.tar" "$WORKDIR/verify.tar"; then
      info "Verification succeeded: backup decrypts correctly."
    else
      die "Decrypted content does not match original. Investigate before trusting this backup."
    fi
  else
    die "Decryption test failed."
  fi

  echo
  info "Done."
  echo "  Encrypted backup: $outfile"
  echo "  Store this file somewhere safe (offline media, encrypted drive)."
  echo "  The S2K iteration count only helps if your passphrase itself"
  echo "  has real entropy (e.g. a long diceware passphrase)."
}

do_import() {
  local infile="$1"

  if [[ ! -f $infile ]]; then
    die "File not found: $infile"
  fi

  info "Decrypting $infile ..."
  echo "  You will be prompted for the backup's passphrase."
  echo "  Note: this may take a while due to the high S2K iteration count."
  gpg --decrypt "$infile" >"$WORKDIR/gpg-full-backup.tar"

  info "Extracting archive..."
  tar -C "$WORKDIR" -xf "$WORKDIR/gpg-full-backup.tar"

  info "Importing public keys..."
  gpg --import "$WORKDIR/public-keys.asc"

  info "Importing secret keys..."
  gpg --import "$WORKDIR/secret-keys.asc"

  if [[ -s "$WORKDIR/secret-subkeys.asc" ]]; then
    info "Importing secret subkeys..."
    gpg --import "$WORKDIR/secret-subkeys.asc" || true
  fi

  if [[ -f "$WORKDIR/ownertrust.txt" ]]; then
    info "Importing owner trust database..."
    gpg --import-ownertrust "$WORKDIR/ownertrust.txt"
  fi

  if [[ -d "$WORKDIR/revocation-certs" ]] && [[ -n "$(ls -A "$WORKDIR/revocation-certs" 2>/dev/null)" ]]; then
    info "Restoring revocation certificates..."
    mkdir -p "$HOME/.gnupg/openpgp-revocs.d"
    cp "$WORKDIR"/revocation-certs/*.rev "$HOME/.gnupg/openpgp-revocs.d/" 2>/dev/null || true
  fi

  echo
  info "Done. Keys imported into your GPG keyring."
  echo "  Run 'gpg --list-secret-keys' to confirm."
}

# Main

if [[ $# -ne 2 ]]; then
  usage
fi

command="$1"
filename="$2"

case "$command" in
export)
  do_export "$filename"
  ;;
import)
  do_import "$filename"
  ;;
*)
  usage
  ;;
esac