Skip to content

Commit

Permalink
Support RequestedAlgorithms (#130)
Browse files Browse the repository at this point in the history
* Add **RequestedAlgorithms** parameter to **New-YubiKeyFIDO2Credential**
* Add cookbook entry for Entroll-FIDO2-On-Behalf-Of-Entra-ID.ps1
  • Loading branch information
virot authored Jan 25, 2025
1 parent df2e5fd commit 5c2acdf
Show file tree
Hide file tree
Showing 2 changed files with 146 additions and 4 deletions.
136 changes: 136 additions & 0 deletions Docs/Cookbook/Enroll-FIDO2-On-Behalf-Of-Entra-ID.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
<#
.SYNOPSIS
Enroll a YubiKey FIDO2 credential for a User
.DESCRIPTION
This scripts uses powershellYK and MS Graph to onboard a user with a YubiKey without the need to interaction other than pushing the touch.
This script also allows for delayed Attestion, where the YubiKey can be transported without being attested in EntraId, making it unusable.
.PARAMETER UserID
The ID or UPN of the user that we are enrolling for
.EXAMPLE
Enroll-FIDO2-On-Behalf-Of-Endra-ID.ps1 -UserID [email protected]
Do a complete enrollment for the user specified
.EXAMPLE
Enroll-FIDO2-On-Behalf-Of-Endra-ID.ps1 -UserID [email protected] -DisplayName "Dont loose this one"
Do a complete enrollment for the user specified and name the FIDO2 credential in Entra ID
.EXAMPLE
Enroll-FIDO2-On-Behalf-Of-Endra-ID.ps1 -UserID [email protected] -AttestionFileOut usersave.xml
Create a fido2 credential but do not attest it, rather save it for a later attest.
.EXAMPLE
Enroll-FIDO2-On-Behalf-Of-Endra-ID.ps1 -UserID [email protected] -AttestionFileIn usersave.xml
Attest the earler created credential. This will make it functional.
.LINK
https://github.com/virot/powershellYK/docs/Cookbook/Enroll-FIDO2-On-Behalf-Of-Endra-ID.ps1
.LINK
https://github.com/virot/powershellYK
#>

#Requires -PSEdition Core
#Requires -Modules powershellYK, Microsoft.Graph.Authentication
#Requires -RunAsAdministrator
[CmdletBinding(DefaultParameterSetName = 'Complete')]
param
(
[Parameter(Mandatory=$True,HelpMessage = "The ID or UPN of the user that we are enrolling the ")]
[string]
$UserID,
[Parameter(Mandatory=$False,HelpMessage = "Name of the FIDO2 Key in Entra ID")]
[string]
[ValidateLength(3,30)]
$DisplayName = "$((Get-YubiKey).PrettyName) $((Get-YubiKey).SerialNumber)",
[Parameter(Mandatory=$False,HelpMessage = "The amount of minutes the Challange will be valid for, this is the time to complete the enrollment.", ParameterSetName="CreateCredential")]
[int]
$ChallengeTimeoutInMinutes=5,
[Parameter(Mandatory=$False,HelpMessage = "File to write the attestion data into", ParameterSetName="CreateCredential")]
[string]
$AttestionFileOut,
[ValidateScript({if (-not (Test-Path -Path $_ -PathType Leaf)){throw "File $_ does not exist."}else{$true}})]
[Parameter(Mandatory=$False,HelpMessage = "What action should be completed", ParameterSetName="AttestCredential")]
[string]
$AttestionFileIn
)
Begin
{
#
if (
(Get-MgContext) -eq $Null -or # Verify that we are authenticated to MSGraph
(-not (Microsoft.Graph.Authentication\Get-MgContext).Scopes.Contains('UserAuthenticationMethod.ReadWrite.All')) # Verify that we have the require permissions
)
{
throw "Not connected with correct permissions to MSGraph, run 'Connect-MgGraph -Scopes UserAuthenticationMethod.ReadWrite.All'"
}
if ((Microsoft.Graph.Authentication\Get-MgContext).Account -eq $UserID) # This is checking for same user as authenticated to Graph
{
throw "Microsoft does not allow Enrollment On Behalf Of same user."
}
if (
(Get-YubiKey) -eq $Null -or # No YubiKey inserted
(Get-YubiKeyFIDO2).Options['clientPin'] -eq $False # No PIN set
)
{
throw "Make sure to insert a YubiKey with a set FIDO2 PIN"
}
}
Process
{
Write-Debug -Message "Running with ParameterSetName=$($PSCmdlet.ParameterSetName)"
if ($PSCmdlet.ParameterSetName -in @('CreateCredential','Complete'))
{
Write-Debug -Message "Starting to create parameter in Entra ID"
$FIDO2Options = Invoke-MgGraphRequest -Method "GET" -Uri "/beta/users/$userid/authentication/fido2Methods/creationOptions(challengeTimeoutInMinutes=$($challengeTimeoutInMinutes ?? "5"))"

# prepair the resonse to send to the YubiKey.
$challenge = [powershellYK.FIDO2.Challenge]::new($FIDO2Options.publicKey.challenge)
$userEntity = [Yubico.YubiKey.Fido2.UserEntity]::new([System.Convert]::FromBase64String("$($FIDO2Options.publicKey.user.id -replace "-","+" -replace "_","/")"))
$userEntity.Name = $FIDO2Options.publicKey.user.name
$userentity.DisplayName = $FIDO2Options.publicKey.user.displayName
$RelyingParty = [Yubico.YubiKey.Fido2.RelyingParty]::new($FIDO2Options.publicKey.rp.id)
$Algorithms = $FIDO2Options.publicKey.pubKeyCredParams|Select -Exp Alg

# Inform user to press the YubiKey touch..
Write-Host -Message "Please touch the YubiKey when it starts blinking..." -ForegroundColor Yellow

# The actual creation of the credential
$FIDO2Response = New-YubiKeyFIDO2Credential -RelyingParty $RelyingParty -Discoverable $true -Challenge $challenge -UserEntity $userEntity -RequestedAlgorithms $Algorithms
$ReturnJSON = @{
'displayName' = $DisplayName.Trim();
'publicKeyCredential' = @{
'id' = $FIDO2Response.GetBase64UrlSafeCredentialID();
'response' = @{
'clientDataJSON' = $FIDO2Response.GetBase64clientDataJSON();
'attestationObject' = $FIDO2Response.GetBase64AttestationObject()
}
}
}
}
if ($PSCmdlet.ParameterSetName -eq 'CreateCredential')
{
Export-Clixml -Path $AttestionFileOut -InputObject $ReturnJSON
}
if ($PSCmdlet.ParameterSetName -eq 'AttestCredential')
{
$ReturnJSON = Import-Clixml -Path $AttestionFileIn
}
if ($PSCmdlet.ParameterSetName -in @('AttestCredential','Complete'))
{
# End of $return
if ($PSBoundParameters.ContainsKey('DisplayName'))
{
$ReturnJSON.displayName = $DisplayName.Trim();
}
try
{
Invoke-MgGraphRequest -Method "POST" -Uri "https://graph.microsoft.com/beta/users/$userid/authentication/fido2Methods" -OutputType "Json" -ContentType 'application/json' -Body ($ReturnJSON| ConvertTo-JSON -Depth 4) | ConvertFrom-Json
Write-Host -ForegroundColor Green -Message "YubiKey onboarded with diplayname: $($DisplayName.Trim())"
}
Catch
{
Write-Error -Message "YubiKey failed to onboard. Attestion step failed with message $($_.ErrorDetails)"
}
}
}
14 changes: 10 additions & 4 deletions Module/Cmdlets/FIDO2/NewFIDO2Credential.cs
Original file line number Diff line number Diff line change
@@ -1,13 +1,11 @@
using System.Management.Automation;
using Yubico.YubiKey;
using Yubico.YubiKey.Fido2;
using System.Linq;
using powershellYK.support;
using System.Formats.Cbor;
using static Microsoft.ApplicationInsights.MetricDimensionNames.TelemetryContext;
using Yubico.YubiKey.Cryptography;
using powershellYK.FIDO2;


namespace powershellYK.Cmdlets.Fido
{
[Cmdlet(VerbsCommon.New, "YubiKeyFIDO2Credential", SupportsShouldProcess = true, ConfirmImpact = ConfirmImpact.High)]
Expand Down Expand Up @@ -35,6 +33,8 @@ public class NewYubikeyFIDO2CredentialCmdlet : PSCmdlet
[Parameter(Mandatory = true, ValueFromPipeline = false, HelpMessage = "Supply the user entity in complete form.", ParameterSetName = "UserEntity-HostData")]
[Parameter(Mandatory = true, ValueFromPipeline = false, HelpMessage = "Supply the user entity in complete form.", ParameterSetName = "UserEntity-RelyingParty")]
public UserEntity? UserEntity { get; set; } = new UserEntity(new byte[] { 0, 0 });
[Parameter(Mandatory = false, ValueFromPipeline = false, HelpMessage = "Algorithms the RelyingParty accepts")]
public List<Yubico.YubiKey.Fido2.Cose.CoseAlgorithmIdentifier> RequestedAlgorithms = new List<Yubico.YubiKey.Fido2.Cose.CoseAlgorithmIdentifier> { Yubico.YubiKey.Fido2.Cose.CoseAlgorithmIdentifier.ES256 };
protected override void BeginProcessing()
{
// If no FIDO2 PIN exists, we need to connect to the FIDO2 application
Expand Down Expand Up @@ -62,7 +62,6 @@ protected override void BeginProcessing()

protected override void ProcessRecord()
{
WriteWarning("This cmdlet is still in development and may not work as expected.");
using (var fido2Session = new Fido2Session((YubiKeyDevice)YubiKeyModule._yubikey!))
{
fido2Session.KeyCollector = YubiKeyModule._KeyCollector.YKKeyCollectorDelegate;
Expand Down Expand Up @@ -116,6 +115,13 @@ protected override void ProcessRecord()
_ = digester.TransformFinalBlock(clientDataBytes, 0, clientDataBytes.Length);
make.ClientDataHash = digester.Hash!.AsMemory();

WriteDebug("Adding requested Algorithms");
foreach (var alg in RequestedAlgorithms)
{
WriteDebug($"`tAdding {alg.ToString()}");
make.AddAlgorithm("public-key", alg);
}

WriteDebug($"Sending new credential data into SDK");
MakeCredentialData returnvalue = fido2Session.MakeCredential(make);

Expand Down

0 comments on commit 5c2acdf

Please sign in to comment.