EZGPG source

#!/bin/zsh

# This is free and unencumbered software released into the public domain.
#
# Anyone is free to copy, modify, publish, use, compile, sell, or
# distribute this software, either in source code form or as a compiled
# binary, for any purpose, commercial or non-commercial, and by any
# means.
#
# In jurisdictions that recognize copyright laws, the author or authors
# of this software dedicate any and all copyright interest in the
# software to the public domain. We make this dedication for the benefit
# of the public at large and to the detriment of our heirs and
# successors. We intend this dedication to be an overt act of
# relinquishment in perpetuity of all present and future rights to this
# software under copyright law.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
# IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
# OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
# ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
# OTHER DEALINGS IN THE SOFTWARE.
#
# For more information, please refer to <http://unlicense.org/>

# Define color variables
RED='\033[31m'
GREEN='\033[32m'
YELLOW='\033[33m'
BLUE='\033[34m'
MAGENTA='\033[35m'
CYAN='\033[36m'
RESET='\033[0m'

# Random text color
COLORS=($RED $GREEN $YELLOW $BLUE $MAGENTA $CYAN)
RANDOM_INDEX=$(( $RANDOM % ${#COLORS[@]} ))
RAND_COLOR=${COLORS[RANDOM_INDEX+1]}

CHECK="${GREEN}✔${RESET}"
CROSS="${RED}✘${RESET}"

# Encryption types
key_types=("rsa4096: RSA key with 4096 bits" "ed25519: Ed25519 key")
expire="1y"

INSTRUCTIONS="
You'll need to have the following prepared before running this script to
completion:
1. Pen and paper to write down passphrases and pins
2. External disk plugged in and mounted
3. A single Yubikey plugged in

EZGPG will check for the presence of the necessary packages, and prompt to
install them if missing. You will only need an internet connection if the
packages are missing.

The PGP application on your Yubikey will be reset, wiping the keys stored
there.
"

logo() {
    echo "
    ${RAND_COLOR}███████${RESET}╗${RAND_COLOR}███████${RESET}╗ ${RAND_COLOR}██████${RESET}╗ ${RAND_COLOR}██████${RESET}╗  ${RAND_COLOR}██████${RESET}╗
    ${RAND_COLOR}██${RESET}╔════╝╚══${RAND_COLOR}███${RESET}╔╝${RAND_COLOR}██${RESET}╔════╝ ${RAND_COLOR}██${RESET}╔══${RAND_COLOR}██${RESET}╗${RAND_COLOR}██${RESET}╔════╝
    ${RAND_COLOR}█████${RESET}╗    ${RAND_COLOR}███${RESET}╔╝ ${RAND_COLOR}██${RESET}║  ${RAND_COLOR}███${RESET}╗${RAND_COLOR}██████${RESET}╔╝${RAND_COLOR}██${RESET}║  ${RAND_COLOR}███${RESET}╗
    ${RAND_COLOR}██${RESET}╔══╝   ${RAND_COLOR}███${RESET}╔╝  ${RAND_COLOR}██${RESET}║   ${RAND_COLOR}██${RESET}║${RAND_COLOR}██${RESET}╔═══╝ ${RAND_COLOR}██${RESET}║   ${RAND_COLOR}██${RESET}║
    ${RAND_COLOR}███████${RESET}╗${RAND_COLOR}███████${RESET}╗╚${RAND_COLOR}██████${RESET}╔╝${RAND_COLOR}██${RESET}║     ╚${RAND_COLOR}██████${RESET}╔╝
    ${RESET}╚══════╝╚══════╝ ╚═════╝ ╚═╝      ╚═════╝
           Quick and Easy GPG Set Up
               ${CYAN}https://ezgpg.com${RESET}
"
}

new_line() {
    echo ""
}

detect_os() {
    local os
    case "$(uname)" in
        "Darwin")
            os="macOS"
            ;;
        "Linux")
            os="Linux"
            ;;
        *)
            os="Unknown"
            ;;
    esac
    echo $os
}

is_program_available() {
    local program=$1

    if command -v $program >/dev/null 2>&1; then
        return 0
    else
        return 1
    fi
}

prompt_installation() {
    local os_type=$1
    local program=$2
    local package=$3

    if [[ $os_type == "macOS" ]]; then
        printf "Do you want to install $program with brew? (y/N): "
        read response
        if [[ $response == "y" || $response == "Y" ]]; then
            echo "brew install --quiet $package"
            brew install --quiet $package
            if is_program_available $program; then
                echo "$CHECK $program: Installed successfully."
                return 0
            else
                echo "$CROSS $program: Failed to install."
                return 1
            fi
        else
            echo "$CROSS $program: Installation skipped."
            return 1
        fi
    elif [[ $os_type == "Linux" ]]; then
        echo "$CROSS $program: Not available. Please install using your package manager."
        echo "For example:"
        if command -v apt-get >/dev/null 2>&1; then
            echo "sudo apt-get install $package"
        elif command -v yum >/dev/null 2>&1; then
            echo "sudo yum install $package"
        elif command -v dnf >/dev/null 2>&1; then
            echo "sudo dnf install $package"
        elif command -v pacman >/dev/null 2>&1; then
            echo "sudo pacman -S $package"
        else
            echo "Please install $package using your package manager."
        fi
        return 1
    else
        echo "$CROSS Unsupported operating system."
        return 1
    fi
}

check_pinentry_on_linux() {
    local pinentry_programs=("pinentry-curses" "pinentry-fltk" "pinentry-gnome3" "pinentry-gtk2" "pinentry-qt" "pinentry-tty" "pinentry-x2go")
    local installed_program=""

    for program in "${pinentry_programs[@]}"; do
        if is_program_available $program; then
            installed_program=$program
            break
        fi
    done

    if [[ -n $installed_program ]]; then
        echo "$CHECK pinentry program found: $installed_program"
    else
        echo "$CROSS No pinentry program found. Please install one of the following using your package manager:"
        echo "pinentry-curses, pinentry-fltk, pinentry-gnome3, pinentry-gtk2, pinentry-qt, pinentry-tty, pinentry-x2go"
    fi
}

check_programs() {
    local programs=("$@")
    local unavailable_programs=()
    local os_type=$(detect_os)

    echo "Applications:"
    for program_package in "${programs[@]}"; do
        IFS=',' read -r program package <<< "$program_package"
        if ! is_program_available $program; then
            echo "$CROSS $program: Not available"
            unavailable_programs+=("$program,$package")
        else
            echo "$CHECK $program: Available"
        fi
    done

    if [[ $os_type == "Linux" ]]; then
        check_pinentry_on_linux
    elif [[ $os_type == "macOS" ]]; then
        if ! is_program_available "pinentry-mac"; then
            echo "$CROSS pinentry-mac: Not available"
            unavailable_programs+=("pinentry-mac,pinentry-mac")
        else
            echo "$CHECK pinentry-mac: Available"
        fi
    fi

    if [[ ${#unavailable_programs[@]} -gt 0 ]]; then
        echo "Some applications are not available. Prompting for installation..."
        for program_package in "${unavailable_programs[@]}"; do
            IFS=',' read -r program package <<< "$program_package"
            prompt_installation $os_type $program $package
        done

        local all_available=true
        local final_results=""
        for program_package in "${programs[@]}"; do
            IFS=',' read -r program package <<< "$program_package"
            if is_program_available $program; then
                final_results+="$CHECK $program: Available\n"
            else
                final_results+="$CROSS $program: Not available\n"
                all_available=false
            fi
        done

        if [[ $os_type == "Linux" ]]; then
            if check_pinentry_on_linux; then
                final_results+="$CHECK pinentry program: Available\n"
            else
                final_results+="$CROSS pinentry program: Not available\n"
                all_available=false
            fi
        elif [[ $os_type == "macOS" ]]; then
            if is_program_available "pinentry-mac"; then
                final_results+="$CHECK pinentry-mac: Available\n"
            else
                final_results+="$CROSS pinentry-mac: Not available\n"
                all_available=false
            fi
        fi

        if ! $all_available; then
            echo "Some applications are still not available. Please install the necessary applications."
            echo -e "$final_results"
            exit 1
        fi
    fi

}

# Function to prompt the user for input
prompt_user() {
    local prompt_message="$1"
    local user_input
    read -r "user_input?$prompt_message"
    echo "$user_input"
}

# Function to get GPG version
get_gpg_version() {
    gpg --version | awk '/^gpg \(GnuPG\)/ {print $3}'
}

# Function to retrieve the default global Git email
get_git_email() {
    git config --global user.email
}

# Function to retrieve the default global Git name
get_git_name() {
    git config --global user.name
}

# Function to check Git email and name
check_git_config() {
    local git_email
    local git_name
    local user_input

    git_email=$(get_git_email)
    git_name=$(get_git_name)
    echo "User configuration:"
    echo "${YELLOW}→${RESET} Name:  $git_name"
    echo "${YELLOW}→${RESET} Email: $git_email"

    user_input=$(prompt_user "Are these values correct? (y/N): ")

    if [[ "$user_input" =~ ^[Yy]$ ]]; then
        [ "$verbose" = true ] && echo "No changes made."
    else
         git_name=$(prompt_user "New name:  ")
        git_email=$(prompt_user "New email: ")

        echo "${YELLOW}→${RESET} Updated name:  $git_name"
        echo "${YELLOW}→${RESET} Updated email: $git_email"

        echo "GPG keys and git commits need to match exactly for the email and name to be valid."
        user_input=$(prompt_user "Do you want to configure git with these values? (y/N): ")

        if [[ "$user_input" =~ ^[Yy]$ ]]; then
            git config --global user.email "$git_email"
            git config --global user.name "$git_name"
            echo "$CHECK Git configuration updated."
        else
            [ "$verbose" = true ] && echo "No changes made."
        fi
    fi
}

harden_gpg_conf() {
    local gpg_conf_file="$HOME/.gnupg/gpg.conf"
    echo "Hardening GPG configuration"
    # Define the content to be added to gpg.conf
    local gpg_conf_content=$(cat <<EOF
personal-cipher-preferences AES256 AES192 AES
personal-digest-preferences SHA512 SHA384 SHA256
personal-compress-preferences ZLIB BZIP2 ZIP Uncompressed
default-preference-list SHA512 SHA384 SHA256 AES256 AES192 AES ZLIB BZIP2 ZIP Uncompressed
cert-digest-algo SHA512
s2k-digest-algo SHA512
s2k-cipher-algo AES256
charset utf-8
no-comments
no-emit-version
no-greeting
keyid-format 0xlong
list-options show-uid-validity
verify-options show-uid-validity
with-fingerprint
require-cross-certification
no-symkey-cache
armor
use-agent
throw-keyids
EOF
    )

    # Ensure the gpg.conf file exists
    touch "$gpg_conf_file"

    # Append lines only if they aren't already present
    while IFS= read -r line; do
        if ! grep -Fxq "$line" "$gpg_conf_file"; then
            echo "$line" >> "$gpg_conf_file"
        fi
    done <<< "$gpg_conf_content"
}

enable_pinentry() {
    local gpg_agent_file="$HOME/.gnupg/gpg-agent.conf"

    # Check if pinentry-program is already set
    if grep -q "^pinentry-program" "$gpg_agent_file"; then
        echo "${CHECK} pinentry-program already configured"
        return
    fi

    local pinentry_programs=("pinentry-mac" "pinentry-fltk" "pinentry-gnome3" "pinentry-gtk2" "pinentry-qt" "pinentry-tty" "pinentry-x2go" "pinentry-curses")
    local installed_program=""

    for program in "${pinentry_programs[@]}"; do
        if command -v $program >/dev/null 2>&1; then
            installed_program=$program
            break
        fi
    done

    if [[ -n $installed_program ]]; then
        echo "pinentry-program $(command -v $installed_program)" >> "$gpg_agent_file"
        echo "${CHECK} Set pinentry-program to $installed_program"
    fi
    gpg-connect-agent reloadagent /bye
}


enable_ssh_agent () {
    local gpg_agent_file="$HOME/.gnupg/gpg-agent.conf"
    local gpg_agent_content=$(cat <<EOF
enable-ssh-support
use-standard-socket
EOF
    )
    touch $gpg_agent_file

    # Append lines only if they aren't already present
    while IFS= read -r line; do
        if ! grep -Fxq "$line" "$gpg_agent_file"; then
            echo "$line" >> "$gpg_agent_file"
        fi
    done <<< "$gpg_agent_content"


    local zsh_rc_file="$HOME/.zshrc"
    local zsh_rc_content=$(cat <<EOF
export GPG_TTY=\$(tty)
export SSH_AUTH_SOCK=\$(gpgconf --list-dirs agent-ssh-socket)
gpgconf --launch gpg-agent
EOF
    )

    touch $zsh_rc_file
    while IFS= read -r line; do
        if ! grep -Fxq "$line" "$zsh_rc_file"; then
            echo "$line" >> "$zsh_rc_file"
        fi
    done <<< "$zsh_rc_content"
}

check_internet_connection() {
    local test_domain="ezgpg.com"
    local timeout=1
    echo "Checking for internet connection. This will take a few seconds."
    if nslookup -timeout=$timeout "$test_domain" > /dev/null 2>&1; then
        return 0  # Success status
    else
        return 1  # Failure status
    fi
}

loop_until_disconnected() {
    while check_internet_connection; do
        echo "${CROSS} Internet connection is available."
        user_input=$(prompt_user "Do you want to check again? (y/N/skip): ")
        if [[ "$user_input" =~ ^[Ss][Kk][Ii][Pp]$ ]]; then
            echo "Skipping the check."
            return 0
        elif [[ ! "$user_input" =~ ^[Yy]$ ]]; then
            echo "Stopping the check."
            return 0
        fi
    done

    echo "No internet connection detected."
}

prompt_key_choice() {
    local choice
    set +H  # Disable history expansion
    while true; do
        echo "Please choose a key type:"
        for ((i = 1; i <= ${#key_types[@]}; i++)); do
            key="${key_types[$i]%%:*}"
            comment="${key_types[$i]#*: }"
            echo "$((i))) $key - $comment"
        done
        read -r "choice?> "

        if [[ "$choice" -ge 1 && "$choice" -le ${#key_types[@]} ]]; then
            set -H  # Re-enable history expansion
            return $((choice))  # Return the index of the selected key type
        else
            echo "Invalid option. Please enter a number between 1 and ${#key_types[@]}."
        fi
    done
}

generate_passphrase() {
    LC_ALL=C tr -dc 'A-Z1-9' < /dev/urandom | \
    tr -d "1IOS5U" | head -c 32 | sed 's/.\{4\}/&-/g' | \
    sed 's/-$//'
}


print_in_box() {
    local message="$1"
    local length=${#message}
    local top_border="╔═$(printf '═%.0s' $(seq 1 $length))═╗"
    local bottom_border="╚═$(printf '═%.0s' $(seq 1 $length))═╝"
    local middle="║ $message ║"

    echo "$top_border"
    echo "$middle"
    echo "$bottom_border"
}

print_section_title() {
    local title="$1"
    local length=${#title}
    local underline=$(printf '─%.0s' $(seq 1 $((length))))

    echo "$underline"
    echo "$title"
    echo "$underline"
}

select_external_drive() {
    local selected_mount_point_var=$1
    mounted_filesystems=$(mount)
    # Get a list of all external drives using diskutil
    external_drives=$(diskutil list external | awk '/^\/dev/ {print $1}')
    external_drives=("${(@f)external_drives}")

    # Loop through each external drive and find its mount points
    available_mount_points=()
    for drive in $external_drives; do
        # Get the mount point(s) of the drive
        mount_points=$(echo "$mounted_filesystems" | grep "$drive" | awk '{print $3}')
        read_only=$(echo "$mounted_filesystems" | grep "$drive" | grep -q 'read-only' && echo true || echo false)
        # If there are mount points, store them
        if [[ "$read_only" == false && -n "$mount_points" ]]; then
            mount_points=("${(@f)mount_points}")
            for mount_point in $mount_points; do
                available_mount_points+=("$mount_point")
            done
        fi
    done

    # Prompt the user to select a mount point
    if [ ${#available_mount_points[@]} -eq 0 ]; then
        echo "External Drive:\n${CROSS} No writable external drives found."
        exit 1
    fi

    echo "Please select an external drive for key backup:"
    for i in {1..${#available_mount_points[@]}}; do
        echo "$i) ${available_mount_points[$((i))]}"
    done

    local valid_choice=0
    while [[ $valid_choice -eq 0 ]]; do
        read "user_choice?> "
        if [[ "$user_choice" =~ ^[0-9]+$ ]] && [ "$user_choice" -ge 1 ] && [ "$user_choice" -le ${#available_mount_points[@]} ]; then
            valid_choice=1
        else
            echo "Invalid selection. Please try again."
        fi
    done

    selected_mount_point="${available_mount_points[$((user_choice))]}"
    eval "$selected_mount_point_var='$selected_mount_point'"
}

ensure_yubikey() {
    local count
    count=$(ykman list | wc -l)
    echo "Yubikey:"
    if [ "$count" -eq 0 ]; then
        echo "${CROSS} No yubikey found"
        exit 1
    elif [ "$count" -eq 1 ]; then
        ykman list
    else
        echo "${CROSS} More than one yubikey found. Only one can be plugged in"
        exit 1
    fi
}

ensure_gpg_key() {
    local identity="$1"
    local output
    output=$(gpg --list-keys "$identity" 2>&1)

    if [[ ! "$output" == *"gpg: error reading key: No public key"* ]]; then
        echo "\n${CROSS} GPG key for identity '$identity' already exists."
        exit 1
    fi
}

####################
### SCRIPT START ###
####################

# Parse command-line arguments
verbose=false
while [[ "$1" =~ ^- && ! "$1" == "--" ]]; do case $1 in
  -v | --verbose )
    verbose=true
    ;;
esac; shift; done
if [[ "$1" == '--' ]]; then shift; fi

# Print header
logo
echo $INSTRUCTIONS

print_section_title "Requirements"

# Check programs
programs_to_check=(
    "gpg,gnupg"
    "ykman,ykman"
    "git,git"
)
check_programs "${programs_to_check[@]}"

# Check drive
# Pick an external drive to use for key backup
new_line
selected_drive=""
select_external_drive selected_drive

# Check yubikey
new_line
ensure_yubikey

# Internet check
new_line
echo "Best practice suggests that you disconnect from the internet. Check connection?"
internet_off_continue=$(prompt_user "Continue? (y/skip): ")
if [[ "$internet_off_continue" =~ ^[Ss][Kk][Ii][Pp]$ ]]; then
    echo "${YELLOW}!${RESET} Bypassing internet check"
elif [[ "$internet_off_continue" =~ ^[Yy]$ ]]; then
    loop_until_disconnected
fi
new_line

#########################
# Initial Configuration #
#########################
print_section_title "Initial Configuration"

# Check git config and set up if needed
check_git_config
user_name=$(get_git_name)
user_email=$(get_git_email)

# GPG identity
identity="$user_name <$user_email>"
[ "$verbose" = true ] && echo "GPG Identity: $identity"
ensure_gpg_key $identity

# Key types/algorithms
new_line
prompt_key_choice
key_type="${key_types[$?]%%:*}"  # Extract the key without the comment

# Put in best practices for GPG
new_line
harden_gpg_conf

# Set up temp-dir
gpg_temp=$(mktemp -d -t gnupg-$RANDOM)
cd $gpg_temp
[ "$verbose" = true ] && echo "GPG Temp dir: $gpg_temp"

##################
# Key Generation #
##################
print_section_title "Key generation"

enable_pinentry

echo "Generating passphrase...\nWrite this down somewhere secure:"

# Create Primary (Certify) key passphrase
passphrase=$(generate_passphrase)

print_in_box $passphrase

echo "Generating primary certify key..."
[ "$verbose" = true ] && echo "% gpg --batch --passphrase "$passphrase" --quick-generate-key "$identity" "$key_type" cert never"
gpg --batch --yes --passphrase "$passphrase" --quick-generate-key --no-tty "$identity" "$key_type" cert never

if [ ! $? -eq 0 ]; then
    echo "${CROSS} Failed to generate gpg key"
    exit 1
fi

primary_key_id=$(gpg -k --with-colons "$identity" | awk -F: '/^pub:/ { print $5; exit }')

primary_key_fingerprint=$(gpg -k --with-colons "$identity" | awk -F: '/^fpr:/ { print $10; exit }')

echo "${CHECK} Key generated with fingerprint: ${primary_key_fingerprint}"

for subkey in sign encrypt auth; do
    if [[ "$key_type" == "ed25519" && "$subkey" == "encrypt" ]]; then
        adjusted_key_type="cv25519"
    else
        adjusted_key_type="$key_type"
    fi
    [ "$verbose" = true ] && echo "% gpg --batch --pinentry-mode=loopback --passphrase "$passphrase" --quick-add-key "$primary_key_fingerprint" "$adjusted_key_type" "$subkey" $expire"
    gpg --batch --yes --no-tty --pinentry-mode=loopback --passphrase "$passphrase" --quick-add-key "$primary_key_fingerprint" "$adjusted_key_type" "$subkey" $expire
    if [ ! $? -eq 0 ]; then
        echo "${CROSS} Failed to generate $subkey subkey"
        exit 1
    fi
done

echo "${CHECK} Subkeys generated"

# Export private keys

gpg --output $gpg_temp/$primary_key_id-primary.gpg --batch --pinentry-mode=loopback --passphrase "$passphrase" --armor --export-secret-keys $primary_key_id
if [ ! $? -eq 0 ]; then
    echo "${CROSS} Failed to export primary private key"
    exit 1
fi

gpg --output $gpg_temp/$primary_key_id-subkeys.gpg --batch --pinentry-mode=loopback --passphrase "$passphrase" --armor --export-secret-subkeys $primary_key_id
if [ ! $? -eq 0 ]; then
    echo "${CROSS} Failed to export subkeys"
    exit 1
fi

gpg --output $gpg_temp/$primary_key_id-$(date +%F).asc --armor --export $primary_key_id
if [ ! $? -eq 0 ]; then
    echo "${CROSS} Failed to export public key"
    exit 1
fi

mv $gpg_temp/$primary_key_id-primary.gpg $gpg_temp/$primary_key_id-subkeys.gpg $gpg_temp/$primary_key_id-$(date +%F).asc $selected_drive

if [ $? -eq 0 ]; then
    echo "${CHECK} Exported private keys and backed them up to $selected_drive"
else
    echo "${CROSS} Failed to exported private keys to $selected_drive"
    exit 1
fi

# Remove primary key
gpg --batch --yes --no-tty --pinentry-mode=loopback --passphrase "$passphrase" --delete-secret-keys $primary_key_fingerprint\!

if [ $? -eq 0 ]; then
    echo "${CHECK} Primary key removed"
else
    echo "${CROSS} Failed to remove primary key"
    exit 1
fi

###############
# Git and SSH #
###############

print_section_title "Git and SSH"

sign_fingerprint=$(gpg --batch --no-tty --list-keys --with-subkey-fingerprints $primary_key_id | sed -n '/\[S\]/,+1p' | sed -n 's/.*Key fingerprint = //p' | tr -d ' ')

git config --global commit.gpgsign true
if [ ! $? -eq 0 ]; then
    echo "${CROSS} Failed to enable git signed commits"
    exit 1
fi
git config --global tag.gpgSign true
if [ ! $? -eq 0 ]; then
    echo "${CROSS} Failed to enable git signed tags"
    exit 1
fi
git config --global user.signingkey $sign_fingerprint
if [ ! $? -eq 0 ]; then
    echo "${CROSS} Failed to set git signing key"
    exit 1
fi

echo "${CHECK} Configured git to sign commits and tags"

auth_keygrip=$(gpg --batch --no-tty --list-secret-keys --with-keygrip $primary_key_id | sed -n '/\[A\]/,/Keygrip/p' | awk '/Keygrip/ {print $3}')
auth_fingerprint=$(gpg --batch --no-tty --list-keys --with-subkey-fingerprints $primary_key_id | sed -n '/\[A\]/,+1p' | sed -n 's/.*Key fingerprint = //p' | tr -d ' ')

echo $auth_keygrip >> ~/.gnupg/sshcontrol

if [ $? -eq 0 ]; then
    echo "${CHECK} Added Authentication Key to SSH Control"
    sleep 2
else
    echo "${CROSS} Failed to add Authentication key to SSH Control"
    exit 1
fi

enable_ssh_agent
echo "${CHECK} Enable GPG Agent for SSH"

ssh_public_key=$(gpg --batch --no-tty --export-ssh-key $auth_fingerprint)

echo "SSH Public Key:"
print_in_box $ssh_public_key

###########
# Yubikey #
###########

print_section_title "Yubikey"

ykman config usb --disable otp --force > /dev/null
if [ $? -eq 0 ]; then
    echo "${CHECK} Disabled touch OTP"
    sleep 2
else
    echo "${CROSS} Disable touch OTP failed"
    exit 1
fi


# Reset PGP application to defaults
ykman openpgp reset --force > /dev/null
if [ $? -eq 0 ]; then
    echo "${CHECK} Reset PGP Application to defaults"
    sleep 2
else
    echo "${CROSS} Reset PGP Application to defaults failed"
    exit 1
fi

echo "Generating admin pin for yubikey...\nWrite this down somewhere"

admin_pin=$(LC_ALL=C tr -dc '0-9' < /dev/urandom | fold -w8 | head -1)

print_in_box $admin_pin

gpg --command-fd=0 --pinentry-mode=loopback --change-pin > /dev/null <<EOF
3
12345678
$admin_pin
$admin_pin
q
EOF

if [ $? -eq 0 ]; then
    echo "${CHECK} Admin pin set to $admin_pin"
else
    echo "${CROSS} Failed to set admin pin"
    exit 1
fi
echo "Generating user pin for yubikey...\nWrite this down somewhere"

user_pin=$(LC_ALL=C tr -dc '0-9' < /dev/urandom | fold -w6 | head -1)

print_in_box $user_pin

gpg --command-fd=0 --pinentry-mode=loopback --change-pin > /dev/null <<EOF
1
123456
$user_pin
$user_pin
q
EOF

if [ $? -eq 0 ]; then
    echo "${CHECK} User pin set to $user_pin"
else
    echo "${CROSS} Failed to set user pin"
    exit 1
fi

# Set Identity
gpg --command-fd=0 --pinentry-mode=loopback --edit-card <<EOF
admin
login
$identity
$admin_pin
quit
EOF
if [ $? -eq 0 ]; then
    echo "${CHECK} Configured Identity"
else
    echo "${CROSS} Failed to configure identity"
    exit 1
fi

# Transfer keys

gpg --command-fd=0 --pinentry-mode=loopback --edit-key $primary_key_id <<EOF
key 1
keytocard
1
$passphrase
$admin_pin
save
EOF

if [ $? -eq 0 ]; then
    echo "${CHECK} Moved signature subkey to yubikey"
else
    echo "${CROSS} Failed to move signature subkey"
    exit 1
fi

gpg --command-fd=0 --pinentry-mode=loopback --edit-key $primary_key_id <<EOF
key 2
keytocard
2
$passphrase
$admin_pin
save
EOF

if [ $? -eq 0 ]; then
    echo "${CHECK} Moved encryption subkey to yubikey"
else
    echo "${CROSS} Failed to move encryption subkey"
    exit 1
fi

gpg --command-fd=0 --pinentry-mode=loopback --edit-key $primary_key_id <<EOF
key 3
keytocard
3
$passphrase
$admin_pin
save
EOF

if [ $? -eq 0 ]; then
    echo "${CHECK} Moved authentication subkey to yubikey"
else
    echo "${CROSS} Failed to move authentication subkey"
    exit 1
fi