diff --git a/src/libraries/System.Security.Cryptography.Algorithms/ref/System.Security.Cryptography.Algorithms.cs b/src/libraries/System.Security.Cryptography.Algorithms/ref/System.Security.Cryptography.Algorithms.cs index 86b9871075b1fd..e82baf6c4857a0 100644 --- a/src/libraries/System.Security.Cryptography.Algorithms/ref/System.Security.Cryptography.Algorithms.cs +++ b/src/libraries/System.Security.Cryptography.Algorithms/ref/System.Security.Cryptography.Algorithms.cs @@ -322,6 +322,7 @@ protected ECDiffieHellman() { } public virtual byte[] DeriveKeyMaterial(System.Security.Cryptography.ECDiffieHellmanPublicKey otherPartyPublicKey) { throw null; } public virtual byte[] DeriveKeyTls(System.Security.Cryptography.ECDiffieHellmanPublicKey otherPartyPublicKey, byte[] prfLabel, byte[] prfSeed) { throw null; } public virtual byte[] ExportECPrivateKey() { throw null; } + public string ExportECPrivateKeyPem() { throw null; } public virtual System.Security.Cryptography.ECParameters ExportExplicitParameters(bool includePrivateParameters) { throw null; } public virtual System.Security.Cryptography.ECParameters ExportParameters(bool includePrivateParameters) { throw null; } public override void FromXmlString(string xmlString) { } @@ -337,6 +338,7 @@ public virtual void ImportParameters(System.Security.Cryptography.ECParameters p public override void ImportSubjectPublicKeyInfo(System.ReadOnlySpan source, out int bytesRead) { throw null; } public override string ToXmlString(bool includePrivateParameters) { throw null; } public virtual bool TryExportECPrivateKey(System.Span destination, out int bytesWritten) { throw null; } + public bool TryExportECPrivateKeyPem(System.Span destination, out int charsWritten) { throw null; } public override bool TryExportEncryptedPkcs8PrivateKey(System.ReadOnlySpan passwordBytes, System.Security.Cryptography.PbeParameters pbeParameters, System.Span destination, out int bytesWritten) { throw null; } public override bool TryExportEncryptedPkcs8PrivateKey(System.ReadOnlySpan password, System.Security.Cryptography.PbeParameters pbeParameters, System.Span destination, out int bytesWritten) { throw null; } public override bool TryExportPkcs8PrivateKey(System.Span destination, out int bytesWritten) { throw null; } @@ -367,6 +369,7 @@ protected ECDsa() { } [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCodeAttribute("The default algorithm implementations might be removed, use strong type references like 'RSA.Create()' instead.")] public static new System.Security.Cryptography.ECDsa? Create(string algorithm) { throw null; } public virtual byte[] ExportECPrivateKey() { throw null; } + public string ExportECPrivateKeyPem() { throw null; } public virtual System.Security.Cryptography.ECParameters ExportExplicitParameters(bool includePrivateParameters) { throw null; } public virtual System.Security.Cryptography.ECParameters ExportParameters(bool includePrivateParameters) { throw null; } public override void FromXmlString(string xmlString) { } @@ -396,6 +399,7 @@ public virtual void ImportParameters(System.Security.Cryptography.ECParameters p protected virtual byte[] SignHashCore(System.ReadOnlySpan hash, System.Security.Cryptography.DSASignatureFormat signatureFormat) { throw null; } public override string ToXmlString(bool includePrivateParameters) { throw null; } public virtual bool TryExportECPrivateKey(System.Span destination, out int bytesWritten) { throw null; } + public bool TryExportECPrivateKeyPem(System.Span destination, out int charsWritten) { throw null; } public override bool TryExportEncryptedPkcs8PrivateKey(System.ReadOnlySpan passwordBytes, System.Security.Cryptography.PbeParameters pbeParameters, System.Span destination, out int bytesWritten) { throw null; } public override bool TryExportEncryptedPkcs8PrivateKey(System.ReadOnlySpan password, System.Security.Cryptography.PbeParameters pbeParameters, System.Span destination, out int bytesWritten) { throw null; } public override bool TryExportPkcs8PrivateKey(System.Span destination, out int bytesWritten) { throw null; } @@ -692,7 +696,9 @@ protected RSA() { } public virtual byte[] EncryptValue(byte[] rgb) { throw null; } public abstract System.Security.Cryptography.RSAParameters ExportParameters(bool includePrivateParameters); public virtual byte[] ExportRSAPrivateKey() { throw null; } + public string ExportRSAPrivateKeyPem() { throw null; } public virtual byte[] ExportRSAPublicKey() { throw null; } + public string ExportRSAPublicKeyPem() { throw null; } public override void FromXmlString(string xmlString) { } protected virtual byte[] HashData(byte[] data, int offset, int count, System.Security.Cryptography.HashAlgorithmName hashAlgorithm) { throw null; } protected virtual byte[] HashData(System.IO.Stream data, System.Security.Cryptography.HashAlgorithmName hashAlgorithm) { throw null; } @@ -717,7 +723,9 @@ public override void ImportFromPem(System.ReadOnlySpan input) { } public override bool TryExportEncryptedPkcs8PrivateKey(System.ReadOnlySpan password, System.Security.Cryptography.PbeParameters pbeParameters, System.Span destination, out int bytesWritten) { throw null; } public override bool TryExportPkcs8PrivateKey(System.Span destination, out int bytesWritten) { throw null; } public virtual bool TryExportRSAPrivateKey(System.Span destination, out int bytesWritten) { throw null; } + public bool TryExportRSAPrivateKeyPem(System.Span destination, out int charsWritten) { throw null; } public virtual bool TryExportRSAPublicKey(System.Span destination, out int bytesWritten) { throw null; } + public bool TryExportRSAPublicKeyPem(System.Span destination, out int charsWritten) { throw null; } public override bool TryExportSubjectPublicKeyInfo(System.Span destination, out int bytesWritten) { throw null; } protected virtual bool TryHashData(System.ReadOnlySpan data, System.Span destination, System.Security.Cryptography.HashAlgorithmName hashAlgorithm, out int bytesWritten) { throw null; } public virtual bool TrySignData(System.ReadOnlySpan data, System.Span destination, System.Security.Cryptography.HashAlgorithmName hashAlgorithm, System.Security.Cryptography.RSASignaturePadding padding, out int bytesWritten) { throw null; } diff --git a/src/libraries/System.Security.Cryptography.Algorithms/src/System/Security/Cryptography/ECDiffieHellman.cs b/src/libraries/System.Security.Cryptography.Algorithms/src/System/Security/Cryptography/ECDiffieHellman.cs index 3cc29e9bd87254..d810a7f6eaf54d 100644 --- a/src/libraries/System.Security.Cryptography.Algorithms/src/System/Security/Cryptography/ECDiffieHellman.cs +++ b/src/libraries/System.Security.Cryptography.Algorithms/src/System/Security/Cryptography/ECDiffieHellman.cs @@ -631,5 +631,87 @@ public override void ImportFromEncryptedPem(ReadOnlySpan input, ReadOnlySp // override remains for compatibility. base.ImportFromEncryptedPem(input, passwordBytes); } + + /// + /// Exports the current key in the ECPrivateKey format, PEM encoded. + /// + /// A string containing the PEM-encoded ECPrivateKey. + /// + /// The key could not be exported. + /// + /// + ///

+ /// A PEM-encoded ECPrivateKey will begin with -----BEGIN EC PRIVATE KEY----- + /// and end with -----END EC PRIVATE KEY-----, with the base64 encoded DER + /// contents of the key between the PEM boundaries. + ///

+ ///

+ /// The PEM is encoded according to the IETF RFC 7468 "strict" + /// encoding rules. + ///

+ ///
+ public unsafe string ExportECPrivateKeyPem() + { + byte[] exported = ExportECPrivateKey(); + + // Fixed to prevent GC moves. + fixed (byte* pExported = exported) + { + try + { + return PemKeyHelpers.CreatePemFromData(PemLabels.EcPrivateKey, exported); + } + finally + { + CryptographicOperations.ZeroMemory(exported); + } + } + } + + /// + /// Attempts to export the current key in the PEM-encoded + /// ECPrivateKey format into a provided buffer. + /// + /// + /// The character span to receive the PEM-encoded ECPrivateKey data. + /// + /// + /// When this method returns, contains a value that indicates the number + /// of characters written to . This + /// parameter is treated as uninitialized. + /// + /// + /// if is big enough + /// to receive the output; otherwise, . + /// + /// + /// The key could not be exported. + /// + /// + ///

+ /// A PEM-encoded ECPrivateKey will begin with + /// -----BEGIN EC PRIVATE KEY----- and end with + /// -----END EC PRIVATE KEY-----, with the base64 encoded DER + /// contents of the key between the PEM boundaries. + ///

+ ///

+ /// The PEM is encoded according to the IETF RFC 7468 "strict" + /// encoding rules. + ///

+ ///
+ public bool TryExportECPrivateKeyPem(Span destination, out int charsWritten) + { + static bool Export(ECDiffieHellman alg, Span destination, out int bytesWritten) + { + return alg.TryExportECPrivateKey(destination, out bytesWritten); + } + + return PemKeyHelpers.TryExportToPem( + this, + PemLabels.EcPrivateKey, + Export, + destination, + out charsWritten); + } } } diff --git a/src/libraries/System.Security.Cryptography.Algorithms/src/System/Security/Cryptography/ECDsa.cs b/src/libraries/System.Security.Cryptography.Algorithms/src/System/Security/Cryptography/ECDsa.cs index 77d87b102f2547..4701f5d539edc5 100644 --- a/src/libraries/System.Security.Cryptography.Algorithms/src/System/Security/Cryptography/ECDsa.cs +++ b/src/libraries/System.Security.Cryptography.Algorithms/src/System/Security/Cryptography/ECDsa.cs @@ -1495,5 +1495,87 @@ public override void ImportFromEncryptedPem(ReadOnlySpan input, ReadOnlySp // override remains for compatibility. base.ImportFromEncryptedPem(input, passwordBytes); } + + /// + /// Exports the current key in the ECPrivateKey format, PEM encoded. + /// + /// A string containing the PEM-encoded ECPrivateKey. + /// + /// The key could not be exported. + /// + /// + ///

+ /// A PEM-encoded ECPrivateKey will begin with -----BEGIN EC PRIVATE KEY----- + /// and end with -----END EC PRIVATE KEY-----, with the base64 encoded DER + /// contents of the key between the PEM boundaries. + ///

+ ///

+ /// The PEM is encoded according to the IETF RFC 7468 "strict" + /// encoding rules. + ///

+ ///
+ public unsafe string ExportECPrivateKeyPem() + { + byte[] exported = ExportECPrivateKey(); + + // Fixed to prevent GC moves. + fixed (byte* pExported = exported) + { + try + { + return PemKeyHelpers.CreatePemFromData(PemLabels.EcPrivateKey, exported); + } + finally + { + CryptographicOperations.ZeroMemory(exported); + } + } + } + + /// + /// Attempts to export the current key in the PEM-encoded + /// ECPrivateKey format into a provided buffer. + /// + /// + /// The character span to receive the PEM-encoded ECPrivateKey data. + /// + /// + /// When this method returns, contains a value that indicates the number + /// of characters written to . This + /// parameter is treated as uninitialized. + /// + /// + /// if is big enough + /// to receive the output; otherwise, . + /// + /// + /// The key could not be exported. + /// + /// + ///

+ /// A PEM-encoded ECPrivateKey will begin with + /// -----BEGIN EC PRIVATE KEY----- and end with + /// -----END EC PRIVATE KEY-----, with the base64 encoded DER + /// contents of the key between the PEM boundaries. + ///

+ ///

+ /// The PEM is encoded according to the IETF RFC 7468 "strict" + /// encoding rules. + ///

+ ///
+ public bool TryExportECPrivateKeyPem(Span destination, out int charsWritten) + { + static bool Export(ECDsa alg, Span destination, out int bytesWritten) + { + return alg.TryExportECPrivateKey(destination, out bytesWritten); + } + + return PemKeyHelpers.TryExportToPem( + this, + PemLabels.EcPrivateKey, + Export, + destination, + out charsWritten); + } } } diff --git a/src/libraries/System.Security.Cryptography.Algorithms/src/System/Security/Cryptography/RSA.cs b/src/libraries/System.Security.Cryptography.Algorithms/src/System/Security/Cryptography/RSA.cs index 9aa15207646682..3a756837442c94 100644 --- a/src/libraries/System.Security.Cryptography.Algorithms/src/System/Security/Cryptography/RSA.cs +++ b/src/libraries/System.Security.Cryptography.Algorithms/src/System/Security/Cryptography/RSA.cs @@ -846,6 +846,159 @@ public override void ImportFromEncryptedPem(ReadOnlySpan input, ReadOnlySp base.ImportFromEncryptedPem(input, passwordBytes); } + /// + /// Exports the current key in the PKCS#1 RSAPrivateKey format, PEM encoded. + /// + /// A string containing the PEM-encoded PKCS#1 RSAPrivateKey. + /// + /// The key could not be exported. + /// + /// + ///

+ /// A PEM-encoded PKCS#1 RSAPrivateKey will begin with -----BEGIN RSA PRIVATE KEY----- + /// and end with -----END RSA PRIVATE KEY-----, with the base64 encoded DER + /// contents of the key between the PEM boundaries. + ///

+ ///

+ /// The PEM is encoded according to the IETF RFC 7468 "strict" + /// encoding rules. + ///

+ ///
+ public unsafe string ExportRSAPrivateKeyPem() + { + byte[] exported = ExportRSAPrivateKey(); + + // Fixed to prevent GC moves. + fixed (byte* pExported = exported) + { + try + { + return PemKeyHelpers.CreatePemFromData(PemLabels.RsaPrivateKey, exported); + } + finally + { + CryptographicOperations.ZeroMemory(exported); + } + } + } + + /// + /// Exports the public-key portion of the current key in the PKCS#1 + /// RSAPublicKey format, PEM encoded. + /// + /// A string containing the PEM-encoded PKCS#1 RSAPublicKey. + /// + /// The key could not be exported. + /// + /// + ///

+ /// A PEM-encoded PKCS#1 RSAPublicKey will begin with -----BEGIN RSA PUBLIC KEY----- + /// and end with -----END RSA PUBLIC KEY-----, with the base64 encoded DER + /// contents of the key between the PEM boundaries. + ///

+ ///

+ /// The PEM is encoded according to the IETF RFC 7468 "strict" + /// encoding rules. + ///

+ ///
+ public string ExportRSAPublicKeyPem() + { + byte[] exported = ExportRSAPublicKey(); + return PemKeyHelpers.CreatePemFromData(PemLabels.RsaPublicKey, exported); + } + + /// + /// Attempts to export the current key in the PEM-encoded PKCS#1 + /// RSAPrivateKey format into a provided buffer. + /// + /// + /// The character span to receive the PEM-encoded PKCS#1 RSAPrivateKey data. + /// + /// + /// When this method returns, contains a value that indicates the number + /// of characters written to . This + /// parameter is treated as uninitialized. + /// + /// + /// if is big enough + /// to receive the output; otherwise, . + /// + /// + /// The key could not be exported. + /// + /// + ///

+ /// A PEM-encoded PKCS#1 RSAPrivateKey will begin with + /// -----BEGIN RSA PRIVATE KEY----- and end with + /// -----END RSA PRIVATE KEY-----, with the base64 encoded DER + /// contents of the key between the PEM boundaries. + ///

+ ///

+ /// The PEM is encoded according to the IETF RFC 7468 "strict" + /// encoding rules. + ///

+ ///
+ public bool TryExportRSAPrivateKeyPem(Span destination, out int charsWritten) + { + static bool Export(RSA alg, Span destination, out int bytesWritten) + { + return alg.TryExportRSAPrivateKey(destination, out bytesWritten); + } + + return PemKeyHelpers.TryExportToPem( + this, + PemLabels.RsaPrivateKey, + Export, + destination, + out charsWritten); + } + + /// + /// Attempts to export the current key in the PEM-encoded PKCS#1 + /// RSAPublicKey format into a provided buffer. + /// + /// + /// The character span to receive the PEM-encoded PKCS#1 RSAPublicKey data. + /// + /// + /// When this method returns, contains a value that indicates the number + /// of characters written to . This + /// parameter is treated as uninitialized. + /// + /// + /// if is big enough + /// to receive the output; otherwise, . + /// + /// + /// The key could not be exported. + /// + /// + ///

+ /// A PEM-encoded PKCS#1 RSAPublicKey will begin with + /// -----BEGIN RSA PUBLIC KEY----- and end with + /// -----END RSA PUBLIC KEY-----, with the base64 encoded DER + /// contents of the key between the PEM boundaries. + ///

+ ///

+ /// The PEM is encoded according to the IETF RFC 7468 "strict" + /// encoding rules. + ///

+ ///
+ public bool TryExportRSAPublicKeyPem(Span destination, out int charsWritten) + { + static bool Export(RSA alg, Span destination, out int bytesWritten) + { + return alg.TryExportRSAPublicKey(destination, out bytesWritten); + } + + return PemKeyHelpers.TryExportToPem( + this, + PemLabels.RsaPublicKey, + Export, + destination, + out charsWritten); + } + private static void ClearPrivateParameters(in RSAParameters rsaParameters) { CryptographicOperations.ZeroMemory(rsaParameters.D); diff --git a/src/libraries/System.Security.Cryptography.Algorithms/tests/ECPemExportTests.cs b/src/libraries/System.Security.Cryptography.Algorithms/tests/ECPemExportTests.cs new file mode 100644 index 00000000000000..098cda4f2359d4 --- /dev/null +++ b/src/libraries/System.Security.Cryptography.Algorithms/tests/ECPemExportTests.cs @@ -0,0 +1,202 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.IO; +using Xunit; + +namespace System.Security.Cryptography.Algorithms.Tests +{ + [SkipOnPlatform(TestPlatforms.Browser, "Not supported on Browser")] + public static class ECPemExportTests + { + [Fact] + public static void ExportPem_ECDsa_ExportECPrivateKey() + { + string expectedPem = + "-----BEGIN EC PRIVATE KEY-----\n" + + "cGVubnk=\n" + + "-----END EC PRIVATE KEY-----"; + + static byte[] ExportECPrivateKey() + { + return new byte[] { 0x70, 0x65, 0x6e, 0x6e, 0x79 }; + } + + using (DelegateECDsa ec = new DelegateECDsa()) + { + ec.ExportECPrivateKeyDelegate = ExportECPrivateKey; + Assert.Equal(expectedPem, ec.ExportECPrivateKeyPem()); + } + } + + [Fact] + public static void ExportPem_ECDsa_TryExportECPrivateKey() + { + string expectedPem = + "-----BEGIN EC PRIVATE KEY-----\n" + + "cGVubnk=\n" + + "-----END EC PRIVATE KEY-----"; + + static bool TryExportECPrivateKey(Span destination, out int bytesWritten) + { + ReadOnlySpan result = new byte[] { 0x70, 0x65, 0x6e, 0x6e, 0x79 }; + bytesWritten = result.Length; + result.CopyTo(destination); + return true; + } + + using (DelegateECDsa ec = new DelegateECDsa()) + { + ec.TryExportECPrivateKeyDelegate = TryExportECPrivateKey; + + int written; + bool result; + char[] buffer; + + // buffer not enough + buffer = new char[expectedPem.Length - 1]; + result = ec.TryExportECPrivateKeyPem(buffer, out written); + Assert.False(result, nameof(ec.TryExportECPrivateKeyPem)); + Assert.Equal(0, written); + + // buffer just enough + buffer = new char[expectedPem.Length]; + result = ec.TryExportECPrivateKeyPem(buffer, out written); + Assert.True(result, nameof(ec.TryExportECPrivateKeyPem)); + Assert.Equal(expectedPem.Length, written); + Assert.Equal(expectedPem, new string(buffer)); + + // buffer more than enough + buffer = new char[expectedPem.Length + 20]; + buffer.AsSpan().Fill('!'); + Span bufferSpan = buffer.AsSpan(10); + result = ec.TryExportECPrivateKeyPem(bufferSpan, out written); + Assert.True(result, nameof(ec.TryExportECPrivateKeyPem)); + Assert.Equal(expectedPem.Length, written); + Assert.Equal(expectedPem, new string(bufferSpan.Slice(0, written))); + + // Ensure padding has not been touched. + AssertExtensions.FilledWith('!', buffer[0..10]); + AssertExtensions.FilledWith('!', buffer[^10..]); + } + } + [Fact] + public static void ExportPem_ECDiffieHellman_ExportECPrivateKey() + { + string expectedPem = + "-----BEGIN EC PRIVATE KEY-----\n" + + "cGVubnk=\n" + + "-----END EC PRIVATE KEY-----"; + + static byte[] ExportECPrivateKey() + { + return new byte[] { 0x70, 0x65, 0x6e, 0x6e, 0x79 }; + } + + using (DelegateECDiffieHellman ec = new DelegateECDiffieHellman()) + { + ec.ExportECPrivateKeyDelegate = ExportECPrivateKey; + Assert.Equal(expectedPem, ec.ExportECPrivateKeyPem()); + } + } + + [Fact] + public static void ExportPem_ECDiffieHellman_TryExportECPrivateKey() + { + string expectedPem = + "-----BEGIN EC PRIVATE KEY-----\n" + + "cGVubnk=\n" + + "-----END EC PRIVATE KEY-----"; + + static bool TryExportECPrivateKey(Span destination, out int bytesWritten) + { + ReadOnlySpan result = new byte[] { 0x70, 0x65, 0x6e, 0x6e, 0x79 }; + bytesWritten = result.Length; + result.CopyTo(destination); + return true; + } + + using (DelegateECDiffieHellman ec = new DelegateECDiffieHellman()) + { + ec.TryExportECPrivateKeyDelegate = TryExportECPrivateKey; + + int written; + bool result; + char[] buffer; + + // buffer not enough + buffer = new char[expectedPem.Length - 1]; + result = ec.TryExportECPrivateKeyPem(buffer, out written); + Assert.False(result, nameof(ec.TryExportECPrivateKeyPem)); + Assert.Equal(0, written); + + // buffer just enough + buffer = new char[expectedPem.Length]; + result = ec.TryExportECPrivateKeyPem(buffer, out written); + Assert.True(result, nameof(ec.TryExportECPrivateKeyPem)); + Assert.Equal(expectedPem.Length, written); + Assert.Equal(expectedPem, new string(buffer)); + + // buffer more than enough + buffer = new char[expectedPem.Length + 20]; + buffer.AsSpan().Fill('!'); + Span bufferSpan = buffer.AsSpan(10); + result = ec.TryExportECPrivateKeyPem(bufferSpan, out written); + Assert.True(result, nameof(ec.TryExportECPrivateKeyPem)); + Assert.Equal(expectedPem.Length, written); + Assert.Equal(expectedPem, new string(bufferSpan.Slice(0, written))); + + // Ensure padding has not been touched. + AssertExtensions.FilledWith('!', buffer[0..10]); + AssertExtensions.FilledWith('!', buffer[^10..]); + } + } + + private class DelegateECDsa : ECDsa + { + public delegate bool TryExportFunc(Span destination, out int bytesWritten); + + public Func ExportECPrivateKeyDelegate = null; + public TryExportFunc TryExportECPrivateKeyDelegate = null; + + + public DelegateECDsa() + { + } + + public override byte[] ExportECPrivateKey() => ExportECPrivateKeyDelegate(); + + public override bool TryExportECPrivateKey(Span destination, out int bytesWritten) => + TryExportECPrivateKeyDelegate(destination, out bytesWritten); + + public override byte[] SignHash(byte[] hash) => throw new NotImplementedException(); + public override bool VerifyHash(byte[] hash, byte[] signature) => throw new NotImplementedException(); + protected override byte[] HashData(byte[] data, int offset, int count, HashAlgorithmName hashAlgorithm) => throw new NotImplementedException(); + protected override byte[] HashData(Stream data, HashAlgorithmName hashAlgorithm) => throw new NotImplementedException(); + public override void ImportParameters(ECParameters parameters) => throw new NotImplementedException(); + public override ECParameters ExportParameters(bool includePrivateParameters) => throw new NotImplementedException(); + public override ECParameters ExportExplicitParameters(bool includePrivateParameters) => throw new NotImplementedException(); + public override void GenerateKey(ECCurve curve) => throw new NotImplementedException(); + } + + private class DelegateECDiffieHellman : ECDiffieHellman + { + public delegate bool TryExportFunc(Span destination, out int bytesWritten); + + public Func ExportECPrivateKeyDelegate = null; + public TryExportFunc TryExportECPrivateKeyDelegate = null; + + + public DelegateECDiffieHellman() + { + } + + public override byte[] ExportECPrivateKey() => ExportECPrivateKeyDelegate(); + + public override bool TryExportECPrivateKey(Span destination, out int bytesWritten) => + TryExportECPrivateKeyDelegate(destination, out bytesWritten); + + public override ECDiffieHellmanPublicKey PublicKey => throw new NotImplementedException(); + } + } +} \ No newline at end of file diff --git a/src/libraries/System.Security.Cryptography.Algorithms/tests/RSATests.cs b/src/libraries/System.Security.Cryptography.Algorithms/tests/RSATests.cs index 0e63e04b0859ad..b127365a0d4d61 100644 --- a/src/libraries/System.Security.Cryptography.Algorithms/tests/RSATests.cs +++ b/src/libraries/System.Security.Cryptography.Algorithms/tests/RSATests.cs @@ -230,6 +230,150 @@ public void RSASignaturePadding_Equality() Assert.True(RSASignaturePadding.Pkcs1 != null); } + [Fact] + public static void ExportPem_ExportRSAPublicKey() + { + string expectedPem = + "-----BEGIN RSA PUBLIC KEY-----\n" + + "cGVubnk=\n" + + "-----END RSA PUBLIC KEY-----"; + + static byte[] ExportRSAPublicKey() + { + return new byte[] { 0x70, 0x65, 0x6e, 0x6e, 0x79 }; + } + + using (DelegateRSA rsa = new DelegateRSA()) + { + rsa.ExportRSAPublicKeyDelegate = ExportRSAPublicKey; + Assert.Equal(expectedPem, rsa.ExportRSAPublicKeyPem()); + } + } + + [Fact] + public static void ExportPem_TryExportRSAPublicKey() + { + string expectedPem = + "-----BEGIN RSA PUBLIC KEY-----\n" + + "cGVubnk=\n" + + "-----END RSA PUBLIC KEY-----"; + + static bool TryExportRSAPublicKey(Span destination, out int bytesWritten) + { + ReadOnlySpan result = new byte[] { 0x70, 0x65, 0x6e, 0x6e, 0x79 }; + bytesWritten = result.Length; + result.CopyTo(destination); + return true; + } + + using (DelegateRSA rsa = new DelegateRSA()) + { + rsa.TryExportRSAPublicKeyDelegate = TryExportRSAPublicKey; + + int written; + bool result; + char[] buffer; + + // buffer not enough + buffer = new char[expectedPem.Length - 1]; + result = rsa.TryExportRSAPublicKeyPem(buffer, out written); + Assert.False(result, nameof(rsa.TryExportRSAPublicKeyPem)); + Assert.Equal(0, written); + + // buffer just enough + buffer = new char[expectedPem.Length]; + result = rsa.TryExportRSAPublicKeyPem(buffer, out written); + Assert.True(result, nameof(rsa.TryExportRSAPublicKeyPem)); + Assert.Equal(expectedPem.Length, written); + Assert.Equal(expectedPem, new string(buffer)); + + // buffer more than enough + buffer = new char[expectedPem.Length + 20]; + buffer.AsSpan().Fill('!'); + Span bufferSpan = buffer.AsSpan(10); + result = rsa.TryExportRSAPublicKeyPem(bufferSpan, out written); + Assert.True(result, nameof(rsa.TryExportRSAPublicKeyPem)); + Assert.Equal(expectedPem.Length, written); + Assert.Equal(expectedPem, new string(bufferSpan.Slice(0, written))); + + // Ensure padding has not been touched. + AssertExtensions.FilledWith('!', buffer[0..10]); + AssertExtensions.FilledWith('!', buffer[^10..]); + } + } + + [Fact] + public static void ExportPem_ExportRSAPrivateKey() + { + string expectedPem = + "-----BEGIN RSA PRIVATE KEY-----\n" + + "cGVubnk=\n" + + "-----END RSA PRIVATE KEY-----"; + + static byte[] ExportRSAPrivateKey() + { + return new byte[] { 0x70, 0x65, 0x6e, 0x6e, 0x79 }; + } + + using (DelegateRSA rsa = new DelegateRSA()) + { + rsa.ExportRSAPrivateKeyDelegate = ExportRSAPrivateKey; + Assert.Equal(expectedPem, rsa.ExportRSAPrivateKeyPem()); + } + } + + [Fact] + public static void ExportPem_TryExportRSAPrivateKey() + { + string expectedPem = + "-----BEGIN RSA PRIVATE KEY-----\n" + + "cGVubnk=\n" + + "-----END RSA PRIVATE KEY-----"; + + static bool TryExportRSAPrivateKey(Span destination, out int bytesWritten) + { + ReadOnlySpan result = new byte[] { 0x70, 0x65, 0x6e, 0x6e, 0x79 }; + bytesWritten = result.Length; + result.CopyTo(destination); + return true; + } + + using (DelegateRSA rsa = new DelegateRSA()) + { + rsa.TryExportRSAPrivateKeyDelegate = TryExportRSAPrivateKey; + + int written; + bool result; + char[] buffer; + + // buffer not enough + buffer = new char[expectedPem.Length - 1]; + result = rsa.TryExportRSAPrivateKeyPem(buffer, out written); + Assert.False(result, nameof(rsa.TryExportRSAPrivateKeyPem)); + Assert.Equal(0, written); + + // buffer just enough + buffer = new char[expectedPem.Length]; + result = rsa.TryExportRSAPrivateKeyPem(buffer, out written); + Assert.True(result, nameof(rsa.TryExportRSAPrivateKeyPem)); + Assert.Equal(expectedPem.Length, written); + Assert.Equal(expectedPem, new string(buffer)); + + // buffer more than enough + buffer = new char[expectedPem.Length + 20]; + buffer.AsSpan().Fill('!'); + Span bufferSpan = buffer.AsSpan(10); + result = rsa.TryExportRSAPrivateKeyPem(bufferSpan, out written); + Assert.True(result, nameof(rsa.TryExportRSAPrivateKeyPem)); + Assert.Equal(expectedPem.Length, written); + Assert.Equal(expectedPem, new string(bufferSpan.Slice(0, written))); + + // Ensure padding has not been touched. + AssertExtensions.FilledWith('!', buffer[0..10]); + AssertExtensions.FilledWith('!', buffer[^10..]); + } + } + private sealed class EmptyRSA : RSA { public override RSAParameters ExportParameters(bool includePrivateParameters) => throw new NotImplementedException(); @@ -240,12 +384,17 @@ private sealed class EmptyRSA : RSA private sealed class DelegateRSA : RSA { + public delegate bool TryExportFunc(Span destination, out int bytesWritten); public Func DecryptDelegate; public Func EncryptDelegate; public Func SignHashDelegate = null; public Func VerifyHashDelegate = null; public Func HashDataArrayDelegate = null; public Func HashDataStreamDelegate = null; + public Func ExportRSAPublicKeyDelegate = null; + public TryExportFunc TryExportRSAPublicKeyDelegate = null; + public Func ExportRSAPrivateKeyDelegate = null; + public TryExportFunc TryExportRSAPrivateKeyDelegate = null; public override byte[] Encrypt(byte[] data, RSAEncryptionPadding padding) => EncryptDelegate(data, padding); @@ -265,6 +414,15 @@ protected override byte[] HashData(byte[] data, int offset, int count, HashAlgor protected override byte[] HashData(Stream data, HashAlgorithmName hashAlgorithm) => HashDataStreamDelegate(data, hashAlgorithm); + public override byte[] ExportRSAPublicKey() => ExportRSAPublicKeyDelegate(); + public override byte[] ExportRSAPrivateKey() => ExportRSAPrivateKeyDelegate(); + + public override bool TryExportRSAPublicKey(Span destination, out int bytesWritten) => + TryExportRSAPublicKeyDelegate(destination, out bytesWritten); + + public override bool TryExportRSAPrivateKey(Span destination, out int bytesWritten) => + TryExportRSAPrivateKeyDelegate(destination, out bytesWritten); + public new bool TryHashData(ReadOnlySpan source, Span destination, HashAlgorithmName hashAlgorithm, out int bytesWritten) => base.TryHashData(source, destination, hashAlgorithm, out bytesWritten); diff --git a/src/libraries/System.Security.Cryptography.Algorithms/tests/System.Security.Cryptography.Algorithms.Tests.csproj b/src/libraries/System.Security.Cryptography.Algorithms/tests/System.Security.Cryptography.Algorithms.Tests.csproj index f4f089bea4cf0f..d81c2cd9ea75a5 100644 --- a/src/libraries/System.Security.Cryptography.Algorithms/tests/System.Security.Cryptography.Algorithms.Tests.csproj +++ b/src/libraries/System.Security.Cryptography.Algorithms/tests/System.Security.Cryptography.Algorithms.Tests.csproj @@ -179,6 +179,7 @@ +