I am using Bouncy Castle to read response from Time Stamp server in .NET.Now i want to show time stamp server certificate to client, how can I read time stamp server certificate from response?
Thanks in advance.
Relevant section of RFC 3161:
If the certReq field is present and set to true, the TSA's public key
certificate that is referenced by the ESSCertID identifier inside a
SigningCertificate attribute in the response MUST be provided by the
TSA in the certificates field from the SignedData structure in that
response. That field may also contain other certificates.
So, first of all, you need to make sure that certReq is true in the request. This is an option in the Org.BouncyCastle.Asn1.Tsp.TimeStampReq constructor.
Then, the response will contain the certificate, and since there may be other certificates in there too, you need to fish out the one that was used for the timestamp signature:
TimeStampResponse resp = ...;
TimeStampToken tsToken = resp.TimeStampToken;
IX509Store store = tsToken.GetCertificates("Collection");
SignerID signerID = tsToken.SignerID;
ICollection matches = store.GetMatches(signerID);
That 'matches' collection should have exactly one cert in it.
Related
I am creating a self-hosted owin server inside a windows service. I have implemented an ACME client to get a certificate from Let's Encrypt (to a domain given by the service's config.). However, if the service is run on a server which already has a certificate I do not need to request a new one. How can I determine, in code, if there is a certificate installed which applies for the domain set in the service's config?
The closest thing to a solution I found was to ignore existing certificates (if any) and always request a new one. Then when a certificate is received from Let's Encrypt, I save that certificate's serial to a file. On startup I then use the saved file (if any) to look for the existing certificate from the store:
public async Task<bool> NeedNewCertificate()
{
string certSerial = await AsyncFileHandler.ReadFileAsync(CERT_SERIAL_FILENAME);
using (var store = new X509Store(StoreName.My, StoreLocation.LocalMachine))
{
store.Open(OpenFlags.ReadOnly);
var certCollection = store.Certificates.Find(X509FindType.FindBySerialNumber, certSerial, true);
foreach (var cert in certCollection)
{
if (cert.NotBefore <= DateTime.Now && cert.NotAfter > DateTime.Now.AddDays(30)) // Let's Encrypt certificates are valid 90 days. They recommend renewing certificates every 30 days.
return false;
}
}
return true;
}
That's a good enough way of doing it I think.
Alternatively you can do a search in the certificate store that matches the parameters that you specify: the domain that the certificate is applied to, expiry date, etc. If you find the one that is valid, then you can use it.
That is in fact most likely the definition for what you mean by "certificate is installed on the server".
The attribute you probably would like to check is "Subject", e.g. you wold like these that have "CN = mydomain.com". You can have wildcard certificates as well, so you will need to figure out what types of certificates can be installed.
I am able to successfully identify client certificates in a .NET thick client app, and the user is able to successfully select one.
X509Store store = new X509Store("MY", StoreLocation.CurrentUser);
store.Open(OpenFlags.OpenExistingOnly | OpenFlags.ReadOnly);
var fcollection = store.Certificates.Find(X509FindType.FindByApplicationPolicy, "1.3.6.1.5.5.7.3.2", true);
// other stuff where user selects one of them
Now how do I ask the user to answer the challenge (e.g. PIN in this case)?
I see there's a SignedXML.ComputeSignature() class, but it takes a byte stream, and I'm not sure where that comes from (perhaps in certificate.RawData[]?).
I'm not really as interested in getting the actual pin as I am that the card/pin match.
EDIT:
I tried using the private key from the smart card (and even encrypted from it), but I don't get asked for my PIN.
RSACryptoServiceProvider rsacsp = (RSACryptoServiceProvider)certificate.PrivateKey;
UnicodeEncoding ByteConverter = new UnicodeEncoding();
byte[] dataToEncrypt = ByteConverter.GetBytes("Data to Encrypt");
var encryptedData = RSAEncrypt(dataToEncrypt, rsacsp.ExportParameters(false), false);
Looks like the PIN request happens when I call RSACryptoServiceProvidersa.Decrypt.
Here's example code that worked perfectly for me in a Console app:
http://blog.aggregatedintelligence.com/2010/02/encryptingdecrypting-using.html
Much simpler in ASP.NET (aside from all the IIS config hassles/mysteries ...).
If this is a smartcard, the pin prompt will happen when you try to use the private key of the certificate.
You need to use the certificate somehow, and validate the result. For example, you might use the certificate to sign something. Once that signature operation happens, the pin prompt will appear.
If you don't really need to "use" the certificate, just want to validate that it's there and the user knows the pin, then you need some sort of proof of work. The certificate could be used to sign a challenge, and a remote server could validate the signature uses a key that belongs to a trusted root. Keep in mind this is difficult to get right, such as making sure you aren't open to a replay attack, etc.
In my asp.net MVC web-site, I have implemented SSO in which the IDP/ADFS sends the SAML response and I verify the SAML token to allow users to access the web-site. I am using customized code (using System.IdentityModel.dll and System.IdentityModel.Services.dll libraries to verify the SAML response and token, instead of having.net framework do the checking with web.config settings).
So far the code seems to be working fine, but since I am new to this domain have concern that hacker would able to bypass the validation with properly constructed SAML response. Recently I tried my hands on the SAML token generation part and I realized that my token verification code can be bypassed if the token is generated intelligently.
At high level this is what I am doing to verify the token:
Extract the security token from the request.
Verify the token is valid and untouched by checking the digest value with the provided X509 public key(which is present in the SAML response)
Extract the claims/identity of the user and allow the access.
My concern is that if hacker creates SAML token (like my own token generator code) and add public key in the response and post it to my web site, my web site will successfully validate the response as the response itself is well formed and signed. Is this a valid concern? I am missing some basic validations to handle this scenario?
I can think of following options to mitigate the risk:
Check the URLReferrer and make sure that the SAML response is posted from the expected entity. I am not sure if there is a way to manipulate the URLReferrer.
Avoid using the public key present in the response to validate the digest value. I can store the X509 certificate on my end and use it to validate the response. The hacker won’t able to sign the response with the same certificate as he won’t have the private key. If this is the right way, can someone suggest me how to instruct “tokenhandler” to ignore the public key present in the response and use explicit public key?
Is there a way by which I can make back-end call to IDP, May a web-service call, and check that the token received by my web site is indeed generated by the IDP?
Since I am a newbie, I might be missing the basic SAML validation concept. So please let me know if my concern is legitimate.
Here is the sample code I use to validate the response.
public ActionResult SAMLAssert()
{
var fam = new WSFederationAuthenticationModule();
var request = this.HttpContext.Request;
// Get the security token from the SAML response
var securityToken = fam.GetSecurityToken(request);
var config = new SecurityTokenHandlerConfiguration
{
CertificateValidator = X509CertificateValidator.None,
IssuerNameRegistry = new CustomIssuerNameRegistry(),
};
config.AudienceRestriction.AudienceMode = AudienceUriMode.Never;
var tokenHandler = new Saml2SecurityTokenHandler
{
CertificateValidator = X509CertificateValidator.None,
Configuration = config,
};
//// validate the token and get the ClaimsIdentity out of it
var identity = tokenHandler.ValidateToken(securityToken);
bool isSuccess = identity[0].IsAuthenticated;
// Code to retrieve the claims/user information from the token
//....
return View();
}
And here is the custom "IssuerNameRegistry".
public class CustomIssuerNameRegistry : IssuerNameRegistry
{
public override string GetIssuerName(SecurityToken securityToken)
{
X509SecurityToken x509Token = securityToken as X509SecurityToken;
return x509Token.Certificate.Subject;
}
}
I have suspicion that the custom class is the problematic part as it’s not doing any validation.
I don't think you should check the referrer value. It can easily be spoofed.
The IdP uses its private key to sign responses sending to you. An attacker just doesn't have access to this private key. Therefore, if an attacker wants to spoof a signature, he will need to use his own certificate and put his public key to the token. While you are right that validation code uses the embedded public key for verifying signature, AFAICT it also does one more thing: check if the public key is trusted by our machine. The trust here can be established by adding the public key to your Windows Certificate store -> TrustedPeople. I don't have all the code to verify this though but it should work this way, or it should provide you a way to do that.
If you have full control over the IdP, alternative to embedded public key (aka X509Data), you can use keyname, subject name, thumbprint only. But how much more secure they provide and how to implement are out of the scope of this question.
No, SAML protocol wasn't designed to work that way. The closest thing to this is to use Artifact flow in which the IdP only returns an Artifact to your application and it needs to make an ArtifactResolve request to the IdP to get the actual Response. See What is the purpose of a SAML Artifact?. But then you still need to verify signature of the received Response.
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#.
I have a WCF client that is crashing with the error "The EncryptedKey clause was not wrapped with the required encryption token 'System.IdentityModel.Tokens.X509SecurityToken'." for every response.
I've looked around and this blog post seems to indicate that the problem is with my certificate set up, but I'm not sure what I am doing wrong...
My client uses a custom binding with a MutualCertificateBindingElement for security, I am configuring the certificates in code as follows:
client.ClientCredentials.ServiceCertificate.Authentication.CertificateValidationMode = X509CertificateValidationMode.ChainTrust;
client.ClientCredentials.ServiceCertificate.SetDefaultCertificate
(
StoreLocation.CurrentUser,
StoreName.AddressBook,
X509FindType.FindBySerialNumber,
"[serial number 1]"
);
client.ClientCredentials.ClientCertificate.SetCertificate
(
StoreLocation.CurrentUser,
StoreName.My,
X509FindType.FindBySerialNumber,
"[serial number 2]"
);
The serial numbers match the values in the <X509SerialNumber> elements in both the request and the response messages.
One discrepancy I have noticed is the <X509IssuerName> elements in the request and the response are formatted differently:
Request: CN=[CN], O=[O], L=[L], C=[C]
Response: C=[C],L=[L],O=[O],CN=[CN]
Is it possible this is causing the issue?
UPDATE
Turns out it was the certificate name formatting causing the issue. I managed to resolve it by replacing the cert names in the response with what WCF expects by using a custom encoder. Now I have this ugly hack, but it works so I'll live with it!
public override Message ReadMessage(ArraySegment<byte> buffer, BufferManager bufferManager, string contentType)
{
var msgContents = new byte[buffer.Count];
Array.Copy(buffer.Array, buffer.Offset, msgContents, 0, msgContents.Length);
bufferManager.ReturnBuffer(buffer.Array);
var message = Encoding.UTF8.GetString(msgContents);
// Fix certificate issuer name formatting to match what WCF expects.
message = message.Replace
(
"C=[C],L=[L],O=[O],CN=[CN]",
"CN=[CN], O=[O], L=[L], C=[C]"
);
var stream = new MemoryStream(Encoding.UTF8.GetBytes(message));
return ReadMessage(stream, int.MaxValue);
}
The issuer name order that you mentioned is most probably the issue. Since these names are not signed I suggest you write a custom encoder in your client that replaces the names in the response to be formatted as in the request.
Besides obvious cert mismatch and barring miss-configuration... I have seen an issue trying to access private key. Check that client has appropriate permissions to the cert private key. If you right click on the cert in certmanager you should see AllTasks/Manage Private Keys. Add your client's process identity to the list.
Also make sure that the certificate you are using is correct. I used self-signed certificate which was missing Subject Key Identifier.
WCF : The EncryptedKey clause was not wrapped with the required encryption token 'System.IdentityModel.Tokens.X509SecurityToken'