Skip to content

Commit df86b35

Browse files
committed
Update to use security import instead of the certificate store
1 parent a9f0e34 commit df86b35

File tree

2 files changed

+73
-32
lines changed

2 files changed

+73
-32
lines changed

src/Shared/CertificateGeneration/CertificateManager.cs

Lines changed: 32 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ public IList<X509Certificate2> ListCertificates(
7878
matchingCertificates = matchingCertificates
7979
.Where(c => HasOid(c, AspNetHttpsOid));
8080

81-
Log.DescribeFoundCertificates(CertificateManagerEventSource.ToCertificateDescription(matchingCertificates));
81+
Log.DescribeFoundCertificates(ToCertificateDescription(matchingCertificates));
8282

8383
if (isValid)
8484
{
@@ -95,8 +95,8 @@ public IList<X509Certificate2> ListCertificates(
9595

9696
var invalidCertificates = matchingCertificates.Except(validCertificates);
9797

98-
Log.DescribeValidCertificates(CertificateManagerEventSource.ToCertificateDescription(validCertificates));
99-
Log.DescribeInvalidValidCertificates(CertificateManagerEventSource.ToCertificateDescription(invalidCertificates));
98+
Log.DescribeValidCertificates(ToCertificateDescription(validCertificates));
99+
Log.DescribeInvalidValidCertificates(ToCertificateDescription(invalidCertificates));
100100

101101
matchingCertificates = validCertificates;
102102
}
@@ -165,8 +165,8 @@ public EnsureCertificateResult EnsureAspNetCoreHttpsDevelopmentCertificate(
165165
var filteredCertificates = certificates.Where(c => c.Subject == Subject);
166166
var excludedCertificates = certificates.Except(filteredCertificates);
167167

168-
Log.FilteredCertificates(CertificateManagerEventSource.ToCertificateDescription(filteredCertificates));
169-
Log.ExcludedCertificates(CertificateManagerEventSource.ToCertificateDescription(excludedCertificates));
168+
Log.FilteredCertificates(ToCertificateDescription(filteredCertificates));
169+
Log.ExcludedCertificates(ToCertificateDescription(excludedCertificates));
170170

171171
certificates = filteredCertificates;
172172

@@ -185,7 +185,7 @@ public EnsureCertificateResult EnsureAspNetCoreHttpsDevelopmentCertificate(
185185
{
186186
try
187187
{
188-
Log.CorrectCertificateStateStart(CertificateManagerEventSource.GetDescription(candidate));
188+
Log.CorrectCertificateStateStart(GetDescription(candidate));
189189
CorrectCertificateState(candidate);
190190
Log.CorrectCertificateStateEnd();
191191
}
@@ -195,14 +195,14 @@ public EnsureCertificateResult EnsureAspNetCoreHttpsDevelopmentCertificate(
195195
result = EnsureCertificateResult.FailedToMakeKeyAccessible;
196196
return result;
197197
}
198-
}
198+
}
199199
}
200200
}
201201
else
202202
{
203-
Log.ValidCertificatesFound(CertificateManagerEventSource.ToCertificateDescription(certificates));
203+
Log.ValidCertificatesFound(ToCertificateDescription(certificates));
204204
certificate = certificates.First();
205-
Log.SelectedCertificate(CertificateManagerEventSource.GetDescription(certificate));
205+
Log.SelectedCertificate(GetDescription(certificate));
206206
result = EnsureCertificateResult.ValidCertificatePresent;
207207
}
208208
}
@@ -237,7 +237,7 @@ public EnsureCertificateResult EnsureAspNetCoreHttpsDevelopmentCertificate(
237237
{
238238
try
239239
{
240-
Log.CorrectCertificateStateStart(CertificateManagerEventSource.GetDescription(certificate));
240+
Log.CorrectCertificateStateStart(GetDescription(certificate));
241241
CorrectCertificateState(certificate);
242242
Log.CorrectCertificateStateEnd();
243243
}
@@ -298,8 +298,8 @@ public void CleanupHttpsCertificates()
298298
var filteredCertificates = certificates.Where(c => c.Subject == Subject);
299299
var excludedCertificates = certificates.Except(filteredCertificates);
300300

301-
Log.FilteredCertificates(CertificateManagerEventSource.ToCertificateDescription(filteredCertificates));
302-
Log.ExcludedCertificates(CertificateManagerEventSource.ToCertificateDescription(excludedCertificates));
301+
Log.FilteredCertificates(ToCertificateDescription(filteredCertificates));
302+
Log.ExcludedCertificates(ToCertificateDescription(excludedCertificates));
303303

304304
foreach (var certificate in filteredCertificates)
305305
{
@@ -321,7 +321,7 @@ public void CleanupHttpsCertificates()
321321

322322
internal void ExportCertificate(X509Certificate2 certificate, string path, bool includePrivateKey, string password)
323323
{
324-
Log.ExportCertificateStart(CertificateManagerEventSource.GetDescription(certificate), path, includePrivateKey);
324+
Log.ExportCertificateStart(GetDescription(certificate), path, includePrivateKey);
325325
if (includePrivateKey && password == null)
326326
{
327327
Log.NoPasswordForCertificate();
@@ -416,7 +416,7 @@ internal X509Certificate2 SaveCertificate(X509Certificate2 certificate)
416416
var name = StoreName.My;
417417
var location = StoreLocation.CurrentUser;
418418

419-
Log.SaveCertificateInStoreStart(CertificateManagerEventSource.GetDescription(certificate), name, location);
419+
Log.SaveCertificateInStoreStart(GetDescription(certificate), name, location);
420420

421421
certificate = SaveCertificateCore(certificate);
422422

@@ -428,7 +428,7 @@ internal void TrustCertificate(X509Certificate2 certificate)
428428
{
429429
try
430430
{
431-
Log.TrustCertificateStart(CertificateManagerEventSource.GetDescription(certificate));
431+
Log.TrustCertificateStart(GetDescription(certificate));
432432
TrustCertificateCore(certificate);
433433
Log.TrustCertificateEnd();
434434
}
@@ -528,7 +528,7 @@ private static void RemoveCertificateFromUserStore(X509Certificate2 certificate)
528528
{
529529
try
530530
{
531-
Log.RemoveCertificateFromUserStoreStart(CertificateManagerEventSource.GetDescription(certificate));
531+
Log.RemoveCertificateFromUserStoreStart(GetDescription(certificate));
532532
using var store = new X509Store(StoreName.My, StoreLocation.CurrentUser);
533533
store.Open(OpenFlags.ReadWrite);
534534
var matching = store.Certificates
@@ -546,6 +546,15 @@ private static void RemoveCertificateFromUserStore(X509Certificate2 certificate)
546546
}
547547
}
548548

549+
internal static string ToCertificateDescription(IEnumerable<X509Certificate2> matchingCertificates) =>
550+
string.Join(Environment.NewLine, matchingCertificates
551+
.OrderBy(c => c.Thumbprint)
552+
.Select(c => GetDescription(c))
553+
.ToArray());
554+
555+
internal static string GetDescription(X509Certificate2 c) =>
556+
$"{c.Thumbprint[0..6]} - {c.Subject} - {c.GetEffectiveDateString()} - {c.GetExpirationDateString()} - {Instance.IsHttpsDevelopmentCertificate(c)} - {Instance.IsExportable(c)}";
557+
549558
[EventSource(Name = "Dotnet-dev-certs")]
550559
public class CertificateManagerEventSource : EventSource
551560
{
@@ -719,14 +728,14 @@ public void ExportCertificateStart(string certificate, string path, bool include
719728
[Event(52, Level = EventLevel.Error)]
720729
public void CorrectCertificateStateError(string error) => WriteEvent(53, $"An error has ocurred while correcting the certificate state: {error}.");
721730

722-
internal static string ToCertificateDescription(IEnumerable<X509Certificate2> matchingCertificates) =>
723-
string.Join(Environment.NewLine, matchingCertificates
724-
.OrderBy(c => c.Thumbprint)
725-
.Select(c => GetDescription(c))
726-
.ToArray());
731+
[Event(54, Level = EventLevel.Verbose)]
732+
internal void MacOSAddCertificateToKeyChainStart(string keychain, string certificate) => WriteEvent(54, $"Importing the certificate {certificate} to the keychain '{keychain}'");
733+
734+
[Event(55, Level = EventLevel.Verbose)]
735+
internal void MacOSAddCertificateToKeyChainEnd() => WriteEvent(55, "Finished importing the certificate to the key chain.");
727736

728-
internal static string GetDescription(X509Certificate2 c) =>
729-
$"{c.Thumbprint[0..6]} - {c.Subject} - {c.GetEffectiveDateString()} - {c.GetExpirationDateString()} - {Instance.IsHttpsDevelopmentCertificate(c)} - {Instance.IsExportable(c)}";
737+
[Event(56, Level = EventLevel.Error)]
738+
internal void MacOSAddCertificateToKeyChainError(int exitCode) => WriteEvent(56, $"An error has ocurred while importing the certificate to the keychain: {exitCode}.");
730739
}
731740

732741
internal class UserCancelledTrustException : Exception

src/Shared/CertificateGeneration/MacOSCertificateManager.cs

Lines changed: 41 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11

22
using System;
3+
using System.Buffers.Text;
34
using System.Collections.Generic;
45
using System.Diagnostics;
56
using System.IO;
@@ -13,6 +14,7 @@ namespace Microsoft.AspNetCore.Certificates.Generation
1314
internal class MacOSCertificateManager : CertificateManager
1415
{
1516
private const string CertificateSubjectRegex = "CN=(.*[^,]+).*";
17+
private static readonly string MacOSUserKeyChain = Environment.GetEnvironmentVariable("HOME") + "/Library/Keychains/login.keychain-db";
1618
private const string MacOSSystemKeyChain = "/Library/Keychains/System.keychain";
1719
private const string MacOSFindCertificateCommandLine = "security";
1820
private static readonly string MacOSFindCertificateCommandLineArgumentsFormat = "find-certificate -c {0} -a -Z -p " + MacOSSystemKeyChain;
@@ -24,6 +26,9 @@ internal class MacOSCertificateManager : CertificateManager
2426
private const string MacOSTrustCertificateCommandLine = "sudo";
2527
private static readonly string MacOSTrustCertificateCommandLineArguments = "security add-trusted-cert -d -r trustRoot -k " + MacOSSystemKeyChain + " ";
2628

29+
private const string MacOSAddCertificateToKeyChainCommandLine = "security";
30+
private static readonly string MacOSAddCertificateToKeyChainCommandLineArgumentsFormat = "import {0} -k " + MacOSUserKeyChain + " -t cert -f pkcs12 -P {1} -A";
31+
2732
public const string InvalidCertificateState = "The ASP.NET Core developer certificate is in an invalid state. " +
2833
"To fix this issue, run the following commands 'dotnet dev-certs https --clean' and 'dotnet dev-certs https' to remove all existing ASP.NET Core development certificates " +
2934
"and create a new untrusted developer certificate. " +
@@ -175,13 +180,13 @@ protected override void RemoveCertificateFromTrustedRoots(X509Certificate2 certi
175180
}
176181
else
177182
{
178-
Log.MacOSCertificateUntrusted(CertificateManagerEventSource.GetDescription(certificate));
183+
Log.MacOSCertificateUntrusted(GetDescription(certificate));
179184
}
180185
}
181186

182187
private static void RemoveCertificateTrustRule(X509Certificate2 certificate)
183188
{
184-
Log.MacOSRemoveCertificateTrustRuleStart(CertificateManagerEventSource.GetDescription(certificate));
189+
Log.MacOSRemoveCertificateTrustRuleStart(GetDescription(certificate));
185190
var certificatePath = Path.GetTempFileName();
186191
try
187192
{
@@ -231,7 +236,7 @@ private static void RemoveCertificateFromKeyChain(string keyChain, X509Certifica
231236
RedirectStandardError = true
232237
};
233238

234-
Log.MacOSRemoveCertificateFromKeyChainStart(keyChain, CertificateManagerEventSource.GetDescription(certificate));
239+
Log.MacOSRemoveCertificateFromKeyChainStart(keyChain, GetDescription(certificate));
235240
using (var process = Process.Start(processInfo))
236241
{
237242
var output = process.StandardOutput.ReadToEnd() + process.StandardError.ReadToEnd();
@@ -254,16 +259,43 @@ private static void RemoveCertificateFromKeyChain(string keyChain, X509Certifica
254259

255260
protected override X509Certificate2 SaveCertificateCore(X509Certificate2 certificate)
256261
{
257-
var name = StoreName.My;
258-
var location = StoreLocation.CurrentUser;
262+
// security import https.pfx -k $loginKeyChain -t cert -f pkcs12 -P password -A;
263+
var passwordBytes = new byte[48];
264+
RandomNumberGenerator.Fill(passwordBytes.AsSpan()[0..35]);
265+
var password = Convert.ToBase64String(passwordBytes, 0, 36);
266+
var certBytes = certificate.Export(X509ContentType.Pfx, password);
267+
var certificatePath = Path.GetTempFileName();
268+
File.WriteAllBytes(certificatePath, certBytes);
259269

260-
using (var store = new X509Store(name, location))
270+
var processInfo = new ProcessStartInfo(
271+
MacOSAddCertificateToKeyChainCommandLine,
272+
string.Format(
273+
MacOSAddCertificateToKeyChainCommandLineArgumentsFormat,
274+
certificatePath,
275+
password
276+
))
261277
{
262-
store.Open(OpenFlags.ReadWrite);
263-
store.Add(certificate);
264-
store.Close();
278+
RedirectStandardOutput = true,
279+
RedirectStandardError = true
265280
};
266281

282+
Log.MacOSAddCertificateToKeyChainStart(MacOSUserKeyChain, GetDescription(certificate));
283+
using (var process = Process.Start(processInfo))
284+
{
285+
var output = process.StandardOutput.ReadToEnd() + process.StandardError.ReadToEnd();
286+
process.WaitForExit();
287+
288+
if (process.ExitCode != 0)
289+
{
290+
Log.MacOSAddCertificateToKeyChainError(process.ExitCode);
291+
throw new InvalidOperationException($@"There was an error importing the certificate into the user key chain '{certificate.Thumbprint}'.
292+
293+
{output}");
294+
}
295+
}
296+
297+
Log.MacOSAddCertificateToKeyChainEnd();
298+
267299
return certificate;
268300
}
269301

0 commit comments

Comments
 (0)