Skip to content

Commit 3ec4cc7

Browse files
vcsjonesbartonjs
andauthored
Implement PemEncoding.WriteString
Co-authored-by: Jeremy Barton <[email protected]>
1 parent cf36aae commit 3ec4cc7

File tree

10 files changed

+123
-177
lines changed

10 files changed

+123
-177
lines changed

src/libraries/System.Security.Cryptography/ref/System.Security.Cryptography.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1655,6 +1655,7 @@ public static partial class PemEncoding
16551655
public static bool TryFind(System.ReadOnlySpan<char> pemData, out System.Security.Cryptography.PemFields fields) { throw null; }
16561656
public static bool TryWrite(System.ReadOnlySpan<char> label, System.ReadOnlySpan<byte> data, System.Span<char> destination, out int charsWritten) { throw null; }
16571657
public static char[] Write(System.ReadOnlySpan<char> label, System.ReadOnlySpan<byte> data) { throw null; }
1658+
public static string WriteString(System.ReadOnlySpan<char> label, System.ReadOnlySpan<byte> data) { throw null; }
16581659
}
16591660
public readonly partial struct PemFields
16601661
{

src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/AsymmetricAlgorithm.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -401,7 +401,7 @@ public unsafe string ExportPkcs8PrivateKeyPem()
401401
{
402402
try
403403
{
404-
return PemKeyHelpers.CreatePemFromData(PemLabels.Pkcs8PrivateKey, exported);
404+
return PemEncoding.WriteString(PemLabels.Pkcs8PrivateKey, exported);
405405
}
406406
finally
407407
{
@@ -454,7 +454,7 @@ public unsafe string ExportEncryptedPkcs8PrivateKeyPem(ReadOnlySpan<char> passwo
454454
{
455455
try
456456
{
457-
return PemKeyHelpers.CreatePemFromData(PemLabels.EncryptedPkcs8PrivateKey, exported);
457+
return PemEncoding.WriteString(PemLabels.EncryptedPkcs8PrivateKey, exported);
458458
}
459459
finally
460460
{
@@ -490,7 +490,7 @@ public unsafe string ExportEncryptedPkcs8PrivateKeyPem(ReadOnlySpan<char> passwo
490490
public string ExportSubjectPublicKeyInfoPem()
491491
{
492492
byte[] exported = ExportSubjectPublicKeyInfo();
493-
return PemKeyHelpers.CreatePemFromData(PemLabels.SpkiPublicKey, exported);
493+
return PemEncoding.WriteString(PemLabels.SpkiPublicKey, exported);
494494
}
495495

496496
/// <summary>

src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/ECAlgorithm.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -878,7 +878,7 @@ public unsafe string ExportECPrivateKeyPem()
878878
{
879879
try
880880
{
881-
return PemKeyHelpers.CreatePemFromData(PemLabels.EcPrivateKey, exported);
881+
return PemEncoding.WriteString(PemLabels.EcPrivateKey, exported);
882882
}
883883
finally
884884
{

src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/PemEncoding.cs

Lines changed: 80 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -422,6 +422,24 @@ public static int GetEncodedSize(int labelLength, int dataLength)
422422
/// <paramref name="label"/> contains invalid characters.
423423
/// </exception>
424424
public static bool TryWrite(ReadOnlySpan<char> label, ReadOnlySpan<byte> data, Span<char> destination, out int charsWritten)
425+
{
426+
if (!IsValidLabel(label))
427+
throw new ArgumentException(SR.Argument_PemEncoding_InvalidLabel, nameof(label));
428+
429+
int encodedSize = GetEncodedSize(label.Length, data.Length);
430+
431+
if (destination.Length < encodedSize)
432+
{
433+
charsWritten = 0;
434+
return false;
435+
}
436+
437+
charsWritten = WriteCore(label, data, destination);
438+
Debug.Assert(encodedSize == charsWritten);
439+
return true;
440+
}
441+
442+
private static int WriteCore(ReadOnlySpan<char> label, ReadOnlySpan<byte> data, Span<char> destination)
425443
{
426444
static int Write(ReadOnlySpan<char> str, Span<char> dest, int offset)
427445
{
@@ -442,20 +460,10 @@ static int WriteBase64(ReadOnlySpan<byte> bytes, Span<char> dest, int offset)
442460
return base64Written;
443461
}
444462

445-
if (!IsValidLabel(label))
446-
throw new ArgumentException(SR.Argument_PemEncoding_InvalidLabel, nameof(label));
447-
448463
const string NewLine = "\n";
449464
const int BytesPerLine = 48;
450-
int encodedSize = GetEncodedSize(label.Length, data.Length);
451-
452-
if (destination.Length < encodedSize)
453-
{
454-
charsWritten = 0;
455-
return false;
456-
}
457465

458-
charsWritten = 0;
466+
int charsWritten = 0;
459467
charsWritten += Write(PreEBPrefix, destination, charsWritten);
460468
charsWritten += Write(label, destination, charsWritten);
461469
charsWritten += Write(Ending, destination, charsWritten);
@@ -481,7 +489,7 @@ static int WriteBase64(ReadOnlySpan<byte> bytes, Span<char> dest, int offset)
481489
charsWritten += Write(label, destination, charsWritten);
482490
charsWritten += Write(Ending, destination, charsWritten);
483491

484-
return true;
492+
return charsWritten;
485493
}
486494

487495
/// <summary>
@@ -522,14 +530,68 @@ public static char[] Write(ReadOnlySpan<char> label, ReadOnlySpan<byte> data)
522530
int encodedSize = GetEncodedSize(label.Length, data.Length);
523531
char[] buffer = new char[encodedSize];
524532

525-
if (!TryWrite(label, data, buffer, out int charsWritten))
526-
{
527-
Debug.Fail("TryWrite failed with a pre-sized buffer");
528-
throw new ArgumentException(null, nameof(data));
529-
}
530-
533+
int charsWritten = WriteCore(label, data, buffer);
531534
Debug.Assert(charsWritten == encodedSize);
532535
return buffer;
533536
}
537+
538+
/// <summary>
539+
/// Creates an encoded PEM with the given label and data.
540+
/// </summary>
541+
/// <param name="label">
542+
/// The label to encode.
543+
/// </param>
544+
/// <param name="data">
545+
/// The data to encode.
546+
/// </param>
547+
/// <returns>
548+
/// A string of the encoded PEM.
549+
/// </returns>
550+
/// <remarks>
551+
/// This method always wraps the base-64 encoded text to 64 characters, per the
552+
/// recommended wrapping of RFC-7468. Unix-style line endings are used for line breaks.
553+
/// </remarks>
554+
/// <exception cref="ArgumentOutOfRangeException">
555+
/// <paramref name="label"/> exceeds the maximum possible label length.
556+
///
557+
/// -or-
558+
///
559+
/// <paramref name="data"/> exceeds the maximum possible encoded data length.
560+
/// </exception>
561+
/// <exception cref="ArgumentException">
562+
/// The resulting PEM-encoded text is larger than <see cref="int.MaxValue"/>.
563+
///
564+
/// - or -
565+
///
566+
/// <paramref name="label"/> contains invalid characters.
567+
/// </exception>
568+
public static unsafe string WriteString(ReadOnlySpan<char> label, ReadOnlySpan<byte> data)
569+
{
570+
if (!IsValidLabel(label))
571+
throw new ArgumentException(SR.Argument_PemEncoding_InvalidLabel, nameof(label));
572+
573+
int encodedSize = GetEncodedSize(label.Length, data.Length);
574+
575+
fixed (char* pLabel = label)
576+
fixed (byte* pData = data)
577+
{
578+
return string.Create(
579+
encodedSize,
580+
(LabelPointer : new IntPtr(pLabel), LabelLength : label.Length, DataPointer : new IntPtr(pData), DataLength : data.Length),
581+
static (destination, state) =>
582+
{
583+
ReadOnlySpan<byte> data = new ReadOnlySpan<byte>(state.DataPointer.ToPointer(), state.DataLength);
584+
ReadOnlySpan<char> label = new ReadOnlySpan<char>(state.LabelPointer.ToPointer(), state.LabelLength);
585+
586+
int charsWritten = WriteCore(label, data, destination);
587+
588+
if (charsWritten != destination.Length)
589+
{
590+
Debug.Fail("WriteCore wrote the wrong amount of data");
591+
throw new CryptographicException();
592+
}
593+
});
594+
}
595+
}
534596
}
535597
}

src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/PemKeyHelpers.cs

Lines changed: 0 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -90,23 +90,6 @@ public static unsafe bool TryExportToPem<T>(
9090
}
9191
}
9292

93-
internal static string CreatePemFromData(string label, ReadOnlyMemory<byte> data)
94-
{
95-
int pemSize = PemEncoding.GetEncodedSize(label.Length, data.Length);
96-
97-
return string.Create(pemSize, (label, data), static (destination, args) =>
98-
{
99-
(string label, ReadOnlyMemory<byte> data) = args;
100-
101-
if (!PemEncoding.TryWrite(label, data.Span, destination, out int charsWritten) ||
102-
charsWritten != destination.Length)
103-
{
104-
Debug.Fail("Pre-allocated buffer was not the correct size.");
105-
throw new CryptographicException();
106-
}
107-
});
108-
}
109-
11093
public delegate void ImportKeyAction(ReadOnlySpan<byte> source, out int bytesRead);
11194
public delegate ImportKeyAction? FindImportActionFunc(ReadOnlySpan<char> label);
11295
public delegate void ImportEncryptedKeyAction<TPass>(

src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/RSA.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -843,7 +843,7 @@ public unsafe string ExportRSAPrivateKeyPem()
843843
{
844844
try
845845
{
846-
return PemKeyHelpers.CreatePemFromData(PemLabels.RsaPrivateKey, exported);
846+
return PemEncoding.WriteString(PemLabels.RsaPrivateKey, exported);
847847
}
848848
finally
849849
{
@@ -874,7 +874,7 @@ public unsafe string ExportRSAPrivateKeyPem()
874874
public string ExportRSAPublicKeyPem()
875875
{
876876
byte[] exported = ExportRSAPublicKey();
877-
return PemKeyHelpers.CreatePemFromData(PemLabels.RsaPublicKey, exported);
877+
return PemEncoding.WriteString(PemLabels.RsaPublicKey, exported);
878878
}
879879

880880
/// <summary>

src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/X509Certificates/CertificateData.ManagedDecode.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,7 @@ internal CertificateData(byte[] rawData)
104104
}
105105
catch (Exception e)
106106
{
107-
string pem = new string(PemEncoding.Write(PemLabels.X509Certificate, rawData));
107+
string pem = PemEncoding.WriteString(PemLabels.X509Certificate, rawData);
108108
throw new CryptographicException($"Error in reading certificate:{Environment.NewLine}{pem}", e);
109109
}
110110
#endif

src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/X509Certificates/X509Certificate2.cs

Lines changed: 1 addition & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1137,16 +1137,7 @@ public static X509Certificate2 CreateFromPem(ReadOnlySpan<char> certPem)
11371137
/// </remarks>
11381138
public string ExportCertificatePem()
11391139
{
1140-
int pemSize = PemEncoding.GetEncodedSize(PemLabels.X509Certificate.Length, RawDataMemory.Length);
1141-
1142-
return string.Create(pemSize, this, static (destination, cert) => {
1143-
if (!cert.TryExportCertificatePem(destination, out int charsWritten) ||
1144-
charsWritten != destination.Length)
1145-
{
1146-
Debug.Fail("Pre-allocated buffer was not the correct size.");
1147-
throw new CryptographicException();
1148-
}
1149-
});
1140+
return PemEncoding.WriteString(PemLabels.X509Certificate, RawDataMemory.Span);
11501141
}
11511142

11521143
/// <summary>

src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/X509Certificates/X509Certificate2Collection.cs

Lines changed: 1 addition & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -417,16 +417,7 @@ public string ExportPkcs7Pem()
417417
throw new CryptographicException(SR.Cryptography_X509_ExportFailed);
418418
}
419419

420-
int encodedSize = PemEncoding.GetEncodedSize(PemLabels.Pkcs7Certificate.Length, pkcs7.Length);
421-
422-
return string.Create(encodedSize, pkcs7, static (destination, pkcs7) => {
423-
if (!PemEncoding.TryWrite(PemLabels.Pkcs7Certificate, pkcs7, destination, out int written) ||
424-
written != destination.Length)
425-
{
426-
Debug.Fail("Pre-allocated buffer was not the correct size.");
427-
throw new CryptographicException();
428-
}
429-
});
420+
return PemEncoding.WriteString(PemLabels.Pkcs7Certificate, pkcs7);
430421
}
431422

432423
/// <summary>

0 commit comments

Comments
 (0)