Skip to content

Commit

Permalink
Allow attestion of certificate and updated workflow
Browse files Browse the repository at this point in the history
Update the workflow to do basic Pester
Update Confirm-YubiKeyAttestion to allow for Certificate with embedded AttestionData.
  • Loading branch information
virot authored Dec 28, 2024
1 parent dc44880 commit 3f019b1
Show file tree
Hide file tree
Showing 5 changed files with 152 additions and 72 deletions.
27 changes: 0 additions & 27 deletions .github/workflows/compile.yaml

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# dotnet-format.yaml
name: dotnet Format
# main.yaml
name: main

on:
pull_request:
Expand Down Expand Up @@ -28,3 +28,26 @@ jobs:
echo "There are uncommitted changes after running dotnet format."
exit 1
fi
- name: Install dependencies
run: dotnet restore

- name: Build
run: dotnet build --no-restore -c Release

- name: Test
run: dotnet test --no-restore

- name: Set up PowerShell 7
uses: actions/setup-pwsh@v2
with:
pwsh-version: '7.x'

- name: Run Pester Tests
shell: pwsh
run: |
# Import the module
Import-Module .\Module\bin\module\Release\net8.0\powershellYK.psd1
# Run Pester tests
IInvoke-Pester -TagFilter 'Without-Yubikey' -Output Detailed
20 changes: 20 additions & 0 deletions Docs/Commands/Confirm-YubikeyAttestion.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,11 @@ Confirm-YubikeyAttestion -AttestionCertificate <PSObject> -IntermediateCertifica
[<CommonParameters>]
```

### CertificateIncludingAttestion
```
Confirm-YubikeyAttestion -CertificateIncludingAttestion <PSObject> [<CommonParameters>]
```

## DESCRIPTION
This cmdlet allows for verification of the attestion of YubiKeys. This can be used both to verify the attestion certificate and Certificate Request with and without built in attestion.

Expand Down Expand Up @@ -92,6 +97,21 @@ Accept pipeline input: False
Accept wildcard characters: False
```
### -CertificateIncludingAttestion
CertificateIncludingAttestion
```yaml
Type: PSObject
Parameter Sets: CertificateIncludingAttestion
Aliases:

Required: True
Position: Named
Default value: None
Accept pipeline input: False
Accept wildcard characters: False
```
### -CertificateRequest
Specifies a CertificateRequest to check. The acceptable values for this parameter are:
- A file path
Expand Down
148 changes: 106 additions & 42 deletions Module/Cmdlets/Other/ConfirmYubikeyAttestion.cs
Original file line number Diff line number Diff line change
Expand Up @@ -26,13 +26,20 @@ public class ConfirmYubikeyAttestionCommand : PSCmdlet

[TransformCertificatePath_Certificate()]
[ValidateX509Certificate2_string()]
[Parameter(Mandatory = true, ValueFromPipeline = false, HelpMessage = "AttestionCertificate", ParameterSetName = "requestWithExternalAttestion")]
[Parameter(Mandatory = true, ValueFromPipeline = false, HelpMessage = "IntermediateCertificate", ParameterSetName = "requestWithExternalAttestion")]
[Parameter(Mandatory = true, ValueFromPipeline = false, HelpMessage = "IntermediateCertificate", ParameterSetName = "JustAttestCertificate")]
public PSObject? IntermediateCertificate { get; set; }

[TransformCertificatePath_Certificate()]
[ValidateX509Certificate2_string()]
[Parameter(Mandatory = true, ValueFromPipeline = false, HelpMessage = "CertificateIncludingAttestion", ParameterSetName = "CertificateIncludingAttestion")]
public PSObject? CertificateIncludingAttestion { get; set; }


private CertificateRequest? _CertificateRequest;
private X509Certificate2? _AttestionCertificate;
private X509Certificate2? _IntermediateCertificate;
private X509Certificate2? _CertificateIncludingAttestion;
private static X509Certificate2 _YubikeyValidationCA = new X509Certificate2(new byte[] { 0x30, 0x82, 0x3, 0x17, 0x30, 0x82, 0x1, 0xFF, 0xA0, 0x3, 0x2, 0x1, 0x2, 0x2, 0x3, 0x4, 0x6, 0x47, 0x30, 0xD, 0x6, 0x9, 0x2A, 0x86, 0x48, 0x86, 0xF7, 0xD, 0x1, 0x1, 0xB, 0x5, 0x0, 0x30, 0x2B, 0x31, 0x29, 0x30, 0x27, 0x6, 0x3, 0x55, 0x4, 0x3, 0xC, 0x20, 0x59, 0x75, 0x62, 0x69, 0x63, 0x6F, 0x20, 0x50, 0x49, 0x56, 0x20, 0x52, 0x6F, 0x6F, 0x74, 0x20, 0x43, 0x41, 0x20, 0x53, 0x65, 0x72, 0x69, 0x61, 0x6C, 0x20, 0x32, 0x36, 0x33, 0x37, 0x35, 0x31, 0x30, 0x20, 0x17, 0xD, 0x31, 0x36, 0x30, 0x33, 0x31, 0x34, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x5A, 0x18, 0xF, 0x32, 0x30, 0x35, 0x32, 0x30, 0x34, 0x31, 0x37, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x5A, 0x30, 0x2B, 0x31, 0x29, 0x30, 0x27, 0x6, 0x3, 0x55, 0x4, 0x3, 0xC, 0x20, 0x59, 0x75, 0x62, 0x69, 0x63, 0x6F, 0x20, 0x50, 0x49, 0x56, 0x20, 0x52, 0x6F, 0x6F, 0x74, 0x20, 0x43, 0x41, 0x20, 0x53, 0x65, 0x72, 0x69, 0x61, 0x6C, 0x20, 0x32, 0x36, 0x33, 0x37, 0x35, 0x31, 0x30, 0x82, 0x1, 0x22, 0x30, 0xD, 0x6, 0x9, 0x2A, 0x86, 0x48, 0x86, 0xF7, 0xD, 0x1, 0x1, 0x1, 0x5, 0x0, 0x3, 0x82, 0x1, 0xF, 0x0, 0x30, 0x82, 0x1, 0xA, 0x2, 0x82, 0x1, 0x1, 0x0, 0xC3, 0x76, 0x70, 0xC4, 0xCD, 0x47, 0xA6, 0x2, 0x75, 0xC4, 0xC5, 0x47, 0x1B, 0x8F, 0xCB, 0x7D, 0x4F, 0x69, 0xB4, 0x67, 0xE6, 0x6E, 0xA9, 0x27, 0xE9, 0xD2, 0x13, 0x41, 0xD1, 0x5A, 0x9A, 0x1A, 0x33, 0xC7, 0xDC, 0xF3, 0x1, 0xC2, 0xF9, 0x39, 0x9B, 0xF7, 0xC8, 0xE6, 0x36, 0xF8, 0x56, 0x34, 0x4D, 0x84, 0x8A, 0x55, 0x3C, 0xE6, 0xE6, 0xA, 0x7C, 0x41, 0x4F, 0xF5, 0xDE, 0x90, 0xD8, 0x69, 0xB2, 0xB6, 0xA0, 0x67, 0xC5, 0x9B, 0x0, 0x6B, 0x72, 0xAA, 0x66, 0x20, 0x82, 0xC7, 0x62, 0xF0, 0x43, 0x88, 0x98, 0x10, 0xE6, 0xF5, 0x96, 0x58, 0x28, 0xB5, 0x5A, 0xFF, 0xC2, 0x11, 0x29, 0x75, 0x53, 0xAA, 0x8E, 0x85, 0x34, 0x3F, 0x97, 0xB5, 0x8F, 0x5C, 0xBB, 0x39, 0xFC, 0xE, 0xBE, 0x4C, 0xBF, 0xF8, 0x5, 0xC8, 0x37, 0xFF, 0x57, 0xA7, 0x45, 0x45, 0x95, 0x84, 0x64, 0xDA, 0xD4, 0x3D, 0x19, 0xC7, 0x58, 0x28, 0x39, 0xAA, 0x53, 0xE7, 0x5B, 0xF6, 0x22, 0xB0, 0xA4, 0xC, 0xE2, 0x77, 0x8A, 0x7, 0x5, 0x52, 0xC8, 0x86, 0x60, 0xF7, 0xA6, 0xF9, 0x16, 0x69, 0x10, 0x36, 0x1F, 0x70, 0xC0, 0xF6, 0xDE, 0xC7, 0xFC, 0x73, 0x6A, 0xE6, 0xFD, 0xCE, 0x88, 0xED, 0x63, 0xC8, 0xB6, 0x5E, 0x2A, 0xA6, 0x68, 0x31, 0xB3, 0xCE, 0x6E, 0xBC, 0x6A, 0xE, 0xF, 0xBD, 0x7C, 0xE7, 0x52, 0x87, 0x38, 0x1F, 0xC0, 0x2A, 0xA0, 0x4F, 0x75, 0xD5, 0x99, 0x37, 0xA2, 0xC2, 0xF0, 0x52, 0x4D, 0xCB, 0x72, 0x8B, 0xD9, 0x87, 0x41, 0xF6, 0x1D, 0xD8, 0x3C, 0x24, 0x6A, 0xAC, 0x51, 0x9C, 0xB6, 0xCD, 0x57, 0x22, 0xBD, 0xCE, 0x5F, 0x83, 0xCE, 0x34, 0x86, 0xA7, 0xD2, 0x21, 0x54, 0xF8, 0x95, 0xB4, 0x67, 0xAD, 0x5F, 0x4D, 0x9D, 0xC6, 0x14, 0x27, 0x19, 0x2E, 0xCA, 0xE8, 0x13, 0xB4, 0x41, 0xEF, 0x2, 0x3, 0x1, 0x0, 0x1, 0xA3, 0x42, 0x30, 0x40, 0x30, 0x1D, 0x6, 0x3, 0x55, 0x1D, 0xE, 0x4, 0x16, 0x4, 0x14, 0xCA, 0x5F, 0xCA, 0xF2, 0xC4, 0xA2, 0x31, 0x9C, 0xE9, 0x22, 0x5F, 0xF1, 0xEC, 0xF4, 0xD5, 0xDF, 0x2, 0xBF, 0x83, 0xBF, 0x30, 0xF, 0x6, 0x3, 0x55, 0x1D, 0x13, 0x4, 0x8, 0x30, 0x6, 0x1, 0x1, 0xFF, 0x2, 0x1, 0x1, 0x30, 0xE, 0x6, 0x3, 0x55, 0x1D, 0xF, 0x1, 0x1, 0xFF, 0x4, 0x4, 0x3, 0x2, 0x1, 0x6, 0x30, 0xD, 0x6, 0x9, 0x2A, 0x86, 0x48, 0x86, 0xF7, 0xD, 0x1, 0x1, 0xB, 0x5, 0x0, 0x3, 0x82, 0x1, 0x1, 0x0, 0x5C, 0xEC, 0x88, 0x7C, 0x5, 0xCD, 0x5F, 0x90, 0x2F, 0x85, 0xC8, 0xDD, 0x5F, 0x86, 0x35, 0xA2, 0xA0, 0x10, 0x8C, 0xAF, 0x7B, 0xE3, 0x9D, 0xE8, 0x7B, 0x30, 0xB6, 0xC0, 0xEA, 0x44, 0xA8, 0xC9, 0x61, 0x7B, 0xD0, 0xDD, 0xEC, 0x5E, 0x16, 0xD7, 0xBD, 0x3E, 0x1E, 0x46, 0x1D, 0x21, 0xBF, 0x1A, 0xAF, 0x31, 0x93, 0x63, 0x3D, 0x4F, 0xD5, 0x95, 0x19, 0xFA, 0x80, 0xB5, 0x6D, 0xA0, 0x48, 0xA4, 0xC, 0xBA, 0xD8, 0x15, 0x73, 0x7A, 0x1E, 0x1E, 0x96, 0x9B, 0x2C, 0xB5, 0x19, 0x39, 0xEC, 0xA6, 0x73, 0xAF, 0x32, 0xFC, 0xF6, 0x94, 0xB2, 0xAE, 0xCA, 0x6F, 0x4A, 0x61, 0xD6, 0xB, 0xE, 0x9, 0xE3, 0xDC, 0x17, 0x80, 0xBF, 0x32, 0x21, 0x57, 0x3C, 0xD8, 0x49, 0xE5, 0x3B, 0xEF, 0xF0, 0xAE, 0xA6, 0x87, 0xE3, 0xD3, 0xDD, 0xCE, 0xB8, 0xB, 0x30, 0x5B, 0x48, 0xD8, 0xBD, 0x7B, 0x6, 0x4F, 0x28, 0xB1, 0xE8, 0x1D, 0xDD, 0x6D, 0x6E, 0x72, 0x5A, 0xFC, 0x92, 0xF7, 0x33, 0x57, 0x6A, 0xA1, 0x9A, 0x52, 0x63, 0xF7, 0x53, 0xDF, 0xDB, 0xE8, 0x39, 0x47, 0x74, 0x3A, 0x20, 0x30, 0xBB, 0xB7, 0x54, 0xBA, 0x41, 0x7, 0xD6, 0xE6, 0xE5, 0xB8, 0xDA, 0x29, 0x65, 0x89, 0x62, 0x5, 0xA5, 0xB4, 0x25, 0x60, 0x51, 0xB1, 0x6A, 0x16, 0xAC, 0xA2, 0xE3, 0xE2, 0x44, 0xD3, 0x5E, 0x1C, 0x4A, 0x4, 0x79, 0xEC, 0x97, 0x2E, 0xDD, 0xD6, 0x62, 0x7A, 0x10, 0x7A, 0x52, 0xD0, 0xF, 0x81, 0xA7, 0x7D, 0x2F, 0x97, 0xD, 0xBE, 0xE6, 0xBF, 0x21, 0x64, 0x66, 0x9B, 0xE0, 0xD, 0xCB, 0x73, 0xB6, 0x2C, 0x7F, 0xBE, 0x3F, 0x29, 0x7C, 0x49, 0x11, 0x33, 0x53, 0xCA, 0x27, 0x6C, 0x1B, 0x23, 0x32, 0xF, 0x50, 0xE, 0x24, 0x9F, 0xE6, 0x82, 0x4B, 0x2A, 0xF7, 0x7F, 0x45, 0xE9, 0xFE, 0xCC, 0x66, 0x3B });

private static string pemFormatRegex = "^-----[^-]*-----(?<certificateContent>.*)-----[^-]*-----$";
Expand All @@ -59,16 +66,16 @@ protected override void ProcessRecord()
Match? match;
X509Extension? extensioncsr;

if (CertificateRequest is not null)
if (ParameterSetName == "requestWithBuiltinAttestion")
{
if (CertificateRequest.BaseObject is string)
if (CertificateRequest is not null && CertificateRequest.BaseObject is string)
{
WriteDebug("CertificateRequest is string");
match = regex.Match(CertificateRequest.BaseObject.ToString()!);
if (match.Success)
{
WriteDebug("CertificateRequest is PEM string");
_CertificateRequest = LoadSigningRequestPem((string)CertificateRequest!.BaseObject, HashAlgorithmName.SHA256, CertificateRequestLoadOptions.UnsafeLoadCertificateExtensions);
_CertificateRequest = LoadSigningRequestPem((string)CertificateRequest.BaseObject, HashAlgorithmName.SHA256, CertificateRequestLoadOptions.UnsafeLoadCertificateExtensions);
}
else // Check if it is a file or throw an error.
{
Expand All @@ -87,65 +94,120 @@ protected override void ProcessRecord()
}
}
}
else if (CertificateRequest.BaseObject is byte[])
else if (CertificateRequest is not null && CertificateRequest.BaseObject is byte[])
{
WriteDebug("CertificateRequest is byte[]");
_CertificateRequest = LoadSigningRequest((byte[])CertificateRequest.BaseObject, HashAlgorithmName.SHA256, CertificateRequestLoadOptions.UnsafeLoadCertificateExtensions);
}
}

if (AttestionCertificate is not null)
{
if (AttestionCertificate.BaseObject is X509Certificate2)
{
WriteDebug("AttestionCertificate is X509Certificate2");
_AttestionCertificate = (X509Certificate2)AttestionCertificate.BaseObject;
}
else
{
throw new ArgumentException("Must be of type string or X509Certificate2", "AttestionCertificate");
}
}
else
{
// If the certificateRequest contains the attestion certificate, extract it.
// Due to yubico-piv-tool not storing it in the correct place, we need to check both places.
// https://docs.yubico.com/hardware/oid/oid-piv-arc.html
if (_CertificateRequest!.CertificateExtensions.Any(e => e.Oid!.Value == "1.3.6.1.4.1.41482.3.11"))
{
extensioncsr = _CertificateRequest!.CertificateExtensions
.Cast<X509Extension>()
.FirstOrDefault(e => e.Oid!.Value == "1.3.6.1.4.1.41482.3.11");
.FirstOrDefault(e => e.Oid!.Value == "1.3.6.1.4.1.41482.3.11", new X509Extension(new AsnEncodedData("1.3.6.1.4.1.41482.3.11", new byte[] { 0x00 }), false));
_out_AttestionDataLocation = "1.3.6.1.4.1.41482.3.11";
if (extensioncsr is not null)
try
{
_AttestionCertificate = new X509Certificate2(extensioncsr.RawData);
}
else
catch
{
throw new Exception("Attestion Certificate is missing");
throw new Exception("Failed to parse the embedded attestiondata certificate");
}
}
else if (_CertificateRequest!.CertificateExtensions.Any(e => e.Oid!.Value == "1.3.6.1.4.1.41482.3.1"))
{
extensioncsr = _CertificateRequest!.CertificateExtensions
.Cast<X509Extension>()
.FirstOrDefault(e => e.Oid!.Value == "1.3.6.1.4.1.41482.3.1");
.FirstOrDefault(e => e.Oid!.Value == "1.3.6.1.4.1.41482.3.1", new X509Extension(new AsnEncodedData("1.3.6.1.4.1.41482.3.1", new byte[] { 0x00 }), false));
_out_AttestionDataLocation = "1.3.6.1.4.1.41482.3.1";
if (extensioncsr is not null)
try
{
_AttestionCertificate = new X509Certificate2(extensioncsr.RawData);
}
else
catch
{
throw new ArgumentException("Failed to parse the embedded attestiondata certificate");
}
}
else
{
throw new ArgumentException("The CertificateRequest does not contain embedded attestion data");
}

if (_CertificateRequest!.CertificateExtensions.Any(e => e.Oid!.Value == "1.3.6.1.4.1.41482.3.2"))
{
// Read from CSR 1.3.6.1.4.1.41482.3.2
extensioncsr = _CertificateRequest!.CertificateExtensions
.Cast<X509Extension>()
.FirstOrDefault(e => e.Oid!.Value == "1.3.6.1.4.1.41482.3.2", new X509Extension(new AsnEncodedData("1.3.6.1.4.1.41482.3.2", new byte[] { 0x00 }), false));
try
{
throw new Exception("Attestion Certificate is missing");
_IntermediateCertificate = new X509Certificate2(extensioncsr.RawData);
}
catch
{
throw new ArgumentException("Failed to parse the embedded intermediate attestion certificate");
}
}
else
{
throw new ArgumentException("The CertificateRequest does not contain embedded intermediate attestion certificate");
}
}
else if (ParameterSetName == "CertificateIncludingAttestion")
{
if (CertificateIncludingAttestion is not null && CertificateIncludingAttestion.BaseObject is X509Certificate2)
{
_CertificateIncludingAttestion = (X509Certificate2)CertificateIncludingAttestion.BaseObject;

if (IntermediateCertificate is not null)

if (_CertificateIncludingAttestion.Extensions.Any(e => e.Oid!.Value == "1.3.6.1.4.1.41482.3.1"))
{
WriteDebug("Found 1.3.6.1.4.1.41482.3.2 extension, trying to extract attestiondata certificate");
extensioncsr = _CertificateIncludingAttestion.Extensions.Cast<X509Extension>().FirstOrDefault(e => e.Oid!.Value == "1.3.6.1.4.1.41482.3.1", new X509Extension(new AsnEncodedData("1.3.6.1.4.1.41482.3.1", new byte[] { 0x00 }), false));
_out_AttestionDataLocation = "1.3.6.1.4.1.41482.3.1";
try
{
_AttestionCertificate = new X509Certificate2(extensioncsr.RawData);
}
catch
{
throw new ArgumentException("Failed to parse the embedded attestiondata certificate");
}
}
else
{
throw new Exception("Certificate does not contain attestion data");
}

if (_CertificateIncludingAttestion!.Extensions.Any(e => e.Oid!.Value == "1.3.6.1.4.1.41482.3.2"))
{
WriteDebug("Found 1.3.6.1.4.1.41482.3.2 extension, trying to extract intermediate certificate");
// Read from CSR 1.3.6.1.4.1.41482.3.2
extensioncsr = _CertificateIncludingAttestion.Extensions.Cast<X509Extension>().FirstOrDefault(e => e.Oid!.Value == "1.3.6.1.4.1.41482.3.2", new X509Extension(new AsnEncodedData("1.3.6.1.4.1.41482.3.2", new byte[] { 0x00 }), false));
try
{
_IntermediateCertificate = new X509Certificate2(extensioncsr.RawData);
}
catch
{
throw new ArgumentException("Failed to parse the embedded intermediate attestion certificate");
}

}
}
else
{
throw new ArgumentException("Must be of type string or X509Certificate2", "CertificateWithAttestionData");
}
}
else if (ParameterSetName == "JustAttestCertificate")
{
if (IntermediateCertificate.BaseObject is X509Certificate2)
if (IntermediateCertificate!.BaseObject is X509Certificate2)
{
WriteDebug("IntermediateCertificate is X509Certificate2");
_IntermediateCertificate = (X509Certificate2)IntermediateCertificate.BaseObject;
Expand All @@ -154,22 +216,20 @@ protected override void ProcessRecord()
{
throw new ArgumentException("Must be of type string or X509Certificate2", "IntermediateCertificate");
}
}
else
{
// Read from CSR 1.3.6.1.4.1.41482.3.2
extensioncsr = _CertificateRequest!.CertificateExtensions
.Cast<X509Extension>()
.FirstOrDefault(e => e.Oid!.Value == "1.3.6.1.4.1.41482.3.2");
if (extensioncsr is not null)

if (AttestionCertificate!.BaseObject is X509Certificate2)
{
_IntermediateCertificate = new X509Certificate2(extensioncsr.RawData);
WriteDebug("AttestionCertificate is X509Certificate2");
_AttestionCertificate = (X509Certificate2)AttestionCertificate.BaseObject;
}
else
{
throw new Exception("Intermediate Certificate is missing!");
throw new ArgumentException("Must be of type string or X509Certificate2", "AttestionCertificate");
}

}
else
{
throw new Exception("Invalid ParameterSetName");
}

if (_AttestionCertificate is null || _IntermediateCertificate is null)
Expand Down Expand Up @@ -252,10 +312,14 @@ protected override void ProcessRecord()
}

// Check if the public key in the CSR matches the public key in the attestion certificate
if (_CertificateRequest is not null)
if (ParameterSetName == "requestWithBuiltinAttestion" && _CertificateRequest is not null)
{
_out_AttestionMatchesCSR = _AttestionCertificate.PublicKey.EncodedKeyValue.RawData.SequenceEqual(_CertificateRequest.PublicKey.EncodedKeyValue.RawData);
}
else if(ParameterSetName == "CertificateIncludingAttestion" && _CertificateIncludingAttestion is not null)
{
_out_AttestionMatchesCSR = _AttestionCertificate.PublicKey.EncodedKeyValue.RawData.SequenceEqual(_CertificateIncludingAttestion.PublicKey.EncodedKeyValue.RawData);
}

// Figure out the PublicKey algorithm
if (_AttestionCertificate.PublicKey.Oid.FriendlyName == "RSA")
Expand Down
Loading

0 comments on commit 3f019b1

Please sign in to comment.