Skip to content

[API Proposal]: Introduce a common base type for ECDsa and ECDiffieHellman #61516

@vcsjones

Description

@vcsjones

Background and motivation

Today, ECDsa and ECDiffieHellman directly derive from AsymmetricAlgorithm. Though they offer two different purposes, signing and key exchange respectively, they have quite a bit of shared API surface and functionality because they both use EC keys.

The lack of a common base type means there is a lot of duplication between the two, from unit tests to actual implementation in dotnet/runtime itself. For example, many of the key tests use generics and indirection so the tests are not duplicated1, 2.

Given the increasing shared API surface, I would propose introducing a new abstract class between ECDsa / ECDiffieHellman and AsymmetricAlgorithm.

After looking are the breaking change docs, I believe this type of change is allowed:

✓ Allowed
Introducing a new base class

So long as it does not introduce any new abstract members or change the semantics or behavior of existing members, a type can be introduced into a hierarchy between two existing types. For example, between .NET Framework 1.1 and .NET Framework 2.0, we introduced DbConnection as a new base class for SqlConnection which previously derived from Component.

and for pushing the virtuals down:

✓ Allowed
Moving a method onto a class higher in the hierarchy tree of the type from which it was removed

API Proposal

namespace System.Security.Cryptography
{
+    public abstract class ECAlgorithm : AsymmetricAlgorithm
+    {
#       Existing implementations on ECDsa and ECDiffieHellman that throw NotImplementedException
#       These will throw NotImplementedException, as they do now.
+       public virtual void ImportParameters(ECParameters parameters);
+       public virtual ECParameters ExportParameters(bool includePrivateParameters);
+       public virtual ECParameters ExportExplicitParameters(bool includePrivateParameters);
+       public virtual void GenerateKey(ECCurve curve);

#       Virtuals on ECDsa and ECDiffieHellman with identical implementations.
#       We could push the implementation down to this type.
+       public virtual void ImportECPrivateKey(ReadOnlySpan<byte> source, out int bytesRead);
+       public virtual byte[] ExportECPrivateKey();
+       public virtual bool TryExportECPrivateKey(Span<byte> destination, out int bytesWritten);

#       Overrides from ECDsa and ECDiffieHellman that have identical implementation and
#       can be pushed down to this type.
+       public override void ImportFromPem(ReadOnlySpan<char> input);
+       public override void ImportSubjectPublicKeyInfo(ReadOnlySpan<byte> source, out int bytesRead);
+       public override bool TryExportSubjectPublicKeyInfo(Span<byte> destination, out int bytesWritten);
+       public override void ImportEncryptedPkcs8PrivateKey(ReadOnlySpan<byte> passwordBytes, ReadOnlySpan<byte> source, out int bytesRead);
+       public override void ImportPkcs8PrivateKey(ReadOnlySpan<byte> source, out int bytesRead);
+       public override void TryExportEncryptedPkcs8PrivateKey(ReadOnlySpan<char> password, PbeParameters pbeParameters, Span<byte> destination, out int bytesWritten);

#       APIs that are new in .NET 7 and are not too late to move from ECDsa and ECDiffieHellman
#       since they are non-virtual but identical implementation.
+       public string ExportECPrivateKeyPem();
+       public bool TryExportECPrivateKeyPem(Span<char> destination, out int charsWritten);
+    }

-   public abstract class ECDsa : AsymmetricAlgorithm
+   public abstract class ECDsa : ECAlgorithm
    {
#       existing virtuals that are now handled by the base class.
#       if we need the members to explicitly exist on this type, then they
#       can become overrides that simply call `base.` I've been told that the
#       CLR correctly handles dispatching to the base type when virtuals are removed.
-       public virtual void ImportParameters(ECParameters parameters);
-       public virtual ECParameters ExportParameters(bool includePrivateParameters);
-       public virtual ECParameters ExportExplicitParameters(bool includePrivateParameters);
-       public virtual void GenerateKey(ECCurve curve);
-       public virtual void ImportECPrivateKey(ReadOnlySpan<byte> source, out int bytesRead);
-       public virtual byte[] ExportECPrivateKey();
-       public virtual bool TryExportECPrivateKey(Span<byte> destination, out int bytesWritten);

#      overrides that can be removed since they are now handled by the base
-      public override void ImportFromPem(ReadOnlySpan<char> input);
-      public override void ImportSubjectPublicKeyInfo(ReadOnlySpan<byte> source, out int bytesRead);
-      public override bool TryExportSubjectPublicKeyInfo(Span<byte> destination, out int bytesWritten);
-      public override void ImportEncryptedPkcs8PrivateKey(ReadOnlySpan<byte> passwordBytes, ReadOnlySpan<byte> source, out int bytesRead);
-      public override void ImportPkcs8PrivateKey(ReadOnlySpan<byte> source, out int bytesRead);
-      public override void TryExportEncryptedPkcs8PrivateKey(ReadOnlySpan<char> password, PbeParameters pbeParameters, Span<byte> destination, out int bytesWritten);

#      These are non virtual with identical implementations between ECDsa and ECDiffieHellman
#      They have not shipped in any .NET 7 so we can move them down if this proposal gets accepted for .NET 7.
-      public string ExportECPrivateKeyPem();
-      public bool TryExportECPrivateKeyPem(Span<char> destination, out int charsWritten);
    }

-   public abstract class ECDiffieHellman : AsymmetricAlgorithm
+   public abstract class ECDiffieHellman : ECAlgorithm
    {
#       existing virtuals that are now handled by the base class.
#       if we need the members to explicitly exist on this type, then they
#       can become overrides that simply call `base.` I've been told that the
#       CLR correctly handles dispatching to the base type when virtuals are removed.
-       public virtual void ImportParameters(ECParameters parameters);
-       public virtual ECParameters ExportParameters(bool includePrivateParameters);
-       public virtual ECParameters ExportExplicitParameters(bool includePrivateParameters);
-       public virtual void GenerateKey(ECCurve curve);
-       public virtual void ImportECPrivateKey(ReadOnlySpan<byte> source, out int bytesRead);
-       public virtual byte[] ExportECPrivateKey();
-       public virtual bool TryExportECPrivateKey(Span<byte> destination, out int bytesWritten);

#      overrides that can be removed since they are now handled by the base
-      public override void ImportFromPem(ReadOnlySpan<char> input);
-      public override void ImportSubjectPublicKeyInfo(ReadOnlySpan<byte> source, out int bytesRead);
-      public override bool TryExportSubjectPublicKeyInfo(Span<byte> destination, out int bytesWritten);
-      public override void ImportEncryptedPkcs8PrivateKey(ReadOnlySpan<byte> passwordBytes, ReadOnlySpan<byte> source, out int bytesRead);
-      public override void ImportPkcs8PrivateKey(ReadOnlySpan<byte> source, out int bytesRead);
-      public override void TryExportEncryptedPkcs8PrivateKey(ReadOnlySpan<char> password, PbeParameters pbeParameters, Span<byte> destination, out int bytesWritten);

#      These are non virtual with identical implementations between ECDsa and ECDiffieHellman
#      They have not shipped in any .NET 7 so we can move them down if this proposal gets accepted for .NET 7.
-      public string ExportECPrivateKeyPem();
-      public bool TryExportECPrivateKeyPem(Span<char> destination, out int charsWritten);
    }
}

API Usage

No particular API usage to demonstrate but this box is required. Hovering over this text will have a tooltip of a squid, though.

Alternative Designs

Do nothing.

Risks

Still perhaps somehow disruptive in an unforeseen way.

Metadata

Metadata

Assignees

Labels

Type

No type

Projects

No projects

Milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions