diff --git a/Testcontainers.sln b/Testcontainers.sln
index 4a415068d..bc6af7eaf 100644
--- a/Testcontainers.sln
+++ b/Testcontainers.sln
@@ -137,6 +137,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Testcontainers.Tests", "tes
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Testcontainers.WebDriver.Tests", "tests\Testcontainers.WebDriver.Tests\Testcontainers.WebDriver.Tests.csproj", "{EBA72C3B-57D5-43FF-A5B4-3D55B3B6D4C2}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Testcontainers.Qdrant", "src\Testcontainers.Qdrant\Testcontainers.Qdrant.csproj", "{7C98973D-53D7-49F9-BDFE-E3268F402584}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Testcontainers.Qdrant.Tests", "tests\Testcontainers.Qdrant.Tests\Testcontainers.Qdrant.Tests.csproj", "{9DCE3E7F-B341-4AD0-BAAA-C3B91EB91B0D}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -394,6 +398,18 @@ Global
{EBA72C3B-57D5-43FF-A5B4-3D55B3B6D4C2}.Debug|Any CPU.Build.0 = Debug|Any CPU
{EBA72C3B-57D5-43FF-A5B4-3D55B3B6D4C2}.Release|Any CPU.ActiveCfg = Release|Any CPU
{EBA72C3B-57D5-43FF-A5B4-3D55B3B6D4C2}.Release|Any CPU.Build.0 = Release|Any CPU
+ {23D898F8-36BE-4393-BFE2-41A862C0F951}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {23D898F8-36BE-4393-BFE2-41A862C0F951}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {23D898F8-36BE-4393-BFE2-41A862C0F951}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {23D898F8-36BE-4393-BFE2-41A862C0F951}.Release|Any CPU.Build.0 = Release|Any CPU
+ {7C98973D-53D7-49F9-BDFE-E3268F402584}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {7C98973D-53D7-49F9-BDFE-E3268F402584}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {7C98973D-53D7-49F9-BDFE-E3268F402584}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {7C98973D-53D7-49F9-BDFE-E3268F402584}.Release|Any CPU.Build.0 = Release|Any CPU
+ {9DCE3E7F-B341-4AD0-BAAA-C3B91EB91B0D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {9DCE3E7F-B341-4AD0-BAAA-C3B91EB91B0D}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {9DCE3E7F-B341-4AD0-BAAA-C3B91EB91B0D}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {9DCE3E7F-B341-4AD0-BAAA-C3B91EB91B0D}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{3F2E254F-C203-43FD-A078-DC3E2CBC0F9F} = {673F23AE-7694-4BB9-ABD4-136D6C13634E}
@@ -458,5 +474,7 @@ Global
{1A1983E6-5297-435F-B467-E8E1F11277D6} = {7164F1FB-7F24-444A-ACD2-2C329C2B3CCF}
{27CDB869-A150-4593-958F-6F26E5391E7C} = {7164F1FB-7F24-444A-ACD2-2C329C2B3CCF}
{EBA72C3B-57D5-43FF-A5B4-3D55B3B6D4C2} = {7164F1FB-7F24-444A-ACD2-2C329C2B3CCF}
+ {7C98973D-53D7-49F9-BDFE-E3268F402584} = {673F23AE-7694-4BB9-ABD4-136D6C13634E}
+ {9DCE3E7F-B341-4AD0-BAAA-C3B91EB91B0D} = {7164F1FB-7F24-444A-ACD2-2C329C2B3CCF}
EndGlobalSection
EndGlobal
diff --git a/src/Testcontainers.Qdrant/.editorconfig b/src/Testcontainers.Qdrant/.editorconfig
new file mode 100644
index 000000000..6f066619d
--- /dev/null
+++ b/src/Testcontainers.Qdrant/.editorconfig
@@ -0,0 +1 @@
+root = true
\ No newline at end of file
diff --git a/src/Testcontainers.Qdrant/QdrantBuilder.cs b/src/Testcontainers.Qdrant/QdrantBuilder.cs
new file mode 100644
index 000000000..ce3a1d1b8
--- /dev/null
+++ b/src/Testcontainers.Qdrant/QdrantBuilder.cs
@@ -0,0 +1,109 @@
+using System.IO;
+using System.Security.Cryptography.X509Certificates;
+using Org.BouncyCastle.OpenSsl;
+using Org.BouncyCastle.Security;
+
+namespace Testcontainers.Qdrant;
+
+///
+[PublicAPI]
+public sealed class QdrantBuilder : ContainerBuilder
+{
+ public const string QdrantImage = "qdrant/qdrant:v1.5.0";
+
+ public const ushort QdrantHttpPort = 6333;
+
+ public const ushort QdrantGrpcPort = 6334;
+
+ public const string QdrantLocalConfigurationFilePath = "/qdrant/config/local.yaml";
+
+ public const string QdrantTlsCertFilePath = "/qdrant/tls/cert.pem";
+
+ public const string QdrantTlsKeyFilePath = "/qdrant/tls/key.pem";
+
+ public QdrantBuilder() : this(new QdrantConfiguration()) =>
+ DockerResourceConfiguration = Init().DockerResourceConfiguration;
+
+ private QdrantBuilder(QdrantConfiguration dockerResourceConfiguration) : base(dockerResourceConfiguration) =>
+ DockerResourceConfiguration = dockerResourceConfiguration;
+
+ ///
+ /// A path to a configuration file with which configure the instance.
+ ///
+ /// The path to the configuration file
+ public QdrantBuilder WithConfigFile(string configurationFilePath) =>
+ Merge(DockerResourceConfiguration, new QdrantConfiguration(configurationFilePath: configurationFilePath))
+ .WithBindMount(configurationFilePath, QdrantLocalConfigurationFilePath);
+
+ ///
+ /// The API key used to secure the instance. A certificate should also be provided to
+ /// to enable TLS
+ ///
+ /// The API key
+ public QdrantBuilder WithApiKey(string apiKey) =>
+ Merge(DockerResourceConfiguration, new QdrantConfiguration(apiKey: apiKey))
+ .WithEnvironment("QDRANT__SERVICE__API_KEY", apiKey);
+
+ ///
+ /// A certificate to use to enable Transport Layer Security (TLS). The certificate must contain the
+ /// private key.
+ ///
+ /// A certificate containing a private key
+ public QdrantBuilder WithCertificate(X509Certificate2 certificate)
+ {
+ if (!certificate.HasPrivateKey)
+ {
+ throw new ArgumentException("certificate must contain a private key", nameof(certificate));
+ }
+
+ var builder = new StringBuilder();
+ builder.AppendLine("-----BEGIN CERTIFICATE-----");
+ builder.AppendLine(Convert.ToBase64String(certificate.RawData, Base64FormattingOptions.InsertLineBreaks));
+ builder.AppendLine("-----END CERTIFICATE-----");
+ var cert = builder.ToString();
+ builder.Clear();
+
+ var keyPair = DotNetUtilities.GetKeyPair(certificate.PrivateKey);
+ var pemWriter = new PemWriter(new StringWriter(builder));
+ pemWriter.WriteObject(keyPair.Private);
+ var key = builder.ToString();
+
+ return Merge(DockerResourceConfiguration, new QdrantConfiguration(certificate: certificate))
+ .WithEnvironment("QDRANT__SERVICE__ENABLE_TLS", "1")
+ .WithResourceMapping(Encoding.UTF8.GetBytes(cert), QdrantTlsCertFilePath)
+ .WithEnvironment("QDRANT__TLS__CERT", QdrantTlsCertFilePath)
+ .WithResourceMapping(Encoding.UTF8.GetBytes(key), QdrantTlsKeyFilePath)
+ .WithEnvironment("QDRANT__TLS__KEY", QdrantTlsKeyFilePath);
+ }
+
+ ///
+ public override QdrantContainer Build()
+ {
+ Validate();
+ return new QdrantContainer(DockerResourceConfiguration, TestcontainersSettings.Logger);
+ }
+
+ ///
+ protected override QdrantBuilder Init() =>
+ base.Init()
+ .WithImage(QdrantImage)
+ .WithPortBinding(QdrantHttpPort, true)
+ .WithPortBinding(QdrantGrpcPort, true)
+ .WithWaitStrategy(Wait.ForUnixContainer()
+ .UntilMessageIsLogged(".*Actix runtime found; starting in Actix runtime.*"));
+
+ ///
+ protected override QdrantBuilder Clone(IResourceConfiguration resourceConfiguration) =>
+ Merge(DockerResourceConfiguration, new QdrantConfiguration(resourceConfiguration));
+
+ ///
+ protected override QdrantBuilder Merge(QdrantConfiguration oldValue, QdrantConfiguration newValue) =>
+ new(new QdrantConfiguration(oldValue, newValue));
+
+ ///
+ protected override QdrantConfiguration DockerResourceConfiguration { get; }
+
+ ///
+ protected override QdrantBuilder Clone(IContainerConfiguration resourceConfiguration) =>
+ Merge(DockerResourceConfiguration, new QdrantConfiguration(resourceConfiguration));
+}
diff --git a/src/Testcontainers.Qdrant/QdrantConfiguration.cs b/src/Testcontainers.Qdrant/QdrantConfiguration.cs
new file mode 100644
index 000000000..714303f38
--- /dev/null
+++ b/src/Testcontainers.Qdrant/QdrantConfiguration.cs
@@ -0,0 +1,73 @@
+using System.Security.Cryptography.X509Certificates;
+
+namespace Testcontainers.Qdrant;
+
+///
+[PublicAPI]
+public sealed class QdrantConfiguration : ContainerConfiguration
+{
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public QdrantConfiguration(string apiKey = null, X509Certificate2 certificate = null, string configurationFilePath = null)
+ {
+ ApiKey = apiKey;
+ Certificate = certificate;
+ ConfigurationFilePath = configurationFilePath;
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The Docker resource configuration.
+ public QdrantConfiguration(IResourceConfiguration resourceConfiguration)
+ : base(resourceConfiguration)
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The Docker resource configuration.
+ public QdrantConfiguration(IContainerConfiguration resourceConfiguration)
+ : base(resourceConfiguration)
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The Docker resource configuration.
+ public QdrantConfiguration(QdrantConfiguration resourceConfiguration)
+ : this(new QdrantConfiguration(), resourceConfiguration)
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The old Docker resource configuration.
+ /// The new Docker resource configuration.
+ public QdrantConfiguration(QdrantConfiguration oldValue, QdrantConfiguration newValue)
+ : base(oldValue, newValue)
+ {
+ ApiKey = BuildConfiguration.Combine(oldValue.ApiKey, newValue.ApiKey);
+ Certificate = BuildConfiguration.Combine(oldValue.Certificate, newValue.Certificate);
+ ConfigurationFilePath = BuildConfiguration.Combine(oldValue.ConfigurationFilePath, newValue.ConfigurationFilePath);
+ }
+
+ ///
+ /// Gets the API key used to secure Qdrant
+ ///
+ public string ApiKey { get; }
+
+ ///
+ /// Gets the certificate used to configure Transport Layer Security
+ ///
+ public X509Certificate2 Certificate { get; }
+
+ ///
+ /// Gets the path to the configuration file used to configure Qdrant
+ ///
+ public string ConfigurationFilePath { get; }
+}
diff --git a/src/Testcontainers.Qdrant/QdrantContainer.cs b/src/Testcontainers.Qdrant/QdrantContainer.cs
new file mode 100644
index 000000000..1067e088e
--- /dev/null
+++ b/src/Testcontainers.Qdrant/QdrantContainer.cs
@@ -0,0 +1,27 @@
+namespace Testcontainers.Qdrant;
+
+///
+[PublicAPI]
+public class QdrantContainer : DockerContainer
+{
+ private readonly QdrantConfiguration _configuration;
+
+ public QdrantContainer(QdrantConfiguration configuration, ILogger logger) : base(configuration, logger)
+ {
+ _configuration = configuration;
+ }
+
+ public string GetHttpConnectionString()
+ {
+ var scheme = _configuration.Certificate != null ? Uri.UriSchemeHttps : Uri.UriSchemeHttp;
+ var endpoint = new UriBuilder(scheme, Hostname, GetMappedPublicPort(QdrantBuilder.QdrantHttpPort));
+ return endpoint.ToString();
+ }
+
+ public string GetGrpcConnectionString()
+ {
+ var scheme = _configuration.Certificate != null ? Uri.UriSchemeHttps : Uri.UriSchemeHttp;
+ var endpoint = new UriBuilder(scheme, Hostname, GetMappedPublicPort(QdrantBuilder.QdrantGrpcPort));
+ return endpoint.ToString();
+ }
+}
diff --git a/src/Testcontainers.Qdrant/Testcontainers.Qdrant.csproj b/src/Testcontainers.Qdrant/Testcontainers.Qdrant.csproj
new file mode 100644
index 000000000..4c05d521f
--- /dev/null
+++ b/src/Testcontainers.Qdrant/Testcontainers.Qdrant.csproj
@@ -0,0 +1,13 @@
+
+
+ netstandard2.0;netstandard2.1
+ latest
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/Testcontainers.Qdrant/Usings.cs b/src/Testcontainers.Qdrant/Usings.cs
new file mode 100644
index 000000000..5696bb0bf
--- /dev/null
+++ b/src/Testcontainers.Qdrant/Usings.cs
@@ -0,0 +1,12 @@
+global using System;
+global using System.Collections.Generic;
+global using System.Linq;
+global using System.Text;
+global using System.Threading.Tasks;
+global using Docker.DotNet.Models;
+global using DotNet.Testcontainers;
+global using DotNet.Testcontainers.Builders;
+global using DotNet.Testcontainers.Configurations;
+global using DotNet.Testcontainers.Containers;
+global using JetBrains.Annotations;
+global using Microsoft.Extensions.Logging;
\ No newline at end of file
diff --git a/tests/Testcontainers.Qdrant.Tests/.editorconfig b/tests/Testcontainers.Qdrant.Tests/.editorconfig
new file mode 100644
index 000000000..6f066619d
--- /dev/null
+++ b/tests/Testcontainers.Qdrant.Tests/.editorconfig
@@ -0,0 +1 @@
+root = true
\ No newline at end of file
diff --git a/tests/Testcontainers.Qdrant.Tests/QdrantContainerApiKeyCertificateTest.cs b/tests/Testcontainers.Qdrant.Tests/QdrantContainerApiKeyCertificateTest.cs
new file mode 100644
index 000000000..d55105e4c
--- /dev/null
+++ b/tests/Testcontainers.Qdrant.Tests/QdrantContainerApiKeyCertificateTest.cs
@@ -0,0 +1,82 @@
+using System.Net.Http;
+using System.Security.Cryptography.X509Certificates;
+
+namespace Testcontainers.Qdrant;
+
+public sealed class QdrantContainerApiKeyCertificateTest : IAsyncLifetime
+{
+ private static readonly X509Certificate2 Cert = X509CertificateGenerator.GenerateCert("CN=Testcontainers");
+ private const string ApiKey = "password!";
+
+ private readonly QdrantContainer _qdrantContainer = new QdrantBuilder()
+ .WithApiKey(ApiKey)
+ .WithCertificate(Cert)
+ .Build();
+
+ public Task InitializeAsync()
+ {
+ return _qdrantContainer.StartAsync();
+ }
+
+ public Task DisposeAsync()
+ {
+ return _qdrantContainer.DisposeAsync().AsTask();
+ }
+
+ [Fact]
+ [Trait(nameof(DockerCli.DockerPlatform), nameof(DockerCli.DockerPlatform.Linux))]
+ public async Task PingReturnsValidResponse()
+ {
+ var httpMessageHandler = new HttpClientHandler
+ {
+ ServerCertificateCustomValidationCallback = (_, certificate, _, _) =>
+ certificate.Thumbprint == Cert.Thumbprint,
+ };
+
+ var client = new HttpClient(httpMessageHandler)
+ {
+ BaseAddress = new Uri(_qdrantContainer.GetHttpConnectionString()),
+ };
+
+ client.DefaultRequestHeaders.Add("api-key", ApiKey);
+
+ var response = await client.GetAsync("/collections");
+
+ Assert.True(response.IsSuccessStatusCode);
+ }
+
+ [Fact]
+ [Trait(nameof(DockerCli.DockerPlatform), nameof(DockerCli.DockerPlatform.Linux))]
+ public async Task PingWithoutApiKeyReturnsInvalidResponse()
+ {
+ var httpMessageHandler = new HttpClientHandler
+ {
+ ServerCertificateCustomValidationCallback = (_, _, _, _) => true,
+ };
+
+ var client = new HttpClient(httpMessageHandler)
+ {
+ BaseAddress = new Uri(_qdrantContainer.GetHttpConnectionString()),
+ };
+
+ var response = await client.GetAsync("/collections");
+
+ Assert.False(response.IsSuccessStatusCode);
+ Assert.Equal("Invalid api-key", await response.Content.ReadAsStringAsync());
+ }
+
+ [Fact]
+ [Trait(nameof(DockerCli.DockerPlatform), nameof(DockerCli.DockerPlatform.Linux))]
+ public async Task PingWithoutCertificateValidationReturnsInvalidResponse()
+ {
+ var client = new HttpClient
+ {
+ BaseAddress = new Uri(_qdrantContainer.GetHttpConnectionString()),
+ };
+
+ client.DefaultRequestHeaders.Add("api-key", ApiKey);
+
+ // The SSL connection could not be established
+ await Assert.ThrowsAsync(() => client.GetAsync("/collections"));
+ }
+}
\ No newline at end of file
diff --git a/tests/Testcontainers.Qdrant.Tests/QdrantContainerConfigurationFileTest.cs b/tests/Testcontainers.Qdrant.Tests/QdrantContainerConfigurationFileTest.cs
new file mode 100644
index 000000000..716b92a3a
--- /dev/null
+++ b/tests/Testcontainers.Qdrant.Tests/QdrantContainerConfigurationFileTest.cs
@@ -0,0 +1,64 @@
+using System.IO;
+using System.Net.Http;
+
+namespace Testcontainers.Qdrant;
+
+public sealed class QdrantContainerConfigurationFileTest : IAsyncLifetime
+{
+ private const string ApiKey = "password!";
+
+ private readonly QdrantContainer _qdrantContainer = new QdrantBuilder()
+ .WithConfigFile(CreateConfigFile())
+ .Build();
+
+ private static string CreateConfigFile()
+ {
+ var tempFile = Path.GetTempFileName();
+ File.WriteAllLines(tempFile, new[]
+ {
+ "service:",
+ $" api_key: {ApiKey}",
+ });
+ return tempFile;
+ }
+
+ public Task InitializeAsync()
+ {
+ return _qdrantContainer.StartAsync();
+ }
+
+ public Task DisposeAsync()
+ {
+ return _qdrantContainer.DisposeAsync().AsTask();
+ }
+
+ [Fact]
+ [Trait(nameof(DockerCli.DockerPlatform), nameof(DockerCli.DockerPlatform.Linux))]
+ public async Task PingReturnsValidResponse()
+ {
+ var client = new HttpClient
+ {
+ BaseAddress = new Uri(_qdrantContainer.GetHttpConnectionString()),
+ };
+
+ client.DefaultRequestHeaders.Add("api-key", ApiKey);
+
+ var response = await client.GetAsync("/collections");
+ Assert.True(response.IsSuccessStatusCode);
+ }
+
+ [Fact]
+ [Trait(nameof(DockerCli.DockerPlatform), nameof(DockerCli.DockerPlatform.Linux))]
+ public async Task PingWithoutApiKeyReturnsInvalidResponse()
+ {
+ var client = new HttpClient
+ {
+ BaseAddress = new Uri(_qdrantContainer.GetHttpConnectionString()),
+ };
+
+ var response = await client.GetAsync("/collections");
+
+ Assert.False(response.IsSuccessStatusCode);
+ Assert.Equal("Invalid api-key", await response.Content.ReadAsStringAsync());
+ }
+}
\ No newline at end of file
diff --git a/tests/Testcontainers.Qdrant.Tests/QdrantContainerTest.cs b/tests/Testcontainers.Qdrant.Tests/QdrantContainerTest.cs
new file mode 100644
index 000000000..5cdb730fc
--- /dev/null
+++ b/tests/Testcontainers.Qdrant.Tests/QdrantContainerTest.cs
@@ -0,0 +1,31 @@
+using System.Net.Http;
+
+namespace Testcontainers.Qdrant;
+
+public sealed class QdrantContainerTest : IAsyncLifetime
+{
+ private readonly QdrantContainer _qdrantContainer = new QdrantBuilder().Build();
+
+ public Task InitializeAsync()
+ {
+ return _qdrantContainer.StartAsync();
+ }
+
+ public Task DisposeAsync()
+ {
+ return _qdrantContainer.DisposeAsync().AsTask();
+ }
+
+ [Fact]
+ [Trait(nameof(DockerCli.DockerPlatform), nameof(DockerCli.DockerPlatform.Linux))]
+ public async Task PingReturnsValidResponse()
+ {
+ var client = new HttpClient
+ {
+ BaseAddress = new Uri(_qdrantContainer.GetHttpConnectionString()),
+ };
+
+ var response = await client.GetAsync("/");
+ Assert.True(response.IsSuccessStatusCode);
+ }
+}
\ No newline at end of file
diff --git a/tests/Testcontainers.Qdrant.Tests/Testcontainers.Qdrant.Tests.csproj b/tests/Testcontainers.Qdrant.Tests/Testcontainers.Qdrant.Tests.csproj
new file mode 100644
index 000000000..c243a4546
--- /dev/null
+++ b/tests/Testcontainers.Qdrant.Tests/Testcontainers.Qdrant.Tests.csproj
@@ -0,0 +1,17 @@
+
+
+ net6.0
+ false
+ false
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/tests/Testcontainers.Qdrant.Tests/Usings.cs b/tests/Testcontainers.Qdrant.Tests/Usings.cs
new file mode 100644
index 000000000..58083ebd6
--- /dev/null
+++ b/tests/Testcontainers.Qdrant.Tests/Usings.cs
@@ -0,0 +1,4 @@
+global using System;
+global using System.Threading.Tasks;
+global using DotNet.Testcontainers.Commons;
+global using Xunit;
\ No newline at end of file
diff --git a/tests/Testcontainers.Qdrant.Tests/X509CertificateGenerator.cs b/tests/Testcontainers.Qdrant.Tests/X509CertificateGenerator.cs
new file mode 100644
index 000000000..ef5f2f6fc
--- /dev/null
+++ b/tests/Testcontainers.Qdrant.Tests/X509CertificateGenerator.cs
@@ -0,0 +1,68 @@
+using System.IO;
+using System.Security.Cryptography.X509Certificates;
+using System.Text;
+using Org.BouncyCastle.Asn1.X509;
+using Org.BouncyCastle.Crypto;
+using Org.BouncyCastle.Crypto.Generators;
+using Org.BouncyCastle.Crypto.Operators;
+using Org.BouncyCastle.Crypto.Prng;
+using Org.BouncyCastle.Math;
+using Org.BouncyCastle.OpenSsl;
+using Org.BouncyCastle.Pkcs;
+using Org.BouncyCastle.Security;
+using Org.BouncyCastle.Utilities;
+using Org.BouncyCastle.X509;
+
+namespace Testcontainers.Qdrant;
+
+public static class X509CertificateGenerator
+{
+ public static X509Certificate2 GenerateCert(string subjectName)
+ {
+ var randomGenerator = new CryptoApiRandomGenerator();
+ var random = new SecureRandom(randomGenerator);
+ var serialNumber = BigIntegers.CreateRandomInRange(
+ BigInteger.One,
+ BigInteger.ValueOf(long.MaxValue), random);
+ var subjectDistinguishedName = new X509Name(subjectName);
+ var issuerDistinguishedName = subjectDistinguishedName;
+ var notBefore = DateTime.UtcNow.Date;
+ var notAfter = notBefore.AddYears(1);
+ var keyGenerationParameters = new KeyGenerationParameters(random, 2048);
+ var keyPairGenerator = new RsaKeyPairGenerator();
+ keyPairGenerator.Init(keyGenerationParameters);
+ var subjectKeyPair = keyPairGenerator.GenerateKeyPair();
+ var issuerPrivateKey = subjectKeyPair.Private;
+
+ var certificateGenerator = new X509V3CertificateGenerator();
+ certificateGenerator.SetSerialNumber(serialNumber);
+ certificateGenerator.AddExtension(
+ X509Extensions.ExtendedKeyUsage,
+ true,
+ new ExtendedKeyUsage(KeyPurposeID.id_kp_serverAuth));
+ certificateGenerator.SetIssuerDN(issuerDistinguishedName);
+ certificateGenerator.SetSubjectDN(subjectDistinguishedName);
+ certificateGenerator.SetNotBefore(notBefore);
+ certificateGenerator.SetNotAfter(notAfter);
+ certificateGenerator.SetPublicKey(subjectKeyPair.Public);
+
+ var signatureFactory = new Asn1SignatureFactory("SHA512WITHRSA", issuerPrivateKey, random);
+ var certificate = certificateGenerator.Generate(signatureFactory);
+ var privateKeyInfo = PrivateKeyInfoFactory.CreatePrivateKeyInfo(subjectKeyPair.Private);
+
+ var builder = new StringBuilder();
+ using var writer = new StringWriter(builder);
+ using var pemWriter = new PemWriter(writer);
+
+ pemWriter.WriteObject(certificate);
+ var pemCert = builder.ToString();
+ builder.Clear();
+
+ pemWriter.WriteObject(privateKeyInfo);
+ var pemKey = builder.ToString();
+
+ return X509Certificate2.CreateFromPem(pemCert, pemKey);
+ }
+}
+
+