Skip to content
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

Allow multiple private key references in Unix PFXes #55425

Merged
merged 3 commits into from
Jul 13, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ public static ICertificatePal FromBlob(ReadOnlySpan<byte> rawData, SafePasswordH
{
Debug.Assert(password != null);

bool ephemeralSpecified = keyStorageFlags.HasFlag(X509KeyStorageFlags.EphemeralKeySet);
X509ContentType contentType = X509Certificate2.GetCertContentType(rawData);

switch (contentType)
Expand All @@ -62,7 +63,7 @@ public static ICertificatePal FromBlob(ReadOnlySpan<byte> rawData, SafePasswordH
// We don't support determining this on Android right now, so we throw.
throw new CryptographicException(SR.Cryptography_X509_PKCS7_NoSigner);
case X509ContentType.Pkcs12:
return ReadPkcs12(rawData, password);
return ReadPkcs12(rawData, password, ephemeralSpecified);
case X509ContentType.Cert:
default:
{
Expand Down Expand Up @@ -104,11 +105,11 @@ internal static bool TryReadX509(ReadOnlySpan<byte> rawData, [NotNullWhen(true)]
return true;
}

private static ICertificatePal ReadPkcs12(ReadOnlySpan<byte> rawData, SafePasswordHandle password)
private static ICertificatePal ReadPkcs12(ReadOnlySpan<byte> rawData, SafePasswordHandle password, bool ephemeralSpecified)
{
using (var reader = new AndroidPkcs12Reader(rawData))
{
reader.Decrypt(password);
reader.Decrypt(password, ephemeralSpecified);

UnixPkcs12Reader.CertAndKey certAndKey = reader.GetSingleCert();
AndroidCertificatePal pal = (AndroidCertificatePal)certAndKey.Cert!;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,11 @@ public static ILoaderPal FromBlob(ReadOnlySpan<byte> rawData, SafePasswordHandle
Debug.Assert(password != null);

X509ContentType contentType = X509Certificate2.GetCertContentType(rawData);
bool ephemeralSpecified = keyStorageFlags.HasFlag(X509KeyStorageFlags.EphemeralKeySet);

if (contentType == X509ContentType.Pkcs12)
{
ICertificatePal[] certPals = ReadPkcs12Collection(rawData, password);
ICertificatePal[] certPals = ReadPkcs12Collection(rawData, password, ephemeralSpecified);
return new AndroidCertLoader(certPals);
}
else
Expand Down Expand Up @@ -108,11 +110,14 @@ public static IStorePal FromSystemStore(string storeName, StoreLocation storeLoc
throw new CryptographicException(message, new PlatformNotSupportedException(message));
}

private static ICertificatePal[] ReadPkcs12Collection(ReadOnlySpan<byte> rawData, SafePasswordHandle password)
private static ICertificatePal[] ReadPkcs12Collection(
ReadOnlySpan<byte> rawData,
SafePasswordHandle password,
bool ephemeralSpecified)
{
using (var reader = new AndroidPkcs12Reader(rawData))
{
reader.Decrypt(password);
reader.Decrypt(password, ephemeralSpecified);

ICertificatePal[] certs = new ICertificatePal[reader.GetCertCount()];
int idx = 0;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ internal sealed partial class AppleCertificatePal : ICertificatePal
{
using (ApplePkcs12Reader reader = new ApplePkcs12Reader(rawData))
{
reader.Decrypt(password);
reader.Decrypt(password, ephemeralSpecified: false);

UnixPkcs12Reader.CertAndKey certAndKey = reader.GetSingleCert();
AppleCertificatePal pal = (AppleCertificatePal)certAndKey.Cert!;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ public static ILoaderPal FromBlob(ReadOnlySpan<byte> rawData, SafePasswordHandle
? Interop.AppleCrypto.SecKeychainCopyDefault()
: Interop.AppleCrypto.CreateTemporaryKeychain();

return ImportPkcs12(rawData, password, exportable, keychain);
return ImportPkcs12(rawData, password, exportable, ephemeralSpecified: false, keychain);
}

SafeCFArrayHandle certs = Interop.AppleCrypto.X509ImportCollection(
Expand All @@ -64,13 +64,14 @@ public static ILoaderPal FromBlob(ReadOnlySpan<byte> rawData, SafePasswordHandle
ReadOnlySpan<byte> rawData,
SafePasswordHandle password,
bool exportable,
bool ephemeralSpecified,
SafeKeychainHandle keychain)
{
ApplePkcs12Reader reader = new ApplePkcs12Reader(rawData);

try
{
reader.Decrypt(password);
reader.Decrypt(password, ephemeralSpecified);
return new ApplePkcs12CertLoader(reader, keychain, password, exportable);
}
catch
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,12 +49,13 @@ public static ICertificatePal FromBlob(ReadOnlySpan<byte> rawData, SafePasswordH

ICertificatePal? cert;
Exception? openSslException;
bool ephemeralSpecified = keyStorageFlags.HasFlag(X509KeyStorageFlags.EphemeralKeySet);

if (TryReadX509Der(rawData, out cert) ||
TryReadX509Pem(rawData, out cert) ||
PkcsFormatReader.TryReadPkcs7Der(rawData, out cert) ||
PkcsFormatReader.TryReadPkcs7Pem(rawData, out cert) ||
PkcsFormatReader.TryReadPkcs12(rawData, password, out cert, out openSslException))
PkcsFormatReader.TryReadPkcs12(rawData, password, ephemeralSpecified, out cert, out openSslException))
{
if (cert == null)
{
Expand All @@ -73,6 +74,7 @@ public static ICertificatePal FromBlob(ReadOnlySpan<byte> rawData, SafePasswordH
public static ICertificatePal FromFile(string fileName, SafePasswordHandle password, X509KeyStorageFlags keyStorageFlags)
{
ICertificatePal? pal;
bool ephemeralSpecified = keyStorageFlags.HasFlag(X509KeyStorageFlags.EphemeralKeySet);

// If we can't open the file, fail right away.
using (SafeBioHandle fileBio = Interop.Crypto.BioNewFile(fileName, "rb"))
Expand All @@ -87,6 +89,7 @@ public static ICertificatePal FromFile(string fileName, SafePasswordHandle passw
PkcsFormatReader.TryReadPkcs12(
File.ReadAllBytes(fileName),
password,
ephemeralSpecified,
out pal,
out Exception? exception);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -253,24 +253,49 @@ internal static bool TryReadPkcs7Pem(SafeBioHandle bio, [NotNullWhen(true)] out
return true;
}

internal static bool TryReadPkcs12(ReadOnlySpan<byte> rawData, SafePasswordHandle password, [NotNullWhen(true)] out ICertificatePal? certPal, out Exception? openSslException)
internal static bool TryReadPkcs12(
ReadOnlySpan<byte> rawData,
SafePasswordHandle password,
bool ephemeralSpecified,
[NotNullWhen(true)] out ICertificatePal? certPal,
out Exception? openSslException)
{
List<ICertificatePal>? ignored;

return TryReadPkcs12(rawData, password, true, out certPal!, out ignored, out openSslException);
return TryReadPkcs12(
rawData,
password,
single: true,
ephemeralSpecified,
out certPal!,
out ignored,
out openSslException);
}

internal static bool TryReadPkcs12(ReadOnlySpan<byte> rawData, SafePasswordHandle password, [NotNullWhen(true)] out List<ICertificatePal>? certPals, out Exception? openSslException)
internal static bool TryReadPkcs12(
ReadOnlySpan<byte> rawData,
SafePasswordHandle password,
bool ephemeralSpecified,
[NotNullWhen(true)] out List<ICertificatePal>? certPals,
out Exception? openSslException)
{
ICertificatePal? ignored;

return TryReadPkcs12(rawData, password, false, out ignored, out certPals!, out openSslException);
return TryReadPkcs12(
rawData,
password,
single: false,
ephemeralSpecified,
out ignored,
out certPals!,
out openSslException);
}

private static bool TryReadPkcs12(
ReadOnlySpan<byte> rawData,
SafePasswordHandle password,
bool single,
bool ephemeralSpecified,
out ICertificatePal? readPal,
out List<ICertificatePal>? readCerts,
out Exception? openSslException)
Expand All @@ -287,18 +312,19 @@ internal static bool TryReadPkcs12(ReadOnlySpan<byte> rawData, SafePasswordHandl

using (pfx)
{
return TryReadPkcs12(pfx, password, single, out readPal, out readCerts);
return TryReadPkcs12(pfx, password, single, ephemeralSpecified, out readPal, out readCerts);
}
}

private static bool TryReadPkcs12(
OpenSslPkcs12Reader pfx,
SafePasswordHandle password,
bool single,
bool ephemeralSpecified,
out ICertificatePal? readPal,
out List<ICertificatePal>? readCerts)
{
pfx.Decrypt(password);
pfx.Decrypt(password, ephemeralSpecified);

if (single)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ public static ILoaderPal FromBlob(ReadOnlySpan<byte> rawData, SafePasswordHandle
Debug.Assert(password != null);

ICertificatePal? singleCert;
bool ephemeralSpecified = keyStorageFlags.HasFlag(X509KeyStorageFlags.EphemeralKeySet);

if (OpenSslX509CertificateReader.TryReadX509Der(rawData, out singleCert) ||
OpenSslX509CertificateReader.TryReadX509Pem(rawData, out singleCert))
Expand All @@ -39,7 +40,7 @@ public static ILoaderPal FromBlob(ReadOnlySpan<byte> rawData, SafePasswordHandle

if (PkcsFormatReader.TryReadPkcs7Der(rawData, out certPals) ||
PkcsFormatReader.TryReadPkcs7Pem(rawData, out certPals) ||
PkcsFormatReader.TryReadPkcs12(rawData, password, out certPals, out openSslException))
PkcsFormatReader.TryReadPkcs12(rawData, password, ephemeralSpecified, out certPals, out openSslException))
{
Debug.Assert(certPals != null);

Expand All @@ -52,15 +53,21 @@ public static ILoaderPal FromBlob(ReadOnlySpan<byte> rawData, SafePasswordHandle

public static ILoaderPal FromFile(string fileName, SafePasswordHandle password, X509KeyStorageFlags keyStorageFlags)
{
bool ephemeralSpecified = keyStorageFlags.HasFlag(X509KeyStorageFlags.EphemeralKeySet);

using (SafeBioHandle bio = Interop.Crypto.BioNewFile(fileName, "rb"))
{
Interop.Crypto.CheckValidOpenSslHandle(bio);

return FromBio(fileName, bio, password);
return FromBio(fileName, bio, password, ephemeralSpecified);
}
}

private static ILoaderPal FromBio(string fileName, SafeBioHandle bio, SafePasswordHandle password)
private static ILoaderPal FromBio(
string fileName,
SafeBioHandle bio,
SafePasswordHandle password,
bool ephemeralSpecified)
{
int bioPosition = Interop.Crypto.BioTell(bio);
Debug.Assert(bioPosition >= 0);
Expand Down Expand Up @@ -104,7 +111,7 @@ private static ILoaderPal FromBio(string fileName, SafeBioHandle bio, SafePasswo
// Capture the exception so in case of failure, the call to BioSeek does not override it.
Exception? openSslException;
byte[] data = File.ReadAllBytes(fileName);
if (PkcsFormatReader.TryReadPkcs12(data, password, out certPals, out openSslException))
if (PkcsFormatReader.TryReadPkcs12(data, password, ephemeralSpecified, out certPals, out openSslException))
{
return ListToLoaderPal(certPals);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ internal abstract class UnixPkcs12Reader : IDisposable
private CertAndKey[]? _certs;
private int _certCount;
private PointerMemoryManager<byte>? _tmpManager;
private bool _allowDoubleBind;

protected abstract ICertificatePalCore ReadX509Der(ReadOnlyMemory<byte> data);
protected abstract AsymmetricAlgorithm LoadKey(ReadOnlyMemory<byte> safeBagBagValue);
Expand Down Expand Up @@ -180,11 +181,13 @@ private static void ReturnRentedContentInfos(ContentInfoAsn[] rentedContents)
ArrayPool<ContentInfoAsn>.Shared.Return(rentedContents, clearArray: true);
}

public void Decrypt(SafePasswordHandle password)
public void Decrypt(SafePasswordHandle password, bool ephemeralSpecified)
{
ReadOnlyMemory<byte> authSafeContents =
Helpers.DecodeOctetStringAsMemory(_pfxAsn.AuthSafe.Content);

_allowDoubleBind = !ephemeralSpecified;

bool hasRef = false;
password.DangerousAddRef(ref hasRef);

Expand Down Expand Up @@ -314,6 +317,7 @@ private void Decrypt(ReadOnlySpan<char> password, ReadOnlyMemory<byte> authSafeC
ExtractPrivateKeys(password, keyBags, keyBagIdx, keys, publicKeyInfos);

BuildCertsWithKeys(
password,
certBags,
certBagAttrs,
certs,
Expand Down Expand Up @@ -497,6 +501,7 @@ private static ContentInfoAsn[] DecodeSafeContents(ReadOnlyMemory<byte> authSafe
}

private void BuildCertsWithKeys(
ReadOnlySpan<char> password,
CertBagAsn[] certBags,
AttributeAsn[]?[] certBagAttrs,
CertAndKey[] certs,
Expand Down Expand Up @@ -550,13 +555,26 @@ private static ContentInfoAsn[] DecodeSafeContents(ReadOnlyMemory<byte> authSafe

if (matchingKeyIdx != -1)
{
// Windows compat:
// If the PFX is loaded with EphemeralKeySet, don't allow double-bind.
// Otherwise, reload the key so a second instance is bound (avoiding one
// cert Dispose removing the key of another).
if (keys[matchingKeyIdx] == null)
{
throw new CryptographicException(SR.Cryptography_Pfx_BadKeyReference);
if (_allowDoubleBind)
{
certs[certBagIdx].Key = LoadKey(keyBags[matchingKeyIdx], password);
}
else
{
throw new CryptographicException(SR.Cryptography_Pfx_BadKeyReference);
}
}
else
{
certs[certBagIdx].Key = keys[matchingKeyIdx];
keys[matchingKeyIdx] = null;
}

certs[certBagIdx].Key = keys[matchingKeyIdx];
keys[matchingKeyIdx] = null;
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,8 @@ internal static X509ContentType GetDerCertContentType(ReadOnlySpan<byte> rawData
{
Debug.Assert(password != null);

bool ephemeralSpecified = keyStorageFlags.HasFlag(X509KeyStorageFlags.EphemeralKeySet);

if (contentType == X509ContentType.Pkcs7)
{
throw new CryptographicException(
Expand All @@ -116,7 +118,7 @@ internal static X509ContentType GetDerCertContentType(ReadOnlySpan<byte> rawData
// We ignore keyStorageFlags which is tracked in https://github.com/dotnet/runtime/issues/52434.
// The keys are always imported as ephemeral and never persisted. Exportability is ignored for
// the moment and it needs to be investigated how to map it to iOS keychain primitives.
return ImportPkcs12(rawData, password);
return ImportPkcs12(rawData, password, ephemeralSpecified);
}

SafeSecIdentityHandle identityHandle;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,12 @@ internal sealed partial class AppleCertificatePal : ICertificatePal

private static AppleCertificatePal ImportPkcs12(
ReadOnlySpan<byte> rawData,
SafePasswordHandle password)
SafePasswordHandle password,
bool ephemeralSpecified)
{
using (ApplePkcs12Reader reader = new ApplePkcs12Reader(rawData))
{
reader.Decrypt(password);
reader.Decrypt(password, ephemeralSpecified);
return ImportPkcs12(reader.GetSingleCert());
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ public static ILoaderPal FromBlob(ReadOnlySpan<byte> rawData, SafePasswordHandle
return new CertCollectionLoader(certificateList);
}

bool ephemeralSpecified = keyStorageFlags.HasFlag(X509KeyStorageFlags.EphemeralKeySet);
X509ContentType contentType = AppleCertificatePal.GetDerCertContentType(rawData);

if (contentType == X509ContentType.Pkcs7)
Expand All @@ -52,7 +53,7 @@ public static ILoaderPal FromBlob(ReadOnlySpan<byte> rawData, SafePasswordHandle

try
{
reader.Decrypt(password);
reader.Decrypt(password, ephemeralSpecified);
return new ApplePkcs12CertLoader(reader, password);
}
catch
Expand Down
Loading