Let's say I have three certificates (in Base64 format)
Root
|
--- CA
|
--- Cert (client/signing/whatever)
How can I validate the certs and certificate path/chain in C#?
(All those three certs may not be in my computer cert store)
Edit: BouncyCastle has the function to verify. But I'm trying not to use any third-party library.
byte[] b1 = Convert.FromBase64String(x509Str1);
byte[] b2 = Convert.FromBase64String(x509Str2);
X509Certificate cer1 =
new X509CertificateParser().ReadCertificate(b1);
X509Certificate cer2 =
new X509CertificateParser().ReadCertificate(b2);
cer1.Verify(cer2.GetPublicKey());
If the cer1 is not signed by cert2 (CA or root), there will be exception. This is exactly what I want.
The X509Chain class was designed to do this, you can even customize how it performs the chain building process.
static bool VerifyCertificate(byte[] primaryCertificate, IEnumerable<byte[]> additionalCertificates)
{
var chain = new X509Chain();
foreach (var cert in additionalCertificates.Select(x => new X509Certificate2(x)))
{
chain.ChainPolicy.ExtraStore.Add(cert);
}
// You can alter how the chain is built/validated.
chain.ChainPolicy.RevocationMode = X509RevocationMode.NoCheck;
chain.ChainPolicy.VerificationFlags = X509VerificationFlags.IgnoreWrongUsage;
// Do the validation.
var primaryCert = new X509Certificate2(primaryCertificate);
return chain.Build(primaryCert);
}
The X509Chain will contain additional information about the validation failure after Build() == false if you need it.
Edit: This will merely ensure that your CA's are valid. If you want to ensure that the chain is identical you can check the thumbprints manually. You can use the following method to ensure that the certification chain is correct, it expects the chain in the order: ..., INTERMEDIATE2, INTERMEDIATE1 (Signer of INTERMEDIATE2), CA (Signer of INTERMEDIATE1)
static bool VerifyCertificate(byte[] primaryCertificate, IEnumerable<byte[]> additionalCertificates)
{
var chain = new X509Chain();
foreach (var cert in additionalCertificates.Select(x => new X509Certificate2(x)))
{
chain.ChainPolicy.ExtraStore.Add(cert);
}
// You can alter how the chain is built/validated.
chain.ChainPolicy.RevocationMode = X509RevocationMode.NoCheck;
chain.ChainPolicy.VerificationFlags = X509VerificationFlags.IgnoreWrongUsage;
// Do the preliminary validation.
var primaryCert = new X509Certificate2(primaryCertificate);
if (!chain.Build(primaryCert))
return false;
// Make sure we have the same number of elements.
if (chain.ChainElements.Count != chain.ChainPolicy.ExtraStore.Count + 1)
return false;
// Make sure all the thumbprints of the CAs match up.
// The first one should be 'primaryCert', leading up to the root CA.
for (var i = 1; i < chain.ChainElements.Count; i++)
{
if (chain.ChainElements[i].Certificate.Thumbprint != chain.ChainPolicy.ExtraStore[i - 1].Thumbprint)
return false;
}
return true;
}
I am unable to test this because I don't have a full CA chain with me, so it would be best to debug and step through the code.
The X509Chain does not work reliably for scenarios where you do not have the root certificate in the trusted CA store on the machine.
Others will advocate using bouncy castle. I wanted to avoid bringing in another library just for this task, so I wrote my own.
As see in RFC3280 Section 4.1 the certificate is a ASN1 encoded structure, and at it's base level is comprised of only 3 elements.
The "TBS" (to be signed) certificate
The signature algorithm
and the signature value
Certificate ::= SEQUENCE {
tbsCertificate TBSCertificate,
signatureAlgorithm AlgorithmIdentifier,
signatureValue BIT STRING
}
C# actually has a handy tool for parsing ASN1, the System.Formats.Asn1.AsnDecoder.
Using this, we can extract these 3 elements from the certificate to verify the chain.
The first step was extracting the certificate signature, since the X509Certificate2 class does not expose this information and it is necessary for the purpose of certificate validation.
Example code to extract the signature value part:
public static byte[] Signature(
this X509Certificate2 certificate,
AsnEncodingRules encodingRules = AsnEncodingRules.BER)
{
var signedData = certificate.RawDataMemory;
AsnDecoder.ReadSequence(
signedData.Span,
encodingRules,
out var offset,
out var length,
out _
);
var certificateSpan = signedData.Span[offset..(offset + length)];
AsnDecoder.ReadSequence(
certificateSpan,
encodingRules,
out var tbsOffset,
out var tbsLength,
out _
);
var offsetSpan = certificateSpan[(tbsOffset + tbsLength)..];
AsnDecoder.ReadSequence(
offsetSpan,
encodingRules,
out var algOffset,
out var algLength,
out _
);
return AsnDecoder.ReadBitString(
offsetSpan[(algOffset + algLength)..],
encodingRules,
out _,
out _
);
}
The next step is to extract the TBS certificate. This is the original data which was signed.
example code to extract the TBS certificate data:
public static ReadOnlySpan<byte> TbsCertificate(
this X509Certificate2 certificate,
AsnEncodingRules encodingRules = AsnEncodingRules.BER)
{
var signedData = certificate.RawDataMemory;
AsnDecoder.ReadSequence(
signedData.Span,
encodingRules,
out var offset,
out var length,
out _
);
var certificateSpan = signedData.Span[offset..(offset + length)];
AsnDecoder.ReadSequence(
certificateSpan,
encodingRules,
out var tbsOffset,
out var tbsLength,
out _
);
// include ASN1 4 byte header to get WHOLE TBS Cert
return certificateSpan.Slice(tbsOffset - 4, tbsLength + 4);
}
You may notice that when extracting the TBS certiifcate I needed to include the ASN1 header in the data, this is because the signature of the TBS Certificate INCLUDES this data (this annoyed me for a while).
For the first time in history, the Microsoft does not impede us with their API design, and we are able to obtain the Signature Algorithm directly from the X509Certificate2 object. Then we just need to decide to what extend we are going to implement different hash algorithms.
var signature = signed.Signature();
var tbs = signed.TbsCertificate();
var alg = signed.SignatureAlgorithm;
// https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-gpnap/a48b02b2-2a10-4eb0-bed4-1807a6d2f5ad
switch (alg)
{
case { Value: var value } when value?.StartsWith("1.2.840.113549.1.1.") ?? false:
return signedBy.GetRSAPublicKey()?.VerifyData(
tbs,
signature,
value switch {
"1.2.840.113549.1.1.11" => HashAlgorithmName.SHA256,
"1.2.840.113549.1.1.12" => HashAlgorithmName.SHA384,
"1.2.840.113549.1.1.13" => HashAlgorithmName.SHA512,
_ => throw new UnsupportedSignatureAlgorithm(alg)
},
RSASignaturePadding.Pkcs1
) ?? false;
case { Value: var value } when value?.StartsWith("1.2.840.10045.4.3.") ?? false:
return signedBy.GetECDsaPublicKey()?.VerifyData(
tbs,
signature,
value switch
{
"1.2.840.10045.4.3.2" => HashAlgorithmName.SHA256,
"1.2.840.10045.4.3.3" => HashAlgorithmName.SHA384,
"1.2.840.10045.4.3.4" => HashAlgorithmName.SHA512,
_ => throw new UnsupportedSignatureAlgorithm(alg)
},
DSASignatureFormat.Rfc3279DerSequence
) ?? false;
default: throw new UnsupportedSignatureAlgorithm(alg);
}
As shown in the code above, https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-gpnap/a48b02b2-2a10-4eb0-bed4-1807a6d2f5ad is a good resource to see the mapping of algorithms and OIDs.
Another thing you should be aware of is that there are some articles out there that claim that for elliptical curve algorithms, microsoft expects a R,S formatted key instead of a DER formatted key. I tried to convert the key to this format but it ultimately didn't work. What I discovered was that it was necessary to use the DSASignatureFormat.Rfc3279DerSequence parameter.
Additional certificate checks, like "not before" and "not after", or CRL and OCSP checks can be done in addition to the chain verification.
Related
I am trying to create two different bindings on two different site on IIS with C# from my website.
Those 2 binding needs to be set with a certificate (one specific for each bindings). The problem is, the bindings are correctly created but the two bindings are created with the same certificate.
Here my code :
public async Task AddBindings(string code)
{
await AddBinding("Website1", "website1.com");
await AddBinding("Website2", "website2.com");
}
private async Task AddBinding(string siteName, string urlDomain)
{
using (ServerManager serverMgr = new ServerManager())
{
var site = serverMgr.Sites[siteName];
var certif = GetCertificate("*." + urlDomain);
site.Bindings.Add("*:443:" + urlDomain, certif.GetCertHash(), "My");
serverMgr.CommitChanges();
serverMgr.Dispose();
}
}
private X509Certificate2 GetCertificate(string nameStartWith)
{
var store = new X509Store(StoreName.My, StoreLocation.LocalMachine);
store.Open(OpenFlags.OpenExistingOnly);
X509Certificate2 certif = null;
foreach (var certificate in store.Certificates)
{
var certifDate = DateTime.Parse(certificate.GetEffectiveDateString());
if (certificate.FriendlyName.StartsWith(nameStartWith))
{
certif = certificate;
}
}
store.Close();
return certif;
}
The "funny things" in the function AddBindings, in this order the two bindings are going to be created with the Website2 certificate, and if I change the order of the two rows it's going to use Website1.
Thank you very much for your helps !
Your code definitely leads to that, because nowhere you specify that SNI mappings should be used.
The correct function call you should make is this overloading version,
Add (string bindingInformation, byte[] certificateHash, string certificateStoreName, Microsoft.Web.Administration.SslFlags sslFlags)
You can read more about SNI mappings in Windows HTTP API from here.
I'm trying to implement signed URLs for short lived access to static files.
The idea is:
generate an URL with an expiration timestamp (e.g. https://example.com/file.png?download=false&expires=1586852158)
sign it with HMACSHA256 and a shared secret and append the signature at the end of URL (e.g. https://example.com/file.png?download=false&expires=1586852158&signature=6635ea14baeeaaffe71333cf6c7fa1f0af9f6cd1a17abb4e75ca275dec5906d1
When i receive the request on the server, I take out the signature parameter and verify that the rest of the URL signed with HMACSHA256 and the same shared secret results in the same signature.
The implementation is as follows:
public static class URLSigner
{
private static string GetSignatureForUri(string uri, byte[] key)
{
var hmac = new HMACSHA256(key);
var signature = hmac.ComputeHash(Encoding.UTF8.GetBytes(uri));
var hexSignature = BitConverter.ToString(signature).Replace("-", string.Empty).ToLowerInvariant();
return hexSignature;
}
public static string SignUri(string uri, byte[] key)
{
var hexSignature = GetSignatureForUri(uri, key);
return QueryHelpers.AddQueryString(uri, new Dictionary<string, string> { { "signature", hexSignature }});
}
public static bool VerifyUri(string uri, byte[] key)
{
var signatureRegex = "[\\?&]signature=([a-z0-9]+)$";
var signatureMatch = Regex.Match(uri, signatureRegex);
if (!signatureMatch.Success || signatureMatch.Groups.Count != 2)
return false;
var parsedSignature = signatureMatch.Groups[1].Value;
var originalUri = Regex.Replace(uri, signatureRegex, "");
var hexSignature = GetSignatureForUri(originalUri, key);
return hexSignature == parsedSignature;
}
}
and it's used like so:
var signedUri = URLSigner.SignUri("https://example.com/file.png?download=false", secretKey);
var isVerified = URLSigner.VerifyUri(signedUri, secretKey);
Is this implementation of signed URLs reasonably secure?
Your implementation seems to be missing the verification of the expiration time, so any one key would currently work indefinitely.
Otherwise, I don't see anything wrong with this approach in general. You may want to add in a key beyond just the timestamp for identifying the user or request in some way though.
Here's a good article on how the general approach is used for one time passwords which is essentially what you are doing.
https://www.freecodecamp.org/news/how-time-based-one-time-passwords-work-and-why-you-should-use-them-in-your-app-fdd2b9ed43c3/
Yes, it is secure, as long as the key is treated properly. The hash should be able to ensure data integrity (data in URL are not modified by other people).
Perhaps, one little improvement is to dispose the HMACSHA256 object (maybe by using), but that may not be related to security.
I have one concern. You are saying you want to use HMACSHA256 and a private key, but in security terminology what you're passing to the HMAC is not a private key, it's a shared secret.
If you have to had a public, private key for your sign and verify authentication, I would suggest using the RSACryptoServiceProvider. With RSA you have two keys, public key and private key.
Your client creates a private key and keep it and give its public key to the server. So only client can sign and anyone with public key can verify it.
On another note, no matter what algorithm you ended up using, I would suggest to add the signature to a authorization header instead of query string. This is more common and you don't need to match a regex in each request.
I'm trying to create a keypair running the above code sample. I'm running this inside a activex. I have no problem to run this local, but when I install it on my server, it's not working properly, it only works if I run my IE as administrator.
System.UnauthorizedAccessException:
CertEnroll::CX509PrivateKey::Create: Access Denied. 0x80070005 (WIN32:
5)
em CERTENROLLLib.IX509PrivateKey.Create()
Any tips on how to run this without adm permission? Or there's any other way to create a key pair, send to CA and write the cert to a smartcard?
I'm following this code:
https://blogs.msdn.microsoft.com/alejacma/2008/09/05/how-to-create-a-certificate-request-with-certenroll-and-net-c/
public String CreateBase64KeyPair(string CN)
{
string msg = string.Empty;
try
{
CX509CertificateRequestPkcs10 objPkcs10 = (CX509CertificateRequestPkcs10)Activator.CreateInstance(Type.GetTypeFromProgID("X509Enrollment.CX509CertificateRequestPkcs10"));
IX509PrivateKey objPrivateKey = (IX509PrivateKey)Activator.CreateInstance(Type.GetTypeFromProgID("X509Enrollment.CX509PrivateKey"));
CCspInformation objCSP = (CCspInformation)Activator.CreateInstance(Type.GetTypeFromProgID("X509Enrollment.CCspInformation"));
CCspInformations objCSPs = (CCspInformations)Activator.CreateInstance(Type.GetTypeFromProgID("X509Enrollment.CCspInformations"));
CX500DistinguishedName objDN = (CX500DistinguishedName)Activator.CreateInstance(Type.GetTypeFromProgID("X509Enrollment.CX500DistinguishedName"));
CX509Enrollment objEnroll = (CX509Enrollment)Activator.CreateInstance(Type.GetTypeFromProgID("X509Enrollment.CX509Enrollment"));
CObjectIds objObjectIds = (CObjectIds)Activator.CreateInstance(Type.GetTypeFromProgID("X509Enrollment.CObjectIds"));
CObjectId objObjectId = (CObjectId)Activator.CreateInstance(Type.GetTypeFromProgID("X509Enrollment.CObjectId"));
CX509ExtensionKeyUsage objExtensionKeyUsage = (CX509ExtensionKeyUsage)Activator.CreateInstance(Type.GetTypeFromProgID("X509Enrollment.CX509ExtensionKeyUsage"));
CX509ExtensionEnhancedKeyUsage objX509ExtensionEnhancedKeyUsage = (CX509ExtensionEnhancedKeyUsage)Activator.CreateInstance(Type.GetTypeFromProgID("X509Enrollment.CX509ExtensionEnhancedKeyUsage"));
// Initialize the csp object using the desired Cryptograhic Service Provider (CSP)
objCSP.InitializeFromName(YPSIDCSP_NAME);
objCSP.GetDefaultSecurityDescriptor(true);
// Add this CSP object to the CSP collection object
objCSPs.Add(objCSP);
//Provide key container name, key length and key spec to the private key object
objPrivateKey.Length = 1024; //KEY_LEN_MY_DEFAULT
objPrivateKey.ProviderType = X509ProviderType.XCN_PROV_RSA_FULL; //XEnroll.ProviderType=1
objPrivateKey.KeySpec = X509KeySpec.XCN_AT_SIGNATURE; //XEnroll.KeySpec=AT_KEYEXCHANGE
objPrivateKey.KeyUsage = X509PrivateKeyUsageFlags.XCN_NCRYPT_ALLOW_ALL_USAGES;
objPrivateKey.ExportPolicy = X509PrivateKeyExportFlags.XCN_NCRYPT_ALLOW_EXPORT_FLAG;
objPrivateKey.MachineContext = false;
// Provide the CSP collection object (in this case containing only 1 CSP object) to the private key object
objPrivateKey.CspInformations = objCSPs;
// Create the actual key pair
objPrivateKey.Create();
// Initialize the PKCS#10 certificate request object based on the private key.
// Using the context, indicate that this is a user certificate request and don’t provide a template name
objPkcs10.InitializeFromPrivateKey(X509CertificateEnrollmentContext.ContextUser, objPrivateKey, string.Empty);
// Key Usage Extension
objExtensionKeyUsage.InitializeEncode(
CERTENROLLLib.X509KeyUsageFlags.XCN_CERT_DIGITAL_SIGNATURE_KEY_USAGE |
CERTENROLLLib.X509KeyUsageFlags.XCN_CERT_KEY_ENCIPHERMENT_KEY_USAGE |
CERTENROLLLib.X509KeyUsageFlags.XCN_CERT_DATA_ENCIPHERMENT_KEY_USAGE |
CERTENROLLLib.X509KeyUsageFlags.XCN_CERT_NON_REPUDIATION_KEY_USAGE
);
objPkcs10.X509Extensions.Add((CX509Extension)objExtensionKeyUsage);
// Enhanced Key Usage Extension
objObjectId.InitializeFromValue("1.3.6.1.5.5.7.3.2"); // OID for Client Authentication usage
objObjectIds.Add(objObjectId);
objX509ExtensionEnhancedKeyUsage.InitializeEncode(objObjectIds);
objPkcs10.X509Extensions.Add((CX509Extension)objX509ExtensionEnhancedKeyUsage);
// Encode the name in using the Distinguished Name object
objDN.Encode("CN=" + CN.Trim(), X500NameFlags.XCN_CERT_NAME_STR_NONE);
// Assing the subject name by using the Distinguished Name object initialized above
objPkcs10.Subject = objDN;
// Create enrollment request
objEnroll.InitializeFromRequest(objPkcs10);
return objEnroll.CreateRequest(EncodingType.XCN_CRYPT_STRING_BASE64);
}
catch (Exception ex)
{
return ex.ToString();
}
}
I have to add extensions to a certificate request ( CSR ) in such a way that I respect a given structure. Namely this one
On the left is the structure I must respect for the chalenge password, on the right the structure I get when I simply generate a OID object from the challenge-password OID value, then embedding all this directly into the extension list of the PKCS10 request:
CObjectId cp_oid = new CObjectId();
// OID 1.2.840.113549.1.9.7
// cp_oid.InitializeFromName(CERTENROLL_OBJECTID.XCN_OID_RSA_challengePwd);
cp_oid.InitializeFromValue("1.2.840.113549.1.9.7");
then I create a CX509Extension object add the OID to the PKCS10 request:
CX509Extension extension = new CX509Extension();
string b64__challengePassword=System.Convert.ToBase64String(System.Text.Encoding.UTF8.GetBytes(this.challengePassword));
extension.Initialize(cp_oid, EncodingType.XCN_CRYPT_STRING_BASE64_ANY, b64__challengePassword);
_certificateRequest.X509Extensions.Add(extension);
since the structure is clearly different from what I must obtain ( see the right part of the previous picture ) , I am now using a more sophisticated approach:
_certificateRequest = new CX509CertificateRequestPkcs10();
_certificateRequest.InitializeFromPrivateKey(X509CertificateEnrollmentContext.ContextUser, (CX509PrivateKey)_privateKey, null);
_certificateRequest.Subject = (CX500DistinguishedName)_subjectName;
CObjectIds cp_oids = new CObjectIds();
CObjectId cp_oid = new CObjectId();
// OID 1.2.840.113549.1.9.7
// cp_oid.InitializeFromName(CERTENROLL_OBJECTID.XCN_OID_RSA_challengePwd);
cp_oid.InitializeFromValue("1.2.840.113549.1.9.7");
CX509Extension _extension = new CX509Extension();
cp_oids.Add(cp_oid);
//now how do I add that oid list to the 1.2.840.113549.1.9.14 OID ?
//I try with CX509ExtensionEnhancedKeyUsage instead of a simple CX509Extension
//which one of all these is the correct extensions?
/*
* IX509ExtensionAlternativeNames Specifies one or more alternative name forms for the subject of a certificate.
IX509ExtensionAuthorityKeyIdentifier Represents an AuthorityKeyIdentifier extension.
IX509ExtensionBasicConstraints Specifies whether the certificate subject is a certification authority and, if so, the depth of the subordinate certification authority chain.
IX509ExtensionCertificatePolicies Represents a collection of policy information terms.
IX509ExtensionMSApplicationPolicies Represents a collection of object identifiers that indicate how a certificate can be used by an application.
IX509ExtensionEnhancedKeyUsage Represents a collection of object identifiers that identify the intended uses of the public key contained in a certificate.
IX509ExtensionKeyUsage Represents restrictions on the operations that can be performed by the public key contained in the certificate.
IX509Extensions Manages a collection of IX509Extension objects.
IX509ExtensionSmimeCapabilities Represents a collection that reports the decryption capabilities of an email recipient to an email sender.
IX509ExtensionSubjectKeyIdentifier Represents a SubjectKeyIdentifier extension used to identify a signing certificate.
IX509ExtensionTemplate Represents a CertificateTemplate extension that contains a version 2 template.
IX509ExtensionTemplateName Represents a CertificateTemplateName extension that contains a version 1 template.
*/
CX509ExtensionEnhancedKeyUsage eku = new CX509ExtensionEnhancedKeyUsage();
eku.InitializeEncode(cp_oids);
eku.Critical = false;
CX509AttributeExtensions InitExt = new CX509AttributeExtensions();
// Add the extension objects into an IX509Extensions collection.
CX509Extensions ext1 = new CX509Extensions();
ext1.Add((CX509Extension)eku);
// Use the IX509Extensions collection//to initialize an IX509AttributeExtensions object.
CX509AttributeExtensions ext1att = new CX509AttributeExtensions();
ext1att.InitializeEncode(ext1);
//Add the IX509AttributeExtensions object to an IX509Attributes collection.
CX509Attributes att1 = new CX509Attributes();
att1.Add((CX509Attribute)ext1att);
//Use the IX509Attributes collection to initialize an ICryptAttribute object.
CCryptAttribute crypt1 = new CCryptAttribute();
crypt1.InitializeFromValues(att1);
//Initialize a CMC or PKCS #10 request object and retrieve the ICryptAttributes collection.
//Add the ICryptAttribute object to the ICryptAttributes collection for the request.
_certificateRequest.CryptAttributes.Add(crypt1);
//Console.WriteLine("-- encode");
this.status2 = this.status2 + "-- encode <BR>";
try
{
_certificateRequest.Encode();
}
catch (Exception ex)
{
Console.WriteLine(ex.ToString());
}
string rawData = _certificateRequest.get_RawData();
Console.WriteLine("data=" + rawData);
However I get the puzzling error "The file exists. (Exception from HRESULT: 0x80070050)" at the end of the process when encoding the request , I tried with different smartcards ad the key containers are OK, not full.
Is my approach toward adding this challenge-password extension correct and how can I interpret this error?
The answer to the error you are getting "The file exists. (Exception from HRESULT: 0x80070050)" is because trying to set a key on a template that already has a key. just comment this:
CX509ExtensionEnhancedKeyUsage eku = new CX509ExtensionEnhancedKeyUsage();
eku.InitializeEncode(cp_oids);
eku.Critical = false;
CX509AttributeExtensions InitExt = new CX509AttributeExtensions();
// Add the extension objects into an IX509Extensions collection.
CX509Extensions ext1= new CX509Extensions();
ext1.Add((CX509Extension)eku);
and it should work.
search for this in the in the article Working with Active Directory Certificate Service via C# for:
Seems that we finished, but if we just execute it will throw an
exception to us, said that the file exists when adding some
extensions.
it explains everything.
from the article:
The exception message could be a little bit confusing. In fact this is
because we defined something which had been defined in the certificate
template. If we dig into the source code we can see that the exception
occurred when we added the key usage extension.
And if we get back to the CA server and open the template we are
using, we can find that the key usage had been defined in the
template. This means in the code, or in the certificate request we
should not specify it again.
Hence we need to comment the code for adding the key usage, also we
need to comment the enhanced key usage part since it had been defined
in the template, too. Because we let the request supply the subject
name so here we can still specify the subject information in the
request. The method for generating request message would be like this.
Below is code to include Challenge Password into PKCS10 generated by CertEnroll:
private static byte[] getDerBytes(int tag, byte[] data)
{
if (data.Length > byte.MaxValue)
{
throw new NotSupportedException("Support for integers greater than 255 not yet implemented.");
}
var header = new byte[] { (byte)tag, (byte)data.Length };
return header.Concat(data).ToArray();
}
and
public static byte[] EncodePrintableString(string data)
{
var dataBytes = Encoding.ASCII.GetBytes(data);
return getDerBytes(0x13, dataBytes);
}
and finnally:
CObjectId cp_oid = new CObjectId();
cp_oid.InitializeFromName(CERTENROLL_OBJECTID.XCN_OID_RSA_challengePwd);
byte[] b64__challengePassword = EncodePrintableString("password");
ICryptAttribute ChallengeAttributes = new CCryptAttribute();
ChallengeAttributes.InitializeFromObjectId(cp_oid);
CX509Attribute ChallengeAttribute = new CX509Attribute();
ChallengeAttribute.Initialize(cp_oid, EncodingType.XCN_CRYPT_STRING_BASE64_ANY,
Convert.ToBase64String(b64__challengePassword));
ChallengeAttributes.Values.Add(ChallengeAttribute);
objPkcs10.CryptAttributes.Add((CCryptAttribute)ChallengeAttributes);
I have a bunch of root and intermediate certificates given as byte arrays, and I also have end user certificate. I want to build a certificate chain for given end user certificate. In .NET framework I can do it like this:
using System.Security.Cryptography.X509Certificates;
static IEnumerable<X509ChainElement>
BuildCertificateChain(byte[] primaryCertificate, IEnumerable<byte[]> additionalCertificates)
{
X509Chain chain = new X509Chain();
foreach (var cert in additionalCertificates.Select(x => new X509Certificate2(x)))
{
chain.ChainPolicy.ExtraStore.Add(cert);
}
// You can alter how the chain is built/validated.
chain.ChainPolicy.RevocationMode = X509RevocationMode.NoCheck;
chain.ChainPolicy.VerificationFlags = X509VerificationFlags.IgnoreWrongUsage;
// Do the preliminary validation.
var primaryCert = new X509Certificate2(primaryCertificate);
if (!chain.Build(primaryCert))
throw new Exception("Unable to build certificate chain");
return chain.ChainElements.Cast<X509ChainElement>();
}
How to do it in BouncyCastle? I tried with code below but I get PkixCertPathBuilderException: No certificate found matching targetContraints:
using Org.BouncyCastle;
using Org.BouncyCastle.Pkix;
using Org.BouncyCastle.Utilities.Collections;
using Org.BouncyCastle.X509;
using Org.BouncyCastle.X509.Store;
static IEnumerable<X509Certificate> BuildCertificateChainBC(byte[] primary, IEnumerable<byte[]> additional)
{
X509CertificateParser parser = new X509CertificateParser();
PkixCertPathBuilder builder = new PkixCertPathBuilder();
// Separate root from itermediate
List<X509Certificate> intermediateCerts = new List<X509Certificate>();
HashSet rootCerts = new HashSet();
foreach (byte[] cert in additional)
{
X509Certificate x509Cert = parser.ReadCertificate(cert);
// Separate root and subordinate certificates
if (x509Cert.IssuerDN.Equivalent(x509Cert.SubjectDN))
rootCerts.Add(new TrustAnchor(x509Cert, null));
else
intermediateCerts.Add(x509Cert);
}
// Create chain for this certificate
X509CertStoreSelector holder = new X509CertStoreSelector();
holder.Certificate = parser.ReadCertificate(primary);
// WITHOUT THIS LINE BUILDER CANNOT BEGIN BUILDING THE CHAIN
intermediateCerts.Add(holder.Certificate);
PkixBuilderParameters builderParams = new PkixBuilderParameters(rootCerts, holder);
builderParams.IsRevocationEnabled = false;
X509CollectionStoreParameters intermediateStoreParameters =
new X509CollectionStoreParameters(intermediateCerts);
builderParams.AddStore(X509StoreFactory.Create(
"Certificate/Collection", intermediateStoreParameters));
PkixCertPathBuilderResult result = builder.Build(builderParams);
return result.CertPath.Certificates.Cast<X509Certificate>();
}
Edit: I added the line that fixed my problem. It's commented with all caps. Case closed.
I've done this in Java a number of times. Given that the API seems to be a straight port of the Java one I'll take a stab.
I'm pretty sure when you add the store to the builder, that collection is expected to contain all certs in the chain to be built, not just intermediate ones. So rootCerts and primary should be added.
If that doesn't solve the problem on its own I would try also specifying the desired cert a different way. You can do one of two things:
Implement your own Selector that always only matches your desired cert (primary in the example).
Instead of setting holder.Certificate, set one or more criteria on holder. For instance, setSubject, setSubjectPublicKey, setIssuer.
Those are the two most common problems I had with PkixCertPathBuilder.
The code below does not answer your question (it's a pure Java solution). I only just realized now after typing out everything that it doesn't answer your question! I forgot BouncyCastle has a C# version! Oops.
It still might help you roll your own chain builder. You probably don't need any libraries or frameworks.
Good luck!
http://juliusdavies.ca/commons-ssl/src/java/org/apache/commons/ssl/X509CertificateChainBuilder.java
/**
* #param startingPoint the X509Certificate for which we want to find
* ancestors
*
* #param certificates A pool of certificates in which we expect to find
* the startingPoint's ancestors.
*
* #return Array of X509Certificates, starting with the "startingPoint" and
* ending with highest level ancestor we could find in the supplied
* collection.
*/
public static X509Certificate[] buildPath(
X509Certificate startingPoint, Collection certificates
) throws NoSuchAlgorithmException, InvalidKeyException,
NoSuchProviderException, CertificateException {
LinkedList path = new LinkedList();
path.add(startingPoint);
boolean nodeAdded = true;
// Keep looping until an iteration happens where we don't add any nodes
// to our path.
while (nodeAdded) {
// We'll start out by assuming nothing gets added. If something
// gets added, then nodeAdded will be changed to "true".
nodeAdded = false;
X509Certificate top = (X509Certificate) path.getLast();
if (isSelfSigned(top)) {
// We're self-signed, so we're done!
break;
}
// Not self-signed. Let's see if we're signed by anyone in the
// collection.
Iterator it = certificates.iterator();
while (it.hasNext()) {
X509Certificate x509 = (X509Certificate) it.next();
if (verify(top, x509.getPublicKey())) {
// We're signed by this guy! Add him to the chain we're
// building up.
path.add(x509);
nodeAdded = true;
it.remove(); // Not interested in this guy anymore!
break;
}
// Not signed by this guy, let's try the next guy.
}
}
X509Certificate[] results = new X509Certificate[path.size()];
path.toArray(results);
return results;
}
Requires these two additional methods:
isSelfSigned():
public static boolean isSelfSigned(X509Certificate cert)
throws CertificateException, InvalidKeyException,
NoSuchAlgorithmException, NoSuchProviderException {
return verify(cert, cert.getPublicKey());
}
And verify():
public static boolean verify(X509Certificate cert, PublicKey key)
throws CertificateException, InvalidKeyException,
NoSuchAlgorithmException, NoSuchProviderException {
String sigAlg = cert.getSigAlgName();
String keyAlg = key.getAlgorithm();
sigAlg = sigAlg != null ? sigAlg.trim().toUpperCase() : "";
keyAlg = keyAlg != null ? keyAlg.trim().toUpperCase() : "";
if (keyAlg.length() >= 2 && sigAlg.endsWith(keyAlg)) {
try {
cert.verify(key);
return true;
} catch (SignatureException se) {
return false;
}
} else {
return false;
}
}