-
Notifications
You must be signed in to change notification settings - Fork 297
Use CertHelper on Mac instead of hardcoded PFX paths #5149
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: main
Are you sure you want to change the base?
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 |
|---|---|---|
|
|
@@ -8,6 +8,7 @@ | |
| using System.IO; | ||
| using System.Linq; | ||
| using System.Runtime.CompilerServices; | ||
| using System.Runtime.InteropServices; | ||
| using System.Security.Cryptography.X509Certificates; | ||
| using System.Text; | ||
| using System.Threading.Tasks; | ||
|
|
@@ -21,6 +22,7 @@ public class KeyVaultCert | |
| private readonly string _clientId = "8c4b65ef-5a73-4d5a-a298-962d4a4ef7bc"; | ||
|
|
||
| public X509Certificate2Collection KeyVaultCertificates { get; set; } | ||
| public List<byte[]> KeyVaultCertificateBytes { get; set; } | ||
| public ILocalCert LocalCerts { get; set; } | ||
| private TokenCredential _credential { get; set; } | ||
| private CertificateClient _certClient { get; set; } | ||
|
|
@@ -33,16 +35,28 @@ public KeyVaultCert(TokenCredential? cred = null, CertificateClient? certClient | |
| _certClient = certClient ?? new CertificateClient(new Uri(_keyVaultUrl), _credential); | ||
| _secretClient = secretClient ?? new SecretClient(new Uri(_keyVaultUrl), _credential); | ||
| KeyVaultCertificates = new X509Certificate2Collection(); | ||
| KeyVaultCertificateBytes = new List<byte[]>(); | ||
| } | ||
|
|
||
| public async Task LoadKeyVaultCertsAsync() | ||
| public async Task LoadKeyVaultCertsAsync(bool? rawBytesOnly = null) | ||
| { | ||
| KeyVaultCertificates.Add(await FindCertificateInKeyVaultAsync(Constants.Cert1Name)); | ||
| KeyVaultCertificates.Add(await FindCertificateInKeyVaultAsync(Constants.Cert2Name)); | ||
| bool skipX509Load = rawBytesOnly ?? RuntimeInformation.IsOSPlatform(OSPlatform.OSX); | ||
|
|
||
| if (KeyVaultCertificates.Where(c => c == null).Count() > 0) | ||
| var (cert1, bytes1) = await FindCertificateInKeyVaultAsync(Constants.Cert1Name, skipX509Load); | ||
| var (cert2, bytes2) = await FindCertificateInKeyVaultAsync(Constants.Cert2Name, skipX509Load); | ||
|
|
||
| KeyVaultCertificateBytes.Add(bytes1); | ||
| KeyVaultCertificateBytes.Add(bytes2); | ||
|
|
||
| if (!skipX509Load) | ||
| { | ||
| throw new Exception("One or more certificates not found"); | ||
| KeyVaultCertificates.Add(cert1!); | ||
| KeyVaultCertificates.Add(cert2!); | ||
|
|
||
| if (KeyVaultCertificates.Where(c => c == null).Count() > 0) | ||
| { | ||
| throw new Exception("One or more certificates not found"); | ||
| } | ||
| } | ||
|
Comment on lines
+41
to
60
|
||
| } | ||
|
|
||
|
|
@@ -136,7 +150,7 @@ private async Task<ClientCertificateCredential> GetCertificateCredentialAsync(st | |
| return ccc; | ||
| } | ||
|
|
||
| private async Task<X509Certificate2> FindCertificateInKeyVaultAsync(string certName) | ||
| private async Task<(X509Certificate2?, byte[])> FindCertificateInKeyVaultAsync(string certName, bool rawBytesOnly = false) | ||
| { | ||
| var keyVaultCert = await _certClient.GetCertificateAsync(certName); | ||
| if(keyVaultCert.Value == null) | ||
|
|
@@ -149,12 +163,18 @@ private async Task<X509Certificate2> FindCertificateInKeyVaultAsync(string certN | |
| throw new Exception("Certificate secret not found in Key Vault"); | ||
| } | ||
| var certBytes = Convert.FromBase64String(secret.Value.Value); | ||
|
|
||
| if (rawBytesOnly) | ||
| { | ||
| return (null, certBytes); | ||
| } | ||
|
|
||
| #if NET9_0_OR_GREATER | ||
| var cert = X509CertificateLoader.LoadPkcs12(certBytes, "", X509KeyStorageFlags.Exportable | X509KeyStorageFlags.PersistKeySet); | ||
| #else | ||
| var cert = new X509Certificate2(certBytes, "", X509KeyStorageFlags.Exportable | X509KeyStorageFlags.PersistKeySet); | ||
| #endif | ||
| return cert; | ||
| return (cert, certBytes); | ||
| } | ||
|
|
||
| public bool ShouldRotateCerts() | ||
|
|
||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -3,6 +3,7 @@ | |||||||||||||||||||||||||||||||||||||||||||||||||
| using System.Collections.Generic; | ||||||||||||||||||||||||||||||||||||||||||||||||||
| using System.Linq; | ||||||||||||||||||||||||||||||||||||||||||||||||||
| using System.Runtime.CompilerServices; | ||||||||||||||||||||||||||||||||||||||||||||||||||
| using System.Runtime.InteropServices; | ||||||||||||||||||||||||||||||||||||||||||||||||||
| using System.Security.Cryptography.X509Certificates; | ||||||||||||||||||||||||||||||||||||||||||||||||||
| using System.Text; | ||||||||||||||||||||||||||||||||||||||||||||||||||
| using System.Threading.Tasks; | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -13,19 +14,29 @@ public class LocalCert : ILocalCert | |||||||||||||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||||||||||||||
| public X509Certificate2Collection Certificates { get; set; } | ||||||||||||||||||||||||||||||||||||||||||||||||||
| public bool RequiresBootstrap { get; private set; } | ||||||||||||||||||||||||||||||||||||||||||||||||||
| internal IX509Store LocalMachineCerts { get; set; } | ||||||||||||||||||||||||||||||||||||||||||||||||||
| internal IX509Store? LocalMachineCerts { get; set; } | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| public LocalCert(IX509Store? store = null) | ||||||||||||||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||||||||||||||
| LocalMachineCerts = store ?? new TestableX509Store(); | ||||||||||||||||||||||||||||||||||||||||||||||||||
| Certificates = new X509Certificate2Collection(); | ||||||||||||||||||||||||||||||||||||||||||||||||||
| RequiresBootstrap = false; | ||||||||||||||||||||||||||||||||||||||||||||||||||
| GetLocalCerts(); | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) | ||||||||||||||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||||||||||||||
| // Skip Keychain access on macOS to avoid password prompts. | ||||||||||||||||||||||||||||||||||||||||||||||||||
| // Certs are managed as files on disk instead. | ||||||||||||||||||||||||||||||||||||||||||||||||||
| RequiresBootstrap = true; | ||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||
| else | ||||||||||||||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||||||||||||||
| LocalMachineCerts = store ?? new TestableX509Store(); | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+24
to
+32
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) | |
| { | |
| // Skip Keychain access on macOS to avoid password prompts. | |
| // Certs are managed as files on disk instead. | |
| RequiresBootstrap = true; | |
| } | |
| else | |
| { | |
| LocalMachineCerts = store ?? new TestableX509Store(); | |
| if (store != null) | |
| { | |
| // Honor the injected store on all platforms, including macOS. | |
| LocalMachineCerts = store; | |
| GetLocalCerts(); | |
| } | |
| else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) | |
| { | |
| // Skip default Keychain access on macOS to avoid password prompts. | |
| // Certs are managed as files on disk instead. | |
| RequiresBootstrap = true; | |
| } | |
| else | |
| { | |
| LocalMachineCerts = new TestableX509Store(); |
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -21,31 +21,42 @@ static async Task<int> Main(string[] args) | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| await kvc.LoadKeyVaultCertsAsync(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (kvc.ShouldRotateCerts()) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| using (var localMachineCerts = new X509Store(StoreName.My, StoreLocation.CurrentUser)) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| localMachineCerts.Open(OpenFlags.ReadWrite); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| localMachineCerts.RemoveRange(kvc.LocalCerts.Certificates); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| localMachineCerts.AddRange(kvc.KeyVaultCertificates); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| WriteCertsToDisk(kvc.KeyVaultCertificateBytes); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| else | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| using (var localMachineCerts = new X509Store(StoreName.My, StoreLocation.CurrentUser)) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| localMachineCerts.Open(OpenFlags.ReadWrite); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| localMachineCerts.RemoveRange(kvc.LocalCerts.Certificates); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| localMachineCerts.AddRange(kvc.KeyVaultCertificates); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| var bcc = new BlobContainerClient(new Uri("https://pvscmdupload.blob.core.windows.net/certstatus"), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| new ClientCertificateCredential(TENANT_ID, CERT_CLIENT_ID, kvc.KeyVaultCertificates.First(), new() {SendCertificateChain = true})); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| var currentKeyValutCertThumbprints = ""; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| foreach (var cert in kvc.KeyVaultCertificates) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| currentKeyValutCertThumbprints += $"[{DateTimeOffset.UtcNow}] {cert.Thumbprint}{Environment.NewLine}"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| var blob = bcc.GetBlobClient(System.Environment.MachineName); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (blob.Exists()) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| var result = blob.DownloadContent(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| var currentBlob = result.Value.Content.ToString(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| currentBlob = currentBlob + currentKeyValutCertThumbprints; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| blob.Upload(new MemoryStream(Encoding.UTF8.GetBytes(currentBlob)), overwrite: true); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| else | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (!RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| blob.Upload(new MemoryStream(Encoding.UTF8.GetBytes(currentKeyValutCertThumbprints)), overwrite: false); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| var bcc = new BlobContainerClient(new Uri("https://pvscmdupload.blob.core.windows.net/certstatus"), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| new ClientCertificateCredential(TENANT_ID, CERT_CLIENT_ID, kvc.KeyVaultCertificates.First(), new() {SendCertificateChain = true})); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| var currentKeyValutCertThumbprints = ""; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| foreach (var cert in kvc.KeyVaultCertificates) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| currentKeyValutCertThumbprints += $"[{DateTimeOffset.UtcNow}] {cert.Thumbprint}{Environment.NewLine}"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| var blob = bcc.GetBlobClient(System.Environment.MachineName); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (blob.Exists()) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| var result = blob.DownloadContent(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| var currentBlob = result.Value.Content.ToString(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| currentBlob = currentBlob + currentKeyValutCertThumbprints; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| blob.Upload(new MemoryStream(Encoding.UTF8.GetBytes(currentBlob)), overwrite: true); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| else | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| blob.Upload(new MemoryStream(Encoding.UTF8.GetBytes(currentKeyValutCertThumbprints)), overwrite: false); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+43
to
+58
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| var currentKeyValutCertThumbprints = ""; | |
| foreach (var cert in kvc.KeyVaultCertificates) | |
| { | |
| currentKeyValutCertThumbprints += $"[{DateTimeOffset.UtcNow}] {cert.Thumbprint}{Environment.NewLine}"; | |
| } | |
| var blob = bcc.GetBlobClient(System.Environment.MachineName); | |
| if (blob.Exists()) | |
| { | |
| var result = blob.DownloadContent(); | |
| var currentBlob = result.Value.Content.ToString(); | |
| currentBlob = currentBlob + currentKeyValutCertThumbprints; | |
| blob.Upload(new MemoryStream(Encoding.UTF8.GetBytes(currentBlob)), overwrite: true); | |
| } | |
| else | |
| { | |
| blob.Upload(new MemoryStream(Encoding.UTF8.GetBytes(currentKeyValutCertThumbprints)), overwrite: false); | |
| var currentKeyVaultCertThumbprints = ""; | |
| foreach (var cert in kvc.KeyVaultCertificates) | |
| { | |
| currentKeyVaultCertThumbprints += $"[{DateTimeOffset.UtcNow}] {cert.Thumbprint}{Environment.NewLine}"; | |
| } | |
| var blob = bcc.GetBlobClient(System.Environment.MachineName); | |
| if (blob.Exists()) | |
| { | |
| var result = blob.DownloadContent(); | |
| var currentBlob = result.Value.Content.ToString(); | |
| currentBlob = currentBlob + currentKeyVaultCertThumbprints; | |
| blob.Upload(new MemoryStream(Encoding.UTF8.GetBytes(currentBlob)), overwrite: true); | |
| } | |
| else | |
| { | |
| blob.Upload(new MemoryStream(Encoding.UTF8.GetBytes(currentKeyVaultCertThumbprints)), overwrite: false); |
Copilot
AI
Mar 12, 2026
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.
WriteCertsToDisk writes PFX files (including private keys) to disk but doesn’t set restrictive permissions on the directory/files. On macOS this can leave private key material readable by other users depending on umask/defaults. Consider setting the directory to user-only and setting each .pfx file mode to 0600 (or equivalent) after writing.
| static void WriteCertsToDisk(List<byte[]> certBytes) | |
| { | |
| var certDir = GetMacCertDirectory(); | |
| Directory.CreateDirectory(certDir); | |
| var certNames = new[] { Constants.Cert1Name, Constants.Cert2Name }; | |
| for (int i = 0; i < certBytes.Count && i < certNames.Length; i++) | |
| { | |
| var pfxPath = Path.Combine(certDir, $"{certNames[i]}.pfx"); | |
| File.WriteAllBytes(pfxPath, certBytes[i]); | |
| static void SetMacPermissions(string path, string mode) | |
| { | |
| if (!RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) | |
| { | |
| return; | |
| } | |
| try | |
| { | |
| var psi = new System.Diagnostics.ProcessStartInfo | |
| { | |
| FileName = "/bin/chmod", | |
| RedirectStandardOutput = false, | |
| RedirectStandardError = false, | |
| UseShellExecute = false, | |
| CreateNoWindow = true, | |
| }; | |
| psi.ArgumentList.Add(mode); | |
| psi.ArgumentList.Add(path); | |
| using var process = System.Diagnostics.Process.Start(psi); | |
| process?.WaitForExit(); | |
| } | |
| catch | |
| { | |
| // Best-effort: ignore failures to adjust permissions. | |
| } | |
| } | |
| static void WriteCertsToDisk(List<byte[]> certBytes) | |
| { | |
| var certDir = GetMacCertDirectory(); | |
| Directory.CreateDirectory(certDir); | |
| // Ensure the certificate directory is only accessible by the current user on macOS. | |
| SetMacPermissions(certDir, "700"); | |
| var certNames = new[] { Constants.Cert1Name, Constants.Cert2Name }; | |
| for (int i = 0; i < certBytes.Count && i < certNames.Length; i++) | |
| { | |
| var pfxPath = Path.Combine(certDir, $"{certNames[i]}.pfx"); | |
| File.WriteAllBytes(pfxPath, certBytes[i]); | |
| // Restrict certificate file permissions to user read/write on macOS. | |
| SetMacPermissions(pfxPath, "600"); |
Copilot
AI
Mar 12, 2026
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.
WriteCertsToDisk silently truncates to the smaller of certBytes.Count and the fixed certNames.Length (2). If KeyVaultCertificateBytes is unexpectedly missing a cert (or contains extra entries), the tool will succeed but output an incomplete/mismatched set of files. Consider validating that the expected number of certs is present and failing fast if it isn’t.
Copilot
AI
Mar 12, 2026
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.
ReadCertsFromDisk only logs to stderr when a certificate file is missing and the process still exits 0, which causes callers (scripts/performance/common.py) to treat the run as successful but receive an incomplete cert list. Consider failing with a non-zero exit code (or otherwise signaling failure) when any expected PFX file is missing.
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.
LoadKeyVaultCertsAsync’s default behavior now depends on the current OS (macOS defaults to skipping X509 loading). This makes the method’s semantics non-deterministic across platforms and can cause unit tests/callers that expect KeyVaultCertificates to be populated (when calling with no arguments) to behave differently on macOS. Consider making the default consistent (e.g., always load X509) and having the caller explicitly request raw-bytes-only mode where needed.