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

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

Closed
vcsjones opened this issue Nov 12, 2021 · 6 comments · Fixed by #61753
Closed

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

vcsjones opened this issue Nov 12, 2021 · 6 comments · Fixed by #61753
Assignees
Labels
api-approved API was approved in API review, it can be implemented area-System.Security
Milestone

Comments

@vcsjones
Copy link
Member

vcsjones commented Nov 12, 2021

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.

@vcsjones vcsjones added api-suggestion Early API idea and discussion, it is NOT ready for implementation area-System.Security labels Nov 12, 2021
@dotnet-issue-labeler dotnet-issue-labeler bot added the untriaged New issue has not been triaged by the area owner label Nov 12, 2021
@ghost
Copy link

ghost commented Nov 12, 2021

Tagging subscribers to this area: @bartonjs, @vcsjones, @krwq, @GrabYourPitchforks
See info in area-owners.md if you want to be subscribed.

Issue Details

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 quite a bit 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.

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.

Author: vcsjones
Assignees: -
Labels:

api-suggestion, area-System.Security

Milestone: -

@bartonjs
Copy link
Member

@terrajobst / @stephentoub Would this cause a problem for/from .NET Standard 2.0? I... think it'd be fine; but I'm clearly not confident enough to bring this to the big meeting without first seeking a second assention explicitly 😄.

@stephentoub
Copy link
Member

stephentoub commented Nov 12, 2021

Adding a new intermediate base type shouldn't be a breaking change.

@bartonjs bartonjs added api-ready-for-review API is ready for review, it is NOT ready for implementation and removed api-suggestion Early API idea and discussion, it is NOT ready for implementation untriaged New issue has not been triaged by the area owner labels Nov 12, 2021
@bartonjs bartonjs added this to the 7.0.0 milestone Nov 12, 2021
@terrajobst
Copy link
Member

Yes, we have inserted new types in an existing hierarchy before, most notably TypeInfo. We consider this a non-breaking change, but there are cornercases, like overrides that call base. The compiler burned in a non-virtual call o the old base class. If you run against the new version that code doesn't run against the inserted type, it runs against the old base. So your design cannot assume that the new inserted base is called.

@GrabYourPitchforks
Copy link
Member

The compiler burned in a non-virtual call o the old base class. If you run against the new version that code doesn't run against the inserted type, it runs against the old base. So your design cannot assume that the new inserted base is called.

See also #59289.

@bartonjs
Copy link
Member

bartonjs commented Nov 16, 2021

Video

Looks good as proposed

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);
    }
}

@bartonjs bartonjs added api-approved API was approved in API review, it can be implemented and removed api-ready-for-review API is ready for review, it is NOT ready for implementation labels Nov 16, 2021
@vcsjones vcsjones self-assigned this Nov 16, 2021
@ghost ghost added the in-pr There is an active PR which will close this issue when it is merged label Nov 17, 2021
@ghost ghost removed the in-pr There is an active PR which will close this issue when it is merged label Nov 22, 2021
@dotnet dotnet locked as resolved and limited conversation to collaborators Dec 23, 2021
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
api-approved API was approved in API review, it can be implemented area-System.Security
Projects
None yet
Development

Successfully merging a pull request may close this issue.

5 participants