Skip to content

Commit f6fc3fb

Browse files
authored
ML-KEM: OpenSSL Part 1
This partially implements ML-KEM for Linux on top of OpenSSL 3.5. The current supported operations are key generation, key import and export, and key encapsulation and decapsulation.
1 parent cf4f6be commit f6fc3fb

31 files changed

+3052
-4
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,169 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
using System;
5+
using System.Diagnostics;
6+
using System.Runtime.InteropServices;
7+
using System.Security.Cryptography;
8+
using Microsoft.Win32.SafeHandles;
9+
10+
internal static partial class Interop
11+
{
12+
internal static partial class Crypto
13+
{
14+
[LibraryImport(Libraries.CryptoNative, EntryPoint = "CryptoNative_EvpKemDecapsulate")]
15+
private static partial int CryptoNative_EvpKemDecapsulate(
16+
SafeEvpPKeyHandle kem,
17+
ReadOnlySpan<byte> ciphertext,
18+
int ciphertextLength,
19+
Span<byte> sharedSecret,
20+
int sharedSecretLength);
21+
22+
[LibraryImport(Libraries.CryptoNative, EntryPoint = "CryptoNative_EvpKemGeneratePkey", StringMarshalling = StringMarshalling.Utf8)]
23+
private static partial SafeEvpPKeyHandle CryptoNative_EvpKemGeneratePkey(
24+
string kemName,
25+
ReadOnlySpan<byte> seed,
26+
int seedLength);
27+
28+
[LibraryImport(Libraries.CryptoNative, EntryPoint = "CryptoNative_EvpKemExportPrivateSeed")]
29+
private static partial int CryptoNative_EvpKemExportPrivateSeed(
30+
SafeEvpPKeyHandle key,
31+
Span<byte> destination,
32+
int destinationLength);
33+
34+
[LibraryImport(Libraries.CryptoNative, EntryPoint = "CryptoNative_EvpKemExportDecapsulationKey")]
35+
private static partial int CryptoNative_EvpKemExportDecapsulationKey(
36+
SafeEvpPKeyHandle key,
37+
Span<byte> destination,
38+
int destinationLength);
39+
40+
[LibraryImport(Libraries.CryptoNative, EntryPoint = "CryptoNative_EvpKemExportEncapsulationKey")]
41+
private static partial int CryptoNative_EvpKemExportEncapsulationKey(
42+
SafeEvpPKeyHandle key,
43+
Span<byte> destination,
44+
int destinationLength);
45+
46+
[LibraryImport(Libraries.CryptoNative, EntryPoint = "CryptoNative_EvpKemEncapsulate")]
47+
private static partial int CryptoNative_EvpKemEncapsulate(
48+
SafeEvpPKeyHandle kem,
49+
Span<byte> ciphertext,
50+
int ciphertextLength,
51+
Span<byte> sharedSecret,
52+
int sharedSecretLength);
53+
54+
internal static SafeEvpPKeyHandle EvpKemGeneratePkey(string kemName)
55+
{
56+
SafeEvpPKeyHandle handle = CryptoNative_EvpKemGeneratePkey(kemName, ReadOnlySpan<byte>.Empty, 0);
57+
58+
if (handle.IsInvalid)
59+
{
60+
Exception ex = CreateOpenSslCryptographicException();
61+
handle.Dispose();
62+
throw ex;
63+
}
64+
65+
return handle;
66+
}
67+
68+
internal static SafeEvpPKeyHandle EvpKemGeneratePkey(string kemName, ReadOnlySpan<byte> seed)
69+
{
70+
if (seed.IsEmpty)
71+
{
72+
Debug.Fail("Generating a key with a seed requires a non-empty seed.");
73+
throw new CryptographicException();
74+
}
75+
76+
SafeEvpPKeyHandle handle = CryptoNative_EvpKemGeneratePkey(kemName, seed, seed.Length);
77+
78+
if (handle.IsInvalid)
79+
{
80+
Exception ex = CreateOpenSslCryptographicException();
81+
handle.Dispose();
82+
throw ex;
83+
}
84+
85+
return handle;
86+
}
87+
88+
internal static void EvpKemDecapsulate(SafeEvpPKeyHandle key, ReadOnlySpan<byte> ciphertext, Span<byte> sharedSecret)
89+
{
90+
const int Success = 1;
91+
const int Fail = 0;
92+
93+
int ret = CryptoNative_EvpKemDecapsulate(key, ciphertext, ciphertext.Length, sharedSecret, sharedSecret.Length);
94+
95+
switch (ret)
96+
{
97+
case Success:
98+
return;
99+
case Fail:
100+
sharedSecret.Clear();
101+
throw CreateOpenSslCryptographicException();
102+
default:
103+
sharedSecret.Clear();
104+
Debug.Fail($"Unexpected return value {ret} from {nameof(CryptoNative_EvpKemDecapsulate)}.");
105+
throw new CryptographicException();
106+
}
107+
}
108+
109+
internal static void EvpKemExportPrivateSeed(SafeEvpPKeyHandle key, Span<byte> destination) =>
110+
ExportKeyContents(key, destination, CryptoNative_EvpKemExportPrivateSeed);
111+
112+
internal static void EvpKemExportDecapsulationKey(SafeEvpPKeyHandle key, Span<byte> destination) =>
113+
ExportKeyContents(key, destination, CryptoNative_EvpKemExportDecapsulationKey);
114+
115+
internal static void EvpKemExportEncapsulationKey(SafeEvpPKeyHandle key, Span<byte> destination) =>
116+
ExportKeyContents(key, destination, CryptoNative_EvpKemExportEncapsulationKey);
117+
118+
internal static void EvpKemEncapsulate(SafeEvpPKeyHandle key, Span<byte> ciphertext, Span<byte> sharedSecret)
119+
{
120+
const int Success = 1;
121+
const int Fail = 0;
122+
123+
int ret = CryptoNative_EvpKemEncapsulate(key, ciphertext, ciphertext.Length, sharedSecret, sharedSecret.Length);
124+
125+
switch (ret)
126+
{
127+
case Success:
128+
return;
129+
case Fail:
130+
ciphertext.Clear();
131+
sharedSecret.Clear();
132+
throw CreateOpenSslCryptographicException();
133+
default:
134+
ciphertext.Clear();
135+
sharedSecret.Clear();
136+
Debug.Fail($"Unexpected return value {ret} from {nameof(CryptoNative_EvpKemEncapsulate)}.");
137+
throw new CryptographicException();
138+
}
139+
}
140+
141+
private static void ExportKeyContents(
142+
SafeEvpPKeyHandle key,
143+
Span<byte> destination,
144+
Func<SafeEvpPKeyHandle, Span<byte>, int, int> action)
145+
{
146+
const int Success = 1;
147+
const int Fail = 0;
148+
const int NotRetrievable = -1;
149+
150+
int ret = action(key, destination, destination.Length);
151+
152+
switch (ret)
153+
{
154+
case Success:
155+
return;
156+
case NotRetrievable:
157+
destination.Clear();
158+
throw new CryptographicException(SR.Cryptography_NotRetrievable);
159+
case Fail:
160+
destination.Clear();
161+
throw CreateOpenSslCryptographicException();
162+
default:
163+
destination.Clear();
164+
Debug.Fail($"Unexpected return value {ret}.");
165+
throw new CryptographicException();
166+
}
167+
}
168+
}
169+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
using System.Diagnostics;
5+
using System.Runtime.InteropServices;
6+
using System.Security.Cryptography;
7+
using Microsoft.Win32.SafeHandles;
8+
9+
internal static partial class Interop
10+
{
11+
internal static partial class Crypto
12+
{
13+
internal static partial class EvpKemAlgs
14+
{
15+
internal static string? MlKem512 { get; }
16+
internal static string? MlKem768 { get; }
17+
internal static string? MlKem1024 { get; }
18+
19+
static EvpKemAlgs()
20+
{
21+
CryptoInitializer.Initialize();
22+
23+
// Do not use property initializers for these because we need to ensure CryptoInitializer.Initialize
24+
// is called first. Property initializers happen before cctors, so instead set the property after the
25+
// initializer is run.
26+
MlKem512 = EvpKemAvailable(MLKemAlgorithm.MLKem512.Name);
27+
MlKem768 = EvpKemAvailable(MLKemAlgorithm.MLKem768.Name);
28+
MlKem1024 = EvpKemAvailable(MLKemAlgorithm.MLKem1024.Name);
29+
}
30+
31+
[LibraryImport(Libraries.CryptoNative, EntryPoint = "CryptoNative_EvpKemAvailable", StringMarshalling = StringMarshalling.Utf8)]
32+
private static partial int CryptoNative_EvpKemAvailable(string algorithm);
33+
34+
private static string? EvpKemAvailable(string algorithm)
35+
{
36+
const int Available = 1;
37+
const int NotAvailable = 0;
38+
39+
int ret = CryptoNative_EvpKemAvailable(algorithm);
40+
return ret switch
41+
{
42+
Available => algorithm,
43+
NotAvailable => null,
44+
int other => throw Fail(other),
45+
};
46+
47+
static CryptographicException Fail(int result)
48+
{
49+
Debug.Fail($"Unexpected result {result} from {nameof(CryptoNative_EvpKemAvailable)}");
50+
return new CryptographicException();
51+
}
52+
}
53+
}
54+
}
55+
}

src/libraries/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.EvpPkey.cs

+21
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,27 @@ internal static partial class Crypto
1919
[LibraryImport(Libraries.CryptoNative, EntryPoint = "CryptoNative_EvpPKeyBits")]
2020
internal static partial int EvpPKeyBits(SafeEvpPKeyHandle pkey);
2121

22+
[LibraryImport(Libraries.CryptoNative, EntryPoint = "CryptoNative_EvpPKeyFromData", StringMarshalling = StringMarshalling.Utf8)]
23+
private static partial SafeEvpPKeyHandle CryptoNative_EvpPKeyFromData(
24+
string algorithmName,
25+
ReadOnlySpan<byte> key,
26+
int keyLength,
27+
[MarshalAs(UnmanagedType.Bool)] bool privateKey);
28+
29+
internal static SafeEvpPKeyHandle EvpPKeyFromData(string algorithmName, ReadOnlySpan<byte> key, bool privateKey)
30+
{
31+
SafeEvpPKeyHandle handle = CryptoNative_EvpPKeyFromData(algorithmName, key, key.Length, privateKey);
32+
33+
if (handle.IsInvalid)
34+
{
35+
Exception ex = CreateOpenSslCryptographicException();
36+
handle.Dispose();
37+
throw ex;
38+
}
39+
40+
return handle;
41+
}
42+
2243
internal static int GetEvpPKeySizeBytes(SafeEvpPKeyHandle pkey)
2344
{
2445
// EVP_PKEY_size returns the maximum suitable size for the output buffers for almost all operations that can be done with the key.

0 commit comments

Comments
 (0)