-
Couldn't load subscription status.
- Fork 5.2k
ML-KEM: PKCS12/PFX Loading #115100
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
ML-KEM: PKCS12/PFX Loading #115100
Changes from all commits
67be438
4d83e4c
a98add9
4f2d46b
c45480a
a63c78d
4fc4193
3793804
a09ce69
793e27f
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Large diffs are not rendered by default.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -14,7 +14,7 @@ public static partial class X509CertificateLoader | |
| { | ||
| private static partial Pkcs12Return FromCertAndKey(CertAndKey certAndKey, ImportState importState); | ||
|
|
||
| private static partial AsymmetricAlgorithm? CreateKey(string algorithm); | ||
| private static partial Pkcs12Key? CreateKey(string algorithm, ReadOnlySpan<byte> pkcs8); | ||
|
|
||
| private static partial ICertificatePalCore LoadX509Der(ReadOnlyMemory<byte> data); | ||
|
|
||
|
|
@@ -196,7 +196,7 @@ internal ReadOnlySpan<SafeBagAsn> GetKeysSpan() | |
| private struct CertAndKey | ||
| { | ||
| internal ICertificatePalCore? Cert; | ||
| internal AsymmetricAlgorithm? Key; | ||
| internal Pkcs12Key? Key; | ||
|
|
||
| internal void Dispose() | ||
| { | ||
|
|
@@ -209,7 +209,7 @@ private struct CertKeyMatcher | |
| { | ||
| private CertAndKey[] _certAndKeys; | ||
| private int _certCount; | ||
| private AsymmetricAlgorithm?[] _keys; | ||
| private Pkcs12Key?[] _keys; | ||
| private RentedSubjectPublicKeyInfo[] _rentedSpki; | ||
| private int _keyCount; | ||
|
|
||
|
|
@@ -247,11 +247,11 @@ internal void LoadKeys(ref BagState bagState) | |
| return; | ||
| } | ||
|
|
||
| _keys = ArrayPool<AsymmetricAlgorithm?>.Shared.Rent(bagState.KeyCount); | ||
| _keys = ArrayPool<Pkcs12Key?>.Shared.Rent(bagState.KeyCount); | ||
|
|
||
| foreach (SafeBagAsn safeBag in bagState.GetKeysSpan()) | ||
| { | ||
| AsymmetricAlgorithm? key = null; | ||
| Pkcs12Key? key = null; | ||
|
|
||
| try | ||
| { | ||
|
|
@@ -260,12 +260,10 @@ internal void LoadKeys(ref BagState bagState) | |
| PrivateKeyInfoAsn privateKeyInfo = | ||
| PrivateKeyInfoAsn.Decode(safeBag.BagValue, AsnEncodingRules.BER); | ||
|
|
||
| key = CreateKey(privateKeyInfo.PrivateKeyAlgorithm.Algorithm); | ||
| key = CreateKey(privateKeyInfo.PrivateKeyAlgorithm.Algorithm, safeBag.BagValue.Span); | ||
|
|
||
| if (key is not null) | ||
| { | ||
| ImportPrivateKey(key, safeBag.BagValue.Span); | ||
|
|
||
| if (_rentedSpki is null) | ||
| { | ||
| _rentedSpki = | ||
|
|
@@ -376,7 +374,7 @@ internal CertAndKey[] MatchCertAndKeys(ref BagState bagState, bool allowDoubleBi | |
| if (allowDoubleBind) | ||
| { | ||
|
|
||
| AsymmetricAlgorithm? key = CreateKey(cert.KeyAlgorithm); | ||
| Pkcs12Key? key = CreateKey(cert.KeyAlgorithm, keyBag.BagValue.Span); | ||
|
|
||
| if (key is null) | ||
| { | ||
|
|
@@ -388,7 +386,6 @@ internal CertAndKey[] MatchCertAndKeys(ref BagState bagState, bool allowDoubleBi | |
| } | ||
|
|
||
| _certAndKeys[certBagIdx].Key = key; | ||
| ImportPrivateKey(key, keyBag.BagValue.Span); | ||
| } | ||
| else | ||
| { | ||
|
|
@@ -492,6 +489,8 @@ certKeyParameters is not null && | |
| publicKeyInfo.Algorithm.Parameters.Value.Span.SequenceEqual(certKeyParameters); | ||
| } | ||
|
|
||
| // ML-KEM requires parameters to match exactly. ML-KEM also prohibits parameters, but that is checked | ||
| // by MLKem when loading the key. | ||
| // Any other algorithm matches null/empty parameters as equivalent | ||
| if (!publicKeyInfo.Algorithm.Parameters.HasValue) | ||
| { | ||
|
|
@@ -504,7 +503,7 @@ certKeyParameters is not null && | |
|
|
||
| private static void ExtractPublicKey( | ||
| ref RentedSubjectPublicKeyInfo spki, | ||
| AsymmetricAlgorithm key, | ||
| Pkcs12Key key, | ||
| int sizeHint) | ||
| { | ||
| Debug.Assert(sizeHint > 0); | ||
|
|
@@ -550,7 +549,7 @@ internal void Dispose() | |
| _keys[i]?.Dispose(); | ||
| } | ||
|
|
||
| ArrayPool<AsymmetricAlgorithm?>.Shared.Return(_keys, clearArray: true); | ||
| ArrayPool<Pkcs12Key?>.Shared.Return(_keys, clearArray: true); | ||
| } | ||
|
|
||
| if (_rentedSpki is not null) | ||
|
|
@@ -619,4 +618,73 @@ internal void Dispose() | |
| } | ||
| } | ||
| } | ||
|
|
||
| internal abstract class Pkcs12Key : IDisposable | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think we should just implement AsymmetricAlgorithm at this point, there doesn't seem enough benefit not doing it. At least here you don't seem to need any new public APIs but IMO implementing will just make it easier for everyone There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I’m pretty firmly against it.
Since it doesn’t help consumers of the algorithm, and it doesn’t help with the implementation of the algorithm, I guess I don’t see much of a reason to do it. |
||
| { | ||
| internal abstract IDisposable Key { get; } | ||
|
|
||
| internal abstract bool TryExportSubjectPublicKeyInfo(Span<byte> destination, out int bytesWritten); | ||
|
|
||
| public void Dispose() => Key.Dispose(); | ||
| } | ||
|
|
||
| internal sealed class MLKemPkcs12PrivateKey : Pkcs12Key | ||
| { | ||
| private readonly MLKem _key; | ||
|
|
||
| internal MLKemPkcs12PrivateKey(ReadOnlySpan<byte> pkcs8) | ||
| { | ||
| try | ||
| { | ||
| _key = MLKem.ImportPkcs8PrivateKey(pkcs8); | ||
| } | ||
| catch (PlatformNotSupportedException nse) | ||
| { | ||
| // PKCS12 loader turns PNSE in to a CryptographicException | ||
| throw new CryptographicException(SR.Cryptography_NotValidPrivateKey, nse); | ||
| } | ||
| } | ||
|
|
||
| internal override bool TryExportSubjectPublicKeyInfo(Span<byte> destination, out int bytesWritten) => | ||
| _key.TryExportSubjectPublicKeyInfo(destination, out bytesWritten); | ||
|
|
||
| internal override MLKem Key => _key; | ||
| } | ||
|
|
||
| internal sealed class AsymmetricAlgorithmPkcs12PrivateKey : Pkcs12Key | ||
| { | ||
| private readonly AsymmetricAlgorithm _key; | ||
|
|
||
| internal AsymmetricAlgorithmPkcs12PrivateKey(ReadOnlySpan<byte> pkcs8, Func<AsymmetricAlgorithm> factory) | ||
| { | ||
| _key = factory(); | ||
|
|
||
| try | ||
| { | ||
| _key.ImportPkcs8PrivateKey(pkcs8, out int bytesRead); | ||
| Debug.Assert(bytesRead == pkcs8.Length); | ||
| } | ||
| catch (Exception ex) | ||
| { | ||
| _key.Dispose(); | ||
|
|
||
| if (ex is PlatformNotSupportedException nse) | ||
| { | ||
| // Turn a "curve not supported" PNSE (or other PNSE) | ||
| // into a standardized CryptographicException. | ||
| throw new CryptographicException(SR.Cryptography_NotValidPrivateKey, nse); | ||
| } | ||
| else | ||
| { | ||
| throw; | ||
| } | ||
| } | ||
|
|
||
| } | ||
|
|
||
| internal override bool TryExportSubjectPublicKeyInfo(Span<byte> destination, out int bytesWritten) => | ||
| _key.TryExportSubjectPublicKeyInfo(destination, out bytesWritten); | ||
|
|
||
| internal override AsymmetricAlgorithm Key => _key; | ||
| } | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I know it was already like this, but this change is making me realize how often we repeat code.
It'd be nice (in a future change) to have just a central registration of "here's my RSA importer, or null if I don't support RSA". Something like
but that's a very half-formed idea