-
Notifications
You must be signed in to change notification settings - Fork 380
[MSI v2] Cache MSI v2 cert #5557
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
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,79 @@ | ||
| // Copyright (c) Microsoft Corporation. All rights reserved. | ||
| // Licensed under the MIT License. | ||
|
|
||
| using System; | ||
| using System.Security.Cryptography.X509Certificates; | ||
| using System.Threading; | ||
|
|
||
| namespace Microsoft.Identity.Client.ManagedIdentity.V2 | ||
| { | ||
| /// <summary> | ||
| /// In-memory entry owned by the cache. Disposing the entry disposes the certificate it owns. | ||
| /// </summary> | ||
| internal sealed class CertificateCacheEntry : IDisposable | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Could benefit form IsDisposed public property...
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. updated |
||
| { | ||
| private int _disposed; | ||
|
|
||
| /// <summary> | ||
| /// Represents the minimum remaining lifetime for an operation or resource. | ||
| /// </summary> | ||
| public static readonly TimeSpan MinRemainingLifetime = TimeSpan.FromHours(24); | ||
|
|
||
| /// <summary> | ||
| /// certificate+endpoint+clientId cache entry. | ||
| /// </summary> | ||
| /// <param name="certificate"></param> | ||
| /// <param name="notAfterUtc"></param> | ||
| /// <param name="endpoint"></param> | ||
| /// <param name="clientId"></param> | ||
| /// <exception cref="ArgumentNullException"></exception> | ||
| public CertificateCacheEntry(X509Certificate2 certificate, DateTimeOffset notAfterUtc, string endpoint, string clientId) | ||
| { | ||
| Certificate = certificate ?? throw new ArgumentNullException(nameof(certificate)); | ||
| NotAfterUtc = notAfterUtc; | ||
| Endpoint = endpoint ?? throw new ArgumentNullException(nameof(endpoint)); | ||
| ClientId = clientId ?? throw new ArgumentNullException(nameof(clientId)); | ||
| } | ||
|
|
||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Wouldnt we need comments for public members?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. these are internal |
||
| /// <summary> | ||
| /// certificate owned by this entry. | ||
| /// </summary> | ||
| public X509Certificate2 Certificate { get; } | ||
| /// <summary> | ||
| /// notAfterUtc of the certificate. | ||
| /// </summary> | ||
| public DateTimeOffset NotAfterUtc { get; } | ||
| /// <summary> | ||
| /// endpoint associated with this certificate. | ||
| /// </summary> | ||
| public string Endpoint { get; } | ||
| /// <summary> | ||
| /// clientId associated with this certificate. | ||
| /// </summary> | ||
| public string ClientId { get; } | ||
|
|
||
| /// <summary>Whether this entry has been disposed.</summary> | ||
| public bool IsDisposed => Volatile.Read(ref _disposed) != 0; | ||
|
|
||
| /// <summary> | ||
| /// is expired at the specified time. | ||
| /// </summary> | ||
| /// <param name="nowUtc"></param> | ||
| /// <returns></returns> | ||
| public bool IsExpiredUtc(DateTimeOffset nowUtc) => nowUtc >= (NotAfterUtc - MinRemainingLifetime); | ||
|
|
||
| /// <summary> | ||
| /// dispose the entry and its certificate. | ||
| /// </summary> | ||
| public void Dispose() | ||
| { | ||
| if (Interlocked.Exchange(ref _disposed, 1) != 0) | ||
| { | ||
| return; // already disposed | ||
| } | ||
|
|
||
| // Idempotent due to the atomic guard | ||
| Certificate.Dispose(); | ||
| } | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,34 @@ | ||
| // Copyright (c) Microsoft Corporation. All rights reserved. | ||
| // Licensed under the MIT License. | ||
|
|
||
| using System; | ||
| using System.Security.Cryptography.X509Certificates; | ||
|
|
||
| namespace Microsoft.Identity.Client.ManagedIdentity.V2 | ||
| { | ||
| /// <summary> | ||
| /// Immutable snapshot of a cached certificate and its associated metadata. | ||
| /// </summary> | ||
| internal readonly struct CertificateCacheValue | ||
| { | ||
| public CertificateCacheValue(X509Certificate2 certificate, string endpoint, string clientId) | ||
| { | ||
| if (certificate == null) throw new ArgumentNullException(nameof(certificate)); | ||
| if (endpoint == null) throw new ArgumentNullException(nameof(endpoint)); | ||
| if (clientId == null) throw new ArgumentNullException(nameof(clientId)); | ||
|
|
||
| Certificate = certificate; | ||
| Endpoint = endpoint; | ||
| ClientId = clientId; | ||
| } | ||
|
|
||
| /// <summary>The certificate (clone owned by the caller).</summary> | ||
| public X509Certificate2 Certificate { get; } | ||
|
|
||
| /// <summary>The base endpoint to use with this certificate.</summary> | ||
| public string Endpoint { get; } | ||
|
|
||
| /// <summary>The canonical client id to be posted to the mTLS token endpoint.</summary> | ||
| public string ClientId { get; } | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,39 @@ | ||
| // Copyright (c) Microsoft Corporation. All rights reserved. | ||
| // Licensed under the MIT License. | ||
|
|
||
| using System; | ||
| using System.Security.Cryptography.X509Certificates; | ||
| using Microsoft.Identity.Client.Core; | ||
|
|
||
| namespace Microsoft.Identity.Client.ManagedIdentity.V2 | ||
| { | ||
| /// <summary> | ||
| /// Process-local cache for an mTLS certificate and its endpoint. | ||
| /// Expiration is based solely on certificate.NotAfter. | ||
| /// </summary> | ||
| internal interface ICertificateCache | ||
| { | ||
| /// <summary> | ||
| /// Try to get a cached certificate+endpoint+clientId for the specified cacheKey. | ||
| /// Returns true and non-null outputs if found and not expired. | ||
| /// </summary> | ||
| bool TryGet( | ||
| string cacheKey, | ||
| out CertificateCacheValue value, | ||
| ILoggerAdapter logger = null); | ||
|
|
||
| /// <summary> | ||
| /// Insert or replace the cached certificate+endpoint+clientId for cacheKey. | ||
| /// </summary> | ||
| void Set( | ||
| string cacheKey, | ||
| in CertificateCacheValue value, | ||
| ILoggerAdapter logger = null); | ||
|
|
||
| /// <summary>Remove an entry if present.</summary> | ||
| bool Remove(string cacheKey, ILoggerAdapter logger = null); | ||
|
|
||
| /// <summary>Clear all entries.</summary> | ||
| void Clear(ILoggerAdapter logger = null); | ||
| } | ||
| } |
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.
Since we leak the dependency to test, this utility metod can belong in test itself....
A better approach would be to expose appropriate public methods on well encapsulated components.
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.
moved the one in IMDS V2 to it's own method and call it from here