From 0353b7b785b05625a8bffde8935ded5eeb120cd5 Mon Sep 17 00:00:00 2001 From: Kyle Spearrin Date: Thu, 2 Sep 2021 15:13:23 -0400 Subject: [PATCH] pkcs11 rsa key service implementation for yubihsm --- src/CryptoAgent/CryptoAgent.csproj | 1 + src/CryptoAgent/CryptoAgentSettings.cs | 9 + src/CryptoAgent/Dockerfile | 9 + .../Services/Pkcs11RsaKeyService.cs | 197 ++++++++++++++++++ src/CryptoAgent/Startup.cs | 11 +- 5 files changed, 225 insertions(+), 2 deletions(-) create mode 100644 src/CryptoAgent/Services/Pkcs11RsaKeyService.cs diff --git a/src/CryptoAgent/CryptoAgent.csproj b/src/CryptoAgent/CryptoAgent.csproj index 69e5f1f..c812b67 100644 --- a/src/CryptoAgent/CryptoAgent.csproj +++ b/src/CryptoAgent/CryptoAgent.csproj @@ -26,6 +26,7 @@ + diff --git a/src/CryptoAgent/CryptoAgentSettings.cs b/src/CryptoAgent/CryptoAgentSettings.cs index abb45c6..5bceea6 100644 --- a/src/CryptoAgent/CryptoAgentSettings.cs +++ b/src/CryptoAgent/CryptoAgentSettings.cs @@ -57,6 +57,15 @@ public string AwsAccessKeySecret { get; set; } public string AwsRegion { get; set; } public string AwsKeyId { get; set; } + // pkcs11 + // yubihsm2 + public string Pkcs11Provider { get; set; } + public string Pkcs11LibraryPath { get; set; } + public string Pkcs11SlotTokenSerialNumber { get; set; } + public string Pkcs11LoginUserType { get; set; } + public string Pkcs11LoginPin { get; set; } + public string Pkcs11PrivateKeyLabel { get; set; } + public ulong? Pkcs11PrivateKeyId { get; set; } // Other HSMs... } diff --git a/src/CryptoAgent/Dockerfile b/src/CryptoAgent/Dockerfile index 8668004..4fe8389 100644 --- a/src/CryptoAgent/Dockerfile +++ b/src/CryptoAgent/Dockerfile @@ -8,6 +8,15 @@ RUN apt-get update \ curl \ && rm -rf /var/lib/apt/lists/* +# Install YubiHSM2 SDK +ADD https://developers.yubico.com/YubiHSM2/Releases/yubihsm2-sdk-2021-08-debian10-amd64.tar.gz ./ +RUN tar -xzf yubihsm2-sdk-*.tar.gz \ + && rm yubihsm2-sdk-*.tar.gz \ + && dpkg -i yubihsm2-sdk/libyubihsm-http1_*_amd64.deb \ + && dpkg -i yubihsm2-sdk/libyubihsm1_*_amd64.deb \ + && dpkg -i yubihsm2-sdk/yubihsm-pkcs11_*_amd64.deb \ + && apt-get install -f + ENV ASPNETCORE_URLS http://+:5000 WORKDIR /app EXPOSE 5000 diff --git a/src/CryptoAgent/Services/Pkcs11RsaKeyService.cs b/src/CryptoAgent/Services/Pkcs11RsaKeyService.cs new file mode 100644 index 0000000..965de48 --- /dev/null +++ b/src/CryptoAgent/Services/Pkcs11RsaKeyService.cs @@ -0,0 +1,197 @@ +using System.Collections.Generic; +using System.Linq; +using System.Security.Cryptography.X509Certificates; +using System.Threading.Tasks; +using Net.Pkcs11Interop.Common; +using Net.Pkcs11Interop.HighLevelAPI; + +namespace Bit.CryptoAgent.Services +{ + public class Pkcs11RsaKeyService : IRsaKeyService + { + private readonly ICertificateProviderService _certificateProviderService; + private readonly ICryptoFunctionService _cryptoFunctionService; + private readonly CryptoAgentSettings _settings; + + private X509Certificate2 _certificate; + + public Pkcs11RsaKeyService( + ICertificateProviderService certificateProviderService, + ICryptoFunctionService cryptoFunctionService, + CryptoAgentSettings settings) + { + _certificateProviderService = certificateProviderService; + _cryptoFunctionService = cryptoFunctionService; + _settings = settings; + } + + public async Task EncryptAsync(byte[] data) + { + if (data == null) + { + return null; + } + var encData = await _cryptoFunctionService.RsaEncryptAsync(data, await GetPublicKeyAsync()); + return encData; + } + + public Task DecryptAsync(byte[] data) + { + if (data == null) + { + return null; + } + + using var library = LoadLibrary(); + using var session = CreateNewSession(library); + var privateKey = GetPrivateKey(session); + + var mechanismParams = session.Factories.MechanismParamsFactory.CreateCkRsaPkcsOaepParams( + ConvertUtils.UInt64FromCKM(CKM.CKM_SHA_1), + ConvertUtils.UInt64FromCKG(CKG.CKG_MGF1_SHA1), + ConvertUtils.UInt64FromUInt32(CKZ.CKZ_DATA_SPECIFIED), + null); + var mechanism = session.Factories.MechanismFactory.Create(CKM.CKM_RSA_PKCS_OAEP, mechanismParams); + var plainData = session.Decrypt(mechanism, privateKey, data); + + Cleanup(session, privateKey); + return Task.FromResult(plainData); + } + + public Task SignAsync(byte[] data) + { + if (data == null) + { + return null; + } + + using var library = LoadLibrary(); + using var session = CreateNewSession(library); + var privateKey = GetPrivateKey(session); + + var mechanism = session.Factories.MechanismFactory.Create(CKM.CKM_SHA256_RSA_PKCS); + var signature = session.Sign(mechanism, privateKey, data); + + Cleanup(session, privateKey); + return Task.FromResult(signature); + } + + public async Task VerifyAsync(byte[] data, byte[] signature) + { + if (data == null || signature == null) + { + return false; + } + return await _cryptoFunctionService.RsaVerifyAsync(data, signature, await GetPublicKeyAsync()); + } + + public async Task GetPublicKeyAsync() + { + var certificate = await GetCertificateAsync(); + return certificate.GetRSAPublicKey().ExportSubjectPublicKeyInfo(); + } + + private async Task GetCertificateAsync() + { + if (_certificate == null) + { + _certificate = await _certificateProviderService.GetCertificateAsync(); + } + + return _certificate; + } + + private IObjectHandle GetPrivateKey(ISession session) + { + var attributes = new List + { + session.Factories.ObjectAttributeFactory.Create(CKA.CKA_CLASS, CKO.CKO_PRIVATE_KEY), + session.Factories.ObjectAttributeFactory.Create(CKA.CKA_TOKEN, true) + }; + if (_settings.RsaKey.Pkcs11PrivateKeyId.HasValue) + { + attributes.Add(session.Factories.ObjectAttributeFactory.Create(CKA.CKA_ID, + _settings.RsaKey.Pkcs11PrivateKeyId.Value)); + } + else + { + attributes.Add(session.Factories.ObjectAttributeFactory.Create(CKA.CKA_LABEL, + _settings.RsaKey.Pkcs11PrivateKeyLabel)); + } + + var objects = session.FindAllObjects(attributes); + if (objects.Count == 0) + { + throw new System.Exception("Private key not found."); + } + else if (objects.Count > 1) + { + throw new System.Exception("More than one private key was found. Use a more specific identifier."); + } + + return objects.Single(); + } + private IPkcs11Library LoadLibrary() + { + var libPath = _settings.RsaKey.Pkcs11LibraryPath; + if (string.IsNullOrWhiteSpace(libPath)) + { + var provider = _settings.RsaKey.Pkcs11Provider?.ToLowerInvariant(); + if (provider == "yubihsm2") + { + // TODO: Verify that this path works for Debian-installed YubiHSM SDKs + libPath = "/usr/lib/x86_64-linux-gnu/pkcs11/yubihsm_pkcs11.so"; + } + } + + var factories = new Pkcs11InteropFactories(); + return factories.Pkcs11LibraryFactory.LoadPkcs11Library(factories, libPath, AppType.MultiThreaded); + } + + private ISession CreateNewSession(IPkcs11Library library) + { + ISlot chosenSlot = null; + var slots = library.GetSlotList(SlotsType.WithOrWithoutTokenPresent); + foreach (var slot in slots) + { + var slotInfo = slot.GetSlotInfo(); + if (slotInfo.SlotFlags.TokenPresent) + { + var tokenInfo = slot.GetTokenInfo(); + if (tokenInfo.SerialNumber == _settings.RsaKey.Pkcs11SlotTokenSerialNumber) + { + chosenSlot = slot; + break; + } + } + } + + if (chosenSlot == null) + { + return null; + } + + var session = chosenSlot.OpenSession(SessionType.ReadWrite); + + var userType = CKU.CKU_USER; + var userTypeSetting = _settings.RsaKey.Pkcs11LoginUserType?.ToLowerInvariant(); + if (userTypeSetting == "so") + { + userType = CKU.CKU_SO; + } + else if (userTypeSetting == "context_specific") + { + userType = CKU.CKU_CONTEXT_SPECIFIC; + } + session.Login(userType, _settings.RsaKey.Pkcs11LoginPin); + + return session; + } + + private void Cleanup(ISession session, IObjectHandle privateKey) + { + session.DestroyObject(privateKey); + session.Logout(); + } + } +} diff --git a/src/CryptoAgent/Startup.cs b/src/CryptoAgent/Startup.cs index b111fed..2eb2655 100644 --- a/src/CryptoAgent/Startup.cs +++ b/src/CryptoAgent/Startup.cs @@ -128,9 +128,16 @@ namespace Bit.CryptoAgent private void AddRsaKeyProvider(IServiceCollection services, CryptoAgentSettings settings) { var rsaKeyProvider = settings.RsaKey.Provider?.ToLowerInvariant(); - if (rsaKeyProvider == "certificate") + if (rsaKeyProvider == "certificate" || rsaKeyProvider == "pkcs11") { - services.AddSingleton(); + if (rsaKeyProvider == "certificate") + { + services.AddSingleton(); + } + else if (rsaKeyProvider == "pkcs11") + { + services.AddSingleton(); + } var certificateProvider = settings.Certificate.Provider?.ToLowerInvariant(); if (certificateProvider == "store")