-
Notifications
You must be signed in to change notification settings - Fork 372
Imdsv2: Generate CSR and execute CSR request #5427
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
base: rginsburg/msiv2_feature_branch
Are you sure you want to change the base?
Imdsv2: Generate CSR and execute CSR request #5427
Conversation
src/client/Microsoft.Identity.Client/ManagedIdentity/CsrRequestResponse.cs
Outdated
Show resolved
Hide resolved
src/client/Microsoft.Identity.Client/ManagedIdentity/ImdsV2ManagedIdentitySource.cs
Outdated
Show resolved
Hide resolved
src/client/Microsoft.Identity.Client/ManagedIdentity/CsrRequest.cs
Outdated
Show resolved
Hide resolved
src/client/Microsoft.Identity.Client/ManagedIdentity/CsrRequest.cs
Outdated
Show resolved
Hide resolved
src/client/Microsoft.Identity.Client/ManagedIdentity/CsrRequest.cs
Outdated
Show resolved
Hide resolved
src/client/Microsoft.Identity.Client/ManagedIdentity/ClientCredentialRequestResponse.cs
Outdated
Show resolved
Hide resolved
src/client/Microsoft.Identity.Client/ManagedIdentity/ClientCredentialRequestResponse.cs
Outdated
Show resolved
Hide resolved
src/client/Microsoft.Identity.Client/ManagedIdentity/ImdsV2ManagedIdentitySource.cs
Show resolved
Hide resolved
src/client/Microsoft.Identity.Client/ManagedIdentity/ClientCredentialRequestResponse.cs
Outdated
Show resolved
Hide resolved
src/client/Microsoft.Identity.Client/ManagedIdentity/ImdsV2ManagedIdentitySource.cs
Outdated
Show resolved
Hide resolved
src/client/Microsoft.Identity.Client/ManagedIdentity/ImdsV2ManagedIdentitySource.cs
Outdated
Show resolved
Hide resolved
} | ||
|
||
/// <summary> | ||
/// Encodes an ASN.1 NULL. | ||
/// </summary> | ||
private static byte[] EncodeAsn1Null() | ||
{ | ||
return new byte[] { 0x05, 0x00 }; | ||
} | ||
|
||
/// <summary> | ||
/// Encodes an ASN.1 OBJECT IDENTIFIER. | ||
/// </summary> | ||
private static byte[] EncodeAsn1ObjectIdentifier(int[] oid) | ||
{ | ||
if (oid == null || oid.Length < 2) | ||
throw new ArgumentException("OID must have at least 2 components"); | ||
|
||
var bytes = new System.Collections.Generic.List<byte>(); | ||
|
||
// First two components are encoded as (first * 40 + second) | ||
bytes.AddRange(EncodeOidComponent(oid[0] * 40 + oid[1])); | ||
|
||
// Remaining components | ||
for (int i = 2; i < oid.Length; i++) | ||
{ | ||
bytes.AddRange(EncodeOidComponent(oid[i])); | ||
} | ||
|
||
return EncodeAsn1Tag(0x06, bytes.ToArray()); | ||
} | ||
|
||
/// <summary> | ||
/// Encodes an ASN.1 context-specific tag. | ||
/// </summary> | ||
private static byte[] EncodeAsn1ContextSpecific(int tagNumber, byte[] content) | ||
{ | ||
byte tag = (byte)(0xA0 | tagNumber); // Context-specific, constructed | ||
return EncodeAsn1Tag(tag, content); | ||
} | ||
|
||
/// <summary> | ||
/// Encodes an ASN.1 tag with length and content. | ||
/// </summary> | ||
private static byte[] EncodeAsn1Tag(byte tag, byte[] content) | ||
{ | ||
byte[] lengthBytes = EncodeAsn1Length(content.Length); | ||
byte[] result = new byte[1 + lengthBytes.Length + content.Length]; | ||
result[0] = tag; | ||
Array.Copy(lengthBytes, 0, result, 1, lengthBytes.Length); | ||
Array.Copy(content, 0, result, 1 + lengthBytes.Length, content.Length); | ||
return result; | ||
} | ||
|
||
/// <summary> | ||
/// Encodes ASN.1 length field. | ||
/// </summary> | ||
private static byte[] EncodeAsn1Length(int length) | ||
{ | ||
if (length < 0x80) | ||
{ | ||
return new byte[] { (byte)length }; | ||
} | ||
|
||
var lengthBytes = new System.Collections.Generic.List<byte>(); | ||
int temp = length; | ||
while (temp > 0) | ||
{ | ||
lengthBytes.Insert(0, (byte)(temp & 0xFF)); | ||
temp >>= 8; | ||
} | ||
|
||
byte[] result = new byte[lengthBytes.Count + 1]; | ||
result[0] = (byte)(0x80 | lengthBytes.Count); | ||
lengthBytes.CopyTo(result, 1); | ||
return result; | ||
} | ||
|
||
/// <summary> | ||
/// Encodes a single OID component using variable-length encoding. | ||
/// </summary> | ||
private static byte[] EncodeOidComponent(int value) | ||
{ | ||
if (value == 0) | ||
return new byte[] { 0x00 }; | ||
|
||
var bytes = new System.Collections.Generic.List<byte>(); | ||
int temp = value; | ||
|
||
bytes.Insert(0, (byte)(temp & 0x7F)); | ||
temp >>= 7; | ||
|
||
while (temp > 0) | ||
{ | ||
bytes.Insert(0, (byte)((temp & 0x7F) | 0x80)); | ||
temp >>= 7; | ||
} | ||
|
||
return bytes.ToArray(); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
All of these seem like they can/should use System.Formats.Asn1.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks like this is not available in .NET Framework 462/472 and .NET Standard 2.0. Do you think it's worth it to conditionally use System.Formats.Asn1
for only 8.0? Or would it be clearer to use the same code for all 4 versions (how it is now).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's available, you just need to reference the System.Formats.Asn1 NuGet package.
@@ -57,13 +57,12 @@ public CsrMetadata() { } | |||
/// Validates a JSON decoded CsrMetadata instance. | |||
/// </summary> | |||
/// <param name="csrMetadata">The CsrMetadata object.</param> | |||
/// <returns>false if any field is null.</returns> | |||
/// <returns>false if any required field is null. Note: Vmid is required, Vmssid is optional.</returns> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you keep the naming consistent? Is this a CsrResponse?
/// <summary> | ||
/// Test helper to expose CsrValidator methods for testing malformed PEM. | ||
/// </summary> | ||
internal static class TestCsrValidator |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I can't read any of these tests. At least add a commen on what this does.
Assert.IsFalse(string.IsNullOrWhiteSpace(csrRequest.Pem)); | ||
|
||
// Validate the CSR contents - this should handle null/empty VMSSID gracefully | ||
CsrValidator.ValidateCsrContent(csrRequest.Pem, TestConstants.ClientId, TestConstants.TenantId, cuid); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not clear what exactly you are testing here. Ideally we would test that the CSR is valid and that it contains all the necessary data (e.g. oid=clientId etc.)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(or maybe you are doing this, but I am not able to tell)
Assert.IsFalse(string.IsNullOrWhiteSpace(csrRequest.Pem)); | ||
|
||
// Validate the CSR contents - this should handle null/empty VMSSID gracefully | ||
CsrValidator.ValidateCsrContent(csrRequest.Pem, TestConstants.ClientId, TestConstants.TenantId, cuid); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As a test idea, you can have a test that runs on net8 only and which uses the rutnime to create the CSR and then compare it with yours. Just an idea, not sure how useful it would be.
.NET native CSR generation via RSA and ASN.1 Encoding. Tested with net462, net472, net8.0, netstandard2.0