I have a signed message and I want to know against what certificate is this code checking the signature.
Does the SignedCms always have the signing certificate in it (and it is used to verify the signature) or sometimes the certificate isn't inside the message and it is taken from the verifying machine Certificate Store's?
Basically I'm interested in identifying who is the User that signed that message.
Here is the example of code that makes that verification (from msdn: https://msdn.microsoft.com/en-us/library/aedbc064(v=vs.110).aspx )
// Create a ContentInfo object from the inner content obtained independently from encodedMessage.
ContentInfo contentInfo = new ContentInfo(innerContent);
// Create a new, detached SignedCms message.
SignedCms signedCms = new SignedCms(contentInfo, true);
// encodedMessage is the encoded message received from the sender.
signedCms.Decode(encodedMessage);
// Verify the signature without validating the certificate.
signedCms.CheckSignature(true); //<-- Here is the verification
Thank you, and sorry for my poor english.
SignedCms is represented by an ASN.1 structure SignedData defined in RFC 2315
SignedData ::= SEQUENCE {
version Version,
digestAlgorithms DigestAlgorithmIdentifiers,
contentInfo ContentInfo,
certificates
[0] IMPLICIT ExtendedCertificatesAndCertificates
OPTIONAL,
crls
[1] IMPLICIT CertificateRevocationLists OPTIONAL,
signerInfos SignerInfos }
Property certificates as described by RFC 2315
is a set of PKCS #6 extended certificates and X.509 certificates. It
is intended that the set be sufficient to contain chains from a
recognized "root" or "top-level certification authority" to all of the
signers in the signerInfos field. There may be more certificates than
necessary, and there may be certificates sufficient to contain chains
from two or more independent top-level certification authorities.
There may also be fewer certificates than necessary, if it is expected
that those verifying the signatures have an alternate means of
obtaining necessary certificates (e.g., from a previous set of
certificates).
But it is optional.
signerInfos is described as
signerInfos is a collection of per-signer information. There may be any number of elements in the collection, including zero.
SignerInfo contains IssuerAndSerialNumber element that describes what certificate was used to sign the content.
More info in RFC 2315
In c# you can get the certificate with this code:
signedCms.SignerInfos[0].Certificate
Related
I want to use iText to embed signed hash and public in PDF.
As per arguments to sign method in iText 7 I need to pass certificate chain,
How can I create this certificate object directly from public key string?
Update 1
Below is small c# code. You can see I am trying to get x509 certificate from public key. This certificate will be used to verify the signed data from corresponding private key. Also it will be used to embed this public certificate and signed hash into PDF for digital signature.
In below code I am getting error as below
Error:
'DigiSignDemo.exe' (CLR v4.0.30319: DigiSignDemo.exe): Loaded 'C:\Users\xposs\source\repos\DigiSignDemo\bin\Debug\itext.forms.dll'. Skipped loading symbols. Module is optimized and the debugger option 'Just My Code' is enabled.
Exception thrown: 'System.Security.Cryptography.CryptographicException' in mscorlib.dll
An unhandled exception of type 'System.Security.Cryptography.CryptographicException' occurred in mscorlib.dll
Cannot find the requested object.
public static readonly string publickey = #"-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAuGhYfAvWxqIwsZsO1zUN
NyFT/3US7HGLXiW48NvYn2qNyn/9hm/BFWG901YoJAjlPTcNtMo1t8lUr2dRkc3l
8YyP8SetWKbznerQuXYBZZy31kp8u3Wj+zQSroZsFn69FoMAMWXqhkw9woFumINe
gw4sMtQ1S8CucX0uXJ4a2ElzoaUKp1M+MOCATDnmsXSyf/2/ERO71SpD+alDV2rE
m5DqvEnE0t27fm7PpNeCX0XEHRvx620LooGv1Co+0w5Au37sfSjOZp1B9V0n8KFR
6gLFY7mAZ1krZJscYgkNAPIz2QE6voBR8OVSHMnNcPH+0KLfGuNVHhaTyI4naPH+
0QIDAQAB
-----END PUBLIC KEY-----
";
public static System.Security.Cryptography.X509Certificates.X509Certificate getPublicCertificate()
{
//Here below I am getting error
X509Certificate2 clientCertificate =
new X509Certificate2(Encoding.UTF8.GetBytes(publickey));
return clientCertificate;
}
You can't create a certificate from a public key. It's analogous to asking how to create a car from a steering wheel... you're missing a lot of other stuff before it'd be a car.
Given that you have an RSA public key in the SubjectPublicKeyInfo format, you can import it as an RSA key, starting with .NET Core 3.0, via
RSA rsa = RSA.Create();
rsa.ImportSubjectPublicKeyInfo(
Convert.FromBase64String(#"
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAuGhYfAvWxqIwsZsO1zUN
NyFT/3US7HGLXiW48NvYn2qNyn/9hm/BFWG901YoJAjlPTcNtMo1t8lUr2dRkc3l
8YyP8SetWKbznerQuXYBZZy31kp8u3Wj+zQSroZsFn69FoMAMWXqhkw9woFumINe
gw4sMtQ1S8CucX0uXJ4a2ElzoaUKp1M+MOCATDnmsXSyf/2/ERO71SpD+alDV2rE
m5DqvEnE0t27fm7PpNeCX0XEHRvx620LooGv1Co+0w5Au37sfSjOZp1B9V0n8KFR
6gLFY7mAZ1krZJscYgkNAPIz2QE6voBR8OVSHMnNcPH+0KLfGuNVHhaTyI4naPH+
0QIDAQAB"),
out _);
// the key is loaded now.
If you're not on .NET Core, this is a lot harder. See How to load the RSA public key from file in C# or How to get RSACryptoServiceProvider public and private key only in c# for more information.
#bartonjs in his answer already has mentioned that a X509 certificate is much more than a public key. Here comes an overwiew to show how much more.
For this let's look at a specification of X509 certificates, RFC 5280. The certificate structure is specified in ASN.1 which is quite easy to follow even if one does not know it yet.
Here you'll see that first of all a X509 certificate is something signed:
Certificate ::= SEQUENCE {
tbsCertificate TBSCertificate,
signatureAlgorithm AlgorithmIdentifier,
signatureValue BIT STRING }
The private key used for this signature may again be associated with the public key of another certificate (issuer certificate) which again may be signed using a private key associated with the public key of yet another certificate. This gives rise to a chain of certificates which usually ends in a certificate which is signed using the private key of this certificate itself, a self-signed certificate.
To verify whether one trusts the information in a certificate, one usually follows its issuer chain and checks whether one eventually gets to some certificate which is from a small collection of certificates one trusts explicitly.
The TBSCertificate in it contains the information to-be-signed. It is specified as
TBSCertificate ::= SEQUENCE {
version [0] EXPLICIT Version DEFAULT v1,
serialNumber CertificateSerialNumber,
signature AlgorithmIdentifier,
issuer Name,
validity Validity,
subject Name,
subjectPublicKeyInfo SubjectPublicKeyInfo,
issuerUniqueID [1] IMPLICIT UniqueIdentifier OPTIONAL,
subjectUniqueID [2] IMPLICIT UniqueIdentifier OPTIONAL,
extensions [3] EXPLICIT Extensions OPTIONAL
}
Here you in particular a subject Name, an issue Name and a serialNumber. The subject name describes the holder of this certificate, the issue name is the subject name of the issuer certificate, and the serial number is a unique number determined by the issuer when signing the certificate.
The validity interval indicates the intended time span of validity of the certificate.
Then there eventually is the SubjectPublicKeyInfo which contains the public key of the certificate.
Finally the extensions are an optional collection containing extra information like the allowed usages of the certificate (and its private key) and URLs where revocation information for the certificate can be retrieved.
As you can see you cannot simply the certificate based on the public key alone. You can create a certificate based thereon alone but don't expect anyone to trust it.
I don't really understand how to work with PKCS#7 messages.
I sign some byte array with a X509Certificate2 I have and get also a byte array.
byte[] data = new byte[5] { 110, 111, 112, 113, 114 }, signedData;
X509Certificate2 cert = new X509Certificate2(certPath, password);
ContentInfo content = new ContentInfo(data);
SignedCms envelope = new SignedCms(content);
CmsSigner cmsSigner = new CmsSigner(cert);
envelope.ComputeSignature(cmsSigner);
signedData = envelope.Encode();
The signedData is transmitted to some remote recipient and he gets the SignedCms envelope.
SignedCms envelope = new SignedCms();
envelope.Decode(signedData);
How can he decode the envelope? He doesn't pass my public key as a parameter. There's my public key in the envelope, in SignerInfo property, but is there any reason for that, cause anyone can replace it with the whole signature?
He can the recipient make sure, using my public key that he has, that the actual sender of the envelope is me?
There's method envelope.CheckSignature(new X509Certificate2Collection(certificate), true); but I tried to use wrong certificate and there was no exception thrown.
A PKCS#7 / CMS / S/MIME signed message is a data container which has (in addition to some other metadata):
EncapsulatedContentInfo
ContentInfoType
EncapsulatedContent (the message bytes)
Certificates (Optional)
CRLs (Optional)
SignerInfos
DigestAlgorithm (e.g. SHA-1)
SignedAttributes (Optional, allows other context information to be signed)
SignatureAlgorithm (e.g. RSA, DSA, ECDSA)
SignatureValue (the signature bytes)
UnsignedAttributes (Optional, allows for after-signing information, like counter-signatures)
(This is a summary of RFC 2630 (Cryptographic Message Syntax) Section 5)
SignedCms.Decode reads the encoded message and populates members. Every direct signatory to the message can be read from the SignedCms::SignerInfos property (counter-signers, or entities which have signed that they witnessed the original signature, can be read from SignerInfo::CounterSignerInfos).
When you call SignedCms.CheckSignature, it checks every SigerInfo and verifies that the signature can be successfully verified (or throws an exception), as well as that every counter-signer signature can be worked out.
What it doesn't know is that any of the signers "made sense". For that check you would need to loop over each SignerInfo and look at (for example) the Certificate property; then perform a suitability check:
Perhaps it is a pre-registered public key
Perhaps it chains up to a well-known root or intermediate CA
Perhaps it has some sort of Extension which shows it to be suitable
This part SignedCms cannot realistically do for you, since there's no default notion of "suitable" for messages, unlike the hostname verification of TLS.
If you want to assess the signature of a single signer, you can call SignedInfo::CheckSignature, but that's redundant if you also called SignedCms::CheckSignature.
There's method envelope.CheckSignature(new X509Certificate2Collection(certificate), true); but I tried to use wrong certificate and there was no exception thrown.
The extraCerts overloads provide extra certificates. It's valid to have a SignedCms message which does not embed the signer certificates, leaving it up to the recipient to have known the valid certs ahead of time (e.g. using a per-user database of pre-registered certificates). You didn't get an exception because the correct certificates were found within the provided certificates collection.
You can see what was in the provided certificates collection via the X509Certificate2Collection.Import methods; they can read a PKCS#7 signed-data message and populate the collection with the optional embedded certificates.
A PKCS#7 by itself is just a signature, could it be replaced? sure. envelope.CheckSiganture just validates that pkcs#7 has the right format and length, in other words checks if a pkcs#7 is well constructed.
Broadly putted, you need to implement a PKI (Private Key Infrastructure). Where in one end you construct your pkcs#7 using a public key, and on the other end you must validate that the pkcs#7 you have actually has a valid certificate that you recognize as your own. You must implement an OCSP to validate those certificates and if everything checks out all right you should and must request a timestamp to a third party to vouch for your pkcs#7. Also you will need a vault (database) to keep track of everything: pkcs#7's, data hashes, timestamps, original data, ocsp responses...
But if you are only interested in knowing how to identify a pkcs#7, there are various tools you could use to decode a PKCS#7, this action gives back all the information contained in it. Or you could create your own using c#.
Well, we all knew that, that was about to happen, the Apple WWDR certificate has expired on Valentine's Day (that's what I call "developer love") according to the news release from Apple.
I'm using C# to generate a Push Package for Safari, and, surprise, this does not work any-more. This is the message that I get in my logging endpoint instead:
{"logs":["Signature verification of push package failed"]}
This is how my old PKCS#7 Signing code looked like:
// Sign the message with the private key of the signer.
static byte[] PKCS7SignMessage(byte[] message, X509Certificate2 signerCertificate)
{
// Place message in a ContentInfo object.
// This is required to build a SignedCms object.
ContentInfo contentInfo = new ContentInfo(message);
// Instantiate SignedCms object with the ContentInfo above.
// Has default SubjectIdentifierType IssuerAndSerialNumber.
// Has default Detached property value false, so message is
// included in the encoded SignedCms.
SignedCms signedCms = new SignedCms(contentInfo, true);
// Formulate a CmsSigner object for the signer.
CmsSigner cmsSigner = new CmsSigner(signerCertificate);
cmsSigner.IncludeOption = X509IncludeOption.EndCertOnly;
// Sign the CMS/PKCS #7 message.
signedCms.ComputeSignature(cmsSigner);
// Encode the CMS/PKCS #7 message.
return signedCms.Encode();
}
Apple asks to also "pass the path to the renewed intermediate for the extra certificates parameter".
So I tried this:
X509Certificate2 appleIntermediate = new X509Certificate2();
appleIntermediate.Import(#"Path-to-new-WWRD.cer");
cmsSigner.Certificates.Add(appleIntermediate);
It didn't work. (Signature verification of push package failed)
Later I tried to change this line:
cmsSigner.IncludeOption = X509IncludeOption.WholeChain;
It didn't work. I've got an exception saying:
"A certificate chain could not be built to a trusted root authority".
All right, now I decided to:
Add All of Apple CA root certificates to the local machine's trusted certificate store.
Add the renewed WWRD certificate to the local machine's intermediate certificate store.
Restart the process and try the code again. Good news, it is now signing again including, in theory, the whole certificate chain.
BUT: It didn't work. (Signature verification of push package failed)
According to Apple, fixing this this is piece of cake:
Safari Push Notifications
Update your notification package signing server to include your web push certificate and the renewed intermediate certificate by February 14, 2016. After this date, new users will not be able to sign up for push notifications from your website until your server has been updated. If you were using the openssl_pkcs7_sign function to sign your push package with only your web push certificate, you should pass the path to the renewed intermediate for the extra certificates parameter.
Now, what does that mean in plan English?
And how can I apply that to a C# context?
Apple does not want the whole chain. They only expect your certificate, and their intermediate certificate to be included. So your code should look something like this:
static public byte[] PKCS7SignMessage(byte[] manifest_data, X509Certificate2 signerCertificate) {
X509Certificate2Collection ca_chain;
ContentInfo content;
SignedCms signed_cms;
CmsSigner signer;
signed_cms = new SignedCms();
ca_chain = new X509Certificate2Collection(new X509Certificate2(#"Path-to-new-intermediate-WWRD.cer"));
content = new ContentInfo(manifest_data);
signed_cms = new SignedCms(content, true);
signer = new CmsSigner(SubjectIdentifierType.IssuerAndSerialNumber, signerCertificate);
signer.IncludeOption = X509IncludeOption.ExcludeRoot;
signer.Certificates.AddRange(ca_chain);
signed_cms.ComputeSignature(signer);
return signed_cms.Encode();
}
I am getting the error "InvalidBasicConstraints: A certificate's basic constraint extension has not been observed." This certificate was issued with OpenSSL and will be used as a server-side test certificate for a WCF service (which gives the same error when validating the certificate). I can replicate the error with this code.
X509Certificate2 cert = new X509Certificate2(#"c:\test.cer");
X509Chain chain = X509Chain.Create();
X509ChainPolicy policy = new X509ChainPolicy();
policy.RevocationMode = X509RevocationMode.NoCheck;
chain.ChainPolicy = policy;
bool valid = chain.Build(cert);
Console.WriteLine(string.Join(" -- ", chain.ChainStatus.Select(o => o.Status + ": " + o.StatusInformation)));
Console.WriteLine(valid ? "VALID" : "NOT VALID");
Viewing the certificate I can see these basic constraints,
Subject Type=End Entity
Path Length Constraint=None
What does this error mean and how can I fix it?
The most relevent article I found on basic constraints was this one, http://unitstep.net/blog/2009/03/16/using-the-basic-constraints-extension-in-x509-v3-certificates-for-intermediate-cas/ however it only talks about prohibiting a child certificate from signing / creating more child certificates which in my case I'm not trying to do.
Also any references that explain what the above basic constraints mean would be helpful.
http://forums.juniper.net/t5/SSL-VPN/quot-Failed-to-authenticate-client-certificate-quot-after/td-p/89232/page/4
After reading the above thread I see there are two different subject types,
Basic Constraints: Subject Type=CA, Length Constraint=None
Basic Constraints: Subject Type=End Entity, Path Length Constraint=None
The server-side certificate I'm using is actually the 3rd link in a hiearchy.
A -> B -> C....A issued B which then issued signed C.
Certificate B also has Subject Type=End Entity in the basic constraints. I cannot find any documentation which states the different types of basic contraints or their meanings, but based on the difference of the above 2 types, I would say that End Entity means it CANNOT issue certificates. It is the end of the chain, as opposed to Subject Type=CA.
When .NET is validating the chain it sees that B does not have permission from it's father certificate to issue certificates and throws the error, A certificate's basic constraint extension has not been observed.
Edit: Further testing with a new certificate that was issued by a self-signed certificate verifies the above theory.
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;