#!/bin/bash # Automated setup of TPM, GPG keys, and disk set -o pipefail ## External files sourced . /etc/functions . /etc/luks-functions . /tmp/config TRACE "Under /bin/oem-factory-reset" # use TERM to exit on error trap "exit 1" TERM export TOP_PID=$$ ## Static local variables CLEAR="--clear" CONTINUE="--yes-button Continue" CANCEL="--no-button Cancel" HEIGHT="0" WIDTH="80" USER_PIN_DEF=123456 ADMIN_PIN_DEF=12345678 TPM_PASS_DEF=12345678 USER_PIN="" ADMIN_PIN="" TPM_PASS="" #Circumvent Librem Key/Nitrokey HOTP firmware bug https://github.com/osresearch/heads/issues/1167 MAX_HOTP_GPG_PIN_LENGTH=25 # What are the Security components affected by custom passwords CUSTOM_PASS_AFFECTED_COMPONENTS="" # Default RSA key length #TODO change it back to 3076. Canokey cannot be tested easily and Nitrokey prov1 I have doesn't key-attr to 3076 RSA_KEY_LENGTH=2048 GPG_USER_NAME="OEM Key" GPG_KEY_NAME=$(date +%Y%m%d%H%M%S) GPG_USER_MAIL="oem-${GPG_KEY_NAME}@example.com" GPG_USER_COMMENT="OEM-generated key" SKIP_BOOT="n" ## functions die() { local msg=$1 if [ -n "$msg" ]; then echo -e "\n$msg" fi kill -s TERM $TOP_PID exit 1 } whiptail_error() { local msg=$1 if [ "$msg" = "" ]; then die "whiptail error: An error msg is required" fi whiptail $BG_COLOR_ERROR --msgbox "${msg}\n\n" $HEIGHT $WIDTH $BG_COLOR_ERROR --title "Error" } whiptail_error_die() { whiptail_error "$@" die } #Generate a gpg master key: passwordless, no expiration date, RSA 4096 bits #This key will be used to sign 3 subkeys: encryption, authentication and signing #The master key will be stored on the disk, and the subkeys on the smartcard generate_inmemory_RSA_master_and_subkeys() { TRACE "Under oem-factory-reset:generate_inmemory_RSA_master_and_subkeys" echo "Generating GPG key material in memory:" echo "Generating GPG RSA ${RSA_KEY_LENGTH} bits master key..." # Generate GPG master key { echo "Key-Type: RSA" echo "Key-Length: ${RSA_KEY_LENGTH}" echo "Key-Usage: sign" echo "Name-Real: ${GPG_USER_NAME}" echo "Name-Comment: ${GPG_USER_COMMENT}" echo "Name-Email: ${GPG_USER_MAIL}" echo "Expire-Date: 0" echo "Passphrase: ${ADMIN_PIN}" echo "%commit" } | gpg --batch --gen-key \ >/tmp/gpg_card_edit_output 2>&1 if [ $? -ne 0 ]; then ERROR=$(cat /tmp/gpg_card_edit_output) whiptail_error_die "GPG Key generation failed!\n\n$ERROR" fi echo "Generating GPG RSA ${RSA_KEY_LENGTH} bits signing subkey..." # Add signing subkey { echo addkey echo 4 # RSA (sign only) echo ${RSA_KEY_LENGTH} echo 0 # no expiration echo ${ADMIN_PIN} echo y # confirm echo save } | gpg --command-fd=0 --status-fd=1 --pinentry-mode=loopback --edit-key "${GPG_USER_MAIL}" \ >/tmp/gpg_card_edit_output 2>&1 if [ $? -ne 0 ]; then ERROR=$(cat /tmp/gpg_card_edit_output) whiptail_error_die "GPG Key signing subkey generation failed!\n\n$ERROR" fi echo "Generating GPG RSA ${RSA_KEY_LENGTH} bits encryption subkey..." #Add encryption subkey { echo addkey echo 6 # RSA (encrypt only) echo ${RSA_KEY_LENGTH} echo 0 # no expiration echo ${ADMIN_PIN} echo y # confirm echo save } | gpg --command-fd=0 --status-fd=1 --pinentry-mode=loopback --edit-key "${GPG_USER_MAIL}" \ >/tmp/gpg_card_edit_output 2>&1 if [ $? -ne 0 ]; then ERROR=$(cat /tmp/gpg_card_edit_output) whiptail_error_die "GPG Key encryption subkey generation failed!\n\n$ERROR" fi echo "Generating GPG RSA ${RSA_KEY_LENGTH} bits authentication subkey..." #Add authentication subkey { #Authentication subkey needs gpg in expert mode to select RSA custom mode (8) # in order to disable encryption and signing capabilities of subkey # and then enable authentication capability echo addkey echo 8 # RSA (own capabilite) echo S # disable signing capability echo E # disable encryption capability echo A # enable authentication capability echo Q # quit echo ${RSA_KEY_LENGTH} echo 0 # no expiration echo ${ADMIN_PIN} echo y # confirm echo save } | gpg --command-fd=0 --status-fd=1 --pinentry-mode=loopback --expert --edit-key "${GPG_USER_MAIL}" \ >/tmp/gpg_card_edit_output 2>&1 if [ $? -ne 0 ]; then ERROR=$(cat /tmp/gpg_card_edit_output) whiptail_error_die "GPG Key authentication subkey generation failed!\n\n$ERROR" fi DEBUG "Setting public key to ultimate trust..." #Set the public key to the ultimate trust { echo trust echo 5 # ultimate echo y # confirm echo save } | gpg --command-fd=0 --status-fd=1 --pinentry-mode=loopback --edit-key "${GPG_USER_MAIL}" \ >/tmp/gpg_card_edit_output 2>&1 if [ $? -ne 0 ]; then ERROR=$(cat /tmp/gpg_card_edit_output) whiptail_error_die "GPG Key setting public key to ultimate trust failed!\n\n$ERROR" fi } #Function to move current gpg keyring subkeys to card (keytocard) # This is aimed to be used after having generated master key and subkeys in memory and having backuped them to a LUKS container # This function will keytocard the subkeys from the master key in the keyring # The master key will be kept in the keyring # The master key was already used to sign the subkeys, so it is not needed anymore # Delete the master key from the keyring once key to card is done (already backuped on LUKS private partition) keytocard_subkeys_to_smartcard() { TRACE "Under oem-factory-reset:keytocard_subkeys_to_smartcard" #make sure usb ready and usb dongle ready to communicate with enable_usb enable_usb_storage gpg --card-status >/dev/null 2>&1 || die "Error getting GPG card status" DEBUG "Factory resetting the smartcard..." gpg_key_factory_reset DEBUG "Moving subkeys to smartcard..." #keytocard all subkeys { echo "key 1" #Select Signature key echo "keytocard" echo "1" # Signature key echo "$ADMIN_PIN" #Smartcard admin pin echo "$ADMIN_PIN" #Subkey PIN echo "0" #No expiration date echo "key 1" echo "key 2" echo "keytocard" echo "2" # Encryption key echo "$ADMIN_PIN" echo "$ADMIN_PIN" echo "key 2" echo "key 3" echo "keytocard" echo "3" # Authentication key echo "$ADMIN_PIN" echo "$ADMIN_PIN" echo "key 3" echo "save" } | gpg --command-fd=0 --status-fd=1 --pinentry-mode=loopback --expert --edit-key "${GPG_USER_MAIL}" \ >/tmp/gpg_card_edit_output 2>&1 if [ $? -ne 0 ]; then ERROR=$(cat /tmp/gpg_card_edit_output) whiptail_error_die "GPG Key moving subkeys to smartcard failed!\n\n$ERROR" fi DEBUG "Moving subkeys to smartcard done." } #Whiptail prompt to disconnect any external USB storage device prompt_disconnect_external_USB_storage_device() { TRACE "Under oem-factory-reset:disconnect_external_USB_storage_device" #Whiptail $BG_COLOR_WARNING warning about removing any external USB storage device currently connected whiptail $BG_COLOR_WARNING --title 'WARNING: Please disconnect any external USB storage device' \ --msgbox "An external USB storage device will be WIPED next.\n\nPlease disconnect all external USB storage devices." 0 80 || die "Error displaying warning about removing any external USB storage device currently connected" } #Whiptail prompt to insert to be wiped thumb drive prompt_insert_to_be_wiped_thumb_drive() { TRACE "Under oem-factory-reset:prompt_insert_to_be_wiped_thumb_drive" #Whiptail warning about having only desired to be wiped thumb drive inserted whiptail $BG_COLOR_WARNING --title 'WARNING: Please insert the thumb drive to be wiped' \ --msgbox "The thumb drive will be WIPED next.\n\nPlease have connected only the thumb drive to be wiped." 0 80 || die "Error displaying warning about having only desired to be wiped thumb drive inserted" } #list blkid devices (removing partition numbers) list_blkid_devices() { TRACE "Under oem-factory-reset:list_blkid_devices" blkid | cut -d: -f1 | sed 's/[0-9]$//' } #export master key and subkeys to thumbdrive's private LUKS contained partition export_master_key_subkeys_and_revocation_key_to_private_LUKS_container() { TRACE "Under oem-factory-reset:export_master_key_subkeys_and_revocation_key_to_private_LUKS_container" #Sanity check on passed arguments while [ $# -gt 0 ]; do case "$1" in --mode) mode="$2" shift shift ;; --device) device="$2" shift shift ;; --mountpoint) mountpoint="$2" shift shift ;; --pass) pass="$2" shift shift ;; *) die "Error: unknown argument: $1" ;; esac done mount-usb --mode "$mode" --device "$device" --mountpoint "$mountpoint" --pass "$pass" || die "Error mounting thumb drive's private partition" #Export master key and subkeys to thumb drive DEBUG "Exporting master key and subkeys to private LUKS container's partition..." gpg --export-secret-key --armor --pinentry-mode loopback --passphrase-file <(echo -n "${pass}") "${GPG_USER_MAIL}" >"$mountpoint"/privkey.sec || die "Error exporting master key to private LUKS container's partition" gpg --export-secret-subkeys --armor --pinentry-mode loopback --passphrase-file <(echo -n "${pass}") "${GPG_USER_MAIL}" >"$mountpoint"/subkeys.sec || die "Error exporting subkeys to private LUKS container's partition" #copy whole keyring to thumb drive, including revocation key and trust database cp -af ~/.gnupg "$mountpoint"/.gnupg || die "Error copying whole keyring to private LUKS container's partition" #Unmount private LUKS container's mount point umount "$mountpoint" || die "Error unmounting private LUKS container's mount point" } #Export public key to thumb drive's public partition export_public_key_to_thumbdrive_public_partition() { TRACE "Under oem-factory-reset:export_public_key_to_thumbdrive_public_partition" #Sanity check on passed arguments while [ $# -gt 0 ]; do case "$1" in --mode) mode="$2" shift shift ;; --device) device="$2" shift shift ;; --mountpoint) mountpoint="$2" shift shift ;; *) die "Error: unknown argument: $1" ;; esac done #pass non-empty arguments to --pass, --mountpoint, --device, --mode mount-usb --device "$device" --mode "$mode" --mountpoint "$mountpoint" || die "Error mounting thumb drive's public partition" gpg --export --armor "${GPG_USER_MAIL}" >"$mountpoint"/pubkey.asc || die "Error exporting public key to thumb drive's public partition" umount "$mountpoint" || die "Error unmounting thumb drive's public partition" } #Wipe a thumb drive and export master key and subkeys to it wipe_thumb_drive_and_copy_gpg_key_material() { TRACE "Under oem-factory-reset:wipe_thumb_drive_and_copy_gpg_key_material" prompt_disconnect_external_USB_storage_device actual_devices=$(list_blkid_devices) #enable usb storage enable_usb enable_usb_storage prompt_insert_to_be_wiped_thumb_drive new_devices=$(list_blkid_devices) thumb_drive=$(echo "$new_devices" | grep -v "$actual_devices" | uniq) if [ -z "$thumb_drive" ]; then whiptail_error_die "No new thumb drive detected! Aborting." fi select_luks_container_size_percent #Wipe thumb drive with a LUKS container of size $(cat /tmp/luks_container_size_percent) prepare_thumb_drive --device "$thumb_drive" --percentage "$(cat /tmp/luks_container_size_percent)" --pass "$ADMIN_PIN" #Export master key and subkeys to thumb drive first partition export_master_key_subkeys_and_revocation_key_to_private_LUKS_container --mode rw --device "$thumb_drive"1 --mountpoint /media --pass "$ADMIN_PIN" #Export public key to thumb drive's public partition export_public_key_to_thumbdrive_public_partition --mode rw --device "$thumb_drive"2 --mountpoint /media } gpg_key_factory_reset() { TRACE "Under oem-factory-reset:gpg_key_factory_reset" #enable usb storage enable_usb # Factory reset GPG card DEBUG "GPG factory reset..." { echo admin echo factory-reset echo y echo yes } | gpg --command-fd=0 --status-fd=1 --pinentry-mode=loopback --card-edit \ >/tmp/gpg_card_edit_output 2>&1 if [ $? -ne 0 ]; then ERROR=$(cat /tmp/gpg_card_edit_output) whiptail_error_die "GPG Key factory reset failed!\n\n$ERROR" fi # If Nitrokey Storage is inserted, reset AES keys as well if lsusb | grep -q "20a0:4109" && [ -x /bin/hotp_verification ]; then /bin/hotp_verification regenerate ${ADMIN_PIN_DEF} fi # Toggle forced sig (good security practice, forcing PIN request for each signature request) if gpg --card-status | grep "Signature PIN" | grep -q "not forced"; then DEBUG "GPG toggling forcesig on since off..." { echo admin echo forcesig echo ${ADMIN_PIN_DEF} } | gpg --command-fd=0 --status-fd=1 --pinentry-mode=loopback --card-edit \ >/tmp/gpg_card_edit_output 2>&1 if [ $? -ne 0 ]; then ERROR=$(cat /tmp/gpg_card_edit_output) whiptail_error_die "GPG Key forcesig toggle on failed!\n\n$ERROR" fi fi # use p256 for key generation if requested if [ "$GPG_ALGO" = "p256" ]; then { echo admin echo key-attr echo 2 # ECC echo 3 # P-256 echo ${ADMIN_PIN_DEF} echo 2 # ECC echo 3 # P-256 echo ${ADMIN_PIN_DEF} echo 2 # ECC echo 3 # P-256 echo ${ADMIN_PIN_DEF} } | gpg --command-fd=0 --status-fd=1 --pinentry-mode=loopback --card-edit --expert \ >/tmp/gpg_card_edit_output 2>&1 if [ $? -ne 0 ]; then ERROR=$(cat /tmp/gpg_card_edit_output) whiptail_error_die "Setting key to NIST-P256 in USB security dongle failed." fi # fallback to RSA key generation by default else DEBUG "GPG setting RSA key length to ${RSA_KEY_LENGTH} bits..." # Set RSA key length { echo admin echo key-attr echo 1 # RSA echo ${RSA_KEY_LENGTH} #Signing key size set to RSA_KEY_LENGTH echo ${ADMIN_PIN_DEF} echo 1 # RSA echo ${RSA_KEY_LENGTH} #Encryption key size set to RSA_KEY_LENGTH echo ${ADMIN_PIN_DEF} echo 1 # RSA echo ${RSA_KEY_LENGTH} #Authentication key size set to RSA_KEY_LENGTH echo ${ADMIN_PIN_DEF} } | gpg --command-fd=0 --status-fd=1 --pinentry-mode=loopback --card-edit \ >/tmp/gpg_card_edit_output 2>&1 if [ $? -ne 0 ]; then ERROR=$(cat /tmp/gpg_card_edit_output) whiptail_error_die "Setting key attributed to RSA ${RSA_KEY_LENGTH} bits in USB security dongle failed." fi fi } generate_OEM_gpg_keys() { # Generate OEM GPG keys TRACE "Under oem-factory-reset:generate_OEM_gpg_keys" DEBUG "Generating GPG keys to RSA ${RSA_KEY_LENGTH} bits in smartcard..." { echo admin echo generate echo n echo ${ADMIN_PIN_DEF} echo ${USER_PIN_DEF} echo 0 echo ${GPG_USER_NAME} echo ${GPG_USER_MAIL} echo ${GPG_USER_COMMENT} echo ${USER_PIN_DEF} } | gpg --command-fd=0 --status-fd=2 --pinentry-mode=loopback --card-edit \ >/tmp/gpg_card_edit_output 2>&1 if [ $? -ne 0 ]; then ERROR=$(cat /tmp/gpg_card_edit_output) whiptail_error_die "GPG Key automatic keygen failed!\n\n$ERROR" fi } gpg_key_change_pin() { TRACE "Under oem-factory-reset:gpg_key_change_pin" DEBUG "Changing GPG key PINs..." # 1 = user PIN, 3 = admin PIN PIN_TYPE=$1 PIN_ORIG=$2 PIN_NEW=$3 # Change PIN { echo admin echo passwd echo ${PIN_TYPE} echo ${PIN_ORIG} echo ${PIN_NEW} echo ${PIN_NEW} echo q echo q } | gpg --command-fd=0 --status-fd=2 --pinentry-mode=loopback --card-edit \ >/tmp/gpg_card_edit_output 2>&1 if [ $? -ne 0 ]; then ERROR=$(cat /tmp/gpg_card_edit_output | fold -s) whiptail_error_die "GPG Key PIN change failed!\n\n$ERROR" fi } generate_checksums() { TRACE "Under oem-factory-reset:generate_checksums" # ensure /boot mounted if ! grep -q /boot /proc/mounts; then mount -o rw /boot || whiptail_error_die "Unable to mount /boot" else mount -o remount,rw /boot || whiptail_error_die "Unable to mount /boot" fi #Check if previous TPM Disk unlock Key was set if [ -e /boot/kexec_key_devices.txt ]; then TPM_DISK_ENCRYPTION_KEY_SET=1 fi # clear any existing checksums/signatures rm /boot/kexec* 2>/dev/null # create Heads TPM counter if [ "$CONFIG_TPM" = "y" ]; then if [ "$CONFIG_IGNORE_ROLLBACK" != "y" ]; then tpmr counter_create \ -pwdo "$TPM_PASS" \ -pwdc '' \ -la -3135106223 | tee /tmp/counter || whiptail_error_die "Unable to create TPM counter" TPM_COUNTER=$(cut -d: -f1 /dev/null 2>&1 || whiptail_error_die "Unable to increment tpm counter" # create rollback file sha256sum /tmp/counter-$TPM_COUNTER >/boot/kexec_rollback.txt 2>/dev/null || whiptail_error_die "Unable to create rollback file" else ## needs to exist for initial call to unseal-hotp echo "0" >/boot/kexec_hotp_counter fi fi # set default boot option only if no TPM Disk Unlock Key previously set if [ -z "$TPM_DISK_ENCRYPTION_KEY_SET" ]; then set_default_boot_option fi # generate hashes ( set -e -o pipefail cd /boot find ./ -type f ! -path './kexec*' -print0 | xargs -0 sha256sum >/boot/kexec_hashes.txt 2>/dev/null print_tree >/boot/kexec_tree.txt ) [ $? -eq 0 ] || whiptail_error_die "Error generating kexec hashes" param_files=$(find /boot/kexec*.txt) [ -z "$param_files" ] && whiptail_error_die "No kexec parameter files to sign" # sign kexec boot files if sha256sum $param_files 2>/dev/null | gpg \ --pinentry-mode loopback \ --passphrase "$USER_PIN" \ --digest-algo SHA256 \ --detach-sign \ -a \ >/boot/kexec.sig 2>/tmp/error; then # successful - update the validated params if ! check_config /boot >/dev/null 2>/tmp/error; then cat /tmp/error ret=1 else ret=0 fi else cat /tmp/error ret=1 fi # done writing to /boot, switch back to RO mount -o ro,remount /boot if [ $ret = 1 ]; then ERROR=$(tail -n 1 /tmp/error | fold -s) whiptail_error_die "Error signing kexec boot files:\n\n$ERROR" fi } set_default_boot_option() { TRACE "Under oem-factory-reset:set_default_boot_option" option_file="/tmp/kexec_options.txt" tmp_menu_file="/tmp/kexec/kexec_menu.txt" hash_file="/boot/kexec_default_hashes.txt" mkdir -p /tmp/kexec/ rm $option_file 2>/dev/null # parse boot options from grub.cfg for i in $(find /boot -name "grub.cfg"); do kexec-parse-boot "/boot" "$i" >>$option_file done # FC29/30+ may use BLS format grub config files # https://fedoraproject.org/wiki/Changes/BootLoaderSpecByDefault # only parse these if $option_file is still empty if [ ! -s $option_file ] && [ -d "/boot/loader/entries" ]; then for i in $(find /boot -name "grub.cfg"); do kexec-parse-bls "/boot" "$i" "/boot/loader/entries" >>$option_file done fi [ ! -s $option_file ] && whiptail_error_die "Failed to parse any boot options" # sort boot options sort -r $option_file | uniq >$tmp_menu_file ## save first option as default entry=$(head -n 1 $tmp_menu_file | tail -1) # clear existing default configs rm "/boot/kexec_default.*.txt" 2>/dev/null # get correct index for entry index=$(grep -n "$entry" $option_file | cut -f1 -d ':') # write new config echo "$entry" >/boot/kexec_default.$index.txt # validate boot option (cd /boot && /bin/kexec-boot -b "/boot" -e "$entry" -f | xargs sha256sum >$hash_file 2>/dev/null) || whiptail_error_die "Failed to create hashes of boot files" } report_integrity_measurements() { TRACE "Under oem-factory-reset:report_integrity_measurements" #check for GPG key in keyring GPG_KEY_COUNT=$(gpg -k 2>/dev/null | wc -l) if [ "$GPG_KEY_COUNT" -ne 0 ]; then # Check and report TOTP # update the TOTP code every thirty seconds date=$(date "+%Y-%m-%d %H:%M:%S %Z") seconds=$(date "+%s") half=$(expr \( "$seconds" % 60 \) / 30) if [ "$CONFIG_TPM" != "y" ]; then TOTP="NO TPM" elif [ "$half" != "$last_half" ]; then last_half=$half TOTP=$(unseal-totp) >/dev/null 2>&1 fi # Check and report on HOTP status if [ -x /bin/hotp_verification ]; then HOTP=$(unseal-hotp) >/dev/null 2>&1 enable_usb if ! hotp_verification info >/dev/null 2>&1; then whiptail $CONFIG_WARNING_BG_COLOR --title 'WARNING: Please insert your HOTP enabled USB Security dongle' --msgbox "Your HOTP enabled USB Security dongle was not detected.\n\nPlease remove it and insert it again." 0 80 fi # Don't output HOTP codes to screen, so as to make replay attacks harder hotp_verification check $HOTP case "$?" in 0) HOTP="Success" ;; 4) HOTP="Invalid code" MAIN_MENU_BG_COLOR=$CONFIG_ERROR_BG_COLOR ;; *) HOTP="Error checking code, Insert USB Security dongle and retry" MAIN_MENU_BG_COLOR=$CONFIG_WARNING_BG_COLOR ;; esac else HOTP='N/A' fi # Check for detached signed digest and report on /boot integrity status check_config /boot force TMP_HASH_FILE="/tmp/kexec/kexec_hashes.txt" if (cd /boot && sha256sum -c "$TMP_HASH_FILE" >/tmp/hash_output); then HASH="OK" else HASH="ALTERED" fi #Show results whiptail $MAIN_MENU_BG_COLOR --title "Measured Integrity Report" --msgbox "$date\nTOTP: $TOTP | HOTP: $HOTP\n/BOOT INTEGRITY: $HASH\n\nPress OK to continue or Ctrl+Alt+Delete to reboot" 0 80 fi } usb_security_token_capabilities_check() { TRACE "Under /bin/oem-factory-reset:usb_security_token_capabilities_check" enable_usb # ... first set board config preference if [ -n "$CONFIG_GPG_ALGO" ]; then GPG_ALGO=$CONFIG_GPG_ALGO DEBUG "Setting GPG_ALGO to (board-)configured: $CONFIG_GPG_ALGO" fi # ... overwrite with usb-token capability #TODO: revert. Testing test firmware for Nitrokey 3 which is supposed to support RSA 3076 now #if lsusb | grep -q "20a0:42b2"; then # GPG_ALGO="p256" # DEBUG "Nitrokey 3 detected: Setting GPG_ALGO to: $GPG_ALGO" #fi } ## main script start # check for args if [ "$1" != "" ]; then title_text=$1 else title_text="OEM Factory Reset / Re-Ownership" fi if [ "$2" != "" ]; then bg_color=$2 else bg_color="" fi # show warning prompt if [ "$CONFIG_TPM" = "y" ]; then TPM_STR=" * ERASE the TPM and own it with a password\n" else TPM_STR="" fi if ! whiptail --yesno " This operation will automatically:\n $TPM_STR * ERASE any keys or passwords on the GPG smart card,\n reset it to a factory state, generate new keys\n and optionally set custom PIN(s)\n * Add the new GPG key to the firmware and reflash it\n * Sign all of the files in /boot with the new GPG key\n\n It requires that you already have an OS installed on a\n dedicated /boot partition. Do you wish to continue?" \ $HEIGHT $WIDTH $CONTINUE $CANCEL $CLEAR $bg_color --title "$title_text"; then exit 1 fi # We show current integrity measurements status and time report_integrity_measurements # Determine gpg algorithm to be used, based on available usb-token usb_security_token_capabilities_check use_defaults=n if [ "$CONFIG_OEMRESET_OFFER_DEFAULTS" = y ]; then echo -e -n "Would you like to use default configuration options?\nIf N, you will be prompted for each option [Y/n]: " read -n 1 use_defaults fi if [ "$use_defaults" == "n" -o "$use_defaults" == "N" ]; then #Give general guidance to user on how to answer prompts echo "The following questionnaire will help you to configure the security components of your system." echo "You will be prompted for each option to answer a single letter at prompts (Y/n/m)." echo "If you don't know what to answer, just press Enter to use default value which is shown between [] brackets as the uppercase letter." # Re-ownership of encrypted disk key, content and passphrase echo -e -n "\n\nWould you like to change the current LUKS Disk Recovery Key passphrase?\n (Highly recommended if you didn't install the Operating System yourself, so that past provisioned passphrase would not permit to access content.\n Note that without re-encrypting disk, a backuped header could be restored to access encrypted content with old passphrase) [y/N]: " read -n 1 prompt_output echo if [ "$prompt_output" == "y" \ -o "$prompt_output" == "Y" ]; then luks_new_Disk_Recovery_Key_passphrase_desired=1 echo -e "\n" fi echo -e -n "Would you like to re-encrypt LUKS encrypted container and generate new Disk Recovery key?\n (Highly recommended if you didn't install the operating system yourself: this would prevent any LUKS backuped header to be restored to access encrypted data) [y/N]: " read -n 1 prompt_output echo if [ "$prompt_output" == "y" \ -o "$prompt_output" == "Y" ]; then test_luks_current_disk_recovery_key_passphrase luks_new_Disk_Recovery_Key_desired=1 echo -e "\n" fi #Prompt to ask if user wants to generate GPG key material in memory or on smartcard echo -e -n "Would you like to generate GPG key material in (m)emory or (S)olely on the security element of the USB security dongle? [m/S]: " read -n 1 prompt_output echo if [ "$prompt_output" == "m" \ -o "$prompt_output" == "M" ] \ ; then GPG_GEN_KEY_IN_MEMORY=1 #TODO: present steps clearer for user echo "Master key and subkeys will be generated in memory, backuped to dedicated LUKS container and then subkeys imported to factory resetted smartcard." else GPG_GEN_KEY_IN_MEMORY=0 fi # TODO: add LUKS container passphrase = ADMIN_PIN in security components provisioned # Adapt message to be given to user in terms of security components that will be applied. if [ -n "$luks_new_Disk_Recovery_Key_passphrase_desired" -o -n "$luks_new_Disk_Recovery_Key_passphrase" ]; then CUSTOM_PASS_AFFECTED_COMPONENTS="LUKS Disk Recovery Key passphrase" fi if [ "$CONFIG_TPM" = "y" ]; then CUSTOM_PASS_AFFECTED_COMPONENTS="$CUSTOM_PASS_AFFECTED_COMPONENTS TPM Owner Password" fi CUSTOM_PASS_AFFECTED_COMPONENTS="$CUSTOM_PASS_AFFECTED_COMPONENTS GPG Admin PIN GPG User PIN" # Inform user of security components affected for the following prompts echo -e "The following security components will be provisioned with defaults or chosen PINs/passwords: $CUSTOM_PASS_AFFECTED_COMPONENTS\n" # Prompt to change default passwords echo -e -n "Would you like to set a single custom password that will be provisioned to previously stated security components? [y/N]: " read -n 1 prompt_output echo if [ "$prompt_output" == "y" \ -o "$prompt_output" == "Y" ]; then echo -e "\nThe chosen custom password must be between 8 and $MAX_HOTP_GPG_PIN_LENGTH characters in length.\n" echo while [[ ${#CUSTOM_SINGLE_PASS} -lt 8 ]] || [[ ${#CUSTOM_SINGLE_PASS} -gt $MAX_HOTP_GPG_PIN_LENGTH ]]; do echo -e -n "Enter the custom password: " read CUSTOM_SINGLE_PASS done echo TPM_PASS=$CUSTOM_SINGLE_PASS USER_PIN=$CUSTOM_SINGLE_PASS ADMIN_PIN=$CUSTOM_SINGLE_PASS # Only set if user said desired. Matches rest of logic if [ -n "$luks_new_Disk_Recovery_Key_passphrase_desired" ]; then luks_new_Disk_Recovery_Key_passphrase=$CUSTOM_SINGLE_PASS fi else echo -e -n "Would you like to set distinct PINs/passwords to be provisioned to previously stated security components? [y/N]: " read -n 1 prompt_output echo if [ "$prompt_output" == "y" \ -o "$prompt_output" == "Y" ]; then echo -e "\nThey must be each at least 8 characters in length.\n" echo if [ "$CONFIG_TPM" = "y" ]; then while [[ ${#TPM_PASS} -lt 8 ]]; do echo -e -n "Enter desired TPM Owner Password: " read TPM_PASS done fi while [[ ${#ADMIN_PIN} -lt 8 ]] || [[ ${#ADMIN_PIN} -gt $MAX_HOTP_GPG_PIN_LENGTH ]]; do echo -e -n "\nThis PIN should be between 8 to $MAX_HOTP_GPG_PIN_LENGTH characters in length.\n" echo -e -n "Enter desired GPG Admin PIN: " read ADMIN_PIN done while [[ ${#USER_PIN} -lt 8 ]] || [[ ${#USER_PIN} -gt 64 ]]; do echo -e -n "\nThis PIN should be between 8 to 64 characters in length.\n" echo -e -n "Enter desired GPG User PIN: " read USER_PIN done echo fi fi if [ -n "$luks_new_Disk_Recovery_Key_passphrase_desired" -a -z "$luks_new_Disk_Recovery_Key_passphrase" ]; then # We catch here if changing LUKS Disk Recovery Key passphrase was desired # but yet undone. This is if not being covered by the single password echo -e "\nEnter desired replacement for current Disk Recovery Key passphrase (At least 8 characters long):" while [[ ${#luks_new_Disk_Recovery_Key_passphrase} -lt 8 ]]; do { read -r luks_new_Disk_Recovery_Key_passphrase } done #We test that current Disk Recovery Key passphrase is known prior of going further test_luks_current_disk_recovery_key_passphrase echo -e "\n" fi # Prompt to change default GnuPG key information echo -e -n "Would you like to set custom user information for the GnuPG key? [y/N]: " read -n 1 prompt_output echo if [ "$prompt_output" == "y" \ -o "$prompt_output" == "Y" ]; then echo -e "\n\n" echo -e "We will generate a GnuPG (PGP) keypair identifiable with the following text form:" echo -e "Real Name (Comment) email@address.org" echo -e "\nEnter your Real Name (Optional):" read -r GPG_USER_NAME echo -e "\nEnter your email@adress.org:" read -r GPG_USER_MAIL while ! $(expr "$GPG_USER_MAIL" : '.*@' >/dev/null); do { echo -e "\nEnter your email@address.org:" read -r GPG_USER_MAIL } done echo -e "\nEnter Comment (Optional, to distinguish this key from others with same previous attributes. Must be smaller then 60 characters):" read -r GPG_USER_COMMENT while [[ ${#GPG_USER_COMMENT} -gt 60 ]]; do { echo -e "\nEnter Comment (Optional, to distinguish this key from others with same previous attributes. Must be smaller then 60 characters):" read -r GPG_USER_COMMENT } done fi fi # If nothing is stored in custom variables, we set them to their defaults if [ "$TPM_PASS" == "" ]; then TPM_PASS=$TPM_PASS_DEF; fi if [ "$USER_PIN" == "" ]; then USER_PIN=$USER_PIN_DEF; fi if [ "$ADMIN_PIN" == "" ]; then ADMIN_PIN=$ADMIN_PIN_DEF; fi ## sanity check the USB, GPG key, and boot device before proceeding further if [ "$GPG_GEN_KEY_IN_MEMORY" == "0" ]; then # Prompt to insert USB drive if desired echo -e -n "\nWould you like to export your public key to an USB drive? [y/N]: " read -n 1 prompt_output echo if [ "$prompt_output" == "y" \ -o "$prompt_output" == "Y" ] \ ; then GPG_EXPORT=1 # mount USB over /media only if not already mounted if ! grep -q /media /proc/mounts; then # mount USB in rw if ! mount-usb --mode rw 2>/tmp/error; then ERROR=$(tail -n 1 /tmp/error | fold -s) whiptail_error_die "Unable to mount USB on /media:\n\n${ERROR}" fi else #/media already mounted, make sure it is in r+w mode if ! mount -o remount,rw /media 2>/tmp/error; then ERROR=$(tail -n 1 /tmp/error | fold -s) whiptail_error_die "Unable to remount in read+write USB on /media:\n\n${ERROR}" fi fi else GPG_EXPORT=0 # needed for USB Security dongle below and is ensured via mount-usb in case of GPG_EXPORT=1 enable_usb fi fi # ensure USB Security Dongle connected if GPG_GEN_KEY_IN_MEMORY=0 if [ "$GPG_GEN_KEY_IN_MEMORY" == "0" ]; then echo -e "\nChecking for USB Security Dongle...\n" enable_usb if ! gpg --card-status >/dev/null 2>&1; then whiptail_error "Can't access USB Security Dongle; \nPlease remove and reinsert, then press Enter." if ! gpg --card-status >/dev/null 2>/tmp/error; then ERROR=$(tail -n 1 /tmp/error | fold -s) whiptail_error_die "Unable to detect USB Security Dongle:\n\n${ERROR}" fi fi fi assert_signable # Action time... #TODO: Should we replace text from "Add a new GPG key" to "Replace current GPG key"? Should we wipe current keyring? #Current logic is for factory reset, where re-ownership adds key to the keyring which is then copied over cbfs. # In the all case, we should wipe the keyring since otherwise, USB security dongle is wiped but not the keyring which exposes past public keys # this seems wrong # clear local keyring rm /.gnupg/* | true # detect and set /boot device echo -e "\nDetecting and setting boot device...\n" if ! detect_boot_device; then SKIP_BOOT="y" else echo -e "Boot device set to $CONFIG_BOOT_DEV\n" fi # update configs if [[ "$SKIP_BOOT" == "n" ]]; then replace_config /etc/config.user "CONFIG_BOOT_DEV" "$CONFIG_BOOT_DEV" combine_configs fi if [ -n "$luks_new_Disk_Recovery_Key_desired" -a -n "$luks_new_Disk_Recovery_Key_passphrase_desired" ]; then #Reencryption of disk, disk recovery key and Disk Recovery Key passphrase change is requested luks_reencrypt luks_change_passphrase elif [ -n "$luks_new_Disk_Recovery_Key_desired" -a -z "$luks_new_Disk_Recovery_Key_passphrase_desired" ]; then #Reencryption of disk was requested but not passphrase change luks_reencrypt elif [ -z "$luks_new_Disk_Recovery_Key_desired" -a -n "$luks_new_Disk_Recovery_Key_passphrase_desired" ]; then #Passphrase change is requested without disk reencryption luks_change_passphrase fi ## reset TPM and set password if [ "$CONFIG_TPM" = "y" ]; then echo -e "\nResetting TPM...\n" tpmr reset "$TPM_PASS" >/dev/null 2>/tmp/error fi if [ $? -ne 0 ]; then ERROR=$(tail -n 1 /tmp/error | fold -s) whiptail_error_die "Error resetting TPM:\n\n${ERROR}" fi # clear local keyring rm /.gnupg/*.gpg 2>/dev/null rm /.gnupg/*.kbx 2>/dev/null gpg --list-keys >/dev/null 2>&1 #Generate key in memory and copy to smartcard if [ "$GPG_GEN_KEY_IN_MEMORY" == "1" ]; then # Generate GPG master key generate_inmemory_RSA_master_and_subkeys #TODO seperate wiping and thumb drive functions with proper validation wipe_thumb_drive_and_copy_gpg_key_material #TODO seperate setting config set_user_config CONFIG_HAVE_GPG_KEY_BACKUP y gpg_key_factory_reset keytocard_subkeys_to_smartcard else #Generate GPG key and subkeys on smartcard ## reset the GPG Key echo -e "\nResetting GPG Key...\n(this will take around 3 minutes...)\n" gpg_key_factory_reset generate_OEM_gpg_keys fi # Obtain GPG key ID GPG_GEN_KEY=$(gpg --list-keys --with-colons | grep "^fpr" | cut -d: -f10 | head -n1) #Where to export the public key PUBKEY="/tmp/${GPG_GEN_KEY}.asc" DEBUG "GPG_GEN_KEY: $GPG_GEN_KEY" DEBUG "PUBKEY: $PUBKEY" # export pubkey to file if ! gpg --export --armor "$GPG_GEN_KEY" >"${PUBKEY}" 2>/tmp/error; then ERROR=$(tail -n 1 /tmp/error | fold -s) whiptail_error_die "GPG Key gpg export to file failed!\n\n$ERROR" fi #Applying custom GPG PINs if keys were not generated in memory if [ "$GPG_GEN_KEY_IN_MEMORY" == "0" ]; then if [ "$USER_PIN" != "" -o "$ADMIN_PIN" != "" ]; then echo -e "\nChanging default GPG Admin PIN\n" gpg_key_change_pin "3" "$ADMIN_PIN_DEF" "$ADMIN_PIN" echo -e "\nChanging default GPG User PIN\n" gpg_key_change_pin "1" "$USER_PIN_DEF" "$USER_PIN" fi ## export pubkey to USB if [ $GPG_EXPORT -ne 0 ]; then echo -e "\nExporting generated key to USB...\n" # copy to USB if ! cp "${PUBKEY}" "/media/${GPG_GEN_KEY}.asc" 2>/tmp/error; then ERROR=$(tail -n 1 /tmp/error | fold -s) whiptail_error_die "Key export error: unable to copy ${GPG_GEN_KEY}.asc to /media:\n\n$ERROR" fi mount -o remount,ro /media 2>/dev/null fi fi ## flash generated key to ROM echo -e "\nReading current firmware...\n(this will take a minute or two)\n" /bin/flash.sh -r /tmp/oem-setup.rom >/dev/null 2>/tmp/error if [ ! -s /tmp/oem-setup.rom ]; then ERROR=$(tail -n 1 /tmp/error | fold -s) whiptail_error_die "Error reading current firmware:\n\n$ERROR" fi # ensure key imported locally if ! cat "$PUBKEY" | gpg --import >/dev/null 2>/tmp/error; then ERROR=$(tail -n 1 /tmp/error | fold -s) whiptail_error_die "Error importing GPG key:\n\n$ERROR" fi # update /.gnupg/trustdb.gpg to ultimately trust all user provided public keys if ! gpg --list-keys --fingerprint --with-colons 2>/dev/null | sed -E -n -e 's/^fpr:::::::::([0-9A-F]+):$/\1:6:/p' | gpg --import-ownertrust >/dev/null 2>/tmp/error; then ERROR=$(tail -n 1 /tmp/error | fold -s) whiptail_error_die "Error importing GPG ownertrust:\n\n$ERROR" fi if ! gpg --update-trust >/dev/null 2>/tmp/error; then ERROR=$(tail -n 1 /tmp/error | fold -s) whiptail_error_die "Error updating GPG ownertrust:\n\n$ERROR" fi # clear any existing heads/gpg files from current firmware for i in $(cbfs.sh -o /tmp/oem-setup.rom -l | grep -e "heads/"); do cbfs.sh -o /tmp/oem-setup.rom -d "$i" done # add heads/gpg files to current firmware if [ -e /.gnupg/pubring.kbx ]; then cbfs.sh -o /tmp/oem-setup.rom -a "heads/initrd/.gnupg/pubring.kbx" -f /.gnupg/pubring.kbx if [ -e /.gnupg/pubring.gpg ]; then rm /.gnupg/pubring.gpg fi elif [ -e /.gnupg/pubring.gpg ]; then cbfs.sh -o /tmp/oem-setup.rom -a "heads/initrd/.gnupg/pubring.gpg" -f /.gnupg/pubring.gpg fi if [ -e /.gnupg/trustdb.gpg ]; then cbfs.sh -o /tmp/oem-setup.rom -a "heads/initrd/.gnupg/trustdb.gpg" -f /.gnupg/trustdb.gpg fi # persist user config changes (boot device) if [ -e /etc/config.user ]; then cbfs.sh -o /tmp/oem-setup.rom -a "heads/initrd/etc/config.user" -f /etc/config.user fi # flash updated firmware image echo -e "\nAdding generated key to current firmware and re-flashing...\n" if ! /bin/flash.sh /tmp/oem-setup.rom 2>/tmp/error; then ERROR=$(tail -n 1 /tmp/error | fold -s) whiptail_error_die "Error flashing updated firmware image:\n\n$ERROR" fi ## sign files in /boot and generate checksums if [[ "$SKIP_BOOT" == "n" ]]; then echo -e "\nSigning boot files and generating checksums...\n" generate_checksums fi # Prepare whiptail output of provisioned secrets if [ -z "$luks_new_Disk_Recovery_Key_passphrase" -o -z "$luks_new_Disk_Recovery_Key_passphrase_desired" ]; then luks_passphrase_changed="" else luks_passphrase_changed="LUKS Disk Recovery Key passphrase:\n $luks_new_Disk_Recovery_Key_passphrase" fi if [ "$CONFIG_TPM" = "y" ]; then tpm_owner_password_changed=" TPM Owner Password: $TPM_PASS\n" else tpm_owner_password_changed="" fi ## Show to user current provisioned secrets prior of rebooting whiptail --msgbox " $luks_passphrase_changed $tpm_owner_password_changed GPG Admin PIN: $ADMIN_PIN\n GPG User PIN: $USER_PIN\n\n" \ $HEIGHT $WIDTH --title "Provisioned secrets" ## all done -- reboot whiptail --msgbox " OEM Factory Reset / Re-Ownership has completed successfully\n\n After rebooting, you will need to generate new TOTP/HOTP secrets\n when prompted in order to complete the setup process.\n\n Press Enter to reboot.\n" \ $HEIGHT $WIDTH --title "OEM Factory Reset / Re-Ownership Complete" # Clean LUKS secrets luks_secrets_cleanup unset luks_passphrase_changed unset tpm_owner_password_changed reboot