Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Working New-YubiKeyFIDO2Credential and examples #125

Merged
merged 2 commits into from
Jan 24, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 5 additions & 15 deletions Docs/Examples/Enroll YubiKey FIDO2 against demo.yubico.com.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# UNDER CONSTRUCTION #
# Enroll YubiKey FIDO2 against demo.yubico.com

### Lets start by creating the information prior to creation.
```pwsh
Expand All @@ -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.

37 changes: 16 additions & 21 deletions Docs/Examples/Enroll YubiKey FIDO2 against login.microsoft.com.md
Original file line number Diff line number Diff line change
@@ -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 = '<your tenant>'
```

### Install things that we need
Expand All @@ -25,38 +24,35 @@ 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)
$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
Expand All @@ -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.
20 changes: 17 additions & 3 deletions Module/Cmdlets/FIDO2/NewFIDO2Credential.cs
Original file line number Diff line number Diff line change
Expand Up @@ -97,11 +97,25 @@ protected override void ProcessRecord()
make.AddHmacSecretExtension(fido2Session.AuthenticatorInfo);
}

//ReadOnlyMemory<byte> 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);
}
}
}
Expand Down
19 changes: 2 additions & 17 deletions Module/types/FIDO2/Challenge.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
{
Expand All @@ -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("=", "");
Expand Down
31 changes: 24 additions & 7 deletions Module/types/FIDO2/CredentialData.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -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("=", "");
}
}
}
Loading