using Org.BouncyCastle.Asn1;
using Org.BouncyCastle.Asn1.Pkcs;
using Org.BouncyCastle.Asn1.X509;
using Org.BouncyCastle.Crypto;
using Org.BouncyCastle.Crypto.Operators;
using Org.BouncyCastle.Pkcs;
using PKISharp.WACS.Plugins.Base.Options;
using PKISharp.WACS.Plugins.Interfaces;
using PKISharp.WACS.Services;
using PKISharp.WACS.Services.Serialization;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Security.Cryptography.X509Certificates;
using System.Threading.Tasks;
using X509Extension = Org.BouncyCastle.Asn1.X509.X509Extension;
namespace PKISharp.WACS.Plugins.CsrPlugins
{
///
/// Common implementation between RSA and EC certificates
///
public abstract class CsrPlugin : ICsrPlugin
where TOptions : CsrPluginOptions
where TPlugin : ICsrPlugin
{
protected readonly ILogService _log;
protected readonly ISettingsService _settings;
protected readonly TOptions _options;
private readonly PemService _pemService;
protected string? _cacheData;
private AsymmetricCipherKeyPair? _keyPair;
public CsrPlugin(ILogService log, ISettingsService settings, TOptions options, PemService pemService)
{
_log = log;
_options = options;
_settings = settings;
_pemService = pemService;
}
public virtual Task PostProcess(X509Certificate2 original) => Task.FromResult(original);
async Task ICsrPlugin.GenerateCsr(string cachePath, string commonName, List identifiers)
{
var extensions = new Dictionary();
LoadFromCache(cachePath);
var dn = CommonName(commonName, identifiers);
var keys = await GetKeys();
ProcessMustStaple(extensions);
ProcessSan(identifiers, extensions);
var csr = new Pkcs10CertificationRequest(
new Asn1SignatureFactory(GetSignatureAlgorithm(), keys.Private),
dn,
keys.Public,
new DerSet(new AttributePkcs(
PkcsObjectIdentifiers.Pkcs9AtExtensionRequest,
new DerSet(new X509Extensions(extensions)))));
SaveToCache(cachePath);
return csr;
}
///
/// Load cached key information from disk, if needed
///
///
private void LoadFromCache(string cachePath)
{
if (_options.ReusePrivateKey == true)
{
try
{
var fi = new FileInfo(cachePath);
if (fi.Exists)
{
var rawData = new ProtectedString(File.ReadAllText(cachePath), _log);
if (!rawData.Error)
{
_cacheData = rawData.Value;
_log.Warning("Re-using key data generated at {time}", fi.LastWriteTime);
}
else
{
_log.Warning("Key reuse is enabled but file {cachePath} cannot be decrypted, creating new key...", cachePath);
}
}
else
{
_log.Warning("Key reuse is enabled but file {cachePath} does't exist yet, creating new key...", cachePath);
}
}
catch
{
throw new Exception($"Unable to read from cache file {cachePath}");
}
}
}
///
/// Save cached key information to disk, if needed
///
///
private void SaveToCache(string cachePath)
{
if (_options.ReusePrivateKey == true)
{
var rawData = new ProtectedString(_cacheData);
File.WriteAllText(cachePath, rawData.DiskValue(_settings.Security.EncryptConfig));
}
}
public abstract string GetSignatureAlgorithm();
///
/// Get public and private keys
///
///
public Task GetKeys()
{
if (_keyPair == null)
{
if (_cacheData == null)
{
_keyPair = GenerateNewKeyPair();
_cacheData = _pemService.GetPem(_keyPair);
}
else
{
try
{
_keyPair = _pemService.ParsePem(_cacheData);
if (_keyPair == null)
{
throw new InvalidDataException("key");
}
}
catch
{
_log.Error($"Unable to read cache data, creating new key...");
_cacheData = null;
return GetKeys();
}
}
}
return Task.FromResult(_keyPair);
}
///
/// Generate new public/private key pair
///
///
internal abstract AsymmetricCipherKeyPair GenerateNewKeyPair();
///
/// Add SAN list
///
///
///
private void ProcessSan(List identifiers, Dictionary extensions)
{
// SAN
var names = new GeneralNames(identifiers.
Select(n => new GeneralName(GeneralName.DnsName, n)).
ToArray());
Asn1OctetString asn1ost = new DerOctetString(names);
extensions.Add(X509Extensions.SubjectAlternativeName, new X509Extension(false, asn1ost));
}
///
/// Optionally add the OCSP Must-Stable extension
///
///
private void ProcessMustStaple(Dictionary extensions)
{
// OCSP Must-Staple
if (_options.OcspMustStaple == true)
{
_log.Information("Enable OCSP Must-Staple extension");
extensions.Add(
new DerObjectIdentifier("1.3.6.1.5.5.7.1.24"),
new X509Extension(
false,
new DerOctetString(new byte[]
{
0x30, 0x03, 0x02, 0x01, 0x05
})));
}
}
///
/// Determine the common name
///
///
///
///
private X509Name CommonName(string? commonName, List identifiers)
{
var idn = new IdnMapping();
if (!string.IsNullOrWhiteSpace(commonName))
{
commonName = idn.GetAscii(commonName);
if (!identifiers.Contains(commonName, StringComparer.InvariantCultureIgnoreCase))
{
_log.Warning($"Common name {commonName} provided is invalid.");
commonName = null;
}
}
var finalCommonName = commonName ?? identifiers.FirstOrDefault();
IDictionary attrs = new Hashtable
{
[X509Name.CN] = finalCommonName
};
IList ord = new ArrayList
{
X509Name.CN
};
var issuerDN = new X509Name(ord, attrs);
return issuerDN;
}
(bool, string?) IPlugin.Disabled => (false, null);
}
}