I want to implement certificate/public key pinning in my C# application. I already saw a lot of solutions that pin the certificate of the server directly as e.g. in this question. However, to be more flexible I want to pin the root certificate only. The certificate the server gets in the setup is signed by an intermediate CA which itself is signed by the root.
What I implemented so far is a server that loads its own certificate, the private key, intermediate certificate, and the root certificate from an PKCS#12 (.pfx) file. I created the file using the following command:
openssl pkcs12 -export -inkey privkey.pem -in server_cert.pem -certfile chain.pem -out outfile.pfx
The chain.pem file contains the root and intermediate certificate.
The server loads this certificate and wants to authenticate itself against the client:
// certPath is the path to the .pfx file created before
var cert = new X509Certificate2(certPath, certPass)
var clientSocket = Socket.Accept();
var sslStream = new SslStream(
new NetworkStream(clientSocket),
false
);
try {
sslStream.AuthenticateAsServer(cert, false, SslProtocols.Tls12, false);
} catch(Exception) {
// Error during authentication
}
Now, the client wants to authenticate the server:
public void Connect() {
var con = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
con.Connect(new IPEndPoint(this.address, this.port));
var sslStream = new SslStream(
new NetworkStream(con),
false,
new RemoteCertificateValidationCallback(ValidateServerCertificate),
null
);
sslStream.AuthenticateAsClient("serverCN");
}
public static bool ValidateServerCertificate(
object sender,
X509Certificate certificate,
X509Chain chain,
SslPolicyErrors sslPolicyErrors
)
{
// ??
}
The problem now is that the server only sends its own certificate to the client. Also the chain parameter does not contain further information. This is somehow plausible as the X509Certificate2 cert (in the server code) only contains the server certificate and no information about the intermediate or root certificate. However, the client is not able to validate the whole chain as (at least) the intermediate certificate is missing.
So far, I did not find any possiblity to make .NET send the whole certificate chain, but I do not want to pin the server certificate iself or the intermediate one as this destroys the flexibility of root certificate pinning.
Therefore, does anyone know a possibility to make SslStream sending the whole chain for authentication or implement the functionality using another approach? Or do I have to pack the certificates differently?
Thanks!
Edit:
I made some other tests to detect the problem. As suggested in the comments, I created a X509Store
that contains all the certificates. After that, I built a X509Chain
using my server's certificate and the store. On the server itself, the new chain contains all the certificates correctly, but not in the ValidateServerCertificate
function..