-
Notifications
You must be signed in to change notification settings - Fork 373
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
Merged
Robbie-Microsoft
merged 32 commits into
rginsburg/msiv2_feature_branch
from
rginsburg/msiv2_csr
Aug 27, 2025
+670
−56
Merged
Changes from 25 commits
Commits
Show all changes
32 commits
Select commit
Hold shift + click to select a range
5498968
Initial commit. 2 TODOs
Robbie-Microsoft e04e408
Merge branch 'rginsburg/msiv2_feature_branch' into rginsburg/msiv2_csr
Robbie-Microsoft 4e096b7
Merge branch 'rginsburg/msiv2_feature_branch' into rginsburg/msiv2_csr
Robbie-Microsoft 6bc2164
Implemented CSR generator
Robbie-Microsoft 762ccdf
first pass at improved unit tests
Robbie-Microsoft 4ea6c09
Finished improving unit tests
Robbie-Microsoft 009f948
Updates to CUID
Robbie-Microsoft 21d4ef3
Unit test improvements
Robbie-Microsoft cd013a3
Implemented Feedback
Robbie-Microsoft 480ae9e
renamed file
Robbie-Microsoft 0aa8692
small improvement
Robbie-Microsoft 621c566
added missing awaitor for async method
Robbie-Microsoft 068461b
Fixed bugs discovered from unit testing in child branch
Robbie-Microsoft 2034b25
undid changes to .proj
Robbie-Microsoft 2b7486a
undid change to global.json
Robbie-Microsoft 189ff9e
added missing sets
Robbie-Microsoft 92b325f
Inplemented some feedback
Robbie-Microsoft 067c83c
Implemented some feedback
Robbie-Microsoft f7d6f88
PKCS1 -> Pss padding
Robbie-Microsoft 74e8e60
re-used imports
Robbie-Microsoft 152f396
Implemented feedback
Robbie-Microsoft d46c853
Changes from manual testing.
Robbie-Microsoft 3f75e3a
ImdsV2: Reworked Custom ASN1 Encoder to use System.Formats.Asn1 Nuget…
Robbie-Microsoft 253993d
Merge branch 'rginsburg/msiv2_feature_branch' into rginsburg/msiv2_csr
Robbie-Microsoft 3481c39
Implemented feedback
Robbie-Microsoft 92158bb
Small rework due to spec changes
Robbie-Microsoft 729a56a
Additional rework due to spec changes
Robbie-Microsoft 3027392
Implemented feedback
Robbie-Microsoft 3c3dcdf
Removed null check on vmId. Created CuidInfo.IsNullOrEmpty
Robbie-Microsoft f51cdf9
Implemented feedback
Robbie-Microsoft 5e7ab07
Updated min version of imds, spec has incorrect info
Robbie-Microsoft 362b407
Updated a comment
Robbie-Microsoft File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
235 changes: 235 additions & 0 deletions
235
src/client/Microsoft.Identity.Client/ManagedIdentity/V2/CertificateRequest.cs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,235 @@ | ||
// Copyright (c) Microsoft Corporation. All rights reserved. | ||
// Licensed under the MIT License. | ||
|
||
using System; | ||
using System.Collections.ObjectModel; | ||
using System.Formats.Asn1; | ||
using System.Security.Cryptography; | ||
using System.Security.Cryptography.X509Certificates; | ||
using System.Text; | ||
|
||
namespace Microsoft.Identity.Client.ManagedIdentity.V2 | ||
{ | ||
internal class CertificateRequest | ||
Robbie-Microsoft marked this conversation as resolved.
Show resolved
Hide resolved
|
||
{ | ||
private X500DistinguishedName _subjectName; | ||
private RSA _rsa; | ||
private HashAlgorithmName _hashAlgorithmName; | ||
private RSASignaturePadding _rsaPadding; | ||
|
||
internal CertificateRequest( | ||
X500DistinguishedName subjectName, | ||
RSA key, | ||
HashAlgorithmName hashAlgorithm, | ||
RSASignaturePadding padding) | ||
{ | ||
_subjectName = subjectName; | ||
_rsa = key; | ||
_hashAlgorithmName = hashAlgorithm; | ||
_rsaPadding = padding; | ||
} | ||
|
||
internal Collection<AsnEncodedData> OtherRequestAttributes { get; } = new Collection<AsnEncodedData>(); | ||
|
||
private static string MakePem(byte[] ber, string header) | ||
{ | ||
const int LineLength = 64; | ||
|
||
string base64 = Convert.ToBase64String(ber); | ||
int offset = 0; | ||
|
||
StringBuilder builder = new StringBuilder("-----BEGIN "); | ||
builder.Append(header); | ||
builder.AppendLine("-----"); | ||
|
||
while (offset < base64.Length) | ||
{ | ||
int lineEnd = Math.Min(offset + LineLength, base64.Length); | ||
builder.AppendLine(base64.Substring(offset, lineEnd - offset)); | ||
offset = lineEnd; | ||
} | ||
|
||
builder.Append("-----END "); | ||
builder.Append(header); | ||
builder.AppendLine("-----"); | ||
|
||
return builder.ToString(); | ||
} | ||
|
||
internal string CreateSigningRequestPem() | ||
{ | ||
byte[] csr = CreateSigningRequest(); | ||
return MakePem(csr, "CERTIFICATE REQUEST"); | ||
} | ||
|
||
internal byte[] CreateSigningRequest() | ||
{ | ||
if (_hashAlgorithmName != HashAlgorithmName.SHA256) | ||
{ | ||
throw new NotSupportedException("Signature Processing has only been written for SHA256"); | ||
} | ||
|
||
AsnWriter writer = new AsnWriter(AsnEncodingRules.DER); | ||
|
||
// RSAPublicKey ::= SEQUENCE { | ||
// modulus INTEGER, -- n | ||
// publicExponent INTEGER -- e | ||
// } | ||
|
||
using (writer.PushSequence()) | ||
{ | ||
RSAParameters rsaParameters = _rsa.ExportParameters(false); | ||
writer.WriteIntegerUnsigned(rsaParameters.Modulus); | ||
writer.WriteIntegerUnsigned(rsaParameters.Exponent); | ||
} | ||
|
||
byte[] publicKey = writer.Encode(); | ||
writer.Reset(); | ||
|
||
// CertificationRequestInfo ::= SEQUENCE { | ||
Robbie-Microsoft marked this conversation as resolved.
Show resolved
Hide resolved
|
||
// version INTEGER { v1(0) } (v1,...), | ||
// subject Name, | ||
// subjectPKInfo SubjectPublicKeyInfo{{ PKInfoAlgorithms }}, | ||
// attributes [0] Attributes{{ CRIAttributes }} | ||
// } | ||
// | ||
// SubjectPublicKeyInfo { ALGORITHM: IOSet} ::= SEQUENCE { | ||
// algorithm AlgorithmIdentifier { { IOSet} }, | ||
// subjectPublicKey BIT STRING | ||
// } | ||
// | ||
// Attributes { ATTRIBUTE:IOSet } ::= SET OF Attribute{{ IOSet }} | ||
// | ||
// Attribute { ATTRIBUTE:IOSet } ::= SEQUENCE { | ||
// type ATTRIBUTE.&id({IOSet}), | ||
// values SET SIZE(1..MAX) OF ATTRIBUTE.&Type({IOSet}{@type}) | ||
// } | ||
|
||
using (writer.PushSequence()) | ||
{ | ||
writer.WriteInteger(0); | ||
writer.WriteEncodedValue(_subjectName.RawData); | ||
|
||
// subjectPKInfo | ||
using (writer.PushSequence()) | ||
{ | ||
// algorithm | ||
using (writer.PushSequence()) | ||
{ | ||
writer.WriteObjectIdentifier("1.2.840.113549.1.1.1"); | ||
// RSA uses an explicit NULL value for parameters | ||
writer.WriteNull(); | ||
} | ||
|
||
writer.WriteBitString(publicKey); | ||
} | ||
|
||
if (OtherRequestAttributes.Count > 0) | ||
{ | ||
// attributes | ||
using (writer.PushSetOf(new Asn1Tag(TagClass.ContextSpecific, 0))) | ||
{ | ||
foreach (AsnEncodedData attribute in OtherRequestAttributes) | ||
{ | ||
using (writer.PushSequence()) | ||
{ | ||
writer.WriteObjectIdentifier(attribute.Oid.Value); | ||
|
||
using (writer.PushSetOf()) | ||
{ | ||
writer.WriteEncodedValue(attribute.RawData); | ||
} | ||
} | ||
} | ||
} | ||
} | ||
} | ||
|
||
byte[] certReqInfo = writer.Encode(); | ||
writer.Reset(); | ||
|
||
// CertificationRequest ::= SEQUENCE { | ||
// certificationRequestInfo CertificationRequestInfo, | ||
// signatureAlgorithm AlgorithmIdentifier{{ SignatureAlgorithms }}, | ||
// signature BIT STRING | ||
// } | ||
|
||
using (writer.PushSequence()) | ||
{ | ||
writer.WriteEncodedValue(certReqInfo); | ||
|
||
// signatureAlgorithm | ||
using (writer.PushSequence()) | ||
{ | ||
if (_rsaPadding == RSASignaturePadding.Pss) | ||
{ | ||
if (_hashAlgorithmName != HashAlgorithmName.SHA256) | ||
{ | ||
throw new NotSupportedException("Only SHA256 is supported with PSS padding."); | ||
} | ||
|
||
writer.WriteObjectIdentifier("1.2.840.113549.1.1.10"); | ||
Robbie-Microsoft marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
// RSASSA-PSS-params ::= SEQUENCE { | ||
// hashAlgorithm [0] HashAlgorithm DEFAULT sha1, | ||
// maskGenAlgorithm [1] MaskGenAlgorithm DEFAULT mgf1SHA1, | ||
// saltLength [2] INTEGER DEFAULT 20, | ||
// trailerField [3] TrailerField DEFAULT trailerFieldBC | ||
// } | ||
|
||
using (writer.PushSequence()) | ||
{ | ||
string digestOid = "2.16.840.1.101.3.4.2.1"; | ||
|
||
// hashAlgorithm | ||
using (writer.PushSequence(new Asn1Tag(TagClass.ContextSpecific, 0))) | ||
{ | ||
using (writer.PushSequence()) | ||
{ | ||
writer.WriteObjectIdentifier(digestOid); | ||
} | ||
} | ||
|
||
using (writer.PushSequence(new Asn1Tag(TagClass.ContextSpecific, 1))) | ||
{ | ||
using (writer.PushSequence()) | ||
{ | ||
writer.WriteObjectIdentifier("1.2.840.113549.1.1.8"); | ||
|
||
using (writer.PushSequence()) | ||
{ | ||
writer.WriteObjectIdentifier(digestOid); | ||
} | ||
} | ||
} | ||
|
||
// saltLength (SHA256.Length, 32 bytes) | ||
using (writer.PushSequence(new Asn1Tag(TagClass.ContextSpecific, 2))) | ||
{ | ||
writer.WriteInteger(32); | ||
} | ||
|
||
// trailerField 1, which is trailerFieldBC, which is the DEFAULT, | ||
// so don't write it down. | ||
} | ||
} | ||
else if (_rsaPadding == RSASignaturePadding.Pkcs1) | ||
{ | ||
writer.WriteObjectIdentifier("1.2.840.113549.1.1.11"); | ||
// RSA PKCS1 uses an explicit NULL value for parameters | ||
writer.WriteNull(); | ||
} | ||
else | ||
{ | ||
throw new NotSupportedException("Unsupported RSA padding."); | ||
} | ||
} | ||
|
||
byte[] signature = _rsa.SignData(certReqInfo, _hashAlgorithmName, _rsaPadding); | ||
writer.WriteBitString(signature); | ||
} | ||
|
||
return writer.Encode(); | ||
} | ||
} | ||
} |
56 changes: 56 additions & 0 deletions
56
src/client/Microsoft.Identity.Client/ManagedIdentity/V2/CertificateRequestResponse.cs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,56 @@ | ||
// Copyright (c) Microsoft Corporation. All rights reserved. | ||
// Licensed under the MIT License. | ||
|
||
using System.Net; | ||
#if SUPPORTS_SYSTEM_TEXT_JSON | ||
using JsonProperty = System.Text.Json.Serialization.JsonPropertyNameAttribute; | ||
#else | ||
using Microsoft.Identity.Json; | ||
#endif | ||
|
||
namespace Microsoft.Identity.Client.ManagedIdentity.V2 | ||
{ | ||
/// <summary> | ||
/// Represents the response for a Managed Identity CSR request. | ||
/// </summary> | ||
internal class CertificateRequestResponse | ||
{ | ||
[JsonProperty("client_id")] | ||
public string ClientId { get; set; } | ||
|
||
[JsonProperty("tenant_id")] | ||
public string TenantId { get; set; } | ||
|
||
[JsonProperty("client_credential")] | ||
public string ClientCredential { get; set; } | ||
|
||
[JsonProperty("regional_token_url")] | ||
public string RegionalTokenUrl { get; set; } | ||
|
||
[JsonProperty("expires_in")] | ||
public int ExpiresIn { get; set; } | ||
|
||
[JsonProperty("refresh_in")] | ||
public int RefreshIn { get; set; } | ||
Robbie-Microsoft marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
public CertificateRequestResponse() { } | ||
|
||
public static void Validate(CertificateRequestResponse certificateRequestResponse) | ||
{ | ||
if (string.IsNullOrEmpty(certificateRequestResponse.ClientId) || | ||
string.IsNullOrEmpty(certificateRequestResponse.TenantId) || | ||
string.IsNullOrEmpty(certificateRequestResponse.ClientCredential) || | ||
string.IsNullOrEmpty(certificateRequestResponse.RegionalTokenUrl) || | ||
certificateRequestResponse.ExpiresIn <= 0 || | ||
certificateRequestResponse.RefreshIn <= 0) | ||
Robbie-Microsoft marked this conversation as resolved.
Show resolved
Hide resolved
|
||
{ | ||
throw MsalServiceExceptionFactory.CreateManagedIdentityException( | ||
MsalError.ManagedIdentityRequestFailed, | ||
$"[ImdsV2] ImdsV2ManagedIdentitySource.ExecuteCertificateRequestAsync failed because the certificate request response is malformed. Status code: 200", | ||
null, | ||
ManagedIdentitySource.ImdsV2, | ||
(int)HttpStatusCode.OK); | ||
} | ||
} | ||
} | ||
} |
51 changes: 51 additions & 0 deletions
51
src/client/Microsoft.Identity.Client/ManagedIdentity/V2/Csr.cs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,51 @@ | ||
// Copyright (c) Microsoft Corporation. All rights reserved. | ||
// Licensed under the MIT License. | ||
|
||
using System.Formats.Asn1; | ||
using System.Security.Cryptography; | ||
using System.Security.Cryptography.X509Certificates; | ||
using Microsoft.Identity.Client.Utils; | ||
|
||
namespace Microsoft.Identity.Client.ManagedIdentity.V2 | ||
{ | ||
internal class Csr | ||
{ | ||
internal static string Generate(string clientId, string tenantId, CuidInfo cuid) | ||
{ | ||
using (RSA rsa = CreateRsaKeyPair()) | ||
{ | ||
CertificateRequest req = new CertificateRequest( | ||
new X500DistinguishedName($"CN={clientId}, DC={tenantId}"), | ||
rsa, | ||
HashAlgorithmName.SHA256, | ||
RSASignaturePadding.Pss); | ||
|
||
AsnWriter writer = new AsnWriter(AsnEncodingRules.DER); | ||
writer.WriteCharacterString(UniversalTagNumber.UTF8String, JsonHelper.SerializeToJson(cuid)); | ||
|
||
req.OtherRequestAttributes.Add( | ||
new AsnEncodedData( | ||
"1.2.840.113549.1.9.7", | ||
Robbie-Microsoft marked this conversation as resolved.
Show resolved
Hide resolved
|
||
writer.Encode())); | ||
|
||
return req.CreateSigningRequestPem(); | ||
} | ||
} | ||
|
||
private static RSA CreateRsaKeyPair() | ||
{ | ||
// TODO: use the strongest key on the machine i.e. a TPM key | ||
RSA rsa = null; | ||
|
||
#if NET462 || NET472 | ||
// .NET Framework runs only on Windows, so RSACng (Windows-only) is always available | ||
rsa = new RSACng(); | ||
#else | ||
// Cross-platform .NET - RSA.Create() returns appropriate PSS-capable implementation | ||
rsa = RSA.Create(); | ||
#endif | ||
rsa.KeySize = 2048; | ||
return rsa; | ||
} | ||
} | ||
} |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.