Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion build/opencertserver.build/opencertserver.build.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
<ItemGroup>
<PackageReference Include="Cake.Frosting" Version="6.1.0" />
<PackageReference Include="Cake.Docker" Version="1.3.0" />
<PackageReference Include="GitVersion.MsBuild" Version="6.6.2">
<PackageReference Include="GitVersion.MsBuild" Version="6.7.0">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
Expand Down
2 changes: 1 addition & 1 deletion src/CertesSlim/CertesSlim.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.IdentityModel.Tokens" Version="8.16.0" />
<PackageReference Include="Microsoft.IdentityModel.Tokens" Version="8.17.0" />
</ItemGroup>

<ItemGroup>
Expand Down
46 changes: 25 additions & 21 deletions src/opencertserver.ca.server/Extensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -37,14 +37,16 @@ public IServiceCollection AddInMemoryCertificateStore()
/// <returns>A configured <see cref="IServiceCollection"/>.</returns>
public IServiceCollection AddCertificateAuthority(
CaConfiguration configuration,
Func<X509Chain, bool>? chainValidation = null)
IValidateX509Chains? chainValidation = null)
{
services.AddSingleton(configuration);
services.AddSingleton(configuration.Profiles);
return services.AddSingleton<ICertificateAuthority>(sp =>
{
return new CertificateAuthority(
configuration,
sp.GetRequiredService<IStoreCertificates>(),
chainValidation ?? (_ => true),
chainValidation ?? new ValidateAll(),
sp.GetRequiredService<ILogger<CertificateAuthority>>());
});
}
Expand All @@ -66,31 +68,33 @@ public IServiceCollection AddSelfSignedCertificateAuthority(
string[]? crlUrls = null,
string[]? caIssuersUrls = null,
TimeSpan certificateValidity = default,
Func<X509Chain, bool>? chainValidation = null)
IValidateX509Chains? chainValidation = null)
{
var config = new CaConfiguration(
new CaProfileSet(
"default",
CertificateAuthority.CreateSelfSignedRsa(
"default",
distinguishedName,
certificateValidity == TimeSpan.Zero ? TimeSpan.FromDays(90) : certificateValidity,
BigInteger.Zero),
CertificateAuthority.CreateSelfSignedEcdsa(
"ecdsa",
distinguishedName,
certificateValidity == TimeSpan.Zero ? TimeSpan.FromDays(90) : certificateValidity,
BigInteger.Zero)
),
ocspUrls ?? [],
crlUrls ?? [],
caIssuersUrls ?? []);
services.AddSingleton(config);
services.AddSingleton(config.Profiles);
return services.AddSingleton<ICertificateAuthority>(sp =>
{
var config = new CaConfiguration(
new CaProfileSet(
"default",
CertificateAuthority.CreateSelfSignedRsa(
"default",
distinguishedName,
certificateValidity == TimeSpan.Zero ? TimeSpan.FromDays(90) : certificateValidity,
BigInteger.Zero),
CertificateAuthority.CreateSelfSignedEcdsa(
"ecdsa",
distinguishedName,
certificateValidity == TimeSpan.Zero ? TimeSpan.FromDays(90) : certificateValidity,
BigInteger.Zero)
),
ocspUrls ?? [],
crlUrls ?? [],
caIssuersUrls ?? []);
var certificateAuthority = new CertificateAuthority(
config,
sp.GetRequiredService<IStoreCertificates>(),
chainValidation ?? (_ => true),
chainValidation ?? new ValidateAll(),
sp.GetRequiredService<ILogger<CertificateAuthority>>(),
validators: sp.GetServices<IValidateCertificateRequests>().ToArray());
return certificateAuthority;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,9 @@ public static async Task HandleGet(HttpContext context)
var thumbCerts = store.GetCertificatesByThumbprint(
thumbprints.Where(s => s != null)
.Select(tp => tp.AsMemory()));
var idCerts = store.GetCertificatesById(ids.Where(s => s != null)
var idCerts = store.GetCertificatesById(
context.RequestAborted,
ids.Where(s => s != null)
.Select(tp => new ReadOnlyMemory<byte>(Convert.FromHexString(tp!))));

context.Response.ContentType = "application/x-pem-file";
Expand Down
8 changes: 5 additions & 3 deletions src/opencertserver.ca.server/Handlers/CsrHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,13 @@ public static async Task<IResult> Handle(
[FromRoute] string? profileName,
ClaimsPrincipal user,
ICertificateAuthority ca,
[FromBody] Stream body)
[FromBody] Stream body,
CancellationToken cancellationToken)
{
using var reader = new StreamReader(body);
var csrPem = await reader.ReadToEndAsync().ConfigureAwait(false);
var certResponse = ca.SignCertificateRequestPem(csrPem, profileName, user.Identity as ClaimsIdentity);
var csrPem = await reader.ReadToEndAsync(cancellationToken).ConfigureAwait(false);
var certResponse = await ca.SignCertificateRequestPem(csrPem, profileName, user.Identity as ClaimsIdentity,
cancellationToken: cancellationToken);
if (certResponse is SignCertificateResponse.Success success)
{
return Results.Text(success.Certificate.ToPemChain(success.Issuers), Constants.PemMimeType);
Expand Down
11 changes: 11 additions & 0 deletions src/opencertserver.ca.server/ValidateAll.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
using System.Security.Cryptography.X509Certificates;

namespace OpenCertServer.Ca.Server;

internal class ValidateAll : IValidateX509Chains
{
public Task<bool> Validate(X509Chain chain, CancellationToken cancellationToken = default)
{
return Task.FromResult(true);
}
}
31 changes: 29 additions & 2 deletions src/opencertserver.ca.utils/Ca/CaProfile.cs
Original file line number Diff line number Diff line change
@@ -1,29 +1,56 @@
namespace OpenCertServer.Ca.Utils.Ca;

using System.Numerics;
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;

namespace OpenCertServer.Ca;

/// <summary>
/// Defines a CA profile with private key, certificate chain, and other properties.
/// </summary>
public record CaProfile : IDisposable
{
private BigInteger _crlNumber;

/// <summary>
/// Gets or sets the name of the CA profile.
/// </summary>
public required string Name { get; init; }

/// <summary>
/// Gets the private key associated with the CA profile.
/// </summary>
public required AsymmetricAlgorithm PrivateKey { get; init; }

/// <summary>
/// Gets the certificate chain associated with the CA profile.
/// </summary>
public required X509Certificate2Collection CertificateChain { get; init; }

/// <summary>
/// Gets the validity period for certificates issued by this CA profile.
/// </summary>
public TimeSpan CertificateValidity { get; init; }

/// <summary>
/// Gets or sets the CRL number for the CA profile.
/// </summary>
public BigInteger CrlNumber
{
get { return _crlNumber; }
init { _crlNumber = value; }
}

/// <summary>
/// Gets the next CRL number for the CA profile.
/// </summary>
/// <returns>The next CRL number</returns>
public BigInteger GetNextCrlNumber()
{
_crlNumber += BigInteger.One;
return CrlNumber;
}

/// <inheritdoc />
public void Dispose()
{
PrivateKey.Dispose();
Expand Down
44 changes: 0 additions & 44 deletions src/opencertserver.ca.utils/Ca/CertificateItem.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,50 +2,6 @@ namespace OpenCertServer.Ca.Utils.Ca;

using System.Security.Cryptography.X509Certificates;

/// <summary>
/// Represents certificate metadata used for inventory and revocation list responses.
/// </summary>
public class CertificateItemInfo
{
/// <summary>
/// Gets or sets the certificate serial number.
/// </summary>
public required string SerialNumber { get; set; }
/// <summary>
/// Gets or sets the subject distinguished name.
/// </summary>
public required string DistinguishedName { get; set; }
/// <summary>
/// Gets or sets the certificate validity start time.
/// </summary>
public DateTime NotBefore { get; set; }
/// <summary>
/// Gets or sets the certificate validity end time.
/// </summary>
public DateTime NotAfter { get; set; }

/// <summary>
/// Represents the IsRevoked.
/// </summary>
public bool IsRevoked
{
get { return RevocationReason != null; }
}

/// <summary>
/// Gets or sets the revocation reason when the certificate is revoked.
/// </summary>
public X509RevocationReason? RevocationReason { get; set; }
/// <summary>
/// Gets or sets the revocation timestamp when the certificate is revoked.
/// </summary>
public DateTimeOffset? RevocationDate { get; set; }
/// <summary>
/// Gets or sets the certificate thumbprint.
/// </summary>
public required string Thumbprint { get; set; }
}

/// <summary>
/// Represents a stored certificate record including public key PEM material.
/// </summary>
Expand Down
47 changes: 47 additions & 0 deletions src/opencertserver.ca.utils/Ca/CertificateItemInfo.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
using System.Security.Cryptography.X509Certificates;

namespace OpenCertServer.Ca.Utils.Ca;

/// <summary>
/// Represents certificate metadata used for inventory and revocation list responses.
/// </summary>
public class CertificateItemInfo
{
/// <summary>
/// Gets or sets the certificate serial number.
/// </summary>
public required string SerialNumber { get; set; }
/// <summary>
/// Gets or sets the subject distinguished name.
/// </summary>
public required string DistinguishedName { get; set; }
/// <summary>
/// Gets or sets the certificate validity start time.
/// </summary>
public DateTime NotBefore { get; set; }
/// <summary>
/// Gets or sets the certificate validity end time.
/// </summary>
public DateTime NotAfter { get; set; }

/// <summary>
/// Represents the IsRevoked.
/// </summary>
public bool IsRevoked
{
get { return RevocationReason != null; }
}

/// <summary>
/// Gets or sets the revocation reason when the certificate is revoked.
/// </summary>
public X509RevocationReason? RevocationReason { get; set; }
/// <summary>
/// Gets or sets the revocation timestamp when the certificate is revoked.
/// </summary>
public DateTimeOffset? RevocationDate { get; set; }
/// <summary>
/// Gets or sets the certificate thumbprint.
/// </summary>
public required string Thumbprint { get; set; }
}
53 changes: 46 additions & 7 deletions src/opencertserver.ca.utils/Ca/ICertificateAuthority.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,21 +11,60 @@
/// </summary>
public interface ICertificateAuthority
{
SignCertificateResponse SignCertificateRequest(
/// <summary>
/// Signs a certificate request.
/// </summary>
/// <param name="request">The certificate request to sign.</param>
/// <param name="profileName">The name of the profile to use for signing the request.</param>
/// <param name="requestor">The identity of the requestor.</param>
/// <param name="reenrollingFrom">The certificate to reenroll from, if applicable.</param>
/// <param name="cancellationToken">The cancellation token to use for the operation.</param>
/// <returns>The response containing the signed certificate.</returns>
Task<SignCertificateResponse> SignCertificateRequest(
CertificateRequest request,
string? profileName = null,
ClaimsIdentity? requestor = null,
X509Certificate2? reenrollingFrom = null);
X509Certificate2? reenrollingFrom = null,
CancellationToken cancellationToken = default);

SignCertificateResponse SignCertificateRequestPem(
/// <summary>
/// Signs a certificate request using PEM format.
/// </summary>
/// <param name="request">The PEM-encoded certificate request.</param>
/// <param name="profileName">The name of the profile to use for signing the request.</param>
/// <param name="requestor">The identity of the requestor.</param>
/// <param name="reenrollingFrom">The certificate to reenroll from, if applicable.</param>
/// <param name="cancellationToken">The cancellation token to use for the operation.</param>
/// <returns>The response containing the signed certificate.</returns>
Task<SignCertificateResponse> SignCertificateRequestPem(
string request,
string? profileName = null,
ClaimsIdentity? requestor = null,
X509Certificate2? reenrollingFrom = null);
X509Certificate2? reenrollingFrom = null,
CancellationToken cancellationToken = default);

X509Certificate2Collection GetRootCertificates(string? profileName = null);
/// <summary>
/// Gets the root certificates for the specified profile.
/// </summary>
/// <param name="profileName">The name of the profile to get the root certificates for.</param>
/// <param name="cancellationToken">The cancellation token to use for the operation.</param>
/// <returns>The root certificates as a collection of X509Certificate2 objects.</returns>
Task<X509Certificate2Collection> GetRootCertificates(string? profileName = null, CancellationToken cancellationToken = default);

Task<bool> RevokeCertificate(string serialNumber, X509RevocationReason reason);
/// <summary>
/// Revokes a certificate by its serial number.
/// </summary>
/// <param name="serialNumber">The serial number of the certificate to revoke.</param>
/// <param name="reason">The reason for revoking the certificate.</param>
/// <param name="cancellationToken">The cancellation token to use for the operation.</param>
/// <returns>True if the certificate was successfully revoked, false otherwise.</returns>
Task<bool> RevokeCertificate(string serialNumber, X509RevocationReason reason, CancellationToken cancellationToken = default);

Task<byte[]> GetRevocationList(string? profileName = null);
/// <summary>
/// Gets the revocation list for the specified profile.
/// </summary>
/// <param name="profileName">The name of the profile to get the revocation list for.</param>
/// <param name="cancellationToken">The cancellation token to use for the operation.</param>
/// <returns>The revocation list as a byte array.</returns>
Task<byte[]> GetRevocationList(string? profileName = null, CancellationToken cancellationToken = default);
}
11 changes: 10 additions & 1 deletion src/opencertserver.ca.utils/Ca/IStoreCaProfiles.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,15 @@
namespace OpenCertServer.Ca.Utils.Ca;

/// <summary>
/// Defines the interface for storing and retrieving CA profiles.
/// </summary>
public interface IStoreCaProfiles : IDisposable
{
CaProfile GetProfile(string? name);
/// <summary>
/// Gets the CA profile with the specified name. If no profile matches, then returns the default profile.
/// </summary>
/// <param name="name">The optional name of the CA profile to retrieve. If null, the default profile is returned.</param>
/// <param name="cancellationToken">The cancellation token to observe while retrieving the CA profile.</param>
/// <returns>The CA profile matching the specified name, or the default profile if no match is found.</returns>
Task<CaProfile> GetProfile(string? name, CancellationToken cancellationToken = default);
}
Loading