Skip to content

Commit 4ef4f90

Browse files
authored
ML-KEM: EncryptedPrivateKeyInfo exports
1 parent b28005d commit 4ef4f90

File tree

5 files changed

+747
-2
lines changed

5 files changed

+747
-2
lines changed

src/libraries/Common/src/System/Security/Cryptography/MLKem.cs

+255
Original file line numberDiff line numberDiff line change
@@ -887,6 +887,225 @@ static void ClearAndReturnToPool(byte[] buffer, int clearSize)
887887
/// </exception>
888888
protected abstract bool TryExportPkcs8PrivateKeyCore(Span<byte> destination, out int bytesWritten);
889889

890+
/// <summary>
891+
/// Attempts to export the current key in the PKCS#8 EncryptedPrivateKeyInfo format into a provided buffer,
892+
/// using a char-based password.
893+
/// </summary>
894+
/// <param name="password">
895+
/// The password to use when encrypting the key material.
896+
/// </param>
897+
/// <param name="pbeParameters">
898+
/// The password-based encryption (PBE) parameters to use when encrypting the key material.
899+
/// </param>
900+
/// <param name="destination">
901+
/// The buffer to receive the PKCS#8 EncryptedPrivateKeyInfo value.
902+
/// </param>
903+
/// <param name="bytesWritten">
904+
/// When this method returns, contains the number of bytes written to the <paramref name="destination"/> buffer.
905+
/// This parameter is treated as uninitialized.
906+
/// </param>
907+
/// <returns>
908+
/// <see langword="true" /> if <paramref name="destination"/> was large enough to hold the result;
909+
/// otherwise, <see langword="false" />.
910+
/// </returns>
911+
/// <exception cref="ArgumentNullException">
912+
/// <paramref name="pbeParameters"/> is <see langword="null"/>.
913+
/// </exception>
914+
/// <exception cref="ObjectDisposedException">
915+
/// This instance has been disposed.
916+
/// </exception>
917+
/// <exception cref="CryptographicException">
918+
/// <para>This instance only represents a public key.</para>
919+
/// <para>-or-</para>
920+
/// <para>The private key is not exportable.</para>
921+
/// <para>-or-</para>
922+
/// <para>An error occurred while exporting the key.</para>
923+
/// <para>-or-</para>
924+
/// <para><paramref name="pbeParameters"/> does not represent a valid password-based encryption algorithm.</para>
925+
/// </exception>
926+
public bool TryExportEncryptedPkcs8PrivateKey(
927+
ReadOnlySpan<char> password,
928+
PbeParameters pbeParameters,
929+
Span<byte> destination,
930+
out int bytesWritten)
931+
{
932+
ThrowIfNull(pbeParameters);
933+
PasswordBasedEncryption.ValidatePbeParameters(pbeParameters, password, ReadOnlySpan<byte>.Empty);
934+
ThrowIfDisposed();
935+
936+
AsnWriter writer = ExportEncryptedPkcs8PrivateKeyCore<char>(
937+
password,
938+
pbeParameters,
939+
KeyFormatHelper.WriteEncryptedPkcs8);
940+
return writer.TryEncode(destination, out bytesWritten);
941+
}
942+
943+
/// <summary>
944+
/// Attempts to export the current key in the PKCS#8 EncryptedPrivateKeyInfo format into a provided buffer,
945+
/// using a byte-based password.
946+
/// </summary>
947+
/// <param name="passwordBytes">
948+
/// The password to use when encrypting the key material.
949+
/// </param>
950+
/// <param name="pbeParameters">
951+
/// The password-based encryption (PBE) parameters to use when encrypting the key material.
952+
/// </param>
953+
/// <param name="destination">
954+
/// The buffer to receive the PKCS#8 EncryptedPrivateKeyInfo value.
955+
/// </param>
956+
/// <param name="bytesWritten">
957+
/// When this method returns, contains the number of bytes written to the <paramref name="destination"/> buffer.
958+
/// This parameter is treated as uninitialized.
959+
/// </param>
960+
/// <returns>
961+
/// <see langword="true" /> if <paramref name="destination"/> was large enough to hold the result;
962+
/// otherwise, <see langword="false" />.
963+
/// </returns>
964+
/// <exception cref="ArgumentNullException">
965+
/// <paramref name="pbeParameters"/> is <see langword="null"/>.
966+
/// </exception>
967+
/// <exception cref="ObjectDisposedException">
968+
/// This instance has been disposed.
969+
/// </exception>
970+
/// <exception cref="CryptographicException">
971+
/// <para>This instance only represents a public key.</para>
972+
/// <para>-or-</para>
973+
/// <para>The private key is not exportable.</para>
974+
/// <para>-or-</para>
975+
/// <para>An error occurred while exporting the key.</para>
976+
/// <para>-or-</para>
977+
/// <para><paramref name="pbeParameters"/> does not represent a valid password-based encryption algorithm.</para>
978+
/// </exception>
979+
public bool TryExportEncryptedPkcs8PrivateKey(
980+
ReadOnlySpan<byte> passwordBytes,
981+
PbeParameters pbeParameters,
982+
Span<byte> destination,
983+
out int bytesWritten)
984+
{
985+
ThrowIfNull(pbeParameters);
986+
PasswordBasedEncryption.ValidatePbeParameters(pbeParameters, ReadOnlySpan<char>.Empty, passwordBytes);
987+
ThrowIfDisposed();
988+
989+
AsnWriter writer = ExportEncryptedPkcs8PrivateKeyCore<byte>(
990+
passwordBytes,
991+
pbeParameters,
992+
KeyFormatHelper.WriteEncryptedPkcs8);
993+
return writer.TryEncode(destination, out bytesWritten);
994+
}
995+
996+
/// <summary>
997+
/// Exports the current key in the PKCS#8 EncryptedPrivateKeyInfo format with a byte-based password.
998+
/// </summary>
999+
/// <param name="passwordBytes">
1000+
/// The password to use when encrypting the key material.
1001+
/// </param>
1002+
/// <param name="pbeParameters">
1003+
/// The password-based encryption (PBE) parameters to use when encrypting the key material.
1004+
/// </param>
1005+
/// <returns>
1006+
/// A byte array containing the PKCS#8 EncryptedPrivateKeyInfo representation of the this key.
1007+
/// </returns>
1008+
/// <exception cref="ArgumentNullException">
1009+
/// <paramref name="pbeParameters"/> is <see langword="null"/>.
1010+
/// </exception>
1011+
/// <exception cref="ObjectDisposedException">
1012+
/// This instance has been disposed.
1013+
/// </exception>
1014+
/// <exception cref="CryptographicException">
1015+
/// <para>This instance only represents a public key.</para>
1016+
/// <para>-or-</para>
1017+
/// <para>The private key is not exportable.</para>
1018+
/// <para>-or-</para>
1019+
/// <para>An error occurred while exporting the key.</para>
1020+
/// <para>-or-</para>
1021+
/// <para><paramref name="pbeParameters"/> does not represent a valid password-based encryption algorithm.</para>
1022+
/// </exception>
1023+
public byte[] ExportEncryptedPkcs8PrivateKey(ReadOnlySpan<byte> passwordBytes, PbeParameters pbeParameters)
1024+
{
1025+
ThrowIfNull(pbeParameters);
1026+
PasswordBasedEncryption.ValidatePbeParameters(pbeParameters, ReadOnlySpan<char>.Empty, passwordBytes);
1027+
ThrowIfDisposed();
1028+
1029+
AsnWriter writer = ExportEncryptedPkcs8PrivateKeyCore<byte>(
1030+
passwordBytes,
1031+
pbeParameters,
1032+
KeyFormatHelper.WriteEncryptedPkcs8);
1033+
return writer.Encode();
1034+
}
1035+
1036+
/// <summary>
1037+
/// Exports the current key in the PKCS#8 EncryptedPrivateKeyInfo format with a char-based password.
1038+
/// </summary>
1039+
/// <param name="password">
1040+
/// The password to use when encrypting the key material.
1041+
/// </param>
1042+
/// <param name="pbeParameters">
1043+
/// The password-based encryption (PBE) parameters to use when encrypting the key material.
1044+
/// </param>
1045+
/// <returns>
1046+
/// A byte array containing the PKCS#8 EncryptedPrivateKeyInfo representation of the this key.
1047+
/// </returns>
1048+
/// <exception cref="ArgumentNullException">
1049+
/// <paramref name="pbeParameters"/> is <see langword="null"/>.
1050+
/// </exception>
1051+
/// <exception cref="ObjectDisposedException">
1052+
/// This instance has been disposed.
1053+
/// </exception>
1054+
/// <exception cref="CryptographicException">
1055+
/// <para>This instance only represents a public key.</para>
1056+
/// <para>-or-</para>
1057+
/// <para>The private key is not exportable.</para>
1058+
/// <para>-or-</para>
1059+
/// <para>An error occurred while exporting the key.</para>
1060+
/// <para>-or-</para>
1061+
/// <para><paramref name="pbeParameters"/> does not represent a valid password-based encryption algorithm.</para>
1062+
/// </exception>
1063+
public byte[] ExportEncryptedPkcs8PrivateKey(ReadOnlySpan<char> password, PbeParameters pbeParameters)
1064+
{
1065+
ThrowIfNull(pbeParameters);
1066+
PasswordBasedEncryption.ValidatePbeParameters(pbeParameters, password, ReadOnlySpan<byte>.Empty);
1067+
ThrowIfDisposed();
1068+
1069+
AsnWriter writer = ExportEncryptedPkcs8PrivateKeyCore<char>(
1070+
password,
1071+
pbeParameters,
1072+
KeyFormatHelper.WriteEncryptedPkcs8);
1073+
return writer.Encode();
1074+
}
1075+
1076+
/// <summary>
1077+
/// Exports the current key in the PKCS#8 EncryptedPrivateKeyInfo format with a char-based password.
1078+
/// </summary>
1079+
/// <param name="password">
1080+
/// The password to use when encrypting the key material.
1081+
/// </param>
1082+
/// <param name="pbeParameters">
1083+
/// The password-based encryption (PBE) parameters to use when encrypting the key material.
1084+
/// </param>
1085+
/// <returns>
1086+
/// A byte array containing the PKCS#8 EncryptedPrivateKeyInfo representation of the this key.
1087+
/// </returns>
1088+
/// <exception cref="ArgumentNullException">
1089+
/// <paramref name="pbeParameters" /> or <paramref name="password" /> is <see langword="null" />.
1090+
/// </exception>
1091+
/// <exception cref="ObjectDisposedException">
1092+
/// This instance has been disposed.
1093+
/// </exception>
1094+
/// <exception cref="CryptographicException">
1095+
/// <para>This instance only represents a public key.</para>
1096+
/// <para>-or-</para>
1097+
/// <para>The private key is not exportable.</para>
1098+
/// <para>-or-</para>
1099+
/// <para>An error occurred while exporting the key.</para>
1100+
/// <para>-or-</para>
1101+
/// <para><paramref name="pbeParameters"/> does not represent a valid password-based encryption algorithm.</para>
1102+
/// </exception>
1103+
public byte[] ExportEncryptedPkcs8PrivateKey(string password, PbeParameters pbeParameters)
1104+
{
1105+
ThrowIfNull(password);
1106+
return ExportEncryptedPkcs8PrivateKey(password.AsSpan(), pbeParameters);
1107+
}
1108+
8901109
/// <summary>
8911110
/// Imports an ML-KEM encapsulation key from an X.509 SubjectPublicKeyInfo structure.
8921111
/// </summary>
@@ -1452,5 +1671,41 @@ private static void ThrowIfNull(
14521671
}
14531672
#endif
14541673
}
1674+
1675+
private AsnWriter ExportEncryptedPkcs8PrivateKeyCore<TChar>(
1676+
ReadOnlySpan<TChar> password,
1677+
PbeParameters pbeParameters,
1678+
WriteEncryptedPkcs8Func<TChar> encryptor)
1679+
{
1680+
// There are 28 bytes of overhead on a plain PKCS#8 export for an expanded key. Add a little extra for
1681+
// some extra space.
1682+
int initialSize = Algorithm.DecapsulationKeySizeInBytes + 32;
1683+
byte[] rented = CryptoPool.Rent(initialSize);
1684+
int written;
1685+
1686+
while (!TryExportPkcs8PrivateKey(rented, out written))
1687+
{
1688+
CryptoPool.Return(rented, 0);
1689+
rented = CryptoPool.Rent(rented.Length * 2);
1690+
}
1691+
1692+
AsnWriter tmp = new(AsnEncodingRules.BER, initialCapacity: written);
1693+
1694+
try
1695+
{
1696+
tmp.WriteEncodedValueForCrypto(rented.AsSpan(0, written));
1697+
return encryptor(password, tmp, pbeParameters);
1698+
}
1699+
finally
1700+
{
1701+
tmp.Reset();
1702+
CryptoPool.Return(rented, written);
1703+
}
1704+
}
1705+
1706+
private delegate AsnWriter WriteEncryptedPkcs8Func<TChar>(
1707+
ReadOnlySpan<TChar> password,
1708+
AsnWriter writer,
1709+
PbeParameters pbeParameters);
14551710
}
14561711
}

0 commit comments

Comments
 (0)