-
Notifications
You must be signed in to change notification settings - Fork 37
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add support for authenticating a user with an X.509 cert
- Loading branch information
Showing
14 changed files
with
394 additions
and
52 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,62 @@ | ||
name: Test EE | ||
|
||
on: | ||
pull_request: | ||
push: | ||
branches: | ||
- master | ||
tags: | ||
- v* | ||
|
||
jobs: | ||
test: | ||
timeout-minutes: 20 | ||
strategy: | ||
fail-fast: false | ||
matrix: | ||
framework: [ net6.0, net7.0, net8.0 ] | ||
os: [ ubuntu-latest ] | ||
build: [ Streams, PersistentSubscriptions, Operations, UserManagement, ProjectionManagement ] | ||
test: [ Plugins ] | ||
configuration: [ release ] | ||
runs-on: ${{ matrix.os }} | ||
name: EventStore.Client.${{ matrix.test }}/${{ matrix.os }}/${{ matrix.framework }}/24.2.0-jammy | ||
steps: | ||
- name: Checkout | ||
uses: actions/checkout@v3 | ||
- shell: bash | ||
run: | | ||
git fetch --prune --unshallow | ||
- name: Login to Cloudsmith | ||
uses: docker/login-action@v3 | ||
with: | ||
registry: docker.eventstore.com | ||
username: ${{ secrets.CLOUDSMITH_CICD_USER }} | ||
password: ${{ secrets.CLOUDSMITH_CICD_TOKEN }} | ||
- name: Pull EventStore Image | ||
shell: bash | ||
run: | | ||
docker pull docker.eventstore.com/eventstore-ee/eventstoredb-commercial:24.2.0-jammy | ||
- name: Install dotnet SDKs | ||
uses: actions/setup-dotnet@v3 | ||
with: | ||
dotnet-version: | | ||
6.0.x | ||
7.0.x | ||
8.0.x | ||
- name: Compile | ||
shell: bash | ||
run: | | ||
dotnet build --configuration ${{ matrix.configuration }} --framework ${{ matrix.framework }} src/EventStore.Client.${{ matrix.build }} | ||
- name: Run Tests | ||
if: ${{ inputs.docker-registry == '' }} | ||
shell: bash | ||
env: | ||
ES_DOCKER_TAG: 24.2.0-jammy | ||
ES_DOCKER_REGISTRY: docker.eventstore.com/eventstore-ee/eventstoredb-commercial | ||
run: | | ||
sudo ./gencert.sh | ||
dotnet test --configuration ${{ matrix.configuration }} --blame \ | ||
--logger:"GitHubActions;report-warnings=false" --logger:"console;verbosity=normal" \ | ||
--framework ${{ matrix.framework }} \ | ||
test/EventStore.Client.${{ matrix.ee }}.Tests |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,107 @@ | ||
using System; | ||
using System.IO; | ||
using System.Linq; | ||
using System.Security.Cryptography; | ||
using System.Security.Cryptography.X509Certificates; | ||
|
||
#if NET48 | ||
using Org.BouncyCastle.Crypto; | ||
using Org.BouncyCastle.Crypto.Parameters; | ||
using Org.BouncyCastle.OpenSsl; | ||
using Org.BouncyCastle.Security; | ||
#endif | ||
|
||
namespace EventStore.Client; | ||
|
||
/// <summary> | ||
/// Utility class for loading certificates and private keys from files. | ||
/// </summary> | ||
static class CertificateUtils { | ||
private static RSA LoadKey(string privateKeyPath) { | ||
string[] allLines = File.ReadAllLines(privateKeyPath); | ||
var header = allLines[0].Replace("-", ""); | ||
var privateKeyLines = allLines.Skip(1).Take(allLines.Length - 2); | ||
var privateKey = Convert.FromBase64String(string.Join(string.Empty, privateKeyLines)); | ||
|
||
var rsa = RSA.Create(); | ||
switch (header) { | ||
case "BEGIN PRIVATE KEY": | ||
#if NET | ||
rsa.ImportPkcs8PrivateKey(new ReadOnlySpan<byte>(privateKey), out _); | ||
#else | ||
{ | ||
var pemReader = new PemReader(new StringReader(string.Join(Environment.NewLine, allLines))); | ||
var keyPair = (AsymmetricCipherKeyPair)pemReader.ReadObject(); | ||
var privateKeyParams = (RsaPrivateCrtKeyParameters)keyPair.Private; | ||
rsa.ImportParameters(DotNetUtilities.ToRSAParameters(privateKeyParams)); | ||
} | ||
#endif | ||
break; | ||
|
||
case "BEGIN RSA PRIVATE KEY": | ||
#if NET | ||
rsa.ImportRSAPrivateKey(new ReadOnlySpan<byte>(privateKey), out _); | ||
#else | ||
{ | ||
var pemReader = new PemReader(new StringReader(string.Join(Environment.NewLine, allLines))); | ||
object pemObject = pemReader.ReadObject(); | ||
RsaPrivateCrtKeyParameters privateKeyParams; | ||
if (pemObject is RsaPrivateCrtKeyParameters) { | ||
privateKeyParams = (RsaPrivateCrtKeyParameters)pemObject; | ||
} else if (pemObject is AsymmetricCipherKeyPair keyPair) { | ||
privateKeyParams = (RsaPrivateCrtKeyParameters)keyPair.Private; | ||
} else { | ||
throw new NotSupportedException($"Unsupported PEM object type: {pemObject.GetType()}"); | ||
} | ||
|
||
rsa.ImportParameters(DotNetUtilities.ToRSAParameters(privateKeyParams)); | ||
} | ||
#endif | ||
break; | ||
|
||
default: | ||
rsa.Dispose(); | ||
throw new NotSupportedException($"Unsupported private key file format: {header}"); | ||
} | ||
|
||
return rsa; | ||
} | ||
|
||
internal static X509Certificate2 LoadCertificate(string certificatePath) { | ||
return new X509Certificate2(certificatePath); | ||
} | ||
|
||
/// <summary> | ||
/// | ||
/// </summary> | ||
/// <param name="certificatePath"></param> | ||
/// <param name="privateKeyPath"></param> | ||
/// <returns></returns> | ||
/// <exception cref="Exception"></exception> | ||
public static X509Certificate2 LoadFromFile(string certificatePath, string privateKeyPath) { | ||
X509Certificate2? publicCertificate = null; | ||
RSA? rsa = null; | ||
|
||
try { | ||
try { | ||
publicCertificate = LoadCertificate(certificatePath); | ||
} catch (Exception ex) { | ||
throw new Exception($"Failed to load certificate: {ex.Message}"); | ||
} | ||
|
||
try { | ||
rsa = LoadKey(privateKeyPath); | ||
} catch (Exception ex) { | ||
throw new Exception($"Failed to load private key: {ex.Message}"); | ||
} | ||
|
||
using var publicWithPrivate = publicCertificate.CopyWithPrivateKey(rsa); | ||
var certificate = new X509Certificate2(publicWithPrivate.Export(X509ContentType.Pfx)); | ||
|
||
return certificate; | ||
} finally { | ||
publicCertificate?.Dispose(); | ||
rsa?.Dispose(); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.