I would like to verify an X509 certificate's signature using a public key from another CA X509 certificate (knowing this certificate was signed directly by this CA). I cannot add the CA certificate to the trusted root CA list on the system.
The X509Certificate or X509Certificate2 both contain the hash value of the certificate (and I can retrieve it using the GetCertHash() method). But neither of those contain a method to retrieve the certificate's signature. See my code below:
X509Certificate2 sslCert = new X509Certificate2(...);
X509Certificate2 CAcert = new X509Certificate2(...);
var rsa = CAcert.PublicKey.Key as RSACryptoServiceProvider;
rsa.VerifyHash(sslCert.GetCertHash(),CryptoConfig.MapNameToOID("SHA1"), ???)
Is there some (possibly different) way of doing this?
Related
Recently I came across this c# code:
var dn = new X500DistinguishedName($"CN={_appSettings.CommonName};OU={_appSettings.OrganizationalUnit}", X500DistinguishedNameFlags.UseSemicolons);
SubjectAlternativeNameBuilder sanBuilder = new SubjectAlternativeNameBuilder();
sanBuilder.AddUri(new Uri($"urn:{_appSettings.ApplicationUri}"));
using (RSA rsa = RSA.Create(2048))
{
var request = new CertificateRequest(dn, rsa, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1);
request.CertificateExtensions.Add(sanBuilder.Build());
var selfSignedCert = request.CreateSelfSigned(new DateTimeOffset(DateTime.UtcNow.AddDays(-1)), new DateTimeOffset(DateTime.UtcNow.AddDays(3650)));
...
}
...
Having a look closer at the CertificateRequest constructor parameters, the rsa key is described as:
A RSA key whose public key material will be included in the certificate or certificate request. If the CreateSelfSigned(DateTimeOffset, DateTimeOffset) method is called, this key is used as a private key.
The bold part is the one I don't really understand. Does that mean that when self signing the certificate, the certificate is signed using the given RSA key AND adds the same key as public key to the certificate?
In my understanding for TLS, we have two public-key pairs, one for signing and one for encryption. The CA signs a certificate with its private key and offers a public key to the clients to verify the signature by decrypting it with the public key, whereas the provider of a service offers a public key which the clients use to encrypt their keys first in the tls handshake which after that gets decrypted with the service providers private key.
However, in the above code sample, we create a certificate that contains what exactly? Server public key is for encryption, but what key for decryption of the signature?
I have 2 approaches to do the same thing, but Azure has deprecated the one that works, and the other method doesn't work.
The approach that works, but is deprecated:
I store my PFX in Azure Key Vault Secrets. (when I create the secret I see a warning stating that this feature is deprecated)
and use the following code to retrieve it to create my certificate:
SecretBundle secret = await keyVaultClient.GetSecretAsync(keyVaultUrl, "MyCert-Secret");
X509Certificate2Collection exportedCertCollection = new X509Certificate2Collection();
exportedCertCollection.Import(Convert.FromBase64String(secret.Value));
X509Certificate2 certFromSecret = exportedCertCollection.Cast<X509Certificate2>().Single(s => s.HasPrivateKey);
credits to this answer
I'm able to use this certificate to host and access my application successfully.
The approach that doesn't work, but I should be using:
I store my certificate in the Azure Key vault Certificates
and use the following code to retrieve it and create the X509Certificate2:
CertificateBundle certificateBundle = await keyVaultClient.GetCertificateAsync(keyVaultUrl, "MyCert-Certificate");
X509Certificate2 certFromCertificate = new X509Certificate2(certificateBundle.Cer);
The problem with this approach is that the certificate does not contain the private key. i.e. certFromCertificate.HasPrivateKey is false.
My Questions
Why does certFromSecret have the PrivateKey, while certFromCertificate doesn't?
How can I retrieve a certificate from the key vault, where I can create a X509Certificate2 object to host my application in Kestrel with UseHttps.
The 2nd part of #Adrian's answer explains the concepts around the Azure KV Certificates very well, and I have changed my code as below to get the full certificate including the private keys:
SecretBundle secret = await kv.GetSecretAsync(keyVaultUrl, certName);
X509Certificate2 certificate =
new X509Certificate2(Convert.FromBase64String(secret.Value));
The trick was to use GetSecretAsync instead of GetCertificateAsync. Please refer to Adrian's SO answer to see why the secret had to be used to get the full certificate with Private keys.
Note that you should use "Certificate identifier" property (url with "/secrets/") from Azure certificate's property page.
The latest version of the SDK (Azure.Security.KeyVault.Certificates 4.2.0) now has the DownloadCertificateAsync method, which obtains the full cert (i.e. private key too) in a straightforward way.
The documentation states:
Because Cer contains only the public key, this method attempts to
download the managed secret that contains the full certificate.
X509Certificate2 cert = await certificateClient.DownloadCertificateAsync(certName);
I am trying to create a self signed RSA-2048-SHA-256 certificate PFX file, in order to use it for data signing in my WCF requests.
I used some openSSL examples in order to create a certificate PFX file, but even though I set the SHA algorithm to 256, when I load it in my .net app, I see that this certificate's private key, has these settings:
KeyExchangeAlgorithm = RSA-PKCS1-KeyEx
SignatureAlgorithm = http://www.w3.org/2000/09/xmldsig#rsa-sha1
and when I use the code below in order to consume this certificate, I am getting "Invalid algorithm specified exception", but if I change the SHA256CryptoServiceProvider to SHA1CryptoServiceProvider everything works fine.
string msg = "This is my test message";
X509Certificate2 privateCert = new X509Certificate2("C:\\TEMP\\private.pfx", "12345");
byte[] signature = (privateCert.PrivateKey as RSACryptoServiceProvider).SignData(new UTF8Encoding().GetBytes(msg), new SHA256CryptoServiceProvider());
I can only assume that my certificate file was not created with SHA256, but instead with some kind of default SHA1 algorithm.
Those are the steps I used in order to create my certificate:
openssl req -x509 -days 365 -newkey rsa:2048 -sha256 -keyout key.pem -out cert.pem
openssl pkcs12 -export -in cert.pem -inkey key.pem -out private.pfx
What am I doing wrong?
What am I doing wrong?
Believing that those two properties have meaning :).
The two values you're seeing are hard-coded into RSACryptoServiceProvider. Other RSA types (such as RSACng) have different, less confusing, hard-coded values.
The problem is that a key doesn't have either of those attributes. A key can be used for multiple purposes (though NIST recommends against it). A TLS session (or EnvelopedCMS document, etc) can have a key exchange algorithm. A certificate, SignedCMS document, or other such material can have a signature (and thus a signature algorithm).
To know that your certificate was signed with RSA-PKCS1-SHA256 you need to look at X509Certificate2.SignatureAlgorithm.
switch (cert.SignatureAlgorithm.Value)
{
case "1.2.840.113549.1.1.4":
return "RSA-PKCS1-MD5";
case "1.2.840.113549.1.1.5";
return "RSA-PKCS1-SHA1";
case "1.2.840.113549.1.1.11";
return "RSA-PKCS1-SHA2-256"; // Winner
case "1.2.840.113549.1.1.12":
return "RSA-PKCS1-SHA2-384";
case "1.2.840.113549.1.1.13":
return "RSA-PKCS1-SHA2-512";
default:
throw new SomethingFromTheFutureOrMaybeNotRSAException();
}
My IIS (web server) requires client certificate and I need to check for certificate validity and read some information from and record in database (Audit)
I have following code
using System.Security.Cryptography.X509Certificates;
...
HttpClientCertificate cert = Request.ClientCertificate;
if (cert.IsPresent && cert.IsValid) {
X509Certificate2 cer = new X509Certificate2(cert.Certificate);
bool verified = cer.Verify();
...
AuditLog( ... );
}
cert.IsValid shows that certificate is valid. Do I need to instantiate X509Certificate2 object and re-check the validity of certificate (Why)?
If the certificate wasn't valid you wouldn't get this far. IIS should check that during the handshake, and abort the connection if invalid. All you need to do is verify that the identity represented by the Subject DN is authorized to be a client of this application.
I have to write a tool which validates if a X509 certificate is valid or not (input = cert path / subject and password). How can I do that? I don't know much about certs...
Take a look at X509Certificate2.Verify()
In general, RFC 3280 includes almost complete instructions regarding how to perform validation, however those instructions are very non-trivial. Additionally you would need to read RFC 2560 (OCSP) and implement OCSP client.
For most tasks you will find our TElX509CertificateValidator component perfectly suitable. It checks certificate paths, CRL and OCSP revocation (and checks validity of CRLs and OCSP responses as well). It is flexible and powerful enough and lets you perform additional, deeper checks on each step. Also this component can work with both Windows certificate storages and any other certificates, certificate chains and storages that you might have in files or in memory.
X509Certificate2.Verify Method performs a X.509 chain validation using basic validation policy.
This method builds a simple chain for the certificate and applies the base policy to that chain. If you need more information about a failure, validate the certificate directly using the X509Chain object.
X509Certificate2 cert = GetX509Cert();
// Verify the certificate
bool isValid = cert.Verify();
But there are many factors that can affect the validity of an X.509 certificate, including the expiration date, the identity of the signer, and the chain of trust. So you may need to write your custom code for validation depending on your specific requirements if Verify is not enough for you. So you can write your own validation code:
public static bool ValidateCertificate(X509Certificate2 certificate)
{
// Check the certificate has not expired
if (certificate.NotAfter < DateTime.UtcNow)
{
return false;
}
// Performs a X.509 chain validation using basic validation policy
if (!certificate.Verify())
{
return false;
}
// Check that the certificate's subject contains a specific string
if (!certificate.Subject.Contains("CN=example.com"))
{
return false;
}
// Check that the certificate's issuer contains a specific string
if (!certificate.Issuer.Contains("CN=Root CA"))
{
return false;
}
// All checks passed, certificate is valid
return true;
}
You have to initialize a new instance of the X509Certificate2 class to validate it. There are many constructors that allow to create a new instance by the:
certificate's file path
PEM file path or PEM file content
byte array with binary (DER) encoded or Base64-encoded X.509 data or PKCS7 (Authenticode) signed file data
unmanaged handle
var cert = new X509Certificate2(#"C:\yourcertfile.crt");
// or
var cert = new X509Certificate2(#"C:\yourcertfile.crt", password);
// or
var cert = new X509Certificate2(#"C:\yourcertfile.crt");
// or
var cert = X509Certificate2.CreateFromPemFile(#"C:\yourcertfile.pem");
// or
var cert = X509Certificate2.CreateFromPem(File.ReadAllText(#"C:\yourcertfile.pem"));
You can also open the current user or current machine certificate store via X509Store and find certificate by subject name.
X509Store store = new X509Store("MY",StoreLocation.CurrentUser);
store.Open(OpenFlags.ReadOnly | OpenFlags.OpenExistingOnly);
X509Certificate2Collection certs = store.Certificates.Find(X509FindType.FindBySubjectName, "Your Cert's Subject Name", false);
var cert = certs.Count > 0 ? certs[0] : null;