Last active
January 30, 2025 20:51
-
-
Save zx0r/843298b67cd91a0835dcf36aada529d5 to your computer and use it in GitHub Desktop.
GnuPG (GPG) for macOS
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env bash | |
# ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ | |
# :::::::: :::: ::: ::: ::: ::::::::: :::::::: | |
# :+: :+: :+:+: :+: :+: :+: :+: :+: :+: :+: | |
# +:+ :+:+:+ +:+ +:+ +:+ +:+ +:+ +:+ | |
# :#: +#+ +:+ +#+ +#+ +:+ +#++:++#+ :#: | |
# +#+ #+#+# +#+ +#+#+# +#+ +#+ +#+ +#+ #+#+# | |
# #+# #+# #+# #+#+# #+# #+# #+# #+# #+# | |
# ######## ### #### ######## ### ######## | |
# Copyright (c) 2025 zx0r. All rights reserved. | |
# Script: setup-gpg-git-macos.sh | |
# Author: zx0r | |
# License: MIT License | |
# Contact Info: https:#github.com/zx0r | |
# Version: 1.0 | |
# Date: 2025-01-20 | |
# Description: This script installs and configures GnuPG (GPG) for macOS, | |
# sets up Git to use GPG for signing commits and ensures | |
# security settings are applied.It also auto-detects the user | |
# shell, configures GUI App to use GPG for commits and setup | |
# launch agent for gpg-agent | |
# “Stay Hungry, Stay Foolish.” | |
# - Steve Jobs Apple | |
# ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ | |
# Enable strict handling | |
set -euo pipefail | |
IFS=$'\n\t' | |
# Regular Colors | |
BLACK='\033[0;30m' # Black | |
RED='\033[0;31m' # Red | |
GREEN='\033[0;32m' # Green | |
CYAN='\033[0;33m' # CYAN | |
BLUE='\033[1;34m' # Blue | |
CYAN='\033[1;34m' # Blue | |
YELLOW='\033[1;33m' # Yellow | |
PURPLE='\033[0;35m' # Purple | |
CYAN='\033[1;36m' # Cyan | |
WHITE='\033[0;37m' # White | |
BWHITE='\033[1;37m' # Bold White | |
NC='\033[0m' # Text Rese# Function to check if a package is installed | |
# Function to print a step message | |
print_step() { | |
local step_name="$1" | |
echo -e "${BLUE} ➜ ${step_name}${NC}" | |
} | |
# Print colorful messages | |
print_message() { | |
echo -e "${CYAN}💭 $1${NC}" | |
} | |
# Print Success message | |
print_success() { | |
echo -e "\n${GREEN}✅ $1${NC}\n" | |
} | |
# Print Warnining message | |
print_warn() { | |
echo -e "${YELLOW}[Warn] $1${NC}" | |
} | |
# Print Error message and exit | |
print_error() { | |
echo -e "${RED}❗️$1${NC}" | |
exit 1 | |
} | |
# ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ Main ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ | |
# Main function to orchestrate the setup process | |
main() { | |
# First Phase - Setup | |
initialize_installation # Start | |
install_dependencies # Install GPG and related tools | |
detect_shell # Detect the user's shell | |
create_gpg_dir # Create $HOME/.gnupg | |
generate_gpg_key # Generate a new GPG key (if needed) | |
configure_gnupghome # Configure gpg.conf gpg-agent.conf | |
# Second Phase - Configuration | |
configure_vscode_gpg # Configure VSCode/VSCodium to use GPG for commits | |
configure_gui_git_signing # Configure Git to use GPG for signing commits | |
configure_gpg_agent_launch_agent # Set up gpg-agent as a launch agent | |
apply_security_settings # Apply additional security settings | |
configure_ssh_signing # (Option) SSH Key Signing | |
# configure_smime_signing # (Option) S/MIME Signing using smimesign | |
configure_git_gpg # Configure Git commit signing | |
# Third Phase - Verification | |
ptint_hints # Displaying hints | |
#export_gpg_key # Export GPG Key | |
verify_setup # Functional check | |
complete_installation # End | |
# Reload Shell | |
reload_shell # Reload Shell | |
} | |
# ━━━━━━━━━━━━━━━━━━━━━━━━━━━ First Phase - Setup ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ | |
initialize_installation() { | |
print_category "Initialization" | |
print_step "Starting installation process..." | |
} | |
print_category() { | |
local category_name="$1" | |
echo -e "\n${CYAN}━━━━━━━━━━━━━━━━━━━━━━━━━${NC} ${PURPLE}${category_name}${NC} ━━━━━━━━━━━━━━━━━━━━━━━━━${NC}" | |
# step_counter=1 # Reset step counter for each new category | |
} | |
is_installed() { | |
local package_name=$1 | |
if command -v "$package_name" &>/dev/null; then | |
return 0 # Package is installed | |
else | |
return 1 # Package is not installed | |
fi | |
} | |
# Install required packages | |
install_dependencies() { | |
print_step "Installing dependencies..." | |
# Check if Homebrew is installed, and install it if not | |
if ! command -v brew &>/dev/null; then | |
print_step "Homebrew not found. Installing Homebrew..." | |
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)" | |
else | |
print_success "Homebrew is already installed." | |
fi | |
# Install gnupg and pinentry-mac if not already installed | |
if ! is_installed gpg || ! is_installed pinentry-mac || ! is_installed smimesign; then | |
print_step "Installing gnupg and pinentry-mac and smimesign..." | |
brew install gnupg pinentry-mac smimesign | |
else | |
print_step "Update Homebrew and upgrade existing packages" | |
brew doctor && brew update && brew upgrade && brew cleanup && brew doctor | |
fi | |
} | |
# Function to detect the user's shell | |
detect_shell() { | |
shell_name=$(basename "$SHELL") | |
# Determine the shell configuration file | |
case "$shell_name" in | |
bash) | |
shell_config="$HOME/.bashrc" | |
;; | |
zsh) | |
shell_config="$HOME/.zshrc" | |
;; | |
fish) | |
shell_config="$HOME/.config/fish/config.fish" | |
;; | |
*) | |
print_warn "Unsupported shell: $shell_name. Please manually configure your shell." | |
exit 1 | |
;; | |
esac | |
print_step "Detected shell: ${GREEN}$shell_name{NC}" | |
print_step "Using configuration file: ${GREEN}$shell_config${NC}" | |
export SHELL_NAME="$shell_name" | |
export SHELL_CONFIG="$shell_config" | |
} | |
# Configure GPG | |
create_gpg_dir() { | |
local dir="$HOME/.gnupg" | |
# Check if the directory exists; if not, create it | |
if [[ ! -d "$dir" ]]; then | |
print_step "Creating GnuPG directory: ${GREEN}$dir${NC}" | |
# Create GPG config directory | |
mkdir -p "$dir" || print_error "Failed to create directory: $dir" | |
# Set proper permissions | |
chmod 700 $HOME/.gnupg | |
fi | |
} | |
# Function to generate a new GPG key | |
generate_gpg_key() { | |
print_step "Generating a new GPG key..." | |
# Collect user information securely | |
read -rp "Enter Your Name for GPG key: " NAME | |
read -rp "Enter Your Email for GPG key: " EMAIL | |
read -rsp "[Secure Mode] Enter passphrase for GPG key: " PASSPHRASE | |
echo | |
# Ensure clean GPG agent state | |
gpgconf --kill gpg-agent | |
gpg-agent --daemon | |
# Generate key with secure parameters | |
# Name-Real: $(git config user.name) | |
# Name-Email: $(git config user.email) | |
# Name-Comment: GITHUB-KEY | |
gpg --batch --generate-key <<EOF | |
Key-Type: RSA | |
Key-Length: 4096 | |
Subkey-Type: RSA | |
Subkey-Length: 4096 | |
Name-Real: $NAME | |
Name-Email: $EMAIL | |
Passphrase: $PASSPHRASE | |
Expire-Date: 0 | |
%commit | |
EOF | |
GPG_KEY_ID=$(gpg --list-secret-keys --keyid-format LONG | grep sec | tail -1 | awk '{print $2}' | cut -d'/' -f2) | |
print_success "GPG key generated successfully with passphrase protection" | |
print_step "GPG key details:" | |
print_step "• Name: ${GREEN}$NAME${NC}" | |
print_step "• Email: ${GREEN}$EMAIL${NC}" | |
print_step "• KEY ID: ${GREEN}${GPG_KEY_ID}{NC}" | |
} | |
configure_gnupghome() { | |
dir="$HOME/.gnupg" | |
download_url="https://gist.githubusercontent.com/zx0r/843298b67cd91a0835dcf36aada529d5/raw/15a8954620cad5c9bd066237bf210ba475da3014" | |
# Set GPG to use the TTY for passphrase prompts | |
export GPG_TTY=$(tty) | |
export GPG_DIR="$HOME/.gnupg" | |
# Add GPG_TTY to the user's shell configuration file | |
if [[ "$SHELL_NAME" == "zsh" ]]; then | |
echo 'export GPG_TTY=$(tty)' >>"$SHELL_CONFIG" | |
echo "export GNUPGHOME=$GPG_DIR" >>"$SHELL_CONFIG" | |
elif [[ "$SHELL_NAME" == "bash" ]]; then | |
echo 'export GPG_TTY=$(tty)' >>"$SHELL_CONFIG" | |
echo "export GNUPGHOME=$GPG_DIR" >>"$SHELL_CONFIG" | |
elif [[ "$SHELL_NAME" == "fish" ]]; then | |
echo 'set -gx GPG_TTY $(tty)' >>"$SHELL_CONFIG" | |
echo "set -gx GNUPGHOME $GPG_DIR" >>"$SHELL_CONFIG" | |
else | |
print_warn "Unsupported shell. Please manually add 'export GPG_TTY=\$(tty)' to your shell configuration file." | |
fi | |
# Configure GPG to use pinentry-mac | |
curl -fOsSL --output-dir $dir $download_url/gpg-agent.conf | |
curl -fOsSL --output-dir $dir $download_url/gpg.conf | |
# To send the keys to the OpenPgp keyserver: | |
gpg --keyserver keys.openpgp.org --send-key $(gpg --list-secret-keys --with-keygrip --with-colons | grep '^sec:' | awk -F: '{print $5}') | |
# Get current GPG key ID | |
KEY_ID=$(gpg --list-secret-keys --keyid-format=long | grep sec | head -1 | cut -d'/' -f2 | cut -d' ' -f1) | |
# Set it as `default-key` in ~/.gnupg/gpg.conf | |
sed -i '' "s/\(default-key \)[^ ]*/\1$KEY_ID/" ~/.gnupg/gpg.conf | |
print_success "Download files ${BLUE}$dir/gpg-agent.conf and $dir/gpg.conf${NC} completed${NC}" | |
} | |
# Function to configure Git signing in GUI applications (e.g., Git Tower, GitHub Desktop) | |
configure_gui_git_signing() { | |
print_step "Configuring Git signing for GUI applications..." | |
# Get the GPG key ID | |
GPG_KEY_ID=$(gpg --list-secret-keys --keyid-format LONG | grep sec | tail -1 | awk '{print $2}' | cut -d'/' -f2) | |
# Configure Git Tower | |
if [[ -d "/Applications/Git Tower.app" ]]; then | |
echo "✓ Configuring Git Tower signing..." | |
cat >~/.gitconfig.tower <<EOF | |
[commit] | |
gpgsign = true | |
signingkey = $GPG_KEY_ID | |
EOF | |
sleep 1 | |
git config --global include.path ~/.gitconfig.tower | |
fi | |
# Configure GitHub Desktop | |
if [[ -d "/Applications/GitHub Desktop.app" ]]; then | |
print_success "Configuring GitHub Desktop signing..." | |
cat >~/.gitconfig.github <<EOF | |
[commit] | |
gpgsign = true | |
signingkey = $GPG_KEY_ID | |
EOF | |
sleep 1 | |
git config --global include.path ~/.gitconfig.github | |
fi | |
print_success "Git signing configured for GUI applications" | |
} | |
# ━━━━━━━━━━━━━━━━━━━━━━━━━━━ Second Phase - Configuration ━━━━━━━━━━━━━━━━━━━━━━━━━━ | |
configure_vscode_gpg() { | |
local extensions=( | |
"wdhongtw.gpg-indicator" | |
"jheilingbrunner.vscode-gnupg-tool" | |
) | |
local settings='{"git.enableCommitSigning": true}' | |
if command -v code &>/dev/null; then | |
print_step "Configuring VSCode for GPG commit signing..." | |
for ext in "${extensions[@]}"; do | |
code --force --install-extension "$ext" | |
done | |
# code --wait --reuse-window --settings-json "$settings" | |
print_step "VSCode GPG signing configuration complete" | |
elif command -v codium &>/dev/null; then | |
print_step "Configuring VSCodium for GPG commit signing..." | |
for ext in "${extensions[@]}"; do | |
codium --force --install-extension "$ext" | |
done | |
# codium --wait --reuse-window --settings-json "$settings" | |
print_success "VSCodium GPG signing configuration complete" | |
else | |
print_warn "✗ No compatible editor found (VSCode/VSCodium)" | |
return 1 | |
fi | |
} | |
configure_gpg_agent_launch_agent() { | |
echo "Setting up gpg-agent as a launch agent..." | |
# Define paths with more descriptive names | |
readonly LOCAL_BIN_DIR="$HOME/.local/bin" | |
readonly GPG_START_SCRIPT="$LOCAL_BIN_DIR/start-gpg-agent.sh" | |
readonly LAUNCH_AGENTS_DIR="$HOME/Library/LaunchAgents" | |
readonly GPG_AGENT_PLIST="$LAUNCH_AGENTS_DIR/org.gnupg.gpg-agent.plist" | |
# Source URLs | |
readonly PLIST_URL="https://gist.githubusercontent.com/zx0r/843298b67cd91a0835dcf36aada529d5/raw/6673f8ee76898aa8f8f59cfdbfb14728481b1276/org.gnupg.gpg-agent.plist" | |
readonly SCRIPT_URL="https://gist.githubusercontent.com/zx0r/843298b67cd91a0835dcf36aada529d5/raw/6673f8ee76898aa8f8f59cfdbfb14728481b1276/startup-gpg-agent.sh" | |
# Create required directories | |
for dir in "$LOCAL_BIN_DIR" "$LAUNCH_AGENTS_DIR"; do | |
if [[ ! -d "$dir" ]]; then | |
mkdir -p "$dir" || { | |
echo "Failed to create directory: $dir" | |
return 1 | |
} | |
echo "Created directory: $dir" | |
fi | |
done | |
# Download files with error checking | |
if ! curl -fsSL "$PLIST_URL" -o "$GPG_AGENT_PLIST"; then | |
print_warn "Failed to download plist file" | |
return 1 | |
fi | |
if ! curl -fsSL "$SCRIPT_URL" -o "$GPG_START_SCRIPT"; then | |
print_warn "Failed to download start script" | |
return 1 | |
fi | |
# Set correct permissions | |
chmod +x "$GPG_START_SCRIPT" | |
# Load the launch agent | |
launchctl load -w "$GPG_AGENT_PLIST" | |
print_success "GPG agent launch agent setup complete" | |
print_step "Installed files:" | |
print_step "• Launch Agent: ${GREEN}$GPG_AGENT_PLIST${NC}" | |
print_step "• Start Script: ${GREEN}$GPG_START_SCRIPT${NC}" | |
} | |
# Function to apply additional security settings | |
apply_security_settings() { | |
echo "Applying additional security settings..." | |
# Start GPG agent if not running | |
if ! gpg-connect-agent /bye >/dev/null 2>&1; then | |
echo "Starting GPG agent..." | |
gpg-agent --daemon | |
fi | |
# Set recommended security configurations | |
gpg-connect-agent "scd serialno" /bye >/dev/null 2>&1 | |
gpg-connect-agent "learn --force" /bye >/dev/null 2>&1 | |
print_success "Security settings applied successfully." | |
} | |
configure_ssh_signing() { | |
# Define constants | |
readonly SSH_DIR="$HOME/.ssh" | |
readonly SSH_KEY="$SSH_DIR/id_ed25519" | |
GPG_DIR="$HOME/.gnupg" | |
SSHCONTROL="$GPG_DIR/sshcontrol" | |
KEYGRIPS="$(gpg --list-keys --with-keygrip | awk '/^sub/{p=1;next} /Keygrip/{if(p){print $3;p=0}}')" | |
# for Fish Shell | |
FISH_PATH="$HOME/.config/fish/functions" | |
SSH_AGENT_FISH_URL="https://gist.githubusercontent.com/zx0r/843298b67cd91a0835dcf36aada529d5/raw/d59f1f8ca738241c6cb34da8055f417cee73dd86/ssh_agent.fish" | |
GPG_SSH_FISH_URL="https://gist.githubusercontent.com/zx0r/843298b67cd91a0835dcf36aada529d5/raw/d59f1f8ca738241c6cb34da8055f417cee73dd86/gpg_ssh_agent.fish" | |
SSHCONTROL_URL="https://gist.githubusercontent.com/zx0r/843298b67cd91a0835dcf36aada529d5/raw/deaa0d4c123baad306c0b3e8e78289e985587cd6/sshcontrol" | |
SSH_CONFIG="https://gist.githubusercontent.com/zx0r/843298b67cd91a0835dcf36aada529d5/raw/5f946ee0bc44f0fe122f19e4461ef07a8e24e3cc/config" | |
# Create SSH directory with proper permissions | |
if [[ ! -d "$SSH_DIR" ]]; then | |
mkdir -p "$SSH_DIR" | |
chmod 700 "$SSH_DIR" | |
fi | |
# Generate SSH key if needed | |
if [[ ! -f "$SSH_KEY" ]]; then | |
ssh-keygen -t ed25519 -C "git-signing-key" -f "$SSH_KEY" -N "" | |
chmod 600 "$SSH_KEY" | |
chmod 644 "${SSH_KEY}.pub" | |
fi | |
# Generate SSH key if needed | |
if [[ ! -f "$SSH_DIR/config" ]]; then | |
curl -fsSL "$SSH_CONFIG" -o "$SSH_DIR/config" || print_warn "Failed to download $SSH_DIR/config" | |
chmod 600 "$SSH_DIR/config" | |
else | |
mv "$SSH_DIR/config" "$SSH_DIR/config.bak" | |
fi | |
# Add SSH key with keychain integration | |
ssh-add --apple-use-keychain $SSH_DIR/id_ed25519 | |
# Configure Git signing | |
git config --global gpg.format ssh | |
git config --global user.signingkey "$(cat ${SSH_KEY}.pub)" | |
git config --global commit.gpgsign true | |
# Setup GPG directory and sshcontrol | |
mkdir -p "$GPG_DIR" | |
chmod 700 "$GPG_DIR" | |
# Extract and add keygrips | |
curl -fsSL "$SSHCONTROL_URL" -o "$GPG_DIR/sshcontrol" || print_warn "Failed to download $GPG_DIR/sshcontrol" | |
# Add keygrips to sshcontrol | |
if [[ -n "$KEYGRIPS" ]]; then | |
echo "$KEYGRIPS" >>"$SSHCONTROL" | |
chmod 600 "$SSHCONTROL" | |
print_success "SSH control configured with keygrips" | |
fi | |
# Download ssh_agent.fish | |
if [[ "$SHELL_NAME" == "fish" ]]; then | |
curl -fsSL "$SSH_AGENT_FISH_URL" -o "$FISH_PATH/ssh_agent.fish" || print_warn "Failed to download $FISH_PATH/ssh_agent.fish" | |
curl -fsSL "$GPG_SSH_FISH_URL" -o "$FISH_PATH/gpg_ssh_agent.fish" || print_warn "Failed to download $FISH_PATH/gpg_ssh_agent.fish" | |
print_step "Download competed: ${GREEN}$FISH_PATH/ssh_agent.fish and $FISH_PATH/gpg_ssh_agent.fish${NC}" | |
fi | |
print_success "SSH signing setup complete!" | |
print_step "Public key: ${BLUE}$(cat ${SSH_KEY}.pub)${NC}" | |
} | |
# S/MIME Signing using smimesign | |
configure_smime_signing() { | |
# Configure Git to use smimesign | |
git config --global gpg.x509.program smimesign | |
git config --global gpg.format x509 | |
git config --global commit.gpgsign true | |
print_success "S/MIME Signing using smimesign configured" | |
} | |
configure_git_gpg() { | |
# Get the GPG key ID | |
GPG_KEY_ID=$(gpg --list-secret-keys --keyid-format LONG | grep sec | tail -1 | awk '{print $2}' | cut -d'/' -f2) | |
# GPG_KEY_ID=$(gpg --list-secret-keys --with-keygrip --with-colons | grep '^sec:' | awk -F: '{print $5}') | |
# Check and clean existing GPG settings | |
if git config --list --show-origin | grep -q "commit.gpgsign"; then | |
git config --global --unset commit.gpgsign | |
fi | |
# Configure Git with new GPG settings | |
git config --global gpg.program $(which gpg) | |
git config --global user.signingkey "$GPG_KEY_ID" | |
sleep 1 | |
git config --global log.showSignature "true" | |
git config --global commit.gpgsign "true" | |
git config --global tag.gpgSign "true" | |
# Display success and next steps | |
print_success "Git configured to use GPG key: $GPG_KEY_ID" | |
echo -e "\n${YELLOW}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}" | |
echo -e " ${GREEN} Your GPG public key for GitHub: ${NC}" | |
echo -e "\n${YELLOW}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}" | |
# Add to github repository (var 1) | |
# gpg --armor --export $(gpg --list-secret-keys --with-keygrip --with-colons | grep '^sec:' | awk -F: '{print $5}') | |
# Add to github repository(var 2) | |
gpg --armor --export "$GPG_KEY_ID" | |
} | |
ptint_hints() { | |
# Print clear instructions with colors | |
echo -e "\n${YELLOW}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}" | |
echo -e " ${GREEN} Next Steps for GPG Setup ${NC}" | |
echo -e "\n${YELLOW}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}" | |
echo -e "${YELLOW}Step 1. 👆 Copy your GPG key 👆${NC}" | |
echo -e "${YELLOW}• Include both BEGIN and END lines${NC}" | |
echo -e "${YELLOW}• Ensure no extra spaces are copied${NC}" | |
echo -e "${YELLOW}Step 2. Add key to GitHub:${NC}" | |
print_step "${GREEN}https://github.com/settings/gpg/new${NC}" | |
echo -e "${YELLOW}Step 3. Verify your signed commits:${NC}" | |
print_step "${GREEN}'git log --show-signature'${NC}" | |
echo -e "${YELLOW}Step 4. Use commit signature verification:${NC}" | |
print_step "${GREEN}'git commit -S -m "YOUR_COMMIT_MESSAGE"'${NC}" | |
echo -e "${YELLOW}Step 5. Learn more about commit signing:${NC}" | |
print_step "${GREEN}https://docs.github.com/authentication/managing-commit-signature-verification${NC}" | |
} | |
# ━━━━━━━━━━━━━━━━━━━━━━━━━━━ Third Phase - Verification ━━━━━━━━━━━━━━━━━━━━━━━━━━━━ | |
# Export the GPG Key Materials | |
export_gpg_key() { | |
# Get the GPG key ID | |
GPG_KEY_ID=$(gpg --list-secret-keys --keyid-format LONG | grep sec | tail -1 | awk '{print $2}' | cut -d'/' -f2) | |
# Set secure export paths | |
EXPORT_DIR="$HOME/.gnupg/backup" | |
mkdir -p "$EXPORT_DIR" | |
# Export both public and private keys with armor | |
gpg --armor --export "$GPG_KEY_ID" >"$EXPORT_DIR/gpg_public_${GPG_KEY_ID}.asc" | |
gpg --armor --export-secret-key "$GPG_KEY_ID" >"$EXPORT_DIR/gpg_private_${GPG_KEY_ID}.asc" | |
print_success "GPG keys exported successfully:" | |
print_step "• Public key: ${GREEN}$EXPORT_DIR/gpg_public_${GPG_KEY_ID}.asc${NC}" | |
print_step "• Private key: ${GREEN}$EXPORT_DIR/gpg_private_${GPG_KEY_ID}.asc${NC}" | |
print_step "$(git config --list --show-origin | grep user.signingkey)" | |
#print_step "\n$(gpg --list-secret-keys --keyid-format=long)" | |
} | |
verify_setup() { | |
# Print clear instructions with colors | |
echo -e "\n${YELLOW}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}" | |
echo -e " ${GREEN} 🔍 Running system verification... ${NC}" | |
echo -e "\n${YELLOW}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}" | |
print_step "\n📌 GPG Configuration\n" | |
gpg --list-secret-keys --keyid-format=long | |
gpg-connect-agent --quiet /bye | |
print_success "GPG Agent Status: ✔️" | |
echo "\n🔑 SSH Configuration\n" | |
ssh-add -l | |
print_step "• SSH_AUTH_SOCK: ${GREEN}$SSH_AUTH_SOCK${NC}" | |
print_success "• SSH Agent Status: ✔️" | |
print_step "\n✍️ Git Signing Setp\n" | |
git config --global --list | grep -E 'gpg|signing' | |
git verify-commit HEAD 2>/dev/null || print_step "No signed commits yet" | |
print_step "\n🔐 Environment Variables" | |
print_step "• GPG_TTY: ${GREEN}$GPG_TTY${NC}" | |
print_step "• GNUPGHOME: ${GREEN}$GNUPGHOME${NC}" | |
print_step "\n✨ Running signing test...\n" | |
echo "Final Check" | gpg --clearsign && print_success "GPG signing: ✔️" | |
echo -e "\n${YELLOW}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}" | |
echo -e " ${GREEN} 🎉 Verification complete.Happy Secure Committing 🎉 ${NC}" | |
echo -e "\n${YELLOW}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}" | |
} | |
reload_shell() { | |
if [[ -f "$SHELL_CONFIG" ]]; then | |
# print_step "Reloading shell configuration..." | |
source "$SHELL_CONFIG" | |
else | |
print_warn "Error: Shell configuration file not found at $SHELL_CONFIG" >&2 | |
exit 1 | |
fi | |
} | |
complete_installation() { | |
print_category "Completion" | |
print_success "GnuPG and Git configuration completed successfully 🎉" | |
} | |
# ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ End ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ | |
# Execute main function only if script is run directly | |
[[ "${BASH_SOURCE[0]}" == "${0}" ]] && main |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# $HOME/.ssh/config | |
# Global Settings | |
Host * | |
AddKeysToAgent yes | |
UseKeychain yes | |
IdentitiesOnly yes | |
#IdentityFile ~/.ssh/id_ed25519 | |
# Performance | |
Compression yes | |
TCPKeepAlive yes | |
ServerAliveInterval 60 | |
ServerAliveCountMax 30 | |
# Security | |
HashKnownHosts yes | |
PasswordAuthentication no | |
PubkeyAuthentication yes | |
IdentitiesOnly yes | |
# Modern Crypto | |
KexAlgorithms [email protected],diffie-hellman-group-exchange-sha256 | |
Ciphers [email protected],[email protected] | |
MACs [email protected],[email protected] | |
# GitHub | |
Host github.com | |
User git | |
IdentityFile ~/.ssh/github_ed25519 | |
# GitLab | |
Host gitlab.com | |
User git | |
IdentityFile ~/.ssh/gitlab_ed25519 | |
# Custom Server Example | |
Host dev | |
HostName dev.example.com | |
User developer | |
Port 2222 | |
IdentityFile ~/.ssh/dev_ed25519 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# Script: setup-gpg-git-macos.sh | |
# Author: zx0r | |
# Date: 2025-01-30 | |
# Description: This script installs and configures GnuPG (GPG) for macOS, sets up Git to use GPG for signing commits, | |
# and ensures security settings are applied. It also auto-detects the user's shell, configures VSCode/VSCodium | |
# to use GPG for commits, and sets up a launch agent for gpg-agent. | |
# Set cache times for normal and SSH keys (in seconds) | |
default-cache-ttl 3600 | |
max-cache-ttl 7200 | |
default-cache-ttl-ssh 3600 | |
max-cache-ttl-ssh 7200 | |
# Enable SSH support | |
enable-ssh-support | |
# Set pinentry program for macOS | |
pinentry-program /usr/local/bin/pinentry-mac | |
# Set TTY | |
ttyname $GPG_TTY | |
# Write environment file | |
write-env-file ~/.gnupg/gpg-agent.env | |
# Keyboard control settings | |
no-grab | |
# Allow preset passphrases | |
allow-preset-passphrase | |
# Enable loopback for better integration | |
allow-loopback-pinentry | |
# Set longer key grace period | |
ignore-cache-for-signing |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# Script: setup-gpg-git-macos.sh | |
# Author: zx0r | |
# Date: 2025-01-30 | |
# Description: This script installs and configures GnuPG (GPG) for macOS, sets up Git to use GPG for signing commits, | |
# and ensures security settings are applied. It also auto-detects the user's shell, configures VSCode/VSCodium | |
# to use GPG for commits, and sets up a launch agent for gpg-agent. | |
# ┌───────────────────────────────────────────────────────────────────────────┐ | |
# │ Setting defaults │ | |
# └───────────────────────────────────────────────────────────────────────────┘ | |
# Default/trusted key ID to use (helpful with throw-keyids) | |
# $ gpg --list-secret-keys --with-keygrip --with-colons | grep '^sec:' | awk -F: '{print $5}' | |
default-key <YOUR-KEY> | |
# Automatically encrypt replies to encrypted messages to yourself as well | |
default-recipient-self | |
# UTF-8 support for compatibility | |
charset utf-8 | |
# when outputting certificates, view user IDs distinctly from keys: | |
fixed-list-mode | |
# Use the GPG agent for passphrase caching | |
use-agent | |
# Disable recipient key ID in messages | |
throw-keyids | |
# default-preference-list SHA512 SHA384 SHA256 AES256 AES192 AES ZLIB BZIP2 ZIP Uncompressed | |
armor | |
# include an unambiguous indicator of which key made a signature: | |
# (see http://thread.gmane.org/gmane.mail.notmuch.general/3721/focus=7234) | |
# (and http://www.ietf.org/mail-archive/web/openpgp/current/msg00405.html) | |
# sig-notation [email protected]=%g | |
# ┌───────────────────────────────────────────────────────────────────────────┐ | |
# │ Algorithms & Ciphers │ | |
# └───────────────────────────────────────────────────────────────────────────┘ | |
# SHA512 as digest to sign keys | |
cert-digest-algo SHA512 | |
# SHA512 as digest for symmetric ops | |
s2k-digest-algo SHA512 | |
# AES256 as cipher for symmetric ops | |
s2k-cipher-algo AES256 | |
# Set default key generation algorithm to RSA with 4096-bit length [strong protection] | |
default-new-key-algo rsa4096 | |
# Use AES256, 192, or 128 as cipher | |
personal-cipher-preferences AES256 AES192 AES | |
# Use SHA512, 384, or 256 as digest | |
personal-digest-preferences SHA512 SHA384 SHA256 | |
# Use ZLIB, BZIP2, ZIP, or no compression | |
personal-compress-preferences ZLIB BZIP2 ZIP Uncompressed | |
# Default preferences for new keys | |
default-preference-list SHA512 SHA384 SHA256 AES256 AES192 AES ZLIB BZIP2 ZIP Uncompressed | |
# ┌───────────────────────────────────────────────────────────────────────────┐ | |
# │ Behavior of GnuPG │ | |
# └───────────────────────────────────────────────────────────────────────────┘ | |
# Repair legacy PGP subkey bug during import | |
import-options repair-pks-subkey-bug | |
# Remove all signatures from imported keys that are not usable | |
import-options import-clean | |
# Remove all non-exportable signatures during export | |
export-options export-clean | |
# Display Options | |
# Show validity of user IDs during key listings | |
list-options show-uid-validity | |
# Show validity of user IDs during signature verification | |
verify-options show-uid-validity | |
# Show Unix timestamps | |
fixed-list-mode | |
# Disable caching of passphrase for symmetrical ops | |
no-symkey-cache | |
# Disable banner Interface Options | |
no-greeting | |
# No comments on signatures | |
no-comments | |
# No version in signatures | |
no-emit-version | |
# Show unix timestamps | |
fixed-list-mode | |
# Long hexadecimal key format | |
keyid-format 0xlong | |
# Display fingerprint | |
with-fingerprint | |
# Disable caching of passphrasae for symmetrical encryption | |
no-symkey-cache | |
# Disable recipient key ID in messages | |
# throw-keyids | |
trust-model always | |
# Skip time conflict warnings during signature verification | |
# This is useful when system clock differences might cause issues | |
ignore-time-conflict | |
# Ensure cross-certification on signing subkeys | |
# This enhances security by requiring signatures between primary and subkeys | |
require-cross-certification | |
# Commented out: Allow non-standard UIDs during key generation | |
# Enabling this would allow creation of UIDs without email addresses | |
# allow-freeform-uid | |
# Display all keys and their fingerprints | |
with-fingerprint | |
# Display key origins and updates | |
#with-key-origin | |
# Cross-certify subkeys are present and valid | |
require-cross-certification | |
# no-tty | |
# pinentry-mode loopback | |
# ┌───────────────────────────────────────────────────────────────────────────┐ | |
# │ Keyring & Keyserver │ | |
# └───────────────────────────────────────────────────────────────────────────┘ | |
# Disable the use of the default public and secret keyrings | |
# This allows explicit specification of which keyrings to use | |
no-default-keyring | |
# Keyring Options | |
# Specify the primary public keyring | |
# keyring ~/.gnupg/pubring.kbx | |
# trustdb-name ~/.gnupg/trustdb.gpg | |
# primary-keyring ~/.gnupg/pubring.kbx | |
# Automatically retrieve missing keys from keyserver when verifying signatures | |
# This makes signature verification more seamless by fetching required keys | |
auto-key-retrieve | |
# Keyserver Options | |
# Don't add additional comments in downloaded certificates | |
keyserver-options no-include-attributes | |
# Honor the preferred keyserver URL from the key | |
keyserver-options honor-keyserver-url | |
# Do not include key signatures from keyserver responses | |
keyserver-options no-include-revoked | |
# Include subkeys when downloading keys from keyserver | |
keyserver-options include-subkeys | |
# Automatically fetch keys from keyserver when verifying signatures | |
keyserver-options auto-key-retrieve | |
# Include revoked keys in search results | |
keyserver-options include-revoked | |
# Number of seconds to wait for a keyserver response | |
keyserver-options timeout=10 | |
# Default keyserver to use | |
keyserver hkps://keys.openpgp.org | |
keyserver hkp://zkaan2xfbuxia2wpf7ofnkbz6r5zdbbvxbunvp5g2iebopbfc4iqmbad.onion | |
# keyserver hkp://pgp.mit.edu | |
# keyserver hkp://pool.sks-keyservers.net | |
# keyserver hkp://keys.gnupg.net | |
# keyserver hkps://keyserver.ubuntu.com | |
# keyserver hkps://pgp.mit.edu | |
# keyserver hkp://keyoxide.org | |
# keyserver hkp://na.pool.sks-keyservers.net | |
# keyserver https://sks-keyservers.net/status/ | |
# keyserver hkp://keyserver.ubuntu.com | |
# keyserver hkp://keybase.io | |
# keyserver hkp://keyserver.undergrid.net | |
# keyserver hkp://keyring.debian.org | |
# keyserver hkp://hkps.pool.sks-keyservers.net | |
# Define a keygroup named 'purse_keygroup' | |
# This allows you to refer to multiple keys as a single group | |
# group purse_keygroup = 0xFF123456 0xABCDEF01 0x12345678 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# $HOME/.config/fish/functions/gpg_ssh_agent.fish | |
function gpg_ssh_agent --description "Start and manage GPG agent" | |
# Skip for root user | |
test $USER = root; and return | |
# Early return if gpgconf is not available | |
command -q gpgconf; or return 1 | |
# Determine GPG directory | |
set -l GPG_DIR (test -n "$GNUPGHOME"; and echo $GNUPGHOME; or echo "$HOME/.gnupg") | |
# Ensure GPG directory exists with secure permissions | |
if not test -d $GPG_DIR | |
mkdir -p $GPG_DIR | |
chmod 700 $GPG_DIR | |
end | |
# Set GPG environment file path | |
set -l GPG_ENV "$GPG_DIR/gpg-agent.env" | |
# Create or update GPG environment file with secure permissions | |
if not test -f $GPG_ENV | |
touch $GPG_ENV | |
chmod 600 $GPG_ENV | |
end | |
# Source existing environment variables if the file is not empty | |
test -s $GPG_ENV; and source $GPG_ENV ^/dev/null | |
# Clear SSH_AUTH_SOCK | |
set -e SSH_AUTH_SOCK | |
# Set curses for SSH connections if active | |
test -n "$SSH_CONNECTION"; and set -x PINENTRY_USER_DATA "USE_CURSES=1" | |
# Set GPG and SSH environment variables | |
set -gx GPG_TTY (tty) | |
set -gx GPG_AGENT_INFO (gpgconf --list-dirs agent-socket) | |
set -gx SSH_AUTH_SOCK (gpgconf --list-dirs agent-ssh-socket) | |
# Initialize GPG agent | |
gpg-connect-agent updatestartuptty /bye ^/dev/null | |
gpgconf --launch gpg-agent | |
return 0 | |
end | |
# Call the function to initialize GPG agent at startup | |
gpg_ssh_agent |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?xml version="1.0" encoding="UTF-8"?> | |
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> | |
<plist version="1.0"> | |
<dict> | |
<key>Label</key> | |
<string>org.gnupg.gpg-agent</string> | |
<key>ProgramArguments</key> | |
<array> | |
<string>$HOME/.local/bin/start-gpg-agent.sh</string> | |
</array> | |
<key>RunAtLoad</key> | |
<true/> | |
<key>KeepAlive</key> | |
<true/> | |
<key>StandardErrorPath</key> | |
<string>~/.gnupg/gpg-agent.log</string> | |
<key>StandardOutPath</key> | |
<string>~/.gnupg/gpg-agent.log</string> | |
</plist> |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# Configure your SSH client to automatically add keys to the SSH agent | |
# $HOME/.ssh/config | |
# Host * | |
# AddKeysToAgent yes | |
# UseKeychain yes # Optional: This is useful on macOS to use the keychain. | |
# $HOME/.config/fish/functions/ssh_agent.fish | |
function ssh_agent --description "Start and manage SSH agent" | |
set -l SSH_ENV "$HOME/.ssh/ssh-agent.env" | |
# Check if the SSH agent environment file exists | |
if test -f $SSH_ENV; and test -z "$SSH_AGENT_PID" | |
source $SSH_ENV >/dev/null | |
end | |
# If the SSH agent is not running, start it | |
if test -z "$SSH_AGENT_PID" | |
eval (ssh-agent -c) >$SSH_ENV | |
chmod 600 $SSH_ENV | |
echo "SSH agent started." | |
else | |
echo "Using existing SSH agent." | |
end | |
# Add SSH keys | |
set -l SSH_KEYS (fd '^id_' $HOME/.ssh --type f --exclude '*.pub') | |
for KEY in $SSH_KEYS | |
ssh-add $KEY | |
end | |
end | |
# Automatically start the ssh-agent on shell initialization | |
ssh_agent | |
# https://github.com/ivakyb/fish_ssh_agent | |
# | |
# function ssh_agent_is_started -d "check if ssh agent is already started" | |
# if begin | |
# test -f $SSH_ENV; and test -z "$SSH_AGENT_PID" | |
# end | |
# source $SSH_ENV >/dev/null | |
# end | |
# | |
# if test -z "$SSH_AGENT_PID" | |
# return 1 | |
# end | |
# | |
# ps -ef | grep $SSH_AGENT_PID | grep -v grep | grep -q ssh-agent | |
# return $status | |
# end | |
# | |
# function ssh_agent_start -d "start a new ssh agent" | |
# ssh-agent -c | sed 's/^echo/#echo/' >$SSH_ENV | |
# chmod 600 $SSH_ENV | |
# source $SSH_ENV >/dev/null | |
# true # suppress errors from setenv, i.e. set -gx | |
# end | |
# | |
# function fish_ssh_agent --description "Start ssh-agent if not started yet, or uses already started ssh-agent." | |
# if test -z "$SSH_ENV" | |
# set -xg SSH_ENV $HOME/.ssh/ssh-agent.env | |
# end | |
# | |
# if not ssh_agent_is_started | |
# ssh_agent_start | |
# end | |
# end | |
# Automatically start the ssh-agent on shell initialization | |
# fish_ssh_agent |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# Using a PGP key for SSH authentication | |
# List of allowed ssh keys. Only keys present in this file are used | |
# in the SSH protocol. The ssh-add tool may add new entries to this | |
# file to enable them; you may also add them manually. Comment | |
# lines, like this one, as well as empty lines are ignored. Lines do | |
# have a certain length limit but this is not serious limitation as | |
# the format of the entries is fixed and checked by gpg-agent. A | |
# non-comment line starts with optional white spaces, followed by the | |
# keygrip of the key given as 40 hex digits, optionally followed by a | |
# caching TTL in seconds, and another optional field for arbitrary | |
# flags. Prepend the keygrip with an '!' mark to disable it. | |
# gpg --list-keys --with-keygrip | awk '/^sub/{p=1;next} /Keygrip/{if(p){print $3;p=0}}' >> $HOME/.gnupg/sshcontrol | |
# FF8852FA7D6ED25CF4169132698BBA5536A68134 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env bash | |
# The configuration will keep your GPG agent running smoothly across all applications! | |
if [ -f $HOME/.gnupg/.gpg-agent-info ] && [ -n "$(pgrep gpg-agent)" ]; then | |
source $HOME/.gnupg/.gpg-agent-info | |
export GPG_AGENT_INFO | |
else | |
eval $(gpg-agent --daemon --write-env-file $HOME/.gnupg/.gpg-agent-info) | |
fi | |
# This line is important for GUI tools to also find it | |
launchctl setenv GPG_AGENT_INFO $GPG_AGENT_INFO |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment