diff --git a/Docs/Examples/Enroll YubiKey FIDO2 against demo.yubico.com.md b/Docs/Examples/Enroll YubiKey FIDO2 against demo.yubico.com.md index 3025974..ad2974d 100644 --- a/Docs/Examples/Enroll YubiKey FIDO2 against demo.yubico.com.md +++ b/Docs/Examples/Enroll YubiKey FIDO2 against demo.yubico.com.md @@ -1,4 +1,4 @@ -# UNDER CONSTRUCTION # +# Enroll YubiKey FIDO2 against demo.yubico.com ### Lets start by creating the information prior to creation. ```pwsh @@ -25,31 +25,21 @@ $registerBeginReturn = Invoke-RestMethod -Method Post -WebSession $session -Uri $userEntity = [Yubico.YubiKey.Fido2.UserEntity]::new([system.convert]::FromBase64String($registerBeginReturn.data.publicKey.user.id.'$base64')) $userEntity.Name = $registerBeginReturn.data.publicKey.user.name $userentity.DisplayName = $registerBeginReturn.data.publicKey.user.displayname -$out = New-YubiKeyFIDO2Credential -RelyingPartyID $registerBeginReturn.data.publicKey.rp.id -RelyingPartyName $registerBeginReturn.data.publicKey.rp.name -Discoverable $true -Challange $registerBeginReturn.data.publicKey.challenge.'$base64' -UserEntity $userEntity +$out = New-YubiKeyFIDO2Credential -RelyingPartyID $registerBeginReturn.data.publicKey.rp.id -RelyingPartyName $registerBeginReturn.data.publicKey.rp.name -Discoverable $true -Challenge $registerBeginReturn.data.publicKey.challenge.'$base64' -UserEntity $userEntity ``` ### Return the attestion data etc to the site This Data is lost by the SDK so we need to build it backup. Wonder if this is where it breaks. ```pwsh -$a = [powershellYK.FIDO2.CredentialData]::new($out) -#[system.convert]::ToBase64String($a.w3cEncoded()) -$clientDataJSON = @{ - 'type' = 'webauthn.create'; - 'challenge' = $registerBeginReturn.data.publicKey.challenge.'$base64' -replace '\+', '-' -replace '/', '_' -replace '=',''; - 'origin' = "https://demo.yubico.com"; - 'crossOrigin' = $false -} | ConvertTo-JSON -Compress - -# Lets send stuff back to demo.yubico.com to enable the security key $registerFinishBody = @{ 'requestId' = $registerBeginReturn.data.requestId; 'attestation' = @{ - 'attestationObject' = @{'$base64'=[system.convert]::ToBase64String($a.w3cEncoded())}; - 'clientDataJSON' = @{'$base64'=[system.convert]::ToBase64String([System.Text.Encoding]::UTF8.GetBytes($clientDataJSON))} + 'attestationObject' = @{'$base64'=$out.GetBase64AttestationObject()}; + 'clientDataJSON' = @{'$base64'=$out.GetBase64clientDataJSON()} } } | ConvertTo-JSON -Compress $registerFinishReturn = Invoke-RestMethod -Method Post -WebSession $session -Uri "https://demo.yubico.com/api/v1/user/$($userCreation.data.uuid)/webauthn/register-finish" -Body $registerFinishBody -ContentType 'application/json' ``` -Here we should be done, but something is broken somewhere.. +Now you can surf into [Yubikey Demo Site](https://demo.yubico.com/) and logon with your onboarded YubiKey. diff --git a/Docs/Examples/Enroll YubiKey FIDO2 against login.microsoft.com.md b/Docs/Examples/Enroll YubiKey FIDO2 against login.microsoft.com.md index 633e664..58aefba 100644 --- a/Docs/Examples/Enroll YubiKey FIDO2 against login.microsoft.com.md +++ b/Docs/Examples/Enroll YubiKey FIDO2 against login.microsoft.com.md @@ -1,18 +1,17 @@ -# UNDER CONSTRUCTION -# BROKEN +# Enroll YubiKey FIDO2 against login.microsoft.com +To simplify I am using **DSInternals.Passkeys** as **Invoke-MgBetaCreationUserAuthenticationFido2MethodOption** is broken. ### Install prerequisite ```pwsh Install-ModuLe PowershellYK Install-ModuLe Microsoft.Graph.Authentication Install-Module DSInternals.Passkeys - ``` ### Lets start by creating the information prior to creation. ```pwsh -$userid = 'test.yubikey@toriv.com' -$tenent = 'toriv.onmicrosoft.com' +$userid = 'test.yubikey@virot.eu' +$tenent = '' ``` ### Install things that we need @@ -25,10 +24,15 @@ Import-Module DSInternals.Passkeys ### Lets connect to Microsoft Graph ```pwsh Connect-MgGraph -Scopes UserAuthenticationMethod.ReadWrite.All -TenantId $tenant -NoWelcome +``` + +### Lets get the Challenge data from Microsoft Azure +```pwsh $RegistrationOptions = $RegistrationOptions = Get-PasskeyRegistrationOptions -UserId $userid ``` ### Lets begin registering the YubiKey +Lets start with building the Userentity, Challenge and RelayingParty. This can be done or we can sned the different parts to the Cmdlets. ```pwsh $challenge = [powershellYK.FIDO2.Challenge]::new($RegistrationOptions.PublicKeyOptions.Challenge) $userEntity = [Yubico.YubiKey.Fido2.UserEntity]::new($RegistrationOptions.PublicKeyOptions.User.Id) @@ -36,27 +40,19 @@ $userEntity.Name = $RegistrationOptions.PublicKeyOptions.User.Name $userentity.DisplayName = $RegistrationOptions.PublicKeyOptions.User.DisplayName $RelyingParty = [Yubico.YubiKey.Fido2.RelyingParty]::new($RegistrationOptions.PublicKeyOptions.RelyingParty.id) -$passkey = New-YubiKeyFIDO2Credential -RelyingParty $RelyingParty -Discoverable $true -challenge $challenge -UserEntity $userEntity +$passkey = New-YubiKeyFIDO2Credential -RelyingParty $RelyingParty -Discoverable $true -Challenge $challenge -UserEntity $userEntity ``` -### Return the attestion data etc to the site -This Data is lost by the SDK so we need to build it backup. Wonder if this is where it breaks. +### Return the data from the YubiKey to Microsoft so it will work +When we have enrolled the FIDO credential we need to send over some data to Azure ```pwsh -$a = [powershellYK.FIDO2.CredentialData]::new($passkey) -$clientDataJSON = @{ - 'type' = 'webauthn.create'; - 'challenge' = $challenge.UrlEncode(); - 'origin' = "https://login.microsoft.com"; - 'crossOrigin' = $false -} | ConvertTo-JSON -Compress - $return = @{ 'displayName' = 'Name of yubikey'; 'publicKeyCredential' = @{ - 'id' = [convert]::ToBase64String($passkey.AuthenticatorData.CredentialId.id.ToArray()) -replace '\+', '-' -replace '/', '_' -replace '=',''; + 'id' = $passkey.GetBase64UrlSafeCredentialID(); 'response' = @{ - 'clientDataJSON' = [system.convert]::ToBase64String([System.Text.Encoding]::UTF8.GetBytes($clientDataJSON)); - 'attestationObject' = [system.convert]::ToBase64String($a.w3cEncoded()) + 'clientDataJSON' = $passkey.GetBase64clientDataJSON(); + 'attestationObject' = $passkey.GetBase64AttestationObject() } } }| ConvertTo-JSON -Depth 4 @@ -65,5 +61,4 @@ $URI = "https://graph.microsoft.com/beta/users/$userid/authentication/fido2Metho $response = Invoke-MgGraphRequest -Method "POST" -Uri $URI -OutputType "Json" -ContentType 'application/json' -Body $return ``` -Here we should be done, but something is broken somewhere.. - +Your YubiKey is now onboarded. \ No newline at end of file diff --git a/Module/Cmdlets/FIDO2/NewFIDO2Credential.cs b/Module/Cmdlets/FIDO2/NewFIDO2Credential.cs index a0a7a2c..58bcf0d 100644 --- a/Module/Cmdlets/FIDO2/NewFIDO2Credential.cs +++ b/Module/Cmdlets/FIDO2/NewFIDO2Credential.cs @@ -97,11 +97,25 @@ protected override void ProcessRecord() make.AddHmacSecretExtension(fido2Session.AuthenticatorInfo); } - //ReadOnlyMemory clientDataHash = Challange.ToByte().AsMemory(); - make.ClientDataHash = Challenge.CalculateSHA256().AsMemory(); + // Generate ClientDataHash + + var clientData = new + { + type = "webauthn.create", + origin = $"https://{RelyingParty.Id}", + challenge = Challenge.Base64UrlEncode(), + }; + + var clientDataJSON = System.Text.Json.JsonSerializer.Serialize(clientData); + var clientDataBytes = System.Text.Encoding.UTF8.GetBytes(clientDataJSON); + var digester = CryptographyProviders.Sha256Creator(); + _ = digester.TransformFinalBlock(clientDataBytes, 0, clientDataBytes.Length); + make.ClientDataHash = digester.Hash!.AsMemory(); MakeCredentialData returnvalue = fido2Session.MakeCredential(make); - WriteObject(returnvalue); + + var credData = new CredentialData(returnvalue, clientDataJSON, UserEntity, RelyingParty); + WriteObject(credData); } } } diff --git a/Module/types/FIDO2/Challenge.cs b/Module/types/FIDO2/Challenge.cs index 7037a1d..920a22c 100644 --- a/Module/types/FIDO2/Challenge.cs +++ b/Module/types/FIDO2/Challenge.cs @@ -10,16 +10,7 @@ public class Challenge public Challenge(string value) { - // If the length is 32, we assume it's a hex string - if (value.Length == 32 && value.All(c => (c >= '0' && c <= '9') || (c >= 'A' && c <= 'F') || (c >= 'a' && c <= 'f'))) - { - this._challenge = HexConverter.StringToByteArray(value); - } - // else we assume it's a base64 string - else - { - this._challenge = System.Convert.FromBase64String(value); - } + this._challenge = System.Convert.FromBase64String(value); } public Challenge(byte[] value) { @@ -42,13 +33,7 @@ public byte[] ToByte() { return _challenge; } - public byte[] CalculateSHA256() - { - var digester = CryptographyProviders.Sha256Creator(); - _ = digester.TransformFinalBlock(_challenge, 0, _challenge.Length); - return digester.Hash!; - } - public string UrlEncode() + public string Base64UrlEncode() { var base64 = Convert.ToBase64String(_challenge); var urlEncoded = base64.Replace('+', '-').Replace('/', '_').Replace("=", ""); diff --git a/Module/types/FIDO2/CredentialData.cs b/Module/types/FIDO2/CredentialData.cs index c7ddc9f..76a3b44 100644 --- a/Module/types/FIDO2/CredentialData.cs +++ b/Module/types/FIDO2/CredentialData.cs @@ -9,22 +9,32 @@ namespace powershellYK.FIDO2 public class CredentialData { public MakeCredentialData MakeCredentialData { get { return this._makeCredentialData; } } + public string ClientDataJSON { get { return this._clientDataJSON; } } + + private readonly string _clientDataJSON; private readonly MakeCredentialData _makeCredentialData; + private readonly UserEntity _userEntity; + private readonly RelyingParty _relyingParty; - public CredentialData(MakeCredentialData MakeCredentialData) + public CredentialData(MakeCredentialData MakeCredentialData, string clientData, UserEntity userEntity, RelyingParty relyingParty) { this._makeCredentialData = MakeCredentialData; + this._clientDataJSON = clientData; + this._userEntity = userEntity; + this._relyingParty = relyingParty; } + public override string ToString() { - return this.ToString(null); + var username = _userEntity.DisplayName ?? _userEntity.Name ?? "Unknown"; + var site = _relyingParty.Name ?? _relyingParty.Id ?? "Unknown"; + return $"Credential for {username} on {site}"; } - public string ToString(string? format = "x") + public string GetBase64clientDataJSON() { - return ""; + return Convert.ToBase64String(Encoding.UTF8.GetBytes(this._clientDataJSON)); } - - public byte[] w3cEncoded() + public string GetBase64AttestationObject() { var writer = new CborWriter(); writer.WriteStartMap(3); @@ -35,7 +45,14 @@ public byte[] w3cEncoded() writer.WriteTextString("authData"); writer.WriteByteString(_makeCredentialData.AuthenticatorData.EncodedAuthenticatorData.Span); writer.WriteEndMap(); - return writer.Encode(); + var cborEncoded = writer.Encode(); + return Convert.ToBase64String(cborEncoded); + } + + public string GetBase64UrlSafeCredentialID() + { + // Return the CredentialID as a base64url string + return Convert.ToBase64String(this._makeCredentialData.AuthenticatorData.CredentialId!.Id.ToArray()).Replace('+', '-').Replace('/', '_').Replace("=", ""); } } }