I have a rather simple LDAP client that works ok when connecting to the 389 port (LDAP) but fails with a "LDAP server unavailable" when I try to connect to the 636 port (LDAPS).
namespace MyNS
{
class ProgramLdap
{
private static LdapConnection CreateConnection(String baseDn, string usuario, string password)
{
LdapConnection ldapConnection = new LdapConnection(
new LdapDirectoryIdentifier("myserver.example", 636, true, false));
ldapConnection.SessionOptions.SecureSocketLayer = true;
ldapConnection.SessionOptions.ProtocolVersion = 3;
// [CODE MODIFICATION HERE]
ldapConnection.Credential = new NetworkCredential(usuario, password);
ldapConnection.AuthType = AuthType.Basic;
ldapConnection.Timeout = new TimeSpan(1, 0, 0);
return ldapConnection;
}
static void Main(string[] args)
{
LdapConnection ldapConnection = CreateConnection("", "myLdapUser", "noneOfYourBusiness");
SearchRequest searchRequest = new SearchRequest(
"ou=usuarios,dc=Dexter,dc=local",
String.Format("(&(objectclass=person)(cn={0}))", user),
SearchScope.Subtree,
new string[0]);
SearchResponse searchResponse =
(SearchResponse) ldapConnection.SendRequest(searchRequest);
System.Diagnostics.Debug.WriteLine("Resultados " + searchResponse.Entries.Count);
}
}
}
If I add the following at [CODE MODIFICATION HERE] to accept all server certificates, it works:
ldapConnection.SessionOptions.VerifyServerCertificate =
new VerifyServerCertificateCallback((conn, certificate) => true);
The certificate is signed by a self-signed CA, I have added the CA public certificate to the Local Computer list of "Trusted Root Certification Authorities"1.
If I check the server certificate with openSSL using that CA's certificate, it validates it. Also, I have tried LdapAdmin and when the CA is in that list, no warning is shown when connecting to the LDAP server.
If I use the VerifyServerCertificateCallback to print the contents of the certificate:
ldapConnection.SessionOptions.VerifyServerCertificate =
new VerifyServerCertificateCallback(
(conn, certificate) => {
X509Certificate2 certificate2 = new X509Certificate2(certificate);
bool verify = certificate2.Verify();
System.Diagnostics.Debug.WriteLine(
String.Format(
"certificate2.Verify {0}; Name {1}; NameOID {2}; FriendlyName {3}; Thumbprint {4}; Algorithm FriendlyName {5}",
verify,
certificate2.SubjectName.Name,
certificate2.SubjectName.Oid,
certificate2.FriendlyName,
certificate2.Thumbprint,
certificate2.SignatureAlgorithm.FriendlyName));
foreach (X509Extension extension in certificate2.Extensions)
{
System.Diagnostics.Debug.WriteLine(extension.ToString() + " " + extension.Oid.FriendlyName + " " + Encoding.UTF8.GetString(extension.RawData));
}
return verify;
});
it shows me the thumbprint of the server certificate but yet verify fails.
What can I be? It seems that I am missing something very basic, but I cannot understand what.
UPDATE:
I checked #FrankNielsen's suggestion and I added this code in the VerifyServerCertificateCallback:
(conn, certificate) => {
X509Chain ch = new X509Chain();
ch.ChainPolicy.RevocationMode = X509RevocationMode.NoCheck;
ch.Build(new X509Certificate2(certificate));
System.Diagnostics.Debug.WriteLine("Chain Information");
System.Diagnostics.Debug.WriteLine(String.Format("Chain revocation flag: {0}", ch.ChainPolicy.RevocationFlag));
System.Diagnostics.Debug.WriteLine(String.Format("Chain revocation mode: {0}", ch.ChainPolicy.RevocationMode));
System.Diagnostics.Debug.WriteLine(String.Format("Chain verification flag: {0}", ch.ChainPolicy.VerificationFlags));
System.Diagnostics.Debug.WriteLine(String.Format("Chain verification time: {0}", ch.ChainPolicy.VerificationTime));
System.Diagnostics.Debug.WriteLine(String.Format("Chain status length: {0}", ch.ChainStatus.Length));
System.Diagnostics.Debug.WriteLine(String.Format("Chain application policy count: {0}", ch.ChainPolicy.ApplicationPolicy.Count));
System.Diagnostics.Debug.WriteLine(String.Format("Chain certificate policy count: {0} {1}", ch.ChainPolicy.CertificatePolicy.Count, Environment.NewLine));
System.Diagnostics.Debug.WriteLine("Chain Element Information");
System.Diagnostics.Debug.WriteLine(String.Format("Number of chain elements: {0}", ch.ChainElements.Count));
System.Diagnostics.Debug.WriteLine(String.Format("Chain elements synchronized? {0} {1}", ch.ChainElements.IsSynchronized, Environment.NewLine));
foreach (X509ChainElement element in ch.ChainElements)
{
System.Diagnostics.Debug.WriteLine(String.Format("Element issuer name: {0}", element.Certificate.Issuer));
System.Diagnostics.Debug.WriteLine(String.Format("Element certificate valid from: {0}", element.Certificate.NotBefore));
System.Diagnostics.Debug.WriteLine(String.Format("Element certificate valid until: {0}", element.Certificate.NotAfter));
System.Diagnostics.Debug.WriteLine(String.Format("Element certificate is valid: {0}", element.Certificate.Verify()));
System.Diagnostics.Debug.WriteLine(String.Format("Element error status length: {0}", element.ChainElementStatus.Length));
System.Diagnostics.Debug.WriteLine(String.Format("Element information: {0}", element.Information));
System.Diagnostics.Debug.WriteLine(String.Format("Thumbprint: {0}", element.Certificate.Thumbprint));
System.Diagnostics.Debug.WriteLine(String.Format("Number of element extensions: {0}{1}", element.Certificate.Extensions.Count, Environment.NewLine));
if (ch.ChainStatus.Length > 1)
{
for (int index = 0; index < element.ChainElementStatus.Length; index++)
{
System.Diagnostics.Debug.WriteLine(element.ChainElementStatus[index].Status);
System.Diagnostics.Debug.WriteLine(element.ChainElementStatus[index].StatusInformation);
}
}
}
return true;
});
And it returns:
Chain Information
Chain revocation flag: ExcludeRoot
Chain revocation mode: NoCheck
Chain verification flag: NoFlag
Chain verification time: 07/10/2019 15:53:00
Chain status length: 0
Chain application policy count: 0
Chain certificate policy count: 0
Chain Element Information
Number of chain elements: 2
Chain elements synchronized? False
Element issuer name: CN=dexter-SCPDPRDEXTER01V-CA, DC=dexter, DC=local
Element certificate valid from: 02/09/2019 12:24:22
Element certificate valid until: 01/09/2020 12:24:22
Element certificate is valid: False
Element error status length: 0
Element information:
Thumbprint: 63DCF4EFE0C96EF021BCC9CE662E2627A3CDF399
Number of element extensions: 9
Element issuer name: CN=dexter-SCPDPRDEXTER01V-CA, DC=dexter, DC=local
Element certificate valid from: 11/06/2019 7:39:01
Element certificate valid until: 11/06/2069 7:49:01
Element certificate is valid: True
Element error status length: 0
Element information:
Thumbprint: 7BD9C718E336A50FA006CAEF539895C7E3EA5DA0
Number of element extensions: 4
The certificates match what would be expected (the CA is retrieved), the CA return true to Verify() but the server certificate returns false to Verify().
1And for good measure, I also did try adding it to "Intermediate Certification Authorities" to no avail.
Finally the way to properly debug it was inspecting the elements of ChainStatus, as stated in X509Certificate2.Verify() returns false always
That way I did found that my program could not connect to the Certificate Revocation List URL.
The solution (other than opening access to the URL) is to validate the certificate in the callback using the X509Chain class and setting it not to check CRLs:
ldapConnection.SessionOptions.VerifyServerCertificate =
new VerifyServerCertificateCallback(
(conn, certificate) =>
{
X509Chain x509Chain = new X509Chain();
x509Chain.ChainPolicy.RevocationMode = X509RevocationMode.NoCheck;
X509Certificate2 cert2 = new X509Certificate2(certificate);
bool buildResult = x509Chain.Build(cert2);
if (!buildResult)
{
foreach (X509ChainStatus chainStatus in x509Chain.ChainStatus)
{
System.Diagnostics.Debug.WriteLine(
String.Format(
"Chain Status {0} : {1}", chainStatus.Status, chainStatus.StatusInformation));
}
}
return buildResult;
});
Please complete this stemps on each Domain Controller to enabel SSL on LDAP.
Go to each Domain Controller, open MMC
Click File, Add Snap-In
Select Certificate
Select Service Account and click Next
Select Local Computer and click Next
Select Active Directory Services and click Finish to complete the dialog
Click on Ok to add the Snap-In to the MMC
In the Certificate Snap-In now add your Certificate to NTDS/Personal and if needed add the CA-Certificate into the NTDS/Trusted Root Certificate Authorities
I'm from german system so I'm sorry if the technical Names mismatches with yours.
Source: https://www.active-directory-faq.de/2012/08/ldap-over-ssl-oder-sercure-ldap-ldaps-mit-server-2008r2/
Related
I'm currently trying to connect to a LDAPS Server using the following VB.NET Code, which should set the right parameters and use the function seen below to verify the certificate when the Bind function is called.
The value of LdapHost is the IP, 10.100.11.10, and the value for LdapPort is 7636.
connection = new LdapConnection(new LdapDirectoryIdentifier(LdapHost, LdapPort));
connection.AuthType = 2; // Negotiate
connection.SessionOptions.SecureSocketLayer = true;
connection.SessionOptions.VerifyServerCertificate = new VerifyServerCertificateCallback(VerifyServerCertificate);
//Both username and password are correct
connection.Credential = new System.Net.NetworkCredential(strUsername, strPassword);
connection.Bind();
This,
But upon trying to verify the Server Certificate, using the following code:
private bool VerifyServerCertificate(LdapConnection ldapConnection, X509Certificate certificate)
{
try
{
X509Certificate2 certificate2 = new X509Certificate2(certificate);
return certificate2.Verify();
}
catch (Exception ex)
{
throw new LdapException(9999, "Invalid certificate or path.");
}
}
It Errors out at the Bind function saying that it cannot connect to the LDAP Server at all with the message "The LDAP Server cannot be reached"
Although upon testing the connection via PowerShell, the Server is available just fine.
Is there something wrong with my verification method? Should I try a different approach entirely?
I have found the reason why the verification did not work.
Using
X509Chain chain = new X509Chain();
X509Certificate2 certificate2 = new X509Certificate2(certificate);
var chainBuilt = chain.Build(certificate2);
LogEvent("Val", 0, "Chain building status: " + chainBuilt);
if (chainBuilt == false) {
foreach (X509ChainStatus chainStatus in chain.ChainStatus)
LogEvent("Val", 0, "Chain error: " + chainStatus.Status + " " + chainStatus.StatusInformation);
chain.Reset();
return false;
} else {
chain.Reset();
return true;
}
if the verification fails helped me understand that the Root Certificate was not trusted on that specific server.
Furthermore, it told me that it could not reach the Revokation Server to check if the Certificate is still valid.
This couldn't be checked though, since the configuration uses a StartTLS certificate, which does not have a Revokation Server.
Therefore, I added
chain.ChainPolicy.VerificationFlags = X509VerificationFlags.IgnoreRootRevocationUnknown | X509VerificationFlags.IgnoreEndRevocationUnknown | X509VerificationFlags.IgnoreCtlSignerRevocationUnknown;
to ignore every property regarding the Revokation Server. It can now connect as intended.
I have managed to figure out if a cert in a x509Certificate2Collection is a certificate authority cert but how can I safely determine if it's a Root cert or an Intermediate cert please? Is the following safe enough?
var collection = new X509Certificate2Collection();
collection.Import("test.pfx", "password", X509KeyStorageFlags.PersistKeySet);
foreach (X509Certificate2 cert in collection)
{
var basicConstraintExt = cert.Extensions["2.5.29.19"] as X509BasicConstraintsExtension;
if (basicConstraintExt != null)
{
Log.Debug($" Subject is: '{cert.Subject}'");
Log.Debug($" Issuer is: '{cert.Issuer}'");
if (basicConstraintExt.CertificateAuthority)
{
Log.Debug("I am a CA Cert.");
if (cert.Subject == cert.Issuer)
{
Log.Debug("My Subject matches Issuer.");
}
else
{
Log.Debug("My Subject does not match Issuer.");
}
Log.Debug(cert.Verify() ? "I verify" : "I do not verify");
}
else
{
Log.Debug("I am not a CA Cert.");
}
}
}
Results:
Displaying Cert #1 in collection
********************************
Subject is: 'CN=Intermediate-CA, DC=test, DC=lan'
Issuer is: 'CN=Root-CA, DC=test, DC=lan'
- I am a CA Cert.
- My Subject does not match Issuer.
- I do not verify
Displaying Cert #2 in collection
********************************
Subject is: 'CN=Root-CA, DC=test, DC=lan'
Issuer is: 'CN=Root-CA, DC=test, DC=lan'
- I am a CA Cert.
- My Subject matches Issuer.
- I do not verify
Not sure if this helps with Kestrel, but I would try the code below.
We will use X509Chain class to construct and validate the chain.
var collection = new X509Certificate2Collection();
collection.Import("test.pfx", "password");
var chain = new X509Chain();
chain.ChainPolicy.ExtraStore.AddRange(collection);
// untrusted root error raise false-positive errors, for example RevocationOffline
// so skip possible untrusted root chain error.
chain.VerificationFlags |= X509VerificationFlags.AllowUnknownCertificateAuthority;
// revocation checking is client's responsibility. Skip it.
chain.RevocationMode = X509VerificationFlags.NoCheck;
// build the chain.
Boolean isValid = chain.Build(collection[0]);
// explore chain.ChainElements collection. First item should be your leaf
// certificate and last item should be root certificate
All this stuff is located in System.Security.Cryptography.X509Certificates namespace. In this code piece, I'm assuming that first certificate in PFX is leaf certificate (it is in 99% cases unless someone tries to ignore standards). By exploring chain.ChainElements collection, you can find issues with every certificate in the chain.
To authenticate an application internally with client certification I have created a Root Certificate and the client certificate using the makecert application.
Everything works well but when I use the X509Certificate2 Verify method I get the following error:
The revocation function was unable to check revocation for the
certificate
X509Certificate2 cert = actionContext.Request.GetClientCertificate();
cert.Verify();
I can get around this by creating a X509Chain and then set X509ChainPolicy to RevocationMode = X509RevocationMode.NoCheck.
X509Certificate2 cert = actionContext.Request.GetClientCertificate();
if (cert == null)
{
actionContext.Response = new HttpResponseMessage(System.Net.HttpStatusCode.Unauthorized)
{
ReasonPhrase = "Client Certificate Required"
};
}
else
{
X509Chain chain = new X509Chain();
//Needed because the error "The revocation function was unable to check revocation for the certificate" will happen otherwise
chain.ChainPolicy = new X509ChainPolicy()
{
RevocationMode = X509RevocationMode.NoCheck,
};
try
{
var chainBuilt = chain.Build(cert);
Debug.WriteLine(string.Format("Chain building status: {0}", chainBuilt));
if (chainBuilt == false)
{
actionContext.Response = new HttpResponseMessage(System.Net.HttpStatusCode.Unauthorized)
{
ReasonPhrase = "Client Certificate not valid"
};
foreach (X509ChainStatus chainStatus in chain.ChainStatus)
{
Debug.WriteLine(string.Format("Chain error: {0} {1}", chainStatus.Status, chainStatus.StatusInformation));
}
}
}
catch (Exception e)
{
Debug.WriteLine(e.ToString());
}
}
However this got me interested. Is there someway that I can create a Certificate Revocation List (CRL) with makecert and then bundle it to a .pfx with pvk2pfx that will be accepted by X509Certificate2 Verify?
Found a solution on msdn.
https://msdn.microsoft.com/en-us/library/ff648732.aspx
After you have created your Root Certificate run the following command:
makecert -crl -n "CN=RootCATest" -r -sv RootCATest.pvk RootCATest.crl
Install the CRL file on both the server and client machines. Use MMC to install RootCATes.crl on the client and server machines in the Trusted Root Certification Authorities store.
MMC -> File -> Add or Remove Snap-ins -> Certificates -> My user account
Trusted Root Certification Authorities -> Certificates -> Right click -> All tasks -> Import -> RootCATest.crl
I did not manage to bundle it with pvk2pfx but after doing this I could run Verify.
I have ASP.NET MVC web site which I configured to authenticate through Active Directory Federation Service. Everything worked fine until I tried to enable token encryption. As usual, I created one more self-signed certificate on IIS, added it to Trusted Root authorities on my web server and ADFS server and run application to veryfy how it works.
My application correctly redirected me to ADFS service page to enter credentials. But when I submit my login and password, I immediately get "An error occured" message on the same login page with not very useful details section:
Activity ID: 00000000-0000-0000-b039-0080010000e4
Relying party: [My relying party name]
Error time: Fri, 21 Oct 2016 18:48:24 GMT
Cookie: enabled
User agent string: Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/54.0.2840.59 Safari/537.36
I don't get redirected to my web site after that and Network panel doesn't contain any requests.
But I discovered, that if I add the following setting into my web site's web.config, it starts working again:
<certificateValidation certificateValidationMode="None" />
So the error must be related to the fact that my certificate is self-signed. But I have added it to trusted root authorities both on web server and ADFS server (as well as few other "suspicious" certificates).
Does anybody have an idea what could be missing and what can I do to make my test environment work with self-signed certificates, while validating certificate chain?
It appeared that to resolve an error it was enough to add ADFS Token Signing certificate as Trusted Root Certification Authority on my web server.
PS: I'm not sure why token signing certificate chain validation didn't raise errors when encryption was disabled and what relation does it have to encryption at all, but the fact is that it helped for both environments we've used for testing.
I do something similar with an api handler that acts as a pass through and must interrogate certs.
Something that may help your troubleshooting.
Set the cert validation callback to something like:
// validate server cert
ServicePointManager.ServerCertificateValidationCallback += ValidateServerCertificate;
Then in the validation method you can interrogate the chain:
private static bool ValidateServerCertificate(object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors)
{
// default validation bool to false
var isValid = false;
// If the certificate is a valid, signed certificate, return true to short circuit any add'l processing.
if (sslPolicyErrors == SslPolicyErrors.None)
{
return true;
}
else
{
// cast cert as v2 in order to expose thumbprint prop
var requestCertificate = (X509Certificate2)certificate;
// init string builder for creating a long log entry
var logEntry = new StringBuilder();
// capture initial info for the log entry
logEntry.AppendFormat("Certificate Validation Error - SSL Policy Error: {0} - Cert Issuer: {1} - SubjectName: {2}",
sslPolicyErrors.ToString(),
requestCertificate.Issuer,
requestCertificate.SubjectName.Name);
//init special builder for thumprint mismatches
var thumbprintMismatches = new StringBuilder();
// load valid certificate thumbs for comparison later
var validThumbprints = new string[] { "thumbprint A", "thumbprint N" };
// else if a cert name mismatch then assume api pass thru issue and verify thumb print
if (sslPolicyErrors == SslPolicyErrors.RemoteCertificateNameMismatch)
{
// compare thumbprints
var hasMatch = validThumbprints.Contains(requestCertificate.Thumbprint, StringComparer.OrdinalIgnoreCase);
// if match found then we're valid so clear builder and set global valid bool to true
if (hasMatch)
{
thumbprintMismatches.Clear();
isValid = true;
}
// else thumbprint did not match so append to the builder
else
{
thumbprintMismatches.AppendFormat("|Thumbprint mismatch - Issuer: {0} - SubjectName: {1} - Thumbprint: {2}",
requestCertificate.Issuer,
requestCertificate.SubjectName.Name,
requestCertificate.Thumbprint);
}
}
// else if chain issue, then iterate over the chain and attempt find a matching thumbprint
else if (sslPolicyErrors == SslPolicyErrors.RemoteCertificateChainErrors) //Root CA problem
{
// check chain status and log
if (chain != null && chain.ChainStatus != null)
{
// check errors in chain and add to log entry
foreach (var chainStatus in chain.ChainStatus)
{
logEntry.AppendFormat("|Chain Status: {0} - {1}", chainStatus.Status.ToString(), chainStatus.StatusInformation.Trim());
}
// check for thumbprint mismatches
foreach (var chainElement in chain.ChainElements)
{
// compare thumbprints
var hasMatch = validThumbprints.Contains(chainElement.Certificate.Thumbprint, StringComparer.OrdinalIgnoreCase);
// if match found then we're valid so break, clear builder and set global valid bool to true
if (hasMatch)
{
thumbprintMismatches.Clear();
isValid = true;
break;
}
// else thumbprint did not match so append to the builder
else
{
thumbprintMismatches.AppendFormat("|Thumbprint mismatch - Issuer: {0} - SubjectName: {1} - Thumbprint: {2}",
chainElement.Certificate.Issuer,
chainElement.Certificate.SubjectName.Name,
chainElement.Certificate.Thumbprint);
}
}
}
}
// if still invalid and thumbprint builder has items, then continue
if (!isValid && thumbprintMismatches != null && thumbprintMismatches.Length > 0)
{
// append thumbprint entries to the logentry as well
logEntry.Append(thumbprintMismatches.ToString());
}
// log as WARN here and not ERROR - if method ends up returning false then it will bubble up and get logged as an ERROR
LogHelper.Instance.Warning((int)ErrorCode.CertificateValidation, logEntry.ToString().Trim());
}
// determine env
var isDev = EnvironmentHelper.IsDevelopment();
var isTest = EnvironmentHelper.IsTest();
// if env is dev or test then ignore cert errors and return true (reference any log entries created from logic above for troubleshooting)
if (isDev || isTest)
isValid = true;
return isValid;
}
NOTE: you'll need to disable/change some of the custom code - thumbprint stuff, logging etc.
Adding certificates to your CA trusted store only mean you trust the issuer of the certificate, which is the certificate itself in this case because it is a self-signed certificate. What is missing is that certificate validation performs chain-check and revocation check and either one of the two check failed for you. Please note that even if you trust a certificate, it still could have been revoked recently and thus shouldn't no longer be trusted. Therefore, revocation check is always necessary. For testing, disabling revocation check is one way. One ADFS side, you can disable revocation check per relying party. If the check happens on your own code, you can either disable check totally or use Stinky Towel's code to selectively allow some certificates only.
I have a Windows certification authority that I am using to issue client authentication certificates via .net / c#. I have been able to successfully get it to issue certificates programmatically by calling the certification authority's API through COM. I issue a new certificate when I set up a client.
At runtime, these clients attach the certificates to requests to my server. How can I verify programmatically that an X509Certificate2 was signed by the root certificate of my certificate authority (and reject certificates signed by any other source)?
I've done this a lot. Here's some easy code you can use.
The part in the if (!isChainValid) block is to make a pretty error message. You don't have to use that if you don't want, but you should throw an error if the chain cannot be built. The chain elements are necessary to check for your root.
X509Certificate2 authority = GetAuthorityCertificate();
X509Certificate2 certificateToValidate = GetCertificateToValidate();
X509Chain chain = new X509Chain();
chain.ChainPolicy.RevocationMode = X509RevocationMode.NoCheck;
chain.ChainPolicy.RevocationFlag = X509RevocationFlag.ExcludeRoot;
chain.ChainPolicy.VerificationFlags = X509VerificationFlags.AllowUnknownCertificateAuthority;
chain.ChainPolicy.VerificationTime = DateTime.Now;
chain.ChainPolicy.UrlRetrievalTimeout = new TimeSpan(0, 0, 0);
// This part is very important. You're adding your known root here.
// It doesn't have to be in the computer store at all. Neither certificates do.
chain.ChainPolicy.ExtraStore.Add(authority);
bool isChainValid = chain.Build(certificateToValidate);
if (!isChainValid)
{
string[] errors = chain.ChainStatus
.Select(x => String.Format("{0} ({1})", x.StatusInformation.Trim(), x.Status))
.ToArray();
string certificateErrorsString = "Unknown errors.";
if (errors != null && errors.Length > 0)
{
certificateErrorsString = String.Join(", ", errors);
}
throw new Exception("Trust chain did not complete to the known authority anchor. Errors: " + certificateErrorsString);
}
// This piece makes sure it actually matches your known root
var valid = chain.ChainElements
.Cast<X509ChainElement>()
.Any(x => x.Certificate.Thumbprint == authority.Thumbprint);
if (!valid)
{
throw new Exception("Trust chain did not complete to the known authority anchor. Thumbprints did not match.");
}
You can also use the built in method Verify() for X509Certificate2.
X509Certificate2 certificateToValidate = GetCertificateToValidate();
bool valid = certificateToValidate.Verify()
https://msdn.microsoft.com/en-us/library/system.security.cryptography.x509certificates.x509certificate2.verify.aspx
If you say you have a root (which is self-signed) certificate, then your only option is to keep this root certificate available on your server (without the private key of course) and perform certificate validation procedure against your root certificate. This is a mirrored situation to the web client validating server certificate chain.