Open darind opened 1 year ago
I was able to make this work using a X509 certificate with EC private key:
public override TlsCredentials GetCredentials()
{
// The X509 certificate I am importing is using an EC private key (secp384r1)
var x509Cert = new X509Certificate2("cert.pfx", "secret", X509KeyStorageFlags.Exportable);
var crypto = new BcTlsCrypto(new SecureRandom());
var tlsCertificate = new BcTlsCertificate(crypto, DotNetUtilities.FromX509Certificate(x509Cert).CertificateStructure);
var bcCertificate = new Certificate(
CertificateType.X509,
TlsUtilities.EmptyBytes,
new CertificateEntry[] { new CertificateEntry(tlsCertificate, null) });
var privateKey = x509Cert.GetECDsaPrivateKey();
var keyPair = DotNetUtilities.GetKeyPair(privateKey);
var algo = new SignatureAndHashAlgorithm(HashAlgorithm.sha384, SignatureAlgorithm.ecdsa);
var cryptoParams = new TlsCryptoParameters(this.m_context);
return new BcDefaultTlsCredentialedSigner(
cryptoParams,
crypto,
keyPair.Private,
bcCertificate,
algo);
}
The problem is that this enforces me to use a X509 certificate with EC private key, while in theory it should be possible to use TLS 1.3 with a X509 certificate with RSA private key for authentication and a separate DHE/ECDHE key exchange to get the keys for symmetric encryption. How can this be achieved?
@darind Apologies for taking so long to reply. The rules for TLS are quite complicated (TLS 1.3 only simplifies the problem considerably). The best current example is the ProvTlsServer class from the bc-java project. I made a copy of that and simplified it down the example pasted below.
The main points are to record the client's signature schemes and trusted issuers, as well as configuring which signature schemes you wish to support, and the corresponding credentials to use for each.
AbstractTlsServer will call SelectCipherSuite on cipher suites it finds in common b/w client and server (and otherwise prevalidated). It is a convenient method to override to ensure that we have usable credentials for that suite (which includes checks against the client signature schemes and trusted issuers, if any).
This is not well-tested, so if you try it and need help getting it to work, let me know.
using System;
using System.Collections.Generic;
using System.Linq;
using Org.BouncyCastle.Asn1.X509;
using Org.BouncyCastle.Crypto;
using Org.BouncyCastle.Math.Raw;
using Org.BouncyCastle.Tls;
using Org.BouncyCastle.Tls.Crypto;
using Org.BouncyCastle.Tls.Crypto.Impl.BC;
using Org.BouncyCastle.X509;
namespace Org.BouncyCastle.Example
{
public class DefaultTls13Server
: AbstractTlsServer
{
private static readonly int[] DefaultCipherSuites = new int[]
{
/*
* TLS 1.3
*/
CipherSuite.TLS_AES_256_GCM_SHA384,
CipherSuite.TLS_AES_128_GCM_SHA256,
CipherSuite.TLS_CHACHA20_POLY1305_SHA256,
};
private class Credentials
{
internal Certificate Certificate { get; set; }
internal AsymmetricKeyParameter PrivateKey { get; set; }
}
// For each SignatureScheme value we support, store the corresponding credentials
private readonly Dictionary<int, Credentials> m_credentials = new Dictionary<int, Credentials>();
private IList<int> m_peerSigSchemes = null;
private TlsCredentials m_selectedCredentials = null;
private IList<X509Name> m_clientTrustedIssuers = null;
public DefaultTls13Server(BcTlsCrypto crypto)
: base(crypto)
{
// TODO Fill in m_credentials according to which SignatureScheme values we support e.g.:
//m_credentials[SignatureScheme.ecdsa_secp384r1_sha384] = new Credentials { ... };
}
protected override int[] GetSupportedCipherSuites()
{
return TlsUtilities.GetSupportedCipherSuites(Crypto, DefaultCipherSuites);
}
protected override bool SelectCipherSuite(int cipherSuite)
{
TlsCredentials cipherSuiteCredentials = null;
int keyExchangeAlgorithm = TlsUtilities.GetKeyExchangeAlgorithm(cipherSuite);
if (!KeyExchangeAlgorithm.IsAnonymous(keyExchangeAlgorithm))
{
cipherSuiteCredentials = SelectCredentials(keyExchangeAlgorithm);
if (null == cipherSuiteCredentials)
return false;
}
bool result = base.SelectCipherSuite(cipherSuite);
if (result)
{
m_selectedCredentials = cipherSuiteCredentials;
}
return result;
}
public override TlsCredentials GetCredentials()
{
return m_selectedCredentials ?? throw new TlsFatalAlert(AlertDescription.internal_error);
}
public override int GetSelectedCipherSuite()
{
m_peerSigSchemes = m_context.SecurityParameters.ClientSigAlgs.Select(SignatureScheme.From).ToList();
return base.GetSelectedCipherSuite();
}
public override void ProcessClientExtensions(IDictionary<int, byte[]> clientExtensions)
{
base.ProcessClientExtensions(clientExtensions);
m_clientTrustedIssuers = TlsExtensionsUtilities.GetCertificateAuthoritiesExtension(clientExtensions);
}
private TlsCredentials SelectCredentials(int keyExchangeAlgorithm)
{
switch (keyExchangeAlgorithm)
{
case KeyExchangeAlgorithm.NULL:
return SelectServerCredentials13();
default:
return null;
}
}
private TlsCredentials SelectServerCredentials13()
{
X509Name[] issuers = m_clientTrustedIssuers.ToArray();
byte[] certificateRequestContext = TlsUtilities.EmptyBytes;
foreach (int peerSigScheme in m_peerSigSchemes)
{
if (!m_credentials.TryGetValue(peerSigScheme, out var candidateCredentials))
continue;
if (!IsSuitableCredentials(candidateCredentials))
continue;
return CreateCredentialedSigner13(peerSigScheme, candidateCredentials);
}
return null;
}
private TlsCredentials CreateCredentialedSigner13(int signatureScheme, Credentials credentials)
{
return new BcDefaultTlsCredentialedSigner(
new TlsCryptoParameters(m_context),
Crypto as BcTlsCrypto,
credentials.PrivateKey,
credentials.Certificate,
SignatureScheme.GetSignatureAndHashAlgorithm(signatureScheme));
}
private bool IsSuitableCredentials(Credentials credentials)
{
var certificate = credentials.Certificate;
if (certificate.IsEmpty)
return false;
if (TlsUtilities.IsNullOrEmpty(m_clientTrustedIssuers))
return true;
var crypto = Crypto as BcTlsCrypto;
var chain = certificate.GetCertificateList();
int pos = chain.Length;
while (--pos >= 0)
{
var issuer = BcTlsCertificate.Convert(crypto, chain[pos]).X509CertificateStructure.Issuer;
if (MatchesIssuers(m_clientTrustedIssuers, issuer))
return true;
}
var eeCert = new X509Certificate(BcTlsCertificate.Convert(crypto, chain[0]).X509CertificateStructure);
return eeCert.GetBasicConstraints() >= 0
&& MatchesIssuers(m_clientTrustedIssuers, eeCert.SubjectDN);
}
private static bool MatchesIssuers(IList<X509Name> issuers, X509Name name)
{
foreach (var issuer in issuers)
{
if (name.Equivalent(issuer))
return true;
}
return false;
}
}
}
Hi @darind, did you resolve for the issue?
I am jamming on same issue now.
I am trying to implement a TLS 1.3 server using BouncyCastle.Cryptography 2.0.0:
using it like this:
When a TLS 1.3 enabled client attempts to connect it throws a
TlsFatalAlert
inGetCredentials
(The reason for that is becausem_context.SecurityParameters.KeyExchangeAlgorithm
is set to 0 inside GetCredentials).What is the proper way to add support for TLS 1.3 and is it supported? What other methods do I need to override?
Since there are no longer key exchanges and signature algorithms in TLS 1.3, I also tried directly returning a
TlsCredentials
implementation from my certificatein theGetCredentials
method: