Thursday, January 8, 2015

Yubikey and Windows, Certifiably a pain no longer

At my new job, we are looking to use Yubikeys to store our personal certificates to log into Windows servers as a method of multifactor authentication. After searching for quite some time, it became apparent to me that there is little to no documentation that is easily read (at least for Windows machines), nor did I find any scripts to help the process of importing a certificate.

Here is my script that not only asks the user where the .PFX file is located, but also configures the management key and PUK based on a random number, asks the user for their preferred pin, and includes a function that allows you to reset the device easily. This script still requires Yubikey's PIV tool, found at https://developers.yubico.com/yubico-piv-tool/ . This script has been tested/used on version 0.1.3.

 I am not primarily a programmer, so I apologize for any functionality that could have been done better or faster.


####################################################################################################
#
# Script to help out some of the management of YubiKeys in windows using powershell
# Bryan Loveless (bryan.loveless@gmail.com)
# January 2015
# http://actualreverend.blogspot.com/
#
# requires Yubikey PIV tool to be downloaded somewhere (0.1.3 was the verison tested)
# requires PFX to be exported in a pfx file, and not already assigned to a smartcard in use
#          PFX must be protected with a password
#
# User must not use the pin numbers 12345 or 4711, as these are default and Reset-yubikey will fail
#
###################################################################################################

# Show an Open File Dialog and return the file selected by the user. 
#Borrowed from http://blog.danskingdom.com/powershell-multi-line-input-box-dialog-open-file-dialog-folder-browser-dialog-input-box-and-message-box/ 
function Read-OpenFileDialog([string]$WindowTitle, [string]$InitialDirectory, [string]$Filter = "All files (*.*)|*.*", [switch]$AllowMultiSelect)
{ 
    Add-Type -AssemblyName System.Windows.Forms
    $openFileDialog = New-Object System.Windows.Forms.OpenFileDialog
    $openFileDialog.Title = $WindowTitle
    if (![string]::IsNullOrWhiteSpace($InitialDirectory)) { $openFileDialog.InitialDirectory = $InitialDirectory }
    $openFileDialog.Filter = $Filter
    if ($AllowMultiSelect) { $openFileDialog.MultiSelect = $true }
    $openFileDialog.ShowHelp = $true    # Without this line the ShowDialog() function may hang depending on system configuration and running from console vs. ISE.
    $openFileDialog.ShowDialog() > $null
    if ($AllowMultiSelect) { return $openFileDialog.Filenames } else { return $openFileDialog.Filename }
}

# Show an Open Folder Dialog and return the directory selected by the user.
#Borrowed from http://blog.danskingdom.com/powershell-multi-line-input-box-dialog-open-file-dialog-folder-browser-dialog-input-box-and-message-box/
function Read-FolderBrowserDialog([string]$Message, [string]$InitialDirectory, [switch]$NoNewFolderButton)
{
    $browseForFolderOptions = 0
    if ($NoNewFolderButton) { $browseForFolderOptions += 512 }
 
    $app = New-Object -ComObject Shell.Application
    $folder = $app.BrowseForFolder(0, $Message, $browseForFolderOptions, $InitialDirectory)
    if ($folder) { $selectedDirectory = $folder.Self.Path } else { $selectedDirectory = '' }
    [System.Runtime.Interopservices.Marshal]::ReleaseComObject($app) > $null
    return $selectedDirectory
}

# reset the device easily
function Reset-Yubikey{
write-host "Are you sure you want to reset the yubikey?  Ctrl-C to stop now!"
pause

#prompt user where the yubico-piv executable is:
$yubiPIVExecutable = Read-OpenFileDialog -InitialDirectory 'C:\Program Files (x86)' -WindowTitle "Select yubico-piv-tool.exe file" -filter "yubico-piv-exe files (yubico-piv-tool.exe)|yubico-piv-tool.exe"

# lock the pin an puk numbers on device
& "$yubiPIVExecutable" -a verify-pin -P 4711
& "$yubiPIVExecutable" -a verify-pin -P 4711
& "$yubiPIVExecutable" -a verify-pin -P 4711
& "$yubiPIVExecutable" -a verify-pin -P 4711
& "$yubiPIVExecutable" -a change-puk -P 4711 -N 67567
& "$yubiPIVExecutable" -a change-puk -P 4711 -N 67567
& "$yubiPIVExecutable" -a change-puk -P 4711 -N 67567
& "$yubiPIVExecutable" -a change-puk -P 4711 -N 67567
& "$yubiPIVExecutable" -a verify-pin -P 4711
& "$yubiPIVExecutable" -a verify-pin -P 4711
& "$yubiPIVExecutable" -a verify-pin -P 4711
& "$yubiPIVExecutable" -a verify-pin -P 4711
& "$yubiPIVExecutable" -a change-puk -P 4711 -N 67567
& "$yubiPIVExecutable" -a change-puk -P 4711 -N 67567
& "$yubiPIVExecutable" -a change-puk -P 4711 -N 67567
& "$yubiPIVExecutable" -a change-puk -P 4711 -N 67567

& "$yubiPIVExecutable" -a reset
}

# password generator, using it to get new managment key, borrowed from http://blog.morg.nl/2014/01/generate-a-random-strong-password-in-powershell/
function Generate-Password() {
    Param (
    [int]$length = 8,   
    [bool] $includeLowercaseLetters = $true,
    [bool] $includeUppercaseLetters = $true,
    [bool] $includeNumbers = $true,
    [bool] $includeSpecialChars = $false,
    [bool] $noSimilarCharacters = $true
    )
 
    <#
    (c) Morgan de Jonge CC BY SA
    Generates a random password. you're able to specify:
    - The desired password length (minimum = 4)
    - Whether or not to use lowercase characters
    - Whether or not to use uppercase characters
    - Whether or not to use numbers
    - Whether or not to use special characters
    - Whether or not to avoid using similar characters ( e.g. i, l, o, 1, 0, I)
    #>
 
    # Validate params
    if($length -lt 4) {
        $exception = New-Object Exception "The minimum password length is 4"
        Throw $exception
    }
    if ($includeLowercaseLetters -eq $false -and
            $includeUppercaseLetters -eq $false -and
            $includeNumbers -eq $false -and
            $includeSpecialChars -eq $false) {
        $exception = New-Object Exception "At least one set of included characters must be specified"
        Throw $exception
    }
 
    #Available characters
    $CharsToSkip = [char]"i", [char]"l", [char]"o", [char]"1", [char]"0", [char]"I"
    $AvailableCharsForPassword = $null;
    $uppercaseChars = $null
    for($a = 65; $a -le 90; $a++) { if($noSimilarCharacters -eq $false -or [char][byte]$a -notin $CharsToSkip) {$uppercaseChars += ,[char][byte]$a }}
    $lowercaseChars = $null
    for($a = 97; $a -le 122; $a++) { if($noSimilarCharacters -eq $false -or [char][byte]$a -notin $CharsToSkip) {$lowercaseChars += ,[char][byte]$a }}
    $digitChars = $null
    for($a = 48; $a -le 57; $a++) { if($noSimilarCharacters -eq $false -or [char][byte]$a -notin $CharsToSkip) {$digitChars += ,[char][byte]$a }}
    $specialChars = $null
    $specialChars += [char]"=", [char]"+", [char]"_", [char]"?", [char]"!", [char]"-", [char]"#", [char]"$", [char]"*", [char]"&", [char]"@"
 
    $TemplateLetters = $null
    if($includeLowercaseLetters) { $TemplateLetters += "L" }
    if($includeUppercaseLetters) { $TemplateLetters += "U" }
    if($includeNumbers) { $TemplateLetters += "N" }
    if($includeSpecialChars) { $TemplateLetters += "S" }
    $PasswordTemplate = @()
    # Set password template, to ensure that required chars are included
    do {  
        $PasswordTemplate.Clear()
        for($loop = 1; $loop -le $length; $loop++) {
            $PasswordTemplate += $TemplateLetters.Substring((Get-Random -Maximum $TemplateLetters.Length),1)
        }
    }
    while ((
        (($includeLowercaseLetters -eq $false) -or ($PasswordTemplate -contains "L")) -and
        (($includeUppercaseLetters -eq $false) -or ($PasswordTemplate -contains "U")) -and
        (($includeNumbers -eq $false) -or ($PasswordTemplate -contains "N")) -and
        (($includeSpecialChars -eq $false) -or ($PasswordTemplate -contains "S"))) -eq $false
    )
    #$PasswordTemplate now contains an array with at least one of each included character type (uppercase, lowercase, number and/or special)
 
    foreach($char in $PasswordTemplate) {
        switch ($char) {
            L { $Password += $lowercaseChars | Get-Random }
            U { $Password += $uppercaseChars | Get-Random }
            N { $Password += $digitChars | Get-Random }
            S { $Password += $specialChars | Get-Random }
        }
    }
 
    return $Password
}

#function configure-yubikey{

#change key, pin, and puk from default setting:

#generate a random number and store it in keepass as USER-YUBIKEY-ManagementKEY must be 16 characters (010203040506070801020304050607080102030405060708 is default)
#$randomkey = get-random -minimum 999999999999999 -maximum 9999999999999999 
$randomkey = Generate-Password 48 $false $false $true $false $false
write-host "create a keepass entry named yourusername-YUBIKEY-ManagmentKEY and enter $randomkey in for the value" -ForeGroundColor Red
pause

#generate a random number and store it in keepass as USER-YUBIKEY-PUK, must be 8 characters
#$randompuk = get-random -minimum 9999999 -maximum 99999999 
$randompuk = Generate-Password 8 $false $false $true $false $false
write-host "create a keepass entry named yourusername-YUBIKEY-PUK and enter $randompuk in for the value" -ForeGroundColor Red
pause

#prompt user with protected pin entering box:
$pinencrypted = read-host -prompt "What do you want your pin number to be?" -AsSecureString

#extract plain text pin number from encrypted varible above
$BSTR = `
    [System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($pinencrypted)
$pin = [System.Runtime.InteropServices.Marshal]::PtrToStringAuto($BSTR)   

#prompt user where the yubico-piv executable is:
$yubiPIVExecutable = Read-OpenFileDialog -InitialDirectory 'C:\Program Files (x86)' -WindowTitle "Select yubico-piv-tool.exe file" -filter "yubico-piv-exe files (yubico-piv-tool.exe)|yubico-piv-tool.exe"

#ask user where PFX file is
$pfxfile = Read-OpenFileDialog -InitialDirectory c:\ -WindowTitle "Select PFX file" -filter "PFX files (*.pfx)|*.pfx"

#prompt user for PFX password:
$PFXpasswordencrypted = read-host -prompt "What is your PFX password?" -AsSecureString

#extract plain text pin number from encrypted varible above
$BSTR = `
    [System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($PFXpasswordencrypted)
$PFXpassword = [System.Runtime.InteropServices.Marshal]::PtrToStringAuto($BSTR) 

#changing pin from default(none) to random number generated above:
write-host "setting the yubikey managment key now"
& "$yubiPIVExecutable" -a set-mgm-key -n $randomkey

#changing PIN from default to user's seleciton
write-host "Changing the PIN number now"
& "$yubiPIVExecutable" -k $randomkey -a change-pin -P 123456 -N $pin

#Changing pin from default to random number generated above:
write-host "Changing the PUK number now"
& "$yubiPIVExecutable" -k $randomkey -a change-puk -P 12345678 -N $randompuk



# imports the key
& "$yubiPIVExecutable" --slot 9a --input=$pfxfile --password=$PFXpassword --key-format=PKCS12 --action=set-chuid --action=import-key --action=import-certificate -v2 --key=$randomkey


#}

3 comments:

  1. The content shared in this article is impressive and I would like to read this impressive posting. Thanks for sharing it. ddlforall.blogspot.com

    ReplyDelete
  2. Hi, we at Yubico just released a GUI tool for YubiKey PIV management. It might be helpful: https://developers.yubico.com/yubikey-piv-manager/

    ReplyDelete