未验证 提交 420dd4e0 编写于 作者: K Krzysztof Wicher 提交者: GitHub

OpenSSL ENGINE support (#88656)

* OpenSSL ENGINE support

* Remove trailing spaces on README file

* Address PR feedback

* Test for PNSE and run tests on all platforms supporting OSSL

* Update IsOpenSslSupported

* s/IsOpenSslSupported/OpenSslPresentOnSystem

* Fix OpenSslNotPresentOnSystem on Windows

* Add preventive OpenSslIsAvailable check for better error handling
上级 1dedddef
......@@ -214,6 +214,52 @@ internal static ArraySegment<byte> RentEncodeSubjectPublicKeyInfo(SafeEvpPKeyHan
}
}
[LibraryImport(Libraries.CryptoNative, StringMarshalling = StringMarshalling.Utf8)]
private static partial SafeEvpPKeyHandle CryptoNative_LoadPrivateKeyFromEngine(
string engineName,
string keyName);
internal static SafeEvpPKeyHandle LoadPrivateKeyFromEngine(
string engineName,
string keyName)
{
Debug.Assert(engineName is not null);
Debug.Assert(keyName is not null);
SafeEvpPKeyHandle pkey = CryptoNative_LoadPrivateKeyFromEngine(engineName, keyName);
if (pkey.IsInvalid)
{
pkey.Dispose();
throw CreateOpenSslCryptographicException();
}
return pkey;
}
[LibraryImport(Libraries.CryptoNative, StringMarshalling = StringMarshalling.Utf8)]
private static partial SafeEvpPKeyHandle CryptoNative_LoadPublicKeyFromEngine(
string engineName,
string keyName);
internal static SafeEvpPKeyHandle LoadPublicKeyFromEngine(
string engineName,
string keyName)
{
Debug.Assert(engineName is not null);
Debug.Assert(keyName is not null);
SafeEvpPKeyHandle pkey = CryptoNative_LoadPublicKeyFromEngine(engineName, keyName);
if (pkey.IsInvalid)
{
pkey.Dispose();
throw CreateOpenSslCryptographicException();
}
return pkey;
}
internal enum EvpAlgorithmId
{
Unknown = 0,
......
......@@ -111,7 +111,7 @@ public static bool OpenSslPresentOnSystem
{
get
{
if (IsAndroid || UsesMobileAppleCrypto || IsBrowser)
if (IsWindows || IsAndroid || UsesMobileAppleCrypto || IsBrowser)
{
return false;
}
......
......@@ -253,6 +253,7 @@ public static bool IsMetadataTokenSupported
public static bool IsNotDomainJoinedMachine => !IsDomainJoinedMachine;
public static bool IsOpenSslSupported => IsLinux || IsFreeBSD || Isillumos || IsSolaris;
public static bool OpenSslNotPresentOnSystem => !OpenSslPresentOnSystem;
public static bool UsesAppleCrypto => IsOSX || IsMacCatalyst || IsiOS || IstvOS;
public static bool UsesMobileAppleCrypto => IsMacCatalyst || IsiOS || IstvOS;
......
......@@ -2245,6 +2245,18 @@ public sealed partial class SafeEvpPKeyHandle : System.Runtime.InteropServices.S
[System.Runtime.Versioning.UnsupportedOSPlatformAttribute("windows")]
public static long OpenSslVersion { get { throw null; } }
public System.Security.Cryptography.SafeEvpPKeyHandle DuplicateHandle() { throw null; }
[System.Runtime.Versioning.UnsupportedOSPlatformAttribute("android")]
[System.Runtime.Versioning.UnsupportedOSPlatformAttribute("browser")]
[System.Runtime.Versioning.UnsupportedOSPlatformAttribute("ios")]
[System.Runtime.Versioning.UnsupportedOSPlatformAttribute("tvos")]
[System.Runtime.Versioning.UnsupportedOSPlatformAttribute("windows")]
public static System.Security.Cryptography.SafeEvpPKeyHandle OpenPrivateKeyFromEngine(string engineName, string keyId) { throw null; }
[System.Runtime.Versioning.UnsupportedOSPlatformAttribute("android")]
[System.Runtime.Versioning.UnsupportedOSPlatformAttribute("browser")]
[System.Runtime.Versioning.UnsupportedOSPlatformAttribute("ios")]
[System.Runtime.Versioning.UnsupportedOSPlatformAttribute("tvos")]
[System.Runtime.Versioning.UnsupportedOSPlatformAttribute("windows")]
public static System.Security.Cryptography.SafeEvpPKeyHandle OpenPublicKeyFromEngine(string engineName, string keyId) { throw null; }
protected override bool ReleaseHandle() { throw null; }
}
public abstract partial class SHA1 : System.Security.Cryptography.HashAlgorithm
......
......@@ -263,6 +263,22 @@ public SafeEvpPKeyHandle(IntPtr handle, bool ownsHandle) : base(handle, ownsHand
public static long OpenSslVersion =>
throw new PlatformNotSupportedException(SR.PlatformNotSupported_CryptographyOpenSSL);
[UnsupportedOSPlatform("android")]
[UnsupportedOSPlatform("browser")]
[UnsupportedOSPlatform("ios")]
[UnsupportedOSPlatform("tvos")]
[UnsupportedOSPlatform("windows")]
public static SafeEvpPKeyHandle OpenPrivateKeyFromEngine(string engineName, string keyId) =>
throw new PlatformNotSupportedException(SR.PlatformNotSupported_CryptographyOpenSSL);
[UnsupportedOSPlatform("android")]
[UnsupportedOSPlatform("browser")]
[UnsupportedOSPlatform("ios")]
[UnsupportedOSPlatform("tvos")]
[UnsupportedOSPlatform("windows")]
public static SafeEvpPKeyHandle OpenPublicKeyFromEngine(string engineName, string keyId) =>
throw new PlatformNotSupportedException(SR.PlatformNotSupported_CryptographyOpenSSL);
public SafeEvpPKeyHandle DuplicateHandle() => null!;
public override bool IsInvalid => true;
protected override bool ReleaseHandle() => false;
......
......@@ -86,5 +86,110 @@ public SafeEvpPKeyHandle DuplicateHandle()
[UnsupportedOSPlatform("tvos")]
[UnsupportedOSPlatform("windows")]
public static long OpenSslVersion { get; } = Interop.OpenSsl.OpenSslVersionNumber();
/// <summary>
/// Open a named private key using a named OpenSSL <code>ENGINE</code>.
/// </summary>
/// <param name="engineName">
/// The name of the <code>ENGINE</code> to process the private key open request.
/// </param>
/// <param name="keyId">
/// The name of the key to open.
/// </param>
/// <returns>
/// The opened key.
/// </returns>
/// <exception cref="ArgumentNullException">
/// <paramref name="engineName"/> or <paramref name="keyId"/> is <see langword="null" />.
/// </exception>
/// <exception cref="ArgumentException">
/// <paramref name="engineName"/> or <paramref name="keyId"/> is the empty string.
/// </exception>
/// <exception cref="CryptographicException">
/// the key could not be opened via the specified ENGINE.
/// </exception>
/// <remarks>
/// <para>
/// This operation will fail if OpenSSL cannot successfully load the named <code>ENGINE</code>,
/// or if the named <code>ENGINE</code> cannot load the named key.
/// </para>
/// <para>
/// Not all <code>ENGINE</code>s support loading private keys.
/// </para>
/// <para>
/// The syntax for <paramref name="keyId"/> is determined by each individual
/// <code>ENGINE</code>.
/// </para>
/// </remarks>
[UnsupportedOSPlatform("android")]
[UnsupportedOSPlatform("browser")]
[UnsupportedOSPlatform("ios")]
[UnsupportedOSPlatform("tvos")]
[UnsupportedOSPlatform("windows")]
public static SafeEvpPKeyHandle OpenPrivateKeyFromEngine(string engineName, string keyId)
{
ArgumentException.ThrowIfNullOrEmpty(engineName);
ArgumentException.ThrowIfNullOrEmpty(keyId);
if (!Interop.OpenSslNoInit.OpenSslIsAvailable)
{
throw new PlatformNotSupportedException(SR.PlatformNotSupported_CryptographyOpenSSL);
}
return Interop.Crypto.LoadPrivateKeyFromEngine(engineName, keyId);
}
/// <summary>
/// Open a named public key using a named OpenSSL <code>ENGINE</code>.
/// </summary>
/// <param name="engineName">
/// The name of the <code>ENGINE</code> to process the public key open request.
/// </param>
/// <param name="keyId">
/// The name of the key to open.
/// </param>
/// <returns>
/// The opened key.
/// </returns>
/// <exception cref="ArgumentNullException">
/// <paramref name="engineName"/> or <paramref name="keyId"/> is <see langword="null" />.
/// </exception>
/// <exception cref="ArgumentException">
/// <paramref name="engineName"/> or <paramref name="keyId"/> is the empty string.
/// </exception>
/// <exception cref="CryptographicException">
/// the key could not be opened via the specified ENGINE.
/// </exception>
/// <remarks>
/// <para>
/// This operation will fail if OpenSSL cannot successfully load the named <code>ENGINE</code>,
/// or if the named <code>ENGINE</code> cannot load the named key.
/// </para>
/// <para>
/// Not all <code>ENGINE</code>s support loading public keys, even ones that support
/// loading private keys.
/// </para>
/// <para>
/// The syntax for <paramref name="keyId"/> is determined by each individual
/// <code>ENGINE</code>.
/// </para>
/// </remarks>
[UnsupportedOSPlatform("android")]
[UnsupportedOSPlatform("browser")]
[UnsupportedOSPlatform("ios")]
[UnsupportedOSPlatform("tvos")]
[UnsupportedOSPlatform("windows")]
public static SafeEvpPKeyHandle OpenPublicKeyFromEngine(string engineName, string keyId)
{
ArgumentException.ThrowIfNullOrEmpty(engineName);
ArgumentException.ThrowIfNullOrEmpty(keyId);
if (!Interop.OpenSslNoInit.OpenSslIsAvailable)
{
throw new PlatformNotSupportedException(SR.PlatformNotSupported_CryptographyOpenSSL);
}
return Interop.Crypto.LoadPublicKeyFromEngine(engineName, keyId);
}
}
}
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using Test.Cryptography;
using Xunit;
namespace System.Security.Cryptography.Tests
{
// See osslplugins/README.md for instructions on how to build and install the test engine and setup for TPM tests.
public class OpenSslNamedKeysTests
{
private const string EnvVarPrefix = "DOTNET_CRYPTOGRAPHY_TESTS_ENGINE_";
private const string TestEngineEnabledEnvVarName = EnvVarPrefix + "ENABLE";
private const string TestEngineEnsureFailingEnvVarName = EnvVarPrefix + "ENSURE_FAILING";
private const string TpmTssEngineEcDsaKeyHandleEnvVarName = EnvVarPrefix + "TPM_ECDSA_KEY_HANDLE";
private const string NonExistingEngineName = "dntestnonexisting";
private const string NonExistingEngineKeyName = "nonexisting";
private const string TestEngineName = "dntest";
private const string TestEngineKeyId = "first";
private const string TpmTssEngineName = "tpm2tss";
public static string TpmTssEngineEcDsaKeyHandle { get; } = Environment.GetEnvironmentVariable(TpmTssEngineEcDsaKeyHandleEnvVarName);
public static bool ShouldRunEngineTests { get; } = PlatformDetection.OpenSslPresentOnSystem && StringToBool(Environment.GetEnvironmentVariable(TestEngineEnabledEnvVarName));
public static bool ShouldFailTests { get; } = StringToBool(Environment.GetEnvironmentVariable(TestEngineEnsureFailingEnvVarName));
public static bool ShouldRunTpmTssTests => PlatformDetection.OpenSslPresentOnSystem && !string.IsNullOrEmpty(TpmTssEngineEcDsaKeyHandle);
private static bool StringToBool(string? value)
=> "true".Equals(value, StringComparison.OrdinalIgnoreCase) || value == "1";
// PKCS#1 format
private static readonly byte[] s_rsaPrivateKey = (
"3082025C02010002818100BF67168485215A6AB89BCAB9331F6F5F360F4300BE5CF282F77042957E" +
"A202908B2279F34A426D62F59D6C1056E36DC9F6EEA9AEB1B31F8122F583EE9CAE2A86A47144905D" +
"F05441B0A5F29E03C5AC1888D93744D89638D83AC37774B339E4AFB349C714B12238B0F81A71380F" +
"051C585CB27434FA544BDAC679E1E16581D0E902030100010281810084ED8862F2BEAE37CE0C4CA7" +
"808CC5615F7F0BEE99469E1A3CD4973991DFDC5E1C730E34DC0EF43F350B668096878EB92428AE69" +
"A7FA19D82ABA4E2D4A5D5F243D4B7346734D705C4C494FE2B36E2E35C39EE08BFB1172F5AB084AF4" +
"4BD4D03702D04E6469F026EF3749CBED3ECB310746CF49DA3C2785CC17D54215EF18F3ED024100D0" +
"63F89E01EB681CEACB781FE807F87C702B522A76B7D0E06DA44BB7D6202D5E9F3E7BE5BCCC3B32B9" +
"B293AB62F50A8417C2FA9D6A76E465AA962AB61A8A9A13024100EB218F00B7317CC625DF2DFB7181" +
"1DC5DA91D9A2AD859282DCA6BA3B4C674897E9D03D9E5FD2A9FD4CE7D9A3E5B79E948429C21561E7" +
"141D90BCA75733D2489302400D07D349FE10BC47E29EAA7A44460B51ACA9E8CF62F1078CA10E7EF5" +
"95DC193A2B76FAC458D3E477BD88DF16FE6F18233E6120CEAB1398208B542C838A91542502407882" +
"619D9746A8D191957A26B5FCDBFA8CD455BBF7BD4EE2FD1E02B2E3ACC7DAFC3DFB66D16BD22DFD9D" +
"92C15ABA2A6FA9F111050E8175A0D58EAB219970BC3B02404DBF36E5DCBF027AD4ED572E6F5F8383" +
"C08CD5838C0CAE16FA58EE5C5A388B287F9C58647D58609B03912A10D0C772A3259D39651CD1EEB3" +
"A20C5F9AE58E18C0").HexToByteArray();
// PKCS#1 format
private static readonly byte[] s_rsaPubKey = (
"30818902818100BF67168485215A6AB89BCAB9331F6F5F360F4300BE5CF282F77042957EA202908B" +
"2279F34A426D62F59D6C1056E36DC9F6EEA9AEB1B31F8122F583EE9CAE2A86A47144905DF05441B0" +
"A5F29E03C5AC1888D93744D89638D83AC37774B339E4AFB349C714B12238B0F81A71380F051C585C" +
"B27434FA544BDAC679E1E16581D0E90203010001").HexToByteArray();
[ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.OpenSslNotPresentOnSystem))]
public static void NotSupported()
{
Assert.Throws<PlatformNotSupportedException>(() => SafeEvpPKeyHandle.OpenPublicKeyFromEngine(TestEngineName, TestEngineKeyId));
Assert.Throws<PlatformNotSupportedException>(() => SafeEvpPKeyHandle.OpenPrivateKeyFromEngine(TestEngineName, TestEngineKeyId));
}
[ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.OpenSslPresentOnSystem))]
public static void NullArguments()
{
Assert.Throws<ArgumentNullException>("engineName", () => SafeEvpPKeyHandle.OpenPrivateKeyFromEngine(null, TestEngineKeyId));
Assert.Throws<ArgumentNullException>("keyId", () => SafeEvpPKeyHandle.OpenPrivateKeyFromEngine(TestEngineName, null));
Assert.Throws<ArgumentNullException>("engineName", () => SafeEvpPKeyHandle.OpenPublicKeyFromEngine(null, TestEngineKeyId));
Assert.Throws<ArgumentNullException>("keyId", () => SafeEvpPKeyHandle.OpenPublicKeyFromEngine(TestEngineName, null));
}
[ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.OpenSslPresentOnSystem))]
public static void NonExistingEngine()
{
Assert.ThrowsAny<CryptographicException>(() => SafeEvpPKeyHandle.OpenPrivateKeyFromEngine(NonExistingEngineName, TestEngineKeyId));
Assert.ThrowsAny<CryptographicException>(() => SafeEvpPKeyHandle.OpenPublicKeyFromEngine(NonExistingEngineName, TestEngineKeyId));
}
[ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.OpenSslPresentOnSystem))]
public static void NonExistingKey()
{
Assert.ThrowsAny<CryptographicException>(() => SafeEvpPKeyHandle.OpenPrivateKeyFromEngine(TestEngineName, NonExistingEngineKeyName));
Assert.ThrowsAny<CryptographicException>(() => SafeEvpPKeyHandle.OpenPublicKeyFromEngine(TestEngineName, NonExistingEngineKeyName));
}
[ConditionalFact(nameof(ShouldRunEngineTests))]
public static void Engine_SanityTest()
{
Assert.False(ShouldFailTests, "This test is supposed to fail");
}
[ConditionalFact(nameof(ShouldRunTpmTssTests))]
public static void Tpm_SanityTest()
{
Assert.False(ShouldFailTests, "This test is supposed to fail");
}
[ConditionalFact(nameof(ShouldRunEngineTests))]
public static void Engine_OpenExistingPrivateKey()
{
using SafeEvpPKeyHandle priKeyHandle = SafeEvpPKeyHandle.OpenPrivateKeyFromEngine(TestEngineName, TestEngineKeyId);
using RSA priKey = new RSAOpenSsl(priKeyHandle);
RSAParameters rsaParams = priKey.ExportParameters(includePrivateParameters: true);
Assert.NotNull(rsaParams.D);
Assert.Equal(s_rsaPubKey, priKey.ExportRSAPublicKey());
}
[ConditionalFact(nameof(ShouldRunEngineTests))]
public static void Engine_OpenExistingPublicKey()
{
using SafeEvpPKeyHandle pubKeyHandle = SafeEvpPKeyHandle.OpenPublicKeyFromEngine(TestEngineName, TestEngineKeyId);
using RSA pubKey = new RSAOpenSsl(pubKeyHandle);
Assert.ThrowsAny<CryptographicException>(() => pubKey.ExportParameters(includePrivateParameters: true));
RSAParameters rsaParams = pubKey.ExportParameters(includePrivateParameters: false);
Assert.Null(rsaParams.D);
Assert.Equal(s_rsaPubKey, pubKey.ExportRSAPublicKey());
}
[ConditionalFact(nameof(ShouldRunEngineTests))]
public static void Engine_UsePrivateKey()
{
using (SafeEvpPKeyHandle priKeyHandle = SafeEvpPKeyHandle.OpenPrivateKeyFromEngine(TestEngineName, TestEngineKeyId))
using (RSA rsaPri = new RSAOpenSsl(priKeyHandle))
using (RSA rsaPub = RSA.Create())
{
rsaPub.ImportRSAPublicKey(s_rsaPubKey, out int bytesRead);
Assert.Equal(s_rsaPubKey.Length, bytesRead);
byte[] data = new byte[] { 1, 2, 3, 1, 1, 2, 3 };
byte[] signature = rsaPri.SignData(data, HashAlgorithmName.SHA256, RSASignaturePadding.Pss);
Assert.True(rsaPub.VerifyData(data, signature, HashAlgorithmName.SHA256, RSASignaturePadding.Pss));
signature[0] ^= 1;
Assert.False(rsaPub.VerifyData(data, signature, HashAlgorithmName.SHA256, RSASignaturePadding.Pss));
signature[0] ^= 1;
byte[] encrypted = rsaPub.Encrypt(data, RSAEncryptionPadding.OaepSHA256);
Assert.NotEqual(encrypted, data);
byte[] decrypted = rsaPri.Decrypt(encrypted, RSAEncryptionPadding.OaepSHA256);
Assert.Equal(data, decrypted);
}
}
[ConditionalFact(nameof(ShouldRunEngineTests))]
public static void Engine_UsePublicKey()
{
using (SafeEvpPKeyHandle pubKeyHandle = SafeEvpPKeyHandle.OpenPublicKeyFromEngine(TestEngineName, TestEngineKeyId))
using (RSA rsaPub = new RSAOpenSsl(pubKeyHandle))
using (RSA rsaPri = RSA.Create())
{
rsaPri.ImportRSAPrivateKey(s_rsaPrivateKey, out int bytesRead);
Assert.Equal(s_rsaPrivateKey.Length, bytesRead);
byte[] data = new byte[] { 1, 2, 3, 1, 1, 2, 3 };
byte[] signature = rsaPri.SignData(data, HashAlgorithmName.SHA256, RSASignaturePadding.Pss);
Assert.True(rsaPub.VerifyData(data, signature, HashAlgorithmName.SHA256, RSASignaturePadding.Pss));
signature[0] ^= 1;
Assert.False(rsaPub.VerifyData(data, signature, HashAlgorithmName.SHA256, RSASignaturePadding.Pss));
signature[0] ^= 1;
byte[] encrypted = rsaPub.Encrypt(data, RSAEncryptionPadding.OaepSHA256);
Assert.NotEqual(encrypted, data);
byte[] decrypted = rsaPri.Decrypt(encrypted, RSAEncryptionPadding.OaepSHA256);
Assert.Equal(data, decrypted);
}
}
[ConditionalFact(nameof(ShouldRunTpmTssTests))]
public static void Engine_OpenExistingTPMPrivateKey()
{
using SafeEvpPKeyHandle priKeyHandle = SafeEvpPKeyHandle.OpenPrivateKeyFromEngine(TpmTssEngineName, TpmTssEngineEcDsaKeyHandle);
using ECDsa ecdsaPri = new ECDsaOpenSsl(priKeyHandle);
using ECDsa ecdsaBad = ECDsa.Create();
ecdsaBad.KeySize = ecdsaPri.KeySize;
byte[] data = new byte[] { 1, 2, 3, 1, 1, 2, 3 };
byte[] signature = ecdsaPri.SignData(data, HashAlgorithmName.SHA256);
byte[] badSignature = ecdsaBad.SignData(data, HashAlgorithmName.SHA256);
Assert.NotEqual(data, signature);
Assert.True(ecdsaPri.VerifyData(data, signature, HashAlgorithmName.SHA256));
Assert.False(ecdsaPri.VerifyData(data, badSignature, HashAlgorithmName.SHA256));
}
}
}
......@@ -273,6 +273,7 @@
<Compile Include="MD5Tests.cs" />
<Compile Include="OidTests.cs" />
<Compile Include="OidCollectionTests.cs" />
<Compile Include="OpenSslNamedKeysTests.manual.cs" />
<Compile Include="PaddingModeTests.cs" />
<Compile Include="PbeParametersTests.cs" />
<Compile Include="PemEncodingTests.cs" />
......
# Testing instructions for OpenSSL ENGINE
Once everything is setup tests related to TPM and our engine can be run using:
```bash
./test.sh
```
This script will re-build native components, rebuild the product and then build and run subset of manual tests related to our ENGINE.
If TPM environmental variable similar to following is defined:
```bash
# 0x81000007 is just an example, read further how to get it
export DOTNET_CRYPTOGRAPHY_TESTS_ENGINE_TPM_ECDSA_KEY_HANDLE=0x81000007
```
then tests using TPM will be run as well and they will use `0x81000007` handle.
Instructions how to get this handle are described further in this document.
To ensure you're actually running all tests run script with following argument:
```bash
./test.sh --self-check
```
that should cause 2 sanity tests to fail (one for TPM and one for our test engine). Those are meant only as a self-check that tests are actually running. The error message should say that this failure is expected.
If you're seeing single failure and TPM tests skipped it means you have not passed the handle through environmental variable.
If you're seeing no failures or different errors then debug.
## Building and installing test OpenSSL ENGINE
Source code for our test ENGINE implementation is in [e_dntest.c](e_dntest.c) file.
In order to build and install our test engine run:
```bash
./build.sh && ./install-engine.sh
```
You should see following output:
```
INFO: Building dntest ENGINE...
INFO: dntest ENGINE built successfully...
INFO: dntest loading test successful
INFO: Installing dntest.so engine to /usr/lib/x86_64-linux-gnu/engines-3/
INFO: Installation finished successfuly
```
After installation following environmental variable can be used to enable our test engine tests with `dotnet test`:
```bash
export DOTNET_CRYPTOGRAPHY_TESTS_ENGINE_ENABLE=true
```
This step is already done by `test.sh` script.
## TPM testing
In order to test TPM you need:
- tpm2-tss-engine installed
- a TPM handle and set it with environmental variable so that tests pick it up
### Building and installing tpm2-tss-engine
You should follow [tpm2-tss-engine INSTALL.md](https://github.com/tpm2-software/tpm2-tss-engine/blob/master/INSTALL.md) page.
These instructions have worked for me for the most part except:
```bash
./configure
```
produced bunch of warnings which got treated as errors. I have suppressed them and was successfully able to build using:
```bash
./configure CFLAGS='-DOPENSSL_SUPPRESS_DEPRECATED -Wno-incompatible-pointer-types -Wno-discarded-qualifiers'
```
instead.
### Verifying installation
After installation (`sudo make install`) run following command:
```bash
openssl engine -t -c tpm2tss
```
On my installation this has printed following output:
```
(tpm2tss) TPM2-TSS engine for OpenSSL
[RSA, RAND]
[ available ]
4007A032E27F0000:error:1280006A:DSO support routines:dlfcn_bind_func:could not bind to the requested symbol name:../crypto/dso/dso_dlfcn.c:188:symname(EVP_PKEY_base_id): /usr/lib/x86_64-linux-gnu/engines-3/tpm2tss.so: undefined symbol: EVP_PKEY_base_id
4007A032E27F0000:error:1280006A:DSO support routines:DSO_bind_func:could not bind to the requested symbol name:../crypto/dso/dso_lib.c:176:
```
which tells me tpm2tss engine is now available.
Per https://github.com/openssl/openssl/issues/17962 those errors showed in the end can be ignored.
### Debugging tpm2-tss-engine issues
To enable extra logging use following environment variable at runtime:
```
export TSS2_LOG=all+TRACE
```
Most of the time this should not be needed but it might be useful if you're seeing issues when interacting with the ENGINE.
### Getting TPM handle
First, we will need `tpm2-tools`` installed:
```bash
sudo apt install tpm2-tools
```
#### Getting TPM handles
If you already have a handle but you forgot what it is you can list all available handles using following command:
```bash
tpm2_getcap handles-persistent
```
it can be also used to verify if your handle got created correctly.
Command by default will only list handles but no information about them.
To get information about specific handle:
```bash
tpm2_readpublic -c 0x81000007
```
You can also extract public key like this if needed:
```bash
tpm2_readpublic -c 0x81000007 -o /tmp/key.pub
```
#### Testing handle with OpenSSL CLI
In case you find issues with your handle you can test it using OpenSSL CLI, for example `0x81000004` can be tested like following:
##### RSA key
```bash
# create testdata file with some content
echo 'content' > testdata
# hash & sign data
openssl dgst -engine tpm2tss -keyform engine -sha256 -sign 0x81000007 -out testdata.sig testdata
# sign digest
openssl pkeyutl -engine tpm2tss -keyform engine -inkey 0x81000007 -sign -pkeyopt digest:sha256 -in testdata.dgst -out testdata.sig
# get public key (PEM)
openssl pkey -engine tpm2tss -inform engine -in '0x81000007' -pubout -out testkey.pub
# verify data
openssl pkeyutl -verify -in testdata.dgst -sigfile testdata.sig -inkey testkey.pub -pubin -pkeyopt digest:sha256
```
#### Creating keys and handles
##### ECDSA key
```bash
tpm2_createprimary -C o -g sha256 -G ecc256:ecdsa-sha256:null -c primary.ctx -a 'fixedtpm|fixedparent|sensitivedataorigin|userwithauth|noda|sign'
# To create permenent handle and print it:
tpm2_evictcontrol -C o -c primary.ctx
```
##### RSA key (RSAPSS + SHA256)
This is not used by tests but if needed for further testing:
```bash
# To create key
tpm2_createprimary -C o -g sha256 -G rsa2048:rsapss:null -c primary.ctx -a 'fixedtpm|fixedparent|sensitivedataorigin|userwithauth|noda|sign'
# To create permenent handle and print it:
tpm2_evictcontrol -C o -c primary.ctx
```
#!/bin/sh
if [ -e /usr/include/openssl/engine.h ]; then
echo "INFO: Building dntest ENGINE..."
clang -fPIC -o e_dntest.o -c e_dntest.c &&
ld -shared --no-undefined --build-id -o dntest.so e_dntest.o -lcrypto -lc &&
echo "INFO: dntest ENGINE built successfully..."
else
echo "ERROR: Cannot build dntest ENGINE, missing engine.h"
fi
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
#include <string.h>
#define OPENSSL_SUPPRESS_DEPRECATED
#include <openssl/engine.h>
static const char* g_engineId = "dntest";
static const char* g_engineName = "DotNet Test ENGINE";
// Public key and its private key are named `first`.
// Their namespaces (dntest_load_privkey vs dntest_load_pubkey) allow to use
// independent unrelated keys.
// Managed tests validate we're actually loading public key when public is requested
// and that private key is used when private is requested.
// Public key in PKCS#1 format
static char g_pubRsaKey[] = {
0x30,0x81,0x89,0x02,0x81,0x81,0x00,0xBF,0x67,0x16,0x84,0x85,0x21,0x5A,0x6A,0xB8,
0x9B,0xCA,0xB9,0x33,0x1F,0x6F,0x5F,0x36,0x0F,0x43,0x00,0xBE,0x5C,0xF2,0x82,0xF7,
0x70,0x42,0x95,0x7E,0xA2,0x02,0x90,0x8B,0x22,0x79,0xF3,0x4A,0x42,0x6D,0x62,0xF5,
0x9D,0x6C,0x10,0x56,0xE3,0x6D,0xC9,0xF6,0xEE,0xA9,0xAE,0xB1,0xB3,0x1F,0x81,0x22,
0xF5,0x83,0xEE,0x9C,0xAE,0x2A,0x86,0xA4,0x71,0x44,0x90,0x5D,0xF0,0x54,0x41,0xB0,
0xA5,0xF2,0x9E,0x03,0xC5,0xAC,0x18,0x88,0xD9,0x37,0x44,0xD8,0x96,0x38,0xD8,0x3A,
0xC3,0x77,0x74,0xB3,0x39,0xE4,0xAF,0xB3,0x49,0xC7,0x14,0xB1,0x22,0x38,0xB0,0xF8,
0x1A,0x71,0x38,0x0F,0x05,0x1C,0x58,0x5C,0xB2,0x74,0x34,0xFA,0x54,0x4B,0xDA,0xC6,
0x79,0xE1,0xE1,0x65,0x81,0xD0,0xE9,0x02,0x03,0x01,0x00,0x01
};
// Private key in PKCS#1 format for public key above
char g_priRsaKey[] = {
0x30,0x82,0x02,0x5C,0x02,0x01,0x00,0x02,0x81,0x81,0x00,0xBF,0x67,0x16,0x84,0x85,
0x21,0x5A,0x6A,0xB8,0x9B,0xCA,0xB9,0x33,0x1F,0x6F,0x5F,0x36,0x0F,0x43,0x00,0xBE,
0x5C,0xF2,0x82,0xF7,0x70,0x42,0x95,0x7E,0xA2,0x02,0x90,0x8B,0x22,0x79,0xF3,0x4A,
0x42,0x6D,0x62,0xF5,0x9D,0x6C,0x10,0x56,0xE3,0x6D,0xC9,0xF6,0xEE,0xA9,0xAE,0xB1,
0xB3,0x1F,0x81,0x22,0xF5,0x83,0xEE,0x9C,0xAE,0x2A,0x86,0xA4,0x71,0x44,0x90,0x5D,
0xF0,0x54,0x41,0xB0,0xA5,0xF2,0x9E,0x03,0xC5,0xAC,0x18,0x88,0xD9,0x37,0x44,0xD8,
0x96,0x38,0xD8,0x3A,0xC3,0x77,0x74,0xB3,0x39,0xE4,0xAF,0xB3,0x49,0xC7,0x14,0xB1,
0x22,0x38,0xB0,0xF8,0x1A,0x71,0x38,0x0F,0x05,0x1C,0x58,0x5C,0xB2,0x74,0x34,0xFA,
0x54,0x4B,0xDA,0xC6,0x79,0xE1,0xE1,0x65,0x81,0xD0,0xE9,0x02,0x03,0x01,0x00,0x01,
0x02,0x81,0x81,0x00,0x84,0xED,0x88,0x62,0xF2,0xBE,0xAE,0x37,0xCE,0x0C,0x4C,0xA7,
0x80,0x8C,0xC5,0x61,0x5F,0x7F,0x0B,0xEE,0x99,0x46,0x9E,0x1A,0x3C,0xD4,0x97,0x39,
0x91,0xDF,0xDC,0x5E,0x1C,0x73,0x0E,0x34,0xDC,0x0E,0xF4,0x3F,0x35,0x0B,0x66,0x80,
0x96,0x87,0x8E,0xB9,0x24,0x28,0xAE,0x69,0xA7,0xFA,0x19,0xD8,0x2A,0xBA,0x4E,0x2D,
0x4A,0x5D,0x5F,0x24,0x3D,0x4B,0x73,0x46,0x73,0x4D,0x70,0x5C,0x4C,0x49,0x4F,0xE2,
0xB3,0x6E,0x2E,0x35,0xC3,0x9E,0xE0,0x8B,0xFB,0x11,0x72,0xF5,0xAB,0x08,0x4A,0xF4,
0x4B,0xD4,0xD0,0x37,0x02,0xD0,0x4E,0x64,0x69,0xF0,0x26,0xEF,0x37,0x49,0xCB,0xED,
0x3E,0xCB,0x31,0x07,0x46,0xCF,0x49,0xDA,0x3C,0x27,0x85,0xCC,0x17,0xD5,0x42,0x15,
0xEF,0x18,0xF3,0xED,0x02,0x41,0x00,0xD0,0x63,0xF8,0x9E,0x01,0xEB,0x68,0x1C,0xEA,
0xCB,0x78,0x1F,0xE8,0x07,0xF8,0x7C,0x70,0x2B,0x52,0x2A,0x76,0xB7,0xD0,0xE0,0x6D,
0xA4,0x4B,0xB7,0xD6,0x20,0x2D,0x5E,0x9F,0x3E,0x7B,0xE5,0xBC,0xCC,0x3B,0x32,0xB9,
0xB2,0x93,0xAB,0x62,0xF5,0x0A,0x84,0x17,0xC2,0xFA,0x9D,0x6A,0x76,0xE4,0x65,0xAA,
0x96,0x2A,0xB6,0x1A,0x8A,0x9A,0x13,0x02,0x41,0x00,0xEB,0x21,0x8F,0x00,0xB7,0x31,
0x7C,0xC6,0x25,0xDF,0x2D,0xFB,0x71,0x81,0x1D,0xC5,0xDA,0x91,0xD9,0xA2,0xAD,0x85,
0x92,0x82,0xDC,0xA6,0xBA,0x3B,0x4C,0x67,0x48,0x97,0xE9,0xD0,0x3D,0x9E,0x5F,0xD2,
0xA9,0xFD,0x4C,0xE7,0xD9,0xA3,0xE5,0xB7,0x9E,0x94,0x84,0x29,0xC2,0x15,0x61,0xE7,
0x14,0x1D,0x90,0xBC,0xA7,0x57,0x33,0xD2,0x48,0x93,0x02,0x40,0x0D,0x07,0xD3,0x49,
0xFE,0x10,0xBC,0x47,0xE2,0x9E,0xAA,0x7A,0x44,0x46,0x0B,0x51,0xAC,0xA9,0xE8,0xCF,
0x62,0xF1,0x07,0x8C,0xA1,0x0E,0x7E,0xF5,0x95,0xDC,0x19,0x3A,0x2B,0x76,0xFA,0xC4,
0x58,0xD3,0xE4,0x77,0xBD,0x88,0xDF,0x16,0xFE,0x6F,0x18,0x23,0x3E,0x61,0x20,0xCE,
0xAB,0x13,0x98,0x20,0x8B,0x54,0x2C,0x83,0x8A,0x91,0x54,0x25,0x02,0x40,0x78,0x82,
0x61,0x9D,0x97,0x46,0xA8,0xD1,0x91,0x95,0x7A,0x26,0xB5,0xFC,0xDB,0xFA,0x8C,0xD4,
0x55,0xBB,0xF7,0xBD,0x4E,0xE2,0xFD,0x1E,0x02,0xB2,0xE3,0xAC,0xC7,0xDA,0xFC,0x3D,
0xFB,0x66,0xD1,0x6B,0xD2,0x2D,0xFD,0x9D,0x92,0xC1,0x5A,0xBA,0x2A,0x6F,0xA9,0xF1,
0x11,0x05,0x0E,0x81,0x75,0xA0,0xD5,0x8E,0xAB,0x21,0x99,0x70,0xBC,0x3B,0x02,0x40,
0x4D,0xBF,0x36,0xE5,0xDC,0xBF,0x02,0x7A,0xD4,0xED,0x57,0x2E,0x6F,0x5F,0x83,0x83,
0xC0,0x8C,0xD5,0x83,0x8C,0x0C,0xAE,0x16,0xFA,0x58,0xEE,0x5C,0x5A,0x38,0x8B,0x28,
0x7F,0x9C,0x58,0x64,0x7D,0x58,0x60,0x9B,0x03,0x91,0x2A,0x10,0xD0,0xC7,0x72,0xA3,
0x25,0x9D,0x39,0x65,0x1C,0xD1,0xEE,0xB3,0xA2,0x0C,0x5F,0x9A,0xE5,0x8E,0x18,0xC0
};
static EVP_PKEY* load_priv(BIO* bio)
{
RSA* rsaKey = d2i_RSAPrivateKey_bio(bio, NULL);
if (!rsaKey)
{
printf("%s: Error loading RSA Private Key\n", g_engineId);
return NULL;
}
EVP_PKEY* key = EVP_PKEY_new();
if (!EVP_PKEY_assign_RSA(key, rsaKey))
{
printf("%s: Error assigning RSA Private Key to EVP_PKEY\n", g_engineId);
// Assignment has failed we need to free both keys separately
RSA_free(rsaKey);
EVP_PKEY_free(key);
return NULL;
}
// Per documentation EVP_PKEY_assign_RSA sets the referenced RSA key to supplied key internally
// and so key will be freed when the parent pkey is freed.
return key;
}
static EVP_PKEY* load_pub(BIO* bio)
{
RSA *rsaKey = d2i_RSAPublicKey_bio(bio, NULL);
if (!rsaKey)
{
printf("%s: Error loading RSA Public Key\n", g_engineId);
return NULL;
}
EVP_PKEY* key = EVP_PKEY_new();
if (!EVP_PKEY_assign_RSA(key, rsaKey))
{
printf("%s: Error assigning RSA Public Key to EVP_PKEY\n", g_engineId);
// Assignment has failed we need to free both keys separately
RSA_free(rsaKey);
EVP_PKEY_free(key);
return NULL;
}
// Per documentation EVP_PKEY_assign_RSA sets the referenced RSA key to supplied key internally
// and so key will be freed when the parent pkey is freed.
return key;
}
static EVP_PKEY* load_key(
const char* keyId,
const char* key,
int keyLength,
EVP_PKEY* (*load_func)(BIO* bio))
{
printf("%s: Key load request for '%s'. Key length=%d\n", g_engineId, keyId, keyLength);
EVP_PKEY* ret = NULL;
if (keyId != NULL)
{
if (strcmp(keyId, "first") == 0)
{
BIO* bio = BIO_new_mem_buf(key, keyLength);
if (bio != NULL)
{
ret = load_func(bio);
BIO_free(bio);
}
}
else
{
printf("%s: Key load request failed for '%s'\n", g_engineId, keyId);
}
}
return ret;
}
static EVP_PKEY* dntest_load_privkey(
ENGINE* engine,
const char* keyId,
UI_METHOD* ui_method,
void* callback_data)
{
printf("%s: Request to load private key for %s\n", g_engineId, keyId);
return load_key(keyId, g_priRsaKey, sizeof(g_priRsaKey), load_priv);
}
static EVP_PKEY* dntest_load_pubkey(
ENGINE* engine,
const char* keyId,
UI_METHOD* ui_method,
void* callback_data)
{
printf("%s: Request to load public key for %s\n", g_engineId, keyId);
return load_key(keyId, g_pubRsaKey, sizeof(g_pubRsaKey), load_pub);
}
static int bind(ENGINE* engine, const char* id)
{
int ret = 1;
if (!ENGINE_set_id(engine, g_engineId) ||
!ENGINE_set_name(engine, g_engineName) ||
!ENGINE_set_load_privkey_function(engine, dntest_load_privkey) ||
!ENGINE_set_load_pubkey_function(engine, dntest_load_pubkey))
{
ret = 0;
}
return ret;
}
IMPLEMENT_DYNAMIC_BIND_FN(bind)
IMPLEMENT_DYNAMIC_CHECK_FN()
#!/bin/sh
enginepath=`openssl version -a | grep ENGINESDIR | cut -d '"' -f 2`
if [ -z "$enginepath" ]; then
enginepath="/usr/lib/x86_64-linux-gnu/engines-3/"
echo "WARNING: enginepath was not determined and following value will be used: $enginepath"
echo "WARNING: Please update install-engine.sh script."
fi
if ! openssl engine -t -c `pwd`/dntest.so > /dev/null 2>&1; then
echo 'ERROR: Unable to load dntest.so engine.'
exit 1
fi
echo 'INFO: dntest loading test successful'
enginepath="${enginepath%/}/"
echo "INFO: Installing dntest.so engine to $enginepath"
sudo cp dntest.so $enginepath && echo 'INFO: Installation finished successfuly'
#!/bin/bash
# Follow instructions from README.md
ossplugings_path="$(dirname "$(realpath "${BASH_SOURCE[0]}")")"
ssc_tests_path="$(dirname "$ossplugings_path")"
ssc_path="$(dirname "$ssc_tests_path")"
ssc_src_path="$ssc_path/src"
libsrc_path="$(dirname "$ssc_path")"
src_path="$(dirname "$libsrc_path")"
repo_root_path="$(dirname "$src_path")"
dotnet="$repo_root_path/dotnet.sh"
nativelibs_path="$src_path/native/libs"
export DOTNET_CRYPTOGRAPHY_TESTS_ENGINE_ENABLE=true
if [ -z "$DOTNET_CRYPTOGRAPHY_TESTS_ENGINE_TPM_ECDSA_KEY_HANDLE" ]; then
echo "WARNING: TPM tests will not be run"
echo "WARNING: Use following environmental variable to enable them:"
echo "WARNING: export DOTNET_CRYPTOGRAPHY_TESTS_ENGINE_TPM_ECDSA_KEY_HANDLE=YourHandleHere"
echo "WARNING: For example:"
echo "WARNING: export DOTNET_CRYPTOGRAPHY_TESTS_ENGINE_TPM_ECDSA_KEY_HANDLE=0x81000007"
echo "WARNING: Refer to README.md for more information on how to get handle."
fi
if [ "$1" == "--self-check" ]; then
export DOTNET_CRYPTOGRAPHY_TESTS_ENGINE_ENSURE_FAILING=true
else
echo "INFO: To run self-check use:"
echo "INFO: ./test.sh --self-check"
echo "INFO: Expect two test failures."
fi
set -e
cd "$nativelibs_path"
$dotnet build ./build-native.proj
cd "$ssc_src_path"
$dotnet build
cd "$ssc_tests_path"
$dotnet test --filter "FullyQualifiedName~System.Security.Cryptography.Tests.OpenSslNamedKeysTests."
......@@ -215,6 +215,8 @@ static const Entry s_cryptoNative[] =
DllImportEntry(CryptoNative_HmacOneShot)
DllImportEntry(CryptoNative_HmacReset)
DllImportEntry(CryptoNative_HmacUpdate)
DllImportEntry(CryptoNative_LoadPrivateKeyFromEngine)
DllImportEntry(CryptoNative_LoadPublicKeyFromEngine)
DllImportEntry(CryptoNative_LookupFriendlyNameByOid)
DllImportEntry(CryptoNative_NewX509Stack)
DllImportEntry(CryptoNative_ObjNid2Obj)
......
......@@ -17,6 +17,7 @@
#include <openssl/dsa.h>
#include <openssl/ec.h>
#include <openssl/ecdsa.h>
#include <openssl/engine.h>
#include <openssl/err.h>
#include <openssl/evp.h>
#include <openssl/hmac.h>
......@@ -281,6 +282,12 @@ int EVP_DigestFinalXOF(EVP_MD_CTX *ctx, unsigned char *md, size_t len);
REQUIRED_FUNCTION(EC_POINT_mul) \
REQUIRED_FUNCTION(EC_POINT_new) \
REQUIRED_FUNCTION(EC_POINT_set_affine_coordinates_GFp) \
REQUIRED_FUNCTION(ENGINE_by_id) \
REQUIRED_FUNCTION(ENGINE_finish) \
REQUIRED_FUNCTION(ENGINE_free) \
REQUIRED_FUNCTION(ENGINE_init) \
REQUIRED_FUNCTION(ENGINE_load_public_key) \
REQUIRED_FUNCTION(ENGINE_load_private_key) \
REQUIRED_FUNCTION(ERR_clear_error) \
REQUIRED_FUNCTION(ERR_error_string_n) \
REQUIRED_FUNCTION(ERR_get_error) \
......@@ -769,6 +776,12 @@ FOR_ALL_OPENSSL_FUNCTIONS
#define EC_POINT_mul EC_POINT_mul_ptr
#define EC_POINT_new EC_POINT_new_ptr
#define EC_POINT_set_affine_coordinates_GFp EC_POINT_set_affine_coordinates_GFp_ptr
#define ENGINE_by_id ENGINE_by_id_ptr
#define ENGINE_finish ENGINE_finish_ptr
#define ENGINE_free ENGINE_free_ptr
#define ENGINE_init ENGINE_init_ptr
#define ENGINE_load_public_key ENGINE_load_public_key_ptr
#define ENGINE_load_private_key ENGINE_load_private_key_ptr
#define ERR_clear_error ERR_clear_error_ptr
#define ERR_error_string_n ERR_error_string_n_ptr
#define ERR_get_error ERR_get_error_ptr
......
......@@ -275,3 +275,42 @@ int32_t CryptoNative_EncodeSubjectPublicKeyInfo(EVP_PKEY* pkey, uint8_t* buf)
ERR_clear_error();
return i2d_PUBKEY(pkey, &buf);
}
static EVP_PKEY* LoadKeyFromEngine(
const char* engineName,
const char* keyName,
ENGINE_LOAD_KEY_PTR load_func)
{
ERR_clear_error();
EVP_PKEY* ret = NULL;
ENGINE* engine = NULL;
// Per https://github.com/openssl/openssl/discussions/21427
// using EVP_PKEY after freeing ENGINE is correct.
engine = ENGINE_by_id(engineName);
if (engine != NULL)
{
if (ENGINE_init(engine))
{
ret = load_func(engine, keyName, NULL, NULL);
ENGINE_finish(engine);
}
ENGINE_free(engine);
}
return ret;
}
EVP_PKEY* CryptoNative_LoadPrivateKeyFromEngine(const char* engineName, const char* keyName)
{
return LoadKeyFromEngine(engineName, keyName, ENGINE_load_private_key);
}
EVP_PKEY* CryptoNative_LoadPublicKeyFromEngine(const char* engineName, const char* keyName)
{
return LoadKeyFromEngine(engineName, keyName, ENGINE_load_public_key);
}
......@@ -88,3 +88,17 @@ buf must be big enough, or an out of bounds write may occur.
Returns the number of bytes written.
*/
PALEXPORT int32_t CryptoNative_EncodeSubjectPublicKeyInfo(EVP_PKEY* pkey, uint8_t* buf);
/*
Load a named key, via ENGINE_load_private_key, from the named engine.
Returns a valid EVP_PKEY* on success, NULL on failure.
*/
PALEXPORT EVP_PKEY* CryptoNative_LoadPrivateKeyFromEngine(const char* engineName, const char* keyName);
/*
Load a named key, via ENGINE_load_public_key, from the named engine.
Returns a valid EVP_PKEY* on success, NULL on failure.
*/
PALEXPORT EVP_PKEY* CryptoNative_LoadPublicKeyFromEngine(const char* engineName, const char* keyName);
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册