Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 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
@@ -1,7 +1,11 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Buffers;
using System.Diagnostics;
using System.Formats.Asn1;
using System.Runtime.InteropServices;
using System.Security.Cryptography.Asn1;

namespace System.Security.Cryptography
{
Expand All @@ -26,7 +30,9 @@ private ECDsaComponent(ECDsa ecdsa, ECDsaAlgorithm algorithm)
_algorithm = algorithm;
}

// OpenSSL supports the brainpool curves so this can be relaxed on a per-platform basis in the future if desired.
// While some of our OSes support the brainpool curves, not all do.
// Limit this implementation to the NIST curves until we have a better understanding
// of where native implementations of composite are aligning.
public static bool IsAlgorithmSupported(ECDsaAlgorithm algorithm) =>
#if NET
algorithm.CurveOid is Oids.secp256r1 or Oids.secp384r1 or Oids.secp521r1;
Expand All @@ -37,79 +43,96 @@ public static bool IsAlgorithmSupported(ECDsaAlgorithm algorithm) =>
public static ECDsaComponent GenerateKey(ECDsaAlgorithm algorithm)
{
#if NET
ECDsa? ecdsa = null;

try
{
ecdsa = algorithm.CurveOid switch
{
Oids.secp256r1 => ECDsa.Create(ECCurve.NamedCurves.nistP256),
Oids.secp384r1 => ECDsa.Create(ECCurve.NamedCurves.nistP384),
Oids.secp521r1 => ECDsa.Create(ECCurve.NamedCurves.nistP521),
string oid => FailAndThrow<ECDsa>(oid)
};

static T FailAndThrow<T>(string oid)
{
Debug.Fail($"EC-DSA curve not supported ({oid})");
throw new CryptographicException();
}

// DSA key generation is lazy, so we need to force it to happen eagerly.
ecdsa.ExportParameters(includePrivateParameters: false);

return new ECDsaComponent(ecdsa, algorithm);
}
catch (CryptographicException)
{
ecdsa?.Dispose();
throw;
}
return new ECDsaComponent(ECDsa.Create(algorithm.Curve), algorithm);
#else
throw new PlatformNotSupportedException();
#endif
}

public static ECDsaComponent ImportPrivateKey(ECDsaAlgorithm algorithm, ReadOnlySpan<byte> source)
public static unsafe ECDsaComponent ImportPrivateKey(ECDsaAlgorithm algorithm, ReadOnlySpan<byte> source)
{
#if NET
ECDsa? ecdsa = null;

try
{
ecdsa = ECDsa.Create();
ecdsa.ImportECPrivateKey(source, out int bytesRead);

if (bytesRead != source.Length)
AsnDecoder.ReadEncodedValue(
source,
AsnEncodingRules.BER,
out _,
out _,
out int firstValueLength);

if (firstValueLength != source.Length)
{
throw new CryptographicException(SR.Argument_PrivateKeyWrongSizeForAlgorithm);
throw new CryptographicException(SR.Cryptography_Der_Invalid_Encoding);
}

ECParameters parameters = ecdsa.ExportParameters(includePrivateParameters: false);

if (!parameters.Curve.IsNamed || parameters.Curve.Oid.Value != algorithm.CurveOid)
fixed (byte* ptr = &MemoryMarshal.GetReference(source))
{
// The curve specified in ECDomainParameters of ECPrivateKey do not match the required curve for
// the Composite ML-DSA algorithm.
throw new CryptographicException(SR.Cryptography_Der_Invalid_Encoding);
}
using (MemoryManager<byte> manager = new PointerMemoryManager<byte>(ptr, firstValueLength))
{
ECPrivateKey ecPrivateKey = ECPrivateKey.Decode(manager.Memory, AsnEncodingRules.BER);

if (ecPrivateKey.Version != 1)
{
throw new CryptographicException(SR.Cryptography_Der_Invalid_Encoding);
}

// If domain parameters are present, validate that they match the composite ML-DSA algorithm.
if (ecPrivateKey.Parameters is ECDomainParameters domainParameters)
{
if (domainParameters.Named is not string curveOid || curveOid != algorithm.CurveOid)
{
// The curve specified must be named and match the required curve for the composite ML-DSA algorithm.
throw new CryptographicException(SR.Cryptography_Der_Invalid_Encoding);
}
}

byte[]? x = null;
byte[]? y = null;

// If public key is present, add it to the parameters.
if (ecPrivateKey.PublicKey is ReadOnlyMemory<byte> publicKey)
{
EccKeyFormatHelper.GetECPointFromUncompressedPublicKey(publicKey.Span, algorithm.KeySizeInBytes, out x, out y);
}

byte[] d = new byte[ecPrivateKey.PrivateKey.Length];

using (PinAndClear.Track(d))
{
ecPrivateKey.PrivateKey.CopyTo(d);

return new ECDsaComponent(ecdsa, algorithm);
#if NET
ECParameters parameters = new ECParameters
{
Curve = algorithm.Curve,
Q = new ECPoint
{
X = x,
Y = y,
},
D = d
};

parameters.Validate();

return new ECDsaComponent(ECDsa.Create(parameters), algorithm);
#else
throw new PlatformNotSupportedException();
#endif
}
}
}
}
catch (CryptographicException)
catch (AsnContentException e)
{
ecdsa?.Dispose();
throw;
throw new CryptographicException(SR.Cryptography_Der_Invalid_Encoding, e);
}
#else
throw new PlatformNotSupportedException();
#endif

}

public static unsafe ECDsaComponent ImportPublicKey(ECDsaAlgorithm algorithm, ReadOnlySpan<byte> source)
{
#if NET
int fieldWidth = (algorithm.KeySizeInBits + 7) / 8;
int fieldWidth = algorithm.KeySizeInBytes;

if (source.Length != 1 + fieldWidth * 2)
{
Expand All @@ -124,28 +147,18 @@ public static unsafe ECDsaComponent ImportPublicKey(ECDsaAlgorithm algorithm, Re
throw new CryptographicException(SR.Cryptography_NotValidPublicOrPrivateKey);
}

#if NET
ECParameters parameters = new ECParameters()
{
Curve = ECCurve.CreateFromValue(algorithm.CurveOid),
Curve = algorithm.Curve,
Q = new ECPoint()
{
X = source.Slice(1, fieldWidth).ToArray(),
Y = source.Slice(1 + fieldWidth).ToArray(),
}
};

ECDsa? ecdsa = null;

try
{
ecdsa = ECDsa.Create(parameters);
return new ECDsaComponent(ecdsa, algorithm);
}
catch (CryptographicException)
{
ecdsa?.Dispose();
throw;
}
return new ECDsaComponent(ECDsa.Create(parameters), algorithm);
#else
throw new PlatformNotSupportedException();
#endif
Expand All @@ -154,16 +167,84 @@ public static unsafe ECDsaComponent ImportPublicKey(ECDsaAlgorithm algorithm, Re
internal override bool TryExportPrivateKey(Span<byte> destination, out int bytesWritten)
{
#if NET
return _ecdsa.TryExportECPrivateKey(destination, out bytesWritten);
ECParameters ecParameters = _ecdsa.ExportParameters(includePrivateParameters: true);

Debug.Assert(ecParameters.D != null);

using (PinAndClear.Track(ecParameters.D))
{
ecParameters.Validate();

if (ecParameters.D.Length != _algorithm.KeySizeInBytes)
{
Debug.Fail("Unexpected key size.");
throw new CryptographicException();
}

// The curve OID must match the composite ML-DSA algorithm.
if (!ecParameters.Curve.IsNamed ||
(ecParameters.Curve.Oid.Value != _algorithm.Curve.Oid.Value && ecParameters.Curve.Oid.FriendlyName != _algorithm.Curve.Oid.FriendlyName))
{
Debug.Fail("Unexpected curve OID.");
throw new CryptographicException();
}

return TryWriteKey(ecParameters.D, ecParameters.Q.X, ecParameters.Q.Y, _algorithm.CurveOid, destination, out bytesWritten);
}
#else
throw new PlatformNotSupportedException();
return TryWriteKey(d: [], x: [], y: [], curveOid: null, destination, out bytesWritten);
#endif

static bool TryWriteKey(byte[] d, byte[]? x, byte[]? y, string curveOid, Span<byte> destination, out int bytesWritten)
{
#if NET
AsnWriter writer = new AsnWriter(AsnEncodingRules.DER);

try
{
// ECPrivateKey
using (writer.PushSequence())
{
// version 1
writer.WriteInteger(1);

// privateKey
writer.WriteOctetString(d);

// domainParameters
using (writer.PushSequence(new Asn1Tag(TagClass.ContextSpecific, 0, isConstructed: true)))
{
writer.WriteObjectIdentifier(curveOid);
}

// publicKey
if (x != null)
{
Debug.Assert(y != null);

using (writer.PushSequence(new Asn1Tag(TagClass.ContextSpecific, 1, isConstructed: true)))
{
EccKeyFormatHelper.WriteUncompressedPublicKey(x, y, writer);
}
}
}

return writer.TryEncode(destination, out bytesWritten);
}
finally
{
writer.Reset();
}
#else
throw new PlatformNotSupportedException();
#endif
}
}

internal override bool TryExportPublicKey(Span<byte> destination, out int bytesWritten)
{
#if NET
int fieldWidth = (_algorithm.KeySizeInBits + 7) / 8;
int fieldWidth = _algorithm.KeySizeInBytes;

if (destination.Length < 1 + 2 * fieldWidth)
{
Expand All @@ -173,21 +254,21 @@ internal override bool TryExportPublicKey(Span<byte> destination, out int bytesW
return false;
}

ECParameters parameters = _ecdsa.ExportParameters(includePrivateParameters: false);
ECParameters ecParameters = _ecdsa.ExportParameters(includePrivateParameters: false);

ecParameters.Validate();

if (parameters.Q.X is not byte[] x ||
parameters.Q.Y is not byte[] y ||
x.Length != fieldWidth ||
y.Length != fieldWidth)
if (ecParameters.Q.X?.Length != fieldWidth)
{
Debug.Fail("Unexpected key size.");
throw new CryptographicException();
}

// Uncompressed ECPoint format
destination[0] = 0x04;

x.CopyTo(destination.Slice(1, fieldWidth));
y.CopyTo(destination.Slice(1 + fieldWidth));
ecParameters.Q.X.CopyTo(destination.Slice(1, fieldWidth));
ecParameters.Q.Y.CopyTo(destination.Slice(1 + fieldWidth));

bytesWritten = 1 + 2 * fieldWidth;
return true;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -213,7 +213,7 @@ public static RsaComponent ImportPublicKey(RsaAlgorithm algorithm, ReadOnlySpan<
#endif
if (rsa.KeySize != algorithm.KeySizeInBits)
{
throw new CryptographicException(SR.Argument_PrivateKeyWrongSizeForAlgorithm);
throw new CryptographicException(SR.Argument_PublicKeyWrongSizeForAlgorithm);
}

if (bytesRead != source.Length)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -727,6 +727,30 @@ private sealed class ECDsaAlgorithm(int keySizeInBits, string curveOid, HashAlgo
internal int KeySizeInBits { get; } = keySizeInBits;
internal HashAlgorithmName HashAlgorithmName { get; } = hashAlgorithmName;
internal string CurveOid { get; } = curveOid;

internal int KeySizeInBytes => (KeySizeInBits + 7) / 8;

#if NET
internal ECCurve Curve
{
get
{
return CurveOid switch
{
Oids.secp256r1 => ECCurve.NamedCurves.nistP256,
Oids.secp384r1 => ECCurve.NamedCurves.nistP384,
Oids.secp521r1 => ECCurve.NamedCurves.nistP521,
string oid => FailAndThrow(oid)
};

static ECCurve FailAndThrow(string oid)
{
Debug.Fail($"EC-DSA curve not supported ({oid})");
throw new CryptographicException();
}
}
}
#endif
}

private sealed class EdDsaAlgorithm
Expand Down
Loading