Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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 @@ -12,8 +12,6 @@ namespace System.Security.Cryptography
[Experimental(Experimentals.PostQuantumCryptographyDiagId, UrlFormat = Experimentals.SharedUrlFormat)]
public sealed class CompositeMLDsaAlgorithm : IEquatable<CompositeMLDsaAlgorithm>
{
internal const int RandomizerSizeInBytes = 32;

/// <summary>
/// Gets the name of the algorithm.
/// </summary>
Expand Down Expand Up @@ -454,8 +452,8 @@ private static CompositeMLDsaAlgorithm CreateRsa(
mldsaAlgorithm.PrivateSeedSizeInBytes + maxRsaPrivateKeySizeInBytes,
mldsaAlgorithm.PublicKeySizeInBytes + keySizeInBytes, // Private key contains at least n
mldsaAlgorithm.PublicKeySizeInBytes + maxRsaPublicKeySizeInBytes,
RandomizerSizeInBytes + mldsaAlgorithm.SignatureSizeInBytes + keySizeInBytes,
RandomizerSizeInBytes + mldsaAlgorithm.SignatureSizeInBytes + keySizeInBytes,
mldsaAlgorithm.SignatureSizeInBytes + keySizeInBytes,
mldsaAlgorithm.SignatureSizeInBytes + keySizeInBytes,
oid);
}

Expand All @@ -465,29 +463,8 @@ private static CompositeMLDsaAlgorithm CreateECDsa(
int keySizeInBits,
string oid)
{
// The key size calculation depends on the size of the curve algorithm's OID, and only includes the curves
// supported at the time of writing this code. If more curves are added, ensure the OID length is accounted for in the calculation.
Debug.Assert(oid is
Oids.MLDsa44WithECDsaP256PreHashSha256 or
Oids.MLDsa65WithECDsaP256PreHashSha512 or
Oids.MLDsa65WithECDsaP384PreHashSha512 or
Oids.MLDsa87WithECDsaP384PreHashSha512 or
Oids.MLDsa87WithECDsaP521PreHashSha512 or
Oids.MLDsa65WithECDsaBrainpoolP256r1PreHashSha512 or
Oids.MLDsa87WithECDsaBrainpoolP384r1PreHashSha512);

int keySizeInBytes = (keySizeInBits + 7) / 8;

const int MaxUniversalTagLength = 1;

// long form prefix and 4 bytes for length. CLR arrays and spans only support length up to int.MaxValue.
// Padding with leading zero bytes is allowed, but we still limit the length to 4 bytes since the only
// plausible scenario would be encoding a 4-byte numeric data type without trimming.
// Note this bound also covers indefinite length encodings which require only 1 + 2 bytes of overhead.
const int MaxLengthLength = 1 + 4;

const int MaxPrefixLength = MaxUniversalTagLength + MaxLengthLength;

// RFC 5915, Section 3
// ECPrivateKey ::= SEQUENCE {
// version INTEGER { ecPrivkeyVer1(1) } (ecPrivkeyVer1),
Expand All @@ -496,51 +473,32 @@ Oids.MLDsa65WithECDsaBrainpoolP256r1PreHashSha512 or
// publicKey [1] BIT STRING OPTIONAL
// }

int maxPrivateKeySizeInBytes =
MaxPrefixLength +
(
// version
MaxPrefixLength + 1 + // Version should always be 1

// privateKey
MaxPrefixLength + keySizeInBytes +

// parameters
1 + MaxLengthLength + // Explicit tag
(
// RFC5480, Section 2.1.1
// ECParameters ::= CHOICE {
// namedCurve OBJECT IDENTIFIER
// -- implicitCurve NULL
// -- specifiedCurve SpecifiedECDomain
// }
// -- implicitCurve and specifiedCurve MUST NOT be used in PKIX.
//
// So this is a CHOICE with with the only option being a namedCurve OBJECT IDENTIFIER.
//
// Curve | OID | DER Encoding with prefix | Length without prefix
// ----------------|-----------------------|----------------------------------|----------------------
// secp256r1 | 1.2.840.10045.3.1.7 | 06 08 2A 86 48 CE 3D 03 01 07 | 8
// secp384r1 | 1.3.132.0.34 | 06 05 2B 81 04 00 22 | 5
// secp521r1 | 1.3.132.0.35 | 06 05 2B 81 04 00 23 | 5
// brainpoolP256r1 | 1.3.36.3.3.2.8.1.1.7 | 06 09 2B 24 03 03 02 08 01 01 07 | 9
// brainpoolP384r1 | 1.3.36.3.3.2.8.1.1.11 | 06 09 2B 24 03 03 02 08 01 01 0B | 9
MaxPrefixLength + 9 // This doesn't need to be exact, but it does need to consider all supported curves.
) +

// publicKey
1 + MaxLengthLength + // Explicit tag
1 + 2 * keySizeInBytes
);
int versionSizeInBytes =
1 + // Tag for INTEGER
1 + // Length field
1; // Value (always 1)

int privateKeySizeInBytes =
1 + // Tag for OCTET STRING
GetDerLengthLength(keySizeInBytes) + // Length field
keySizeInBytes; // Value

// parameters and publicKey must be omitted for Composite ML-DSA

int ecPrivateKeySizeInBytes =
1 + // Tag for SEQUENCE
GetDerLengthLength(versionSizeInBytes + privateKeySizeInBytes) + // Length field
versionSizeInBytes + // Version
privateKeySizeInBytes;

return new CompositeMLDsaAlgorithm(
name,
mldsaAlgorithm.PrivateSeedSizeInBytes + keySizeInBytes, // ECPrivateKey has at least the private key
mldsaAlgorithm.PrivateSeedSizeInBytes + maxPrivateKeySizeInBytes,
mldsaAlgorithm.PrivateSeedSizeInBytes + ecPrivateKeySizeInBytes,
mldsaAlgorithm.PrivateSeedSizeInBytes + ecPrivateKeySizeInBytes,
mldsaAlgorithm.PublicKeySizeInBytes + 1 + 2 * keySizeInBytes,
mldsaAlgorithm.PublicKeySizeInBytes + 1 + 2 * keySizeInBytes,
RandomizerSizeInBytes + mldsaAlgorithm.SignatureSizeInBytes + 2 + 3 * 2, // 2 non-zero INTEGERS and overhead for 3 ASN.1 values
RandomizerSizeInBytes + mldsaAlgorithm.SignatureSizeInBytes + AsymmetricAlgorithmHelpers.GetMaxDerSignatureSize(keySizeInBits),
mldsaAlgorithm.SignatureSizeInBytes + 2 + 3 * 2, // 2 non-zero INTEGERS and overhead for 3 ASN.1 values
mldsaAlgorithm.SignatureSizeInBytes + AsymmetricAlgorithmHelpers.GetMaxDerSignatureSize(keySizeInBits),
oid);
}

Expand All @@ -559,9 +517,28 @@ private static CompositeMLDsaAlgorithm CreateEdDsa(
mldsaAlgorithm.PrivateSeedSizeInBytes + keySizeInBytes,
mldsaAlgorithm.PublicKeySizeInBytes + keySizeInBytes,
mldsaAlgorithm.PublicKeySizeInBytes + keySizeInBytes,
RandomizerSizeInBytes + mldsaAlgorithm.SignatureSizeInBytes + 2 * keySizeInBytes,
RandomizerSizeInBytes + mldsaAlgorithm.SignatureSizeInBytes + 2 * keySizeInBytes,
mldsaAlgorithm.SignatureSizeInBytes + 2 * keySizeInBytes,
mldsaAlgorithm.SignatureSizeInBytes + 2 * keySizeInBytes,
oid);
}

private static int GetDerLengthLength(int payloadLength)
{
Debug.Assert(payloadLength >= 0);

if (payloadLength <= 0x7F)
return 1;

if (payloadLength <= 0xFF)
return 2;

if (payloadLength <= 0xFFFF)
return 3;

if (payloadLength <= 0xFFFFFF)
return 4;

return 5;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -71,30 +71,13 @@ public static unsafe ECDsaComponent ImportPrivateKey(ECDsaAlgorithm algorithm, R
{
ECPrivateKey ecPrivateKey = ECPrivateKey.Decode(manager.Memory, AsnEncodingRules.BER);

if (ecPrivateKey.Version != 1)
if (ecPrivateKey.Version != 1 ||
ecPrivateKey.Parameters is not null ||
ecPrivateKey.PublicKey is not null)
{
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.CurveOidValue)
{
// 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))
Expand All @@ -107,8 +90,8 @@ public static unsafe ECDsaComponent ImportPrivateKey(ECDsaAlgorithm algorithm, R
Curve = algorithm.Curve,
Q = new ECPoint
{
X = x,
Y = y,
X = null,
Y = null,
},
D = d
};
Expand All @@ -121,14 +104,10 @@ public static unsafe ECDsaComponent ImportPrivateKey(ECDsaAlgorithm algorithm, R
#error ECDsa.Create(ECParameters) is avaliable in .NET Framework 4.7.2 and later, so this workaround is not needed anymore.
#endif
Debug.Assert(!string.IsNullOrEmpty(algorithm.CurveOid.FriendlyName));
Debug.Assert(x is null == y is null);

if (x is null)
{
byte[] zero = new byte[d.Length];
x = zero;
y = zero;
}
byte[] zero = new byte[d.Length];
byte[] x = zero;
byte[] y = zero;

if (!TryValidateNamedCurve(x, y, d))
{
Expand Down Expand Up @@ -227,7 +206,7 @@ internal override bool TryExportPrivateKey(Span<byte> destination, out int bytes

try
{
WriteKey(ecParameters.D, ecParameters.Q.X, ecParameters.Q.Y, _algorithm.CurveOidValue, writer);
WriteKey(ecParameters.D, writer);
return writer.TryEncode(destination, out bytesWritten);
}
finally
Expand Down Expand Up @@ -260,7 +239,7 @@ internal override bool TryExportPrivateKey(Span<byte> destination, out int bytes
throw new CryptographicException();
}

WriteKey(d, x, y, _algorithm.CurveOidValue, writer);
WriteKey(d, writer);
return true;
});
});
Expand All @@ -273,7 +252,7 @@ internal override bool TryExportPrivateKey(Span<byte> destination, out int bytes
}
#endif

static void WriteKey(byte[] d, byte[]? x, byte[]? y, string curveOid, AsnWriter writer)
static void WriteKey(byte[] d, AsnWriter writer)
{
// ECPrivateKey
using (writer.PushSequence())
Expand All @@ -283,23 +262,6 @@ static void WriteKey(byte[] d, byte[]? x, byte[]? y, string curveOid, AsnWriter

// 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);
}
}
}
}
}
Expand Down
Loading
Loading