Universal Second Factor U2F & FIDO: How can I secure my System/OS by using FIDO/U2F in order to do that?

Universal Second Factor U2F & FIDO: How can I secure my System/OS by using FIDO/U2F in order to do that ?

1. Motivation

This Tutorial will show you how you can activate FIDO/U2F in 60 seconds on your Ubuntu system.

FIDO/U2F is one of the most secure option which you can use in order to protect your system/OS and your privacy.

After applying the scripts you have to press the button on the Yubikey in order to prove a certain request, e.g. requesting a root shell or other administration commands.

This solution could be used to avoid an airport shutdown !

This solution requires

  • A USB-Hardware-Key/Yubikey from Yubico (30€ to 50€)
  • The OS Ubuntu 24.04 noble
  • The files of the folder U2F_1, which contain the FIDO/U2F configuration

Why I have myself decided to go for a Yubikey? The answer is for security reasons. The key has no option to change the private key which is the Hardware-Key itself. From my point of view this is a big advantage compared to other USB-FIDO solutions, because it is not possible to manipulate the content of the key. All what you can do, is to define a few pins.
==> Secure by design ! (I am not working for Yubico)

2. Solution

We will review 2 scripts which will need to run in order to activate FIDO/U2F on our system.

Due to the fact that we are going to protect the system with a new Hardware, the Yubikey, we have to perform the necessary steps in a controlled way to make sure not to lockout ourself of the system ! It is a good approach to keep a root shell open as long as you have not proved that everything is working as expected, because than you have still the option to change what you want to change.

Script_1 ==> u2f_1__1_libs.sh

The script will assign and install the mandatory libraries for FIDO/U2F and the Yubikey.

Part_1 of Script_1 ==> Definition of some variables

{
#!/bin/bash
#
####################################################
# Version 1.1                                      #
# Date 2025-01-20                                  #    
# Kay Schiller                                     #
# Description: Configure FIDO/U2F for Ubuntu 24.04 #
#              Part 1: Assign and load libs        #
####################################################
#
FILE_3=u2f_keys
DIR_0="."
DIR_1=""
DIR_2=""
DIR_3="/home/$USER/.config/Yubico"
DIR_4="/etc/pam.d/U2F_0"
DIR_5="/etc/pam.d/U2F_1"
DIR_6="$DIR_0/u2f/U2F_0"
DIR_7="$DIR_0/u2f/U2F_1"
DIR_8="/etc/pam.d/"
DIR_9="/etc/Yubico"
USER_2=$USER
ERROR="0"
#
#------- Functions -------------------------------------------------
#
DFAULTCOLOR="\e[0m"
GREEN="\e[1;92m"
RED="\e[1;91m"
BLUE="\e[1;94m"
}

Part_2 of Script_1 ==> Loading the function STATUS_LAST_CMD

#
# Load Functions
#
. /home/$USER/Documents/USB/functions.sh
if [ $? = 0 ]
    then
    echo -e "${GREEN}Functios imported succesfully${DFAULTCOLOR}"
else
    echo -e "${RED}ERROR: Functions were not imported, check path !${DFAULTCOLOR}"
    exit
fi

functions.sh ==> STATUS_LAST_CMD

The script is using the function STATUS_LAST_CMD, which proves the Error-Level of a command.
Each command which is executed by a shell delivers the so called Error-Level. If the Error-Level=0 it means the previous command was executed sucessfully. In case the value is not equal to 0 it means an error occured during processing.

=> Using this simple function we can prove that the script was executed as we want to see it - successfully !

Part_3 of Script_1 ==> Plotting the content of some variables

echo "DIR_0=$DIR_0"
echo "DIR_1=$DIR_1"
echo "DIR_2=$DIR_2"
echo "DIR_3=$DIR_3"
echo "DIR_4=$DIR_4"
echo "DIR_5=$DIR_5"
echo "DIR_6=$DIR_6"
echo "DIR_7=$DIR_7"
echo "DIR_8=$DIR_8"
echo "DIR_9=$DIR_9"

Part_4 of Script_1 _==> Ask the user whether the system is connected to the internet

#
#ETHERNET = 0
#
#------------------------------------------------------------------
echo "Are you connected to the Internet ? [Y || y]"
ETHERNET=No
while read ETHERNET ;
do
	if [[ $ETHERNET == "Y" ]] || [[ $ETHERNET == "y" ]]; then
	echo "YES the cable is connected"
	break
	#
	else
	echo "NO, the cable is not connected ! Please connect it into the LAN"
	fi
done

#------------------------------------------------------------------

Part_5 of Script_1 _==> Assign the Library/Repository and install it on the system

#------------------------------------------------------------------
#
echo "#######################################"
echo "### sudo apt install Yubikey U2F -y ###"
echo "#######################################"
# Add Yubico Repository
echo "############################################"
echo "### add-apt-repository ppa:yubico/stable ###"
echo "############################################"
sudo add-apt-repository ppa:yubico/stable
STATUS_LAST_CMD $?; STATUS="$?"; if [ "$STATUS" != "0" ]; then ERROR="1"; fi
#
echo "############################################"
echo "### sudo apt install pamu2fcfg           ###"
echo "############################################"
sudo apt purge pamu2fcfg
#STATUS_LAST_CMD $?; STATUS="$?"; if [ "$STATUS" != "0" ]; then ERROR="1"; fi
sudo apt install pamu2fcfg
STATUS_LAST_CMD $?; STATUS="$?"; if [ "$STATUS" != "0" ]; then ERROR="1"; fi
# Install U2F libs for Yubico
echo 
echo "############################################"
echo "### apt install libpam-u2f               ###"
echo "############################################"
sudo apt purge libpam-u2f
#STATUS_LAST_CMD $?; STATUS="$?"; if [ "$STATUS" != "0" ]; then ERROR="1"; fi
sudo apt install libpam-u2f
STATUS_LAST_CMD $?; STATUS="$?"; if [ "$STATUS" != "0" ]; then ERROR="1"; fi
#------------------------------------------------------------------
echo -e "${BLUE}------------------------------------------------------------------${DFAULTCOLOR}"
echo ""
if [ "$ERROR" = "0" ]; then echo -e "${BLUE}Done !${DFAULTCOLOR}"; fi
if [ "$ERROR" != "0" ]; then echo -e "${RED}ERROR ! Something went wrong...${DFAULTCOLOR}"; fi
echo ""
echo -e "${BLUE}------------------------------------------------------------------${DFAULTCOLOR}"

Script_2 ==> u2f_1__2_pam.sh

The script will will configure the Yubikey for Ubuntu in order to enable FIDO/U2F.

It requires to change some existing files which are located at:

/etc/pam.d

In order to keep this script very simple it will generate two subfolders:

  • /etc/pam.d/U2F_0
  • /etc/pam.d/U2F_1

U2F_0 will contain a copy of the original files which were located at /etc/pam.d. (As backup)

U2F_1 will contain manipulated files in order to activate your Yubikey for FIDO/U2F of your Ubuntu.

Part_1 of Script_2 ==> Definition of some variables

#!/bin/bash
#
###############################################
# Version 1.1                                 #
# Date 2025-01-20                             #    
# Kay Schiller                                #
# Description: Configure FIDO/U2F for Ubuntu  #
#              Part 2: Copy Files A2B	      #
###############################################
#
FILE_3=u2f_keys
USER_1="$USER"
USER_2="$USER"
DIR_0="`pwd`"
DIR_1=""
DIR_2=""
DIR_3="/home/$USER_1/.config/Yubico"
DIR_4="/etc/pam.d/U2F_0"
DIR_5="/etc/pam.d/U2F_1"
DIR_6="$DIR_0/u2f/U2F_0"
DIR_7="$DIR_0/u2f/U2F_1"
DIR_8="/etc/pam.d/"
DIR_9="/etc/Yubico"
ERROR="0"
#
#------- Functions -------------------------------------------------
#
DFAULTCOLOR="\e[0m"
GREEN="\e[1;92m"
RED="\e[1;91m"
BLUE="\e[1;94m"

Part_2 of Script_2 ==> Loading the function STATUS_LAST_CMD

#
# Load Functions
#
. /home/$USER/Documents/USB/functions.sh
if [ $? = 0 ]
    then
    echo -e "${GREEN}Functios imported succesfully${DFAULTCOLOR}"
else
    echo -e "${RED}ERROR: Functions were not imported, check path !${DFAULTCOLOR}"
    exit
fi

We can find the description of the function under “Part_2 of Script 1”

Part_3 of Script_2 ==> Plotting the content of some variables

echo "DIR_0=$DIR_0"
echo "DIR_1=$DIR_1"
echo "DIR_2=$DIR_2"
echo "DIR_3=$DIR_3"
echo "DIR_4=$DIR_4"
echo "DIR_5=$DIR_5"
echo "DIR_6=$DIR_6"
echo "DIR_7=$DIR_7"
echo "DIR_8=$DIR_8"
echo "DIR_9=$DIR_9"

Part_4 of Script_2 ==> Ask the user whether the system is connected to the internet

#------------------------------------------------------------------
echo "Is the Ethernet cable connected ? [N || n]"
while read ETHERNET ;
do
	if [[ $ETHERNET == "N" ]] || [[ $ETHERNET == "n" ]]; then
	echo "No the cable is not connected anymore"
	break
	#
	else
	echo "Yes, the cable is still connected !"
	fi
done
#
#------------------------------------------------------------------

Part_5 of Script_2 ==> Save the publik key of the Yubikey to right location

#------------------------------------------------------------------
echo
#read -p "Root shell available ? [ENTER]"
echo -e "${BLUE}Root shell available ? ${RED}[ENTER]${DFAULTCOLOR}"; read -p ""
echo
# Generate Yubico Keys for U2F
echo 
echo "###############################################"
echo "### Generate Keys: $DIR_3 ###"
echo "###############################################"
mkdir -p $DIR_3
chmod 770 $DIR_3
#
echo "pamu2fcfg > ~/.config/Yubico/u2f_keys"
pamu2fcfg > "$DIR_3/u2f_keys"
STATUS_LAST_CMD $?; STATUS="$?"; if [ "$STATUS" != "0" ]; then ERROR="1"; fi
#
# Copy Yubikey to /etc/Yubico
#
sudo mkdir -p $DIR_9
echo "cp -f $DIR_3/* $DIR_9/."
echo ""
sudo rsync -av $DIR_3/* $DIR_9/.
STATUS_LAST_CMD $?; STATUS="$?"; if [ "$STATUS" != "0" ]; then ERROR="1"; fi
sudo chmod -Rf 660 $DIR_9
sudo chown -Rf root $DIR_9
sudo chgrp -Rf root $DIR_9
# End of Generate Yubico Keys for U2F
#------------------------------------------------------------------

Part_6 of Script_2 ==> Save the files of the location /etc/pam.d to /etc/pam.d/U2F_0 as backup

#------------------------------------------------------------------
# cp /etc/pam.d/. /etc/pam.d/U2F_0/.
#
echo
echo "###################################################"
echo "### Configure pam.d => U2F_0: $DIR_8/U2F_0  ###"
echo "###################################################"
if [ ! -d "$DIR_4" ]; then
	sudo mkdir -p "$DIR_4"
	pwd
	sudo rsync -av $DIR_8/. $DIR_4/.
	STATUS_LAST_CMD $?; STATUS="$?"; if [ "$STATUS" != "0" ]; then ERROR="1"; fi
fi
#------------------------------------------------------------------
# cp -f /home/$USER/Documents/USB/u2f/U2F_1/* /etc/pam.d/U2F_1/.
#
echo "######################################################"
echo "### cp -f ${DIR_7}/* ${DIR_5}/.  ###"
echo "######################################################"
#
#DIR_0=/home/$USER/Documents/USB
#DIR_1=
#DIR_2=
#DIR_3=/home/$USER/.config/Yubico
#DIR_4=/etc/pam.d/U2F_0
#DIR_5=/etc/pam.d/U2F_1
#DIR_6=/home/$USER/Documents/USB/u2f/U2F_0
#DIR_7=/home/$USER/Documents/USB/u2f/U2F_1
#DIR_8=/etc/pam.d/
#DIR_9=/etc/Yubico
#
echo 
sudo mkdir -p $DIR_5
echo "DIR_7=$DIR_7"
echo "DIR_5=$DIR_5"
echo
echo "rsync -av $DIR_7/. $DIR_5/."
echo
sudo rsync -av $DIR_7/. $DIR_5/.
STATUS_LAST_CMD $?; STATUS="$?"; if [ "$STATUS" != "0" ]; then ERROR="1"; fi
echo "chmod -Rf 644 $DIR_5"
sudo chmod -Rf 644 $DIR_5
echo "sudo chown -Rf root $DIR_5" 
sudo chown -Rf root $DIR_5
echo "sudo chgrp -Rf root $DIR_5"
sudo chgrp -Rf root $DIR_5
#------------------------------------------------------------------

Part_7 of Script_2 ==> Save the files of the location /etc/pam.d/U2F_1 to /etc/pam.d in order to activate FIDO/U2F for the system

#------------------------------------------------------------------
#
# Configure pam.d using certain files of U2F_1 for: /etc/pam.d/ 
#
#------------------------------------------------------------------
echo "#################################################"
echo "### cp U2F_1 Files to /etc/pam.d  ###"
echo "#################################################"
ls -al $DIR_8
#------------------------------------------------------------------
echo
echo "sudo cp -f $DIR_5/common-auth $DIR_8/."
sudo cp -f "$DIR_5/common-auth" $DIR_8/.
STATUS_LAST_CMD $?; STATUS="$?"; if [ "$STATUS" != "0" ]; then ERROR="1"; fi
echo
#------------------------------------------------------------------
#------------------------------------------------------------------
echo
echo "sudo cp -f $DIR_5/su $DIR_8/."
sudo cp -f "$DIR_5/su" $DIR_8/.
STATUS_LAST_CMD $?; STATUS="$?"; if [ "$STATUS" != "0" ]; then ERROR="1"; fi
echo
#------------------------------------------------------------------
#------------------------------------------------------------------
echo
echo "sudo cp -f $DIR_5/su-l $DIR_8/."
sudo cp -f "$DIR_5/su-l" $DIR_8/.
STATUS_LAST_CMD $?; STATUS="$?"; if [ "$STATUS" != "0" ]; then ERROR="1"; fi
echo
#------------------------------------------------------------------
#------------------------------------------------------------------
echo
echo "sudo cp -f $DIR_5/sudo $DIR_8/."
sudo cp -f "$DIR_5/sudo" $DIR_8/.
STATUS_LAST_CMD $?; STATUS="$?"; if [ "$STATUS" != "0" ]; then ERROR="1"; fi
echo
#------------------------------------------------------------------
#------------------------------------------------------------------
echo
echo "sudo cp -f $DIR_5/sudo-i $DIR_8/."
sudo cp -f "$DIR_5/sudo-i" $DIR_8/.
STATUS_LAST_CMD $?; STATUS="$?"; if [ "$STATUS" != "0" ]; then ERROR="1"; fi
echo
#------------------------------------------------------------------
# Cleaning...
# rm /etc/pam.d/USF_0/U2F_0, rm /etc/pam.d/USF_0/U2F_1 rm /etc/pam.d/USF_1/U2F_0, rm /etc/pam.d/USF_1/U2F_0
echo "rm -rf /etc/pam.d/USF_0/U2F_0"
sudo rm -rf /etc/pam.d/USF_0/U2F_0
echo
#------------------------------------------------------------------ 
echo -e "${BLUE}------------------------------------------------------------------${DFAULTCOLOR}"
echo ""
if [ "$ERROR" = "0" ]; then echo -e "${BLUE}Done !${DFAULTCOLOR}"; fi
if [ "$ERROR" != "0" ]; then echo -e "${RED}ERROR ! Something went wrong...${DFAULTCOLOR}"; fi
echo ""
echo -e "${BLUE}------------------------------------------------------------------${DFAULTCOLOR}"

Files of U2F_1 ==> FIDO/U2F Configuration

The folder /etc/pam.d/U2F_1 contains a bunch of files and those files configure U2F for our OS.

In the following I will list the files which I have currently configured for my system.

For the sake of completeness I will print the content of the files here but I will also attach the files at the very end of this tutorial.
[It looks like, it is not possible to attach files at this location…]

The command line between the # lines are showing the manipulation in order to configure FIDO/U2F.

Command Example_1
###########################################
auth	sufficient	pam_u2f.so	cue
###########################################

This command configures the environment, that we have to press the button of the Yubikey without the need to enter a password in additon.

Command Example_2
################################################################
auth    required        pam_u2f.so authfile=/etc/Yubico/u2f_keys
################################################################

This command points to the file which is used to store the public key of the Yubikey. It is possilbe to store several keys of serveral users in one file.
In general it is recommend to have a second key available just in case we loose one of them.

/etc/pam.d/sudo

#%PAM-1.0

# Set up user limits from /etc/security/limits.conf.
session    required   pam_limits.so
session    required   pam_env.so readenv=1 user_readenv=0
session    required   pam_env.so readenv=1 envfile=/etc/default/locale user_readenv=0
###########################################
auth	sufficient	pam_u2f.so	cue
###########################################

@include common-auth
################################################################
auth    required        pam_u2f.so authfile=/etc/Yubico/u2f_keys
################################################################
@include common-account
@include common-session-noninteractive

/etc/pam.d/sudo-i

#%PAM-1.0

# Set up user limits from /etc/security/limits.conf.
session    required   pam_limits.so
session    required   pam_env.so readenv=1 user_readenv=0
session    required   pam_env.so readenv=1 envfile=/etc/default/locale user_readenv=0
###########################################
auth	sufficient	pam_u2f.so	cue
###########################################

@include common-auth
################################################################
auth    required        pam_u2f.so authfile=/etc/Yubico/u2f_keys
################################################################
@include common-account
@include common-session

/etc/pamm.d/su

#
# The PAM configuration file for the Shadow `su' service
#

# This allows root to su without passwords (normal operation)
auth       sufficient pam_rootok.so

# Uncomment this to force users to be a member of group wheel
# before they can use `su'. You can also add "group=foo"
# to the end of this line if you want to use a group other
# than the default "wheel" (but this may have side effect of
# denying "root" user, unless she's a member of "foo" or explicitly
# permitted earlier by e.g. "sufficient pam_rootok.so").
# (Replaces the `SU_WHEEL_ONLY' option from login.defs)
# auth       required   pam_wheel.so

# Uncomment this if you want wheel members to be able to
# su without a password.
# auth       sufficient pam_wheel.so trust

# Uncomment this if you want members of a specific group to not
# be allowed to use su at all.
# auth       required   pam_wheel.so deny group=nosu

# Uncomment and edit /etc/security/time.conf if you need to set
# time restrainst on su usage.
# (Replaces the `PORTTIME_CHECKS_ENAB' option from login.defs
# as well as /etc/porttime)
# account    requisite  pam_time.so

# This module parses environment configuration file(s)
# and also allows you to use an extended config
# file /etc/security/pam_env.conf.
# 
# parsing /etc/environment needs "readenv=1"
session       required   pam_env.so readenv=1
# locale variables are also kept into /etc/default/locale in etch
# reading this file *in addition to /etc/environment* does not hurt
session       required   pam_env.so readenv=1 envfile=/etc/default/locale

# Defines the MAIL environment variable
# However, userdel also needs MAIL_DIR and MAIL_FILE variables
# in /etc/login.defs to make sure that removing a user 
# also removes the user's mail spool file.
# See comments in /etc/login.defs
#
# "nopen" stands to avoid reporting new mail when su'ing to another user
session    optional   pam_mail.so nopen

# Sets up user limits according to /etc/security/limits.conf
# (Replaces the use of /etc/limits in old login)
session    required   pam_limits.so

# The standard Unix authentication modules, used with
# NIS (man nsswitch) as well as normal /etc/passwd and
# /etc/shadow entries.

@include common-auth
################################################################
auth    required        pam_u2f.so authfile=/etc/Yubico/u2f_keys
################################################################
@include common-account
@include common-session

/etc/pam.d/su-l

#%PAM-1.0
auth		include		su
account		include		su
password	include		su
session		optional	pam_keyinit.so force revoke
session		include		su

@include common-auth
################################################################
auth    required        pam_u2f.so authfile=/etc/Yubico/u2f_keys
################################################################

/etc/pam.d/runuser

#%PAM-1.0
auth		sufficient	pam_rootok.so
session		optional	pam_keyinit.so revoke
session		required	pam_limits.so
session		required	pam_unix.so
@include common-auth
################################################################
auth    required        pam_u2f.so authfile=/etc/Yubico/u2f_keys
################################################################

/etc/pam.d/runuser-l

#%PAM-1.0
auth		include		runuser
session		optional	pam_keyinit.so force revoke
session		optional	pam_systemd.so
session		include		runuser
@include common-auth
################################################################
auth    required        pam_u2f.so authfile=/etc/Yubico/u2f_keys
################################################################

/etc/pam.d/common-auth

#
# /etc/pam.d/common-auth - authentication settings common to all services
#
# This file is included from other service-specific PAM config files,
# and should contain a list of the authentication modules that define
# the central authentication scheme for use on the system
# (e.g., /etc/shadow, LDAP, Kerberos, etc.).  The default is to use the
# traditional Unix authentication mechanisms.
#
# As of pam 1.0.1-6, this file is managed by pam-auth-update by default.
# To take advantage of this, it is recommended that you configure any
# local modules either before or after the default block, and use
# pam-auth-update to manage selection of other modules.  See
# pam-auth-update(8) for details.

# here are the per-package modules (the "Primary" block)
auth	[success=2 default=ignore]	pam_unix.so nullok
auth	[success=1 default=ignore]	pam_sss.so use_first_pass
# here's the fallback if no module succeeds
auth	requisite			pam_deny.so
# prime the stack with a positive return value if there isn't one already;
# this avoids us returning an error just because nothing sets a success code
# since the modules above will each just jump around
auth sufficient pam_u2f.so cue
#auth	required			pam_permit.so
#########################################################################
auth    required        pam_u2f.so      authfile=/etc/Yubico/u2f_keys cue
#########################################################################
# and here are more per-package modules (the "Additional" block)
auth	optional			pam_cap.so 
# end of pam-auth-update config

/etc/pam.d/lightdm

#%PAM-1.0
auth    requisite       pam_nologin.so
auth    sufficient      pam_succeed_if.so user ingroup nopasswdlogin
@include common-auth
-auth    optional        pam_gnome_keyring.so
-auth    optional        pam_kwallet.so
-auth    optional        pam_kwallet5.so
#####################################################################
auth    required        pam_u2f.so      authfile=/etc/Yubico/u2f_keys
#####################################################################
@include common-account
session [success=ok ignore=ignore module_unknown=ignore default=bad] pam_selinux.so close
#session required       pam_loginuid.so
session required        pam_limits.so
@include common-session
session [success=ok ignore=ignore module_unknown=ignore default=bad] pam_selinux.so open
-session optional        pam_gnome_keyring.so auto_start
-session optional        pam_kwallet.so auto_start
-session optional        pam_kwallet5.so auto_start
session required        pam_env.so readenv=1
session required        pam_env.so readenv=1 user_readenv=1 envfile=/etc/default/locale
@include common-password

Location of the Scripts & Files in our System before we can start the execution

Scripts

/home/$USER/Documents/USB/u2f_1__1_libs.sh
/home/$USER/Documents/USB/u2f_1__2_pam.sh
/home/$USER/Documents/USB/functions.sh

Folder with the prepared files

/home/$USER/Documents/USB/u2f/U2F_1

/home/$USER/Documents/USB/u2f/U2F_1/sudo
/home/$USER/Documents/USB/u2f/U2F_1/sudo-i
/home/$USER/Documents/USB/u2f/U2F_1/su
/home/$USER/Documents/USB/u2f/U2F_1/su-l
/home/$USER/Documents/USB/u2f/U2F_1/runuser
/home/$USER/Documents/USB/u2f/U2F_1/runuser-l
/home/$USER/Documents/USB/u2f/U2F_1/common-auth
/home/$USER/Documents/USB/u2f/U2F_1/lightdm

FIDO/U2F Configuration, execution of the Scripts

Now it is time to use the existing scripts.
They will work in case you prepared the files as shown under the headline
Folder with the prepared files” or you have downloaded the files and checked the shasum of the files.

The Scripts and Files of U2F_1

I wanted to provide the scripts below in order to avoid typos.

This is not allowed according to the rules in this forum.

Please skip this part and continue with the headline “Acknowledgement”

[Scripts.zip ]

shasum -a 256 Scripts.zip
e35601443914c864cf96f89fd937c0cd7a2ca072dbd157812e674dd2ba7ca9ad Scripts.zip

![Scripts.zip](file:///home/kay/Downloads/shasum_Scripts.zip.png)

[U2f_1.zip]

shasum -a 256 'U2F_1.zip'
3e8336950b84c912a222d495da3df95f267c26c7f5d9c1bce51706025a461f53 U2F_1.zip

![U2f_1.zip](file:///home/kay/Downloads/shasum_U2f_1.zip.png)

Execution of the Scripts

  1. Open a root shell and keep the shell open !
sudo -i
  1. Open a new terminal and execute the script_1
/home/$USER/Documents/USB/u2f_1__1_libs.sh
  1. Continue with the next script_2
/home/$USER/Documents/USB/u2f_1__2_pam.sh
  1. Test the FIDO/U2F configuration
    Open a new Terminal and write the following command into it
sudo echo Hi!

In case FIDO/U2F was configured in the right way, then you have press the button of the Yubikey in order the get access to the sudo command.
=> now you could close the root shell

In case it was not configured in the right way, then the system will execute it with or without asking for a password.
=> you could use the root shell in order to copy the backup to /etc/pam.d
==> I used this option when I started to write this script.
===> Today the script works without any issues !

Acknowlegment

  • A big thank you to the developer of the FIDO standard,
    this was for sure hard work, hard hard work !
  • In addition I want to thank the developer of the hardware which is secure by design.
    This was also hard work, but fun as well!
  • And of course we have to thank the sponsors who enabled this solution.
    THANK YOU !

Literature

What is U2F?

Ubuntu Linux login guide (U2F)

Das Passwort ist tot – es leben die Passkeys

Zugangssicherheit: 2FA, MFA und FIDO2

How to get the best out of your Yubikey with GPG

Appendix

Some commands in case you are a beginner

    • Open a Terminal ==> press CTRL+ALT+t at the same time
    • $USER is the name of the account who logged into the system.
      We can check this with the command: whoami
      The result of whoami will plot the account name
    • cd ~ change directory, can be used to change to another directory
      cd /home/$USER/Documents
    • ls -alrth ~ will list the content of the current directory
    • mkdir ~ can make a directory
      mkdir /home/$USER/Documents/u2f
      mkdir /home/$USER/Documents/u2f/U2F_1
    • gnome-text-editor filename ~ will open the file filename or
      generate filename in case it is not existing
      gnome-text-editor /home/$USER/Documents/u2f/U2F_1/sudo

u2f_1__1_libs.sh

#!/bin/bash
#
####################################################
# Version 1.1                                      #
# Date 2025-01-20                                  #    
# Kay Schiller                                     #
# Description: Configure Fido/u2f for Ubuntu 24.04 #
#              Part 1: Assign and load libs        #
####################################################
#
FILE_3=u2f_keys
DIR_0="."
DIR_1=""
DIR_2=""
DIR_3="/home/$USER/.config/Yubico"
DIR_4="/etc/pam.d/U2F_0"
DIR_5="/etc/pam.d/U2F_1"
DIR_6="$DIR_0/u2f/U2F_0"
DIR_7="$DIR_0/u2f/U2F_1"
DIR_8="/etc/pam.d/"
DIR_9="/etc/Yubico"
USER_2=$USER
ERROR="0"
#
#------- Functions -------------------------------------------------
#
DFAULTCOLOR="\e[0m"
GREEN="\e[1;92m"
RED="\e[1;91m"
BLUE="\e[1;94m"
#
# Load Functions
#
. /home/$USER/Documents/USB/functions.sh
if [ $? = 0 ]
    then
    echo -e "${GREEN}Functios imported succesfully${DFAULTCOLOR}"
else
    echo -e "${RED}ERROR: Functions were not imported, check path !${DFAULTCOLOR}"
    exit
fi
#------------------------------------------------------------------
echo "DIR_0=$DIR_0"
echo "DIR_1=$DIR_1"
echo "DIR_2=$DIR_2"
echo "DIR_3=$DIR_3"
echo "DIR_4=$DIR_4"
echo "DIR_5=$DIR_5"
echo "DIR_6=$DIR_6"
echo "DIR_7=$DIR_7"
echo "DIR_8=$DIR_8"
echo "DIR_9=$DIR_9"
echo
#
#ETHERNET = 0
#
#------------------------------------------------------------------
echo "Are you connected to the Internet ? [Y || y]"
ETHERNET=No
while read ETHERNET ;
do
	if [[ $ETHERNET == "Y" ]] || [[ $ETHERNET == "y" ]]; then
	echo "YES the cable is connected"
	break
	#
	else
	echo "NO, the cable is not connected ! Please connect it into the LAN"
	fi
done

#------------------------------------------------------------------
#
echo "#######################################"
echo "### sudo apt install Yubikey U2F -y ###"
echo "#######################################"
# Add Yubico Repository
echo "############################################"
echo "### add-apt-repository ppa:yubico/stable ###"
echo "############################################"
sudo add-apt-repository ppa:yubico/stable
STATUS_LAST_CMD $?; STATUS="$?"; if [ "$STATUS" != "0" ]; then ERROR="1"; fi
#
echo "############################################"
echo "### sudo apt install pamu2fcfg           ###"
echo "############################################"
sudo apt purge pamu2fcfg
#STATUS_LAST_CMD $?; STATUS="$?"; if [ "$STATUS" != "0" ]; then ERROR="1"; fi
sudo apt install pamu2fcfg
STATUS_LAST_CMD $?; STATUS="$?"; if [ "$STATUS" != "0" ]; then ERROR="1"; fi
# Install U2F libs for Yubico
echo 
echo "############################################"
echo "### apt install libpam-u2f               ###"
echo "############################################"
sudo apt purge libpam-u2f
#STATUS_LAST_CMD $?; STATUS="$?"; if [ "$STATUS" != "0" ]; then ERROR="1"; fi
sudo apt install libpam-u2f
STATUS_LAST_CMD $?; STATUS="$?"; if [ "$STATUS" != "0" ]; then ERROR="1"; fi
#------------------------------------------------------------------
echo -e "${BLUE}------------------------------------------------------------------${DFAULTCOLOR}"
echo ""
if [ "$ERROR" = "0" ]; then echo -e "${BLUE}Done !${DFAULTCOLOR}"; fi
if [ "$ERROR" != "0" ]; then echo -e "${RED}ERROR ! Something went wrong...${DFAULTCOLOR}"; fi
echo ""
echo -e "${BLUE}------------------------------------------------------------------${DFAULTCOLOR}"

u2f_1__2_pam.sh

#!/bin/bash
#
###############################################
# Version 1.1                                 #
# Date 2025-01-20                             #    
# Kay Schiller                                #
# Description: Configure Fido/U2F for Ubuntu  #
#              Part 2: Copy Files A2B	      #
###############################################
#
FILE_3=u2f_keys
USER_1="$USER"
USER_2="$USER"
DIR_0="`pwd`"
DIR_1=""
DIR_2=""
DIR_3="/home/$USER_1/.config/Yubico"
DIR_4="/etc/pam.d/U2F_0"
DIR_5="/etc/pam.d/U2F_1"
DIR_6="$DIR_0/u2f/U2F_0"
DIR_7="$DIR_0/u2f/U2F_1"
DIR_8="/etc/pam.d/"
DIR_9="/etc/Yubico"
ERROR="0"
#
#------- Functions -------------------------------------------------
#
DFAULTCOLOR="\e[0m"
GREEN="\e[1;92m"
RED="\e[1;91m"
BLUE="\e[1;94m"
#
# Load Functions
#
. /home/$USER/Documents/USB/functions.sh
if [ $? = 0 ]
    then
    echo -e "${GREEN}Functios imported succesfully${DFAULTCOLOR}"
else
    echo -e "${RED}ERROR: Functions were not imported, check path !${DFAULTCOLOR}"
    exit
fi
#------------------------------------------------------------------
echo "DIR_0=$DIR_0"
echo "DIR_1=$DIR_1"
echo "DIR_2=$DIR_2"
echo "DIR_3=$DIR_3"
echo "DIR_4=$DIR_4"
echo "DIR_5=$DIR_5"
echo "DIR_6=$DIR_6"
echo "DIR_7=$DIR_7"
echo "DIR_8=$DIR_8"
echo "DIR_9=$DIR_9"
echo
#
#ETHERNET = 0
#
#------------------------------------------------------------------
echo "Is the Ethernet cable connected ? [N || n]"
while read ETHERNET ;
do
	if [[ $ETHERNET == "N" ]] || [[ $ETHERNET == "n" ]]; then
	echo "No the cable is not connected anymore"
	break
	#
	else
	echo "Yes, the cable is still connected !"
	fi
done
#
#------------------------------------------------------------------
echo
#read -p "Root shell available ? [ENTER]"
echo -e "${BLUE}Root shell available ? ${RED}[ENTER]${DFAULTCOLOR}"; read -p ""
echo
# Generate Yubico Keys for U2F
echo 
echo "###############################################"
echo "### Generate Keys: $DIR_3 ###"
echo "###############################################"
mkdir -p $DIR_3
chmod 770 $DIR_3
#
echo "pamu2fcfg > ~/.config/Yubico/u2f_keys"
pamu2fcfg > "$DIR_3/u2f_keys"
STATUS_LAST_CMD $?; STATUS="$?"; if [ "$STATUS" != "0" ]; then ERROR="1"; fi
#
# Copy Yubikey to /etc/Yubico
#
sudo mkdir -p $DIR_9
echo "cp -f $DIR_3/* $DIR_9/."
echo ""
sudo rsync -av $DIR_3/* $DIR_9/.
STATUS_LAST_CMD $?; STATUS="$?"; if [ "$STATUS" != "0" ]; then ERROR="1"; fi
sudo chmod -Rf 660 $DIR_9
sudo chown -Rf root $DIR_9
sudo chgrp -Rf root $DIR_9
# End of Generate Yubico Keys for U2F
#------------------------------------------------------------------
# cp /etc/pam.d/. /etc/pam.d/U2F_0/.
#
echo
echo "###################################################"
echo "### Configure pam.d => U2F_0: $DIR_8/U2F_0  ###"
echo "###################################################"
if [ ! -d "$DIR_4" ]; then
	sudo mkdir -p "$DIR_4"
	pwd
	sudo rsync -av $DIR_8/. $DIR_4/.
	STATUS_LAST_CMD $?; STATUS="$?"; if [ "$STATUS" != "0" ]; then ERROR="1"; fi
fi
#------------------------------------------------------------------
# cp -f /home/$USER/Documents/USB/u2f/U2F_1/* /etc/pam.d/U2F_1/.
#
echo "######################################################"
echo "### cp -f ${DIR_7}/* ${DIR_5}/.  ###"
echo "######################################################"
#
#DIR_0=/home/$USER/Documents/USB
#DIR_1=
#DIR_2=
#DIR_3=/home/$USER/.config/Yubico
#DIR_4=/etc/pam.d/U2F_0
#DIR_5=/etc/pam.d/U2F_1
#DIR_6=/home/$USER/Documents/USB/u2f/U2F_0
#DIR_7=/home/$USER/Documents/USB/u2f/U2F_1
#DIR_8=/etc/pam.d/
#DIR_9=/etc/Yubico
#
echo 
sudo mkdir -p $DIR_5
echo "DIR_7=$DIR_7"
echo "DIR_5=$DIR_5"
echo
echo "rsync -av $DIR_7/. $DIR_5/."
echo
sudo rsync -av $DIR_7/. $DIR_5/.
STATUS_LAST_CMD $?; STATUS="$?"; if [ "$STATUS" != "0" ]; then ERROR="1"; fi
echo "chmod -Rf 644 $DIR_5"
sudo chmod -Rf 644 $DIR_5
echo "sudo chown -Rf root $DIR_5" 
sudo chown -Rf root $DIR_5
echo "sudo chgrp -Rf root $DIR_5"
sudo chgrp -Rf root $DIR_5
#------------------------------------------------------------------
#
# Configure pam.d using certain files of U2F_1 for: /etc/pam.d/ 
#
#------------------------------------------------------------------
echo "#################################################"
echo "### cp U2F_1 Files to /etc/pam.d  ###"
echo "#################################################"
ls -al $DIR_8
#------------------------------------------------------------------
echo
echo "sudo cp -f $DIR_5/common-auth $DIR_8/."
sudo cp -f "$DIR_5/common-auth" $DIR_8/.
STATUS_LAST_CMD $?; STATUS="$?"; if [ "$STATUS" != "0" ]; then ERROR="1"; fi
echo
#------------------------------------------------------------------
#------------------------------------------------------------------
echo
echo "sudo cp -f $DIR_5/su $DIR_8/."
sudo cp -f "$DIR_5/su" $DIR_8/.
STATUS_LAST_CMD $?; STATUS="$?"; if [ "$STATUS" != "0" ]; then ERROR="1"; fi
echo
#------------------------------------------------------------------
#------------------------------------------------------------------
echo
echo "sudo cp -f $DIR_5/su-l $DIR_8/."
sudo cp -f "$DIR_5/su-l" $DIR_8/.
STATUS_LAST_CMD $?; STATUS="$?"; if [ "$STATUS" != "0" ]; then ERROR="1"; fi
echo
#------------------------------------------------------------------
#------------------------------------------------------------------
echo
echo "sudo cp -f $DIR_5/sudo $DIR_8/."
sudo cp -f "$DIR_5/sudo" $DIR_8/.
STATUS_LAST_CMD $?; STATUS="$?"; if [ "$STATUS" != "0" ]; then ERROR="1"; fi
echo
#------------------------------------------------------------------
#------------------------------------------------------------------
echo
echo "sudo cp -f $DIR_5/sudo-i $DIR_8/."
sudo cp -f "$DIR_5/sudo-i" $DIR_8/.
STATUS_LAST_CMD $?; STATUS="$?"; if [ "$STATUS" != "0" ]; then ERROR="1"; fi
echo
#------------------------------------------------------------------
# Cleaning...
# rm /etc/pam.d/USF_0/U2F_0, rm /etc/pam.d/USF_0/U2F_1 rm /etc/pam.d/USF_1/U2F_0, rm /etc/pam.d/USF_1/U2F_0
echo "rm -rf /etc/pam.d/USF_0/U2F_0"
sudo rm -rf /etc/pam.d/USF_0/U2F_0
echo
#------------------------------------------------------------------ 
echo -e "${BLUE}------------------------------------------------------------------${DFAULTCOLOR}"
echo ""
if [ "$ERROR" = "0" ]; then echo -e "${BLUE}Done !${DFAULTCOLOR}"; fi
if [ "$ERROR" != "0" ]; then echo -e "${RED}ERROR ! Something went wrong...${DFAULTCOLOR}"; fi
echo ""
echo -e "${BLUE}------------------------------------------------------------------${DFAULTCOLOR}"

functions.sh

#!/bin/bash
#
###############################################
# Version 1.9                                 #
# Date 2024-11-05                             #    
# Kay Schiller                                #
# Description: File for functions             #
#              							      #
###############################################
#
#
DFAULTCOLOR="\e[0m"
GREEN="\e[1;92m"
RED="\e[1;91m"
BLUE="\e[1;94m"
#
#
#------- Funkctions -------------------------------------------------
#
STATUS_LAST_CMD()
{
	if [ $1 = 0 ]
	then
    	echo -e "${GREEN}Command succesfully executed${DFAULTCOLOR}"
    	return 0
	else
    	echo -e "${RED}Error, command was not executed !${DFAULTCOLOR}"
    	return 1
	fi
}