Skip to content

Commit

Permalink
Support ECParameters that contain only D on Windows and Linux
Browse files Browse the repository at this point in the history
If D (private key) is supplied but not the public key (Q), permit
this and allow the platform to re-calculate the public key from
the private key.

* Windows uses CNG blobs with the Q.X and Q.Y values set to (0,0).
* LInux uses the ECC math module to recompute Q from D and G.
* macOS is TBD.
  • Loading branch information
vcsjones authored Apr 5, 2020
1 parent 68625db commit 6d395de
Show file tree
Hide file tree
Showing 32 changed files with 886 additions and 78 deletions.
1 change: 1 addition & 0 deletions .config/CredScanSuppressions.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
"/src/libraries/Common/tests/System/Net/Http/PostScenarioTest.cs",
"/src/libraries/Common/tests/System/Net/Prerequisites/Deployment/setup_certificates.ps1",
"/src/libraries/Common/tests/System/Security/Cryptography/AlgorithmImplementations/EC/ECKeyFileTests.cs",
"/src/libraries/Common/tests/System/Security/Cryptography/AlgorithmImplementations/EC/ECKeyFileTests.LimitedPrivate.cs",
"/src/libraries/Common/tests/System/Security/Cryptography/AlgorithmImplementations/RSA/RSAKeyFileTests.cs",
"/src/libraries/System.Data.Common/tests/System/Data/Common/DbConnectionStringBuilderTest.cs",
"/src/libraries/System.Diagnostics.Process/tests/ProcessStartInfoTests.cs",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,14 @@ internal static partial class Crypto
private static extern int EcKeyCreateByKeyParameters(
out SafeEcKeyHandle key,
string oid,
byte[] qx, int qxLength,
byte[] qy, int qyLength,
byte[]? qx, int qxLength,
byte[]? qy, int qyLength,
byte[]? d, int dLength);

internal static SafeEcKeyHandle EcKeyCreateByKeyParameters(
string oid,
byte[] qx, int qxLength,
byte[] qy, int qyLength,
byte[]? qx, int qxLength,
byte[]? qy, int qyLength,
byte[]? d, int dLength)
{
SafeEcKeyHandle key;
Expand Down
101 changes: 98 additions & 3 deletions src/libraries/Common/src/System/Security/Cryptography/CngPkcs8.cs
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,23 @@ internal static unsafe Pkcs8Response ImportPkcs8PrivateKey(ReadOnlySpan<byte> so
}

bytesRead = len;
return ImportPkcs8(source.Slice(0, len));
ReadOnlySpan<byte> pkcs8Source = source.Slice(0, len);

try
{
return ImportPkcs8(pkcs8Source);
}
catch (CryptographicException)
{
AsnWriter? pkcs8ZeroPublicKey = RewritePkcs8ECPrivateKeyWithZeroPublicKey(pkcs8Source);

if (pkcs8ZeroPublicKey == null)
{
throw;
}

return ImportPkcs8(pkcs8ZeroPublicKey.EncodeAsSpan());
}
}

internal static unsafe Pkcs8Response ImportEncryptedPkcs8PrivateKey(
Expand All @@ -147,7 +163,21 @@ internal static unsafe Pkcs8Response ImportEncryptedPkcs8PrivateKey(
}
catch (CryptographicException e)
{
throw new CryptographicException(SR.Cryptography_Pkcs8_EncryptedReadFailed, e);
AsnWriter? pkcs8ZeroPublicKey = RewritePkcs8ECPrivateKeyWithZeroPublicKey(decryptedSpan);

if (pkcs8ZeroPublicKey == null)
{
throw new CryptographicException(SR.Cryptography_Pkcs8_EncryptedReadFailed, e);
}

try
{
return ImportPkcs8(pkcs8ZeroPublicKey.EncodeAsSpan());
}
catch (CryptographicException)
{
throw new CryptographicException(SR.Cryptography_Pkcs8_EncryptedReadFailed, e);
}
}
finally
{
Expand Down Expand Up @@ -199,7 +229,22 @@ internal static unsafe Pkcs8Response ImportEncryptedPkcs8PrivateKey(
}
catch (CryptographicException e)
{
throw new CryptographicException(SR.Cryptography_Pkcs8_EncryptedReadFailed, e);
AsnWriter? pkcs8ZeroPublicKey = RewritePkcs8ECPrivateKeyWithZeroPublicKey(decryptedSpan);

if (pkcs8ZeroPublicKey == null)
{
throw new CryptographicException(SR.Cryptography_Pkcs8_EncryptedReadFailed, e);
}

try
{
bytesRead = len;
return ImportPkcs8(pkcs8ZeroPublicKey.EncodeAsSpan());
}
catch (CryptographicException)
{
throw new CryptographicException(SR.Cryptography_Pkcs8_EncryptedReadFailed, e);
}
}
finally
{
Expand Down Expand Up @@ -305,6 +350,56 @@ private static AsnWriter RewriteEncryptedPkcs8PrivateKey(
}
}

// CNG cannot import a PrivateKeyInfo with the following criteria:
// 1. Is a EC key with explicitly encoded parameters
// 2. Is missing the PublicKey from ECPrivateKey.
// CNG can import an explicit EC PrivateKeyInfo if the PublicKey
// is present. CNG will also re-compute the public key from the
// private key if they do not much. To help CNG be able to import
// these keys, we re-write the PKCS8 to contain a zeroed PublicKey.
//
// If the PKCS8 key does not meet the above criteria, null is returned,
// signaling the original exception should be thrown.
private static unsafe AsnWriter? RewritePkcs8ECPrivateKeyWithZeroPublicKey(ReadOnlySpan<byte> source)
{
fixed (byte* ptr = &MemoryMarshal.GetReference(source))
{
using (MemoryManager<byte> manager = new PointerMemoryManager<byte>(ptr, source.Length))
{
PrivateKeyInfoAsn privateKeyInfo = PrivateKeyInfoAsn.Decode(manager.Memory, AsnEncodingRules.BER);
AlgorithmIdentifierAsn privateAlgorithm = privateKeyInfo.PrivateKeyAlgorithm;

if (privateAlgorithm.Algorithm.Value != Oids.EcPublicKey)
{
return null;
}

ECPrivateKey privateKey = ECPrivateKey.Decode(privateKeyInfo.PrivateKey, AsnEncodingRules.BER);
EccKeyFormatHelper.FromECPrivateKey(privateKey, privateAlgorithm, out ECParameters ecParameters);

fixed (byte* pD = ecParameters.D)
{
try
{
if (!ecParameters.Curve.IsExplicit || ecParameters.Q.X != null || ecParameters.Q.Y != null)
{
return null;
}

byte[] zero = new byte[ecParameters.D!.Length];
ecParameters.Q.Y = zero;
ecParameters.Q.X = zero;
return EccKeyFormatHelper.WritePkcs8PrivateKey(ecParameters, privateKeyInfo.Attributes);
}
finally
{
Array.Clear(ecParameters.D!, 0, ecParameters.D!.Length);
}
}
}
}
}

private static void FillRandomAsciiString(Span<char> destination)
{
Debug.Assert(destination.Length < 128);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,25 @@ public override void ImportParameters(ECParameters parameters)
ThrowIfDisposed();

ECCurve curve = parameters.Curve;
bool includePrivateParamerters = (parameters.D != null);
bool includePrivateParameters = parameters.D != null;
bool hasPublicParameters = parameters.Q.X != null && parameters.Q.Y != null;

if (curve.IsPrime)
{
byte[] ecExplicitBlob = ECCng.GetPrimeCurveBlob(ref parameters, ecdh: true);
ImportFullKeyBlob(ecExplicitBlob, includePrivateParamerters);
if (!hasPublicParameters && includePrivateParameters)
{
byte[] zero = new byte[parameters.D!.Length];
ECParameters ecParamsCopy = parameters;
ecParamsCopy.Q.X = zero;
ecParamsCopy.Q.Y = zero;
byte[] ecExplicitBlob = ECCng.GetPrimeCurveBlob(ref ecParamsCopy, ecdh: true);
ImportFullKeyBlob(ecExplicitBlob, includePrivateParameters: true);
}
else
{
byte[] ecExplicitBlob = ECCng.GetPrimeCurveBlob(ref parameters, ecdh: true);
ImportFullKeyBlob(ecExplicitBlob, includePrivateParameters);
}
}
else if (curve.IsNamed)
{
Expand All @@ -35,8 +48,20 @@ public override void ImportParameters(ECParameters parameters)
SR.Format(SR.Cryptography_InvalidCurveOid, curve.Oid.Value));
}

byte[] ecNamedCurveBlob = ECCng.GetNamedCurveBlob(ref parameters, ecdh: true);
ImportKeyBlob(ecNamedCurveBlob, curve.Oid.FriendlyName, includePrivateParamerters);
if (!hasPublicParameters && includePrivateParameters)
{
byte[] zero = new byte[parameters.D!.Length];
ECParameters ecParamsCopy = parameters;
ecParamsCopy.Q.X = zero;
ecParamsCopy.Q.Y = zero;
byte[] ecNamedCurveBlob = ECCng.GetNamedCurveBlob(ref ecParamsCopy, ecdh: true);
ImportKeyBlob(ecNamedCurveBlob, curve.Oid.FriendlyName, includePrivateParameters: true);
}
else
{
byte[] ecNamedCurveBlob = ECCng.GetNamedCurveBlob(ref parameters, ecdh: true);
ImportKeyBlob(ecNamedCurveBlob, curve.Oid.FriendlyName, includePrivateParameters);
}
}
else
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,21 +34,46 @@ public override void ImportParameters(ECParameters parameters)
ThrowIfDisposed();

ECCurve curve = parameters.Curve;
bool includePrivateParameters = (parameters.D != null);
bool includePrivateParameters = parameters.D != null;
bool hasPublicParameters = parameters.Q.X != null && parameters.Q.Y != null;

if (curve.IsPrime)
{
byte[] ecExplicitBlob = ECCng.GetPrimeCurveBlob(ref parameters, ecdh: false);
ImportFullKeyBlob(ecExplicitBlob, includePrivateParameters);
if (!hasPublicParameters && includePrivateParameters)
{
byte[] zero = new byte[parameters.D!.Length];
ECParameters ecParamsCopy = parameters;
ecParamsCopy.Q.X = zero;
ecParamsCopy.Q.Y = zero;
byte[] ecExplicitBlob = ECCng.GetPrimeCurveBlob(ref ecParamsCopy, ecdh: false);
ImportFullKeyBlob(ecExplicitBlob, includePrivateParameters: true);
}
else
{
byte[] ecExplicitBlob = ECCng.GetPrimeCurveBlob(ref parameters, ecdh: false);
ImportFullKeyBlob(ecExplicitBlob, includePrivateParameters);
}
}
else if (curve.IsNamed)
{
// FriendlyName is required; an attempt was already made to default it in ECCurve
if (string.IsNullOrEmpty(curve.Oid.FriendlyName))
throw new PlatformNotSupportedException(SR.Format(SR.Cryptography_InvalidCurveOid, curve.Oid.Value!.ToString()));

byte[] ecNamedCurveBlob = ECCng.GetNamedCurveBlob(ref parameters, ecdh: false);
ImportKeyBlob(ecNamedCurveBlob, curve.Oid.FriendlyName, includePrivateParameters);
if (!hasPublicParameters && includePrivateParameters)
{
byte[] zero = new byte[parameters.D!.Length];
ECParameters ecParamsCopy = parameters;
ecParamsCopy.Q.X = zero;
ecParamsCopy.Q.Y = zero;
byte[] ecNamedCurveBlob = ECCng.GetNamedCurveBlob(ref ecParamsCopy, ecdh: false);
ImportKeyBlob(ecNamedCurveBlob, curve.Oid.FriendlyName, includePrivateParameters: true);
}
else
{
byte[] ecNamedCurveBlob = ECCng.GetNamedCurveBlob(ref parameters, ecdh: false);
ImportKeyBlob(ecNamedCurveBlob, curve.Oid.FriendlyName, includePrivateParameters);
}
}
else
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -114,8 +114,8 @@ private static SafeEcKeyHandle ImportNamedCurveParameters(ECParameters parameter

SafeEcKeyHandle key = Interop.Crypto.EcKeyCreateByKeyParameters(
oid,
parameters.Q.X!, parameters.Q.X!.Length,
parameters.Q.Y!, parameters.Q.Y!.Length,
parameters.Q.X, parameters.Q.X?.Length ?? 0,
parameters.Q.Y, parameters.Q.Y?.Length ?? 0,
parameters.D, parameters.D == null ? 0 : parameters.D.Length);

return key;
Expand All @@ -126,8 +126,8 @@ private static SafeEcKeyHandle ImportPrimeCurveParameters(ECParameters parameter
Debug.Assert(parameters.Curve.IsPrime);
SafeEcKeyHandle key = Interop.Crypto.EcKeyCreateByExplicitParameters(
parameters.Curve.CurveType,
parameters.Q.X, parameters.Q.X!.Length,
parameters.Q.Y, parameters.Q.Y!.Length,
parameters.Q.X, parameters.Q.X?.Length ?? 0,
parameters.Q.Y, parameters.Q.Y?.Length ?? 0,
parameters.D, parameters.D == null ? 0 : parameters.D.Length,
parameters.Curve.Prime!, parameters.Curve.Prime!.Length,
parameters.Curve.A!, parameters.Curve.A!.Length,
Expand All @@ -146,8 +146,8 @@ private static SafeEcKeyHandle ImportCharacteristic2CurveParameters(ECParameters
Debug.Assert(parameters.Curve.IsCharacteristic2);
SafeEcKeyHandle key = Interop.Crypto.EcKeyCreateByExplicitParameters(
parameters.Curve.CurveType,
parameters.Q.X, parameters.Q.X!.Length,
parameters.Q.Y, parameters.Q.Y!.Length,
parameters.Q.X, parameters.Q.X?.Length ?? 0,
parameters.Q.Y, parameters.Q.Y?.Length ?? 0,
parameters.D, parameters.D == null ? 0 : parameters.D.Length,
parameters.Curve.Polynomial!, parameters.Curve.Polynomial!.Length,
parameters.Curve.A!, parameters.Curve.A!.Length,
Expand Down
Loading

0 comments on commit 6d395de

Please sign in to comment.