The LDAP Server is Unavailable using PrincipalContext and ADLDS - c#

We are making use of ADLDS for our user management and authentication. We can successfully query the instance without problems. However, trying to perform an operation such as SetPassword will fail or even trying to create a new user if a password is not set, it fails. I can successfully update a user as long as its not password I'm trying to update. I've been reading a lot of different articles relating to this but not finding a resolution. Posting to see if I can get some fresh perspective on this issue, thanks for any input.
EXAMPLE
ContextType ctxType = ContextType.ApplicationDirectory;
string server = "myadldsserver.com";
string usersCN = "CN=Users,..."; // container where users reside
ContextOptions ctxOpts = ContextOptions.SimpleBind;
string uname = "myuser";
string pswrd = "mypass";
using(var ctx = new PrincipalContext(ctxType, server, usersCN, ctxOpts, uname, pswrd)
using(var newUser = new UserPrincipal(ctx)) {
newUser.Name = "newusername";
newUser.Enabled = true;
newUser.UserPrincipalName = "newusername";
newUser.Save();
newUser.SetPassword("newuserpassword");
}
ERROR 1
The first problem I encounter if I try to create a new UserPrincipal and call Save without having set the password like in Example above I get the exception A constraint violation occurred. with an InnerException extend message of 0000052D: AtrErr: DSID-033807D7, #1:0: 0000052D: DSID-033807D7, problem 1005 (CONSTRAINT_ATT_TYPE), data 2246, Att 9005a (unicodePwd)
Because of this error I tried moving the SetPassword before calling Save along with other approaches I found online such as getting the DirectoryEntry from the UserPrincipal and trying to call SetPassword but got a different error.
ERROR 2
Calling SetPassword before calling UserPrincipal.Save, when save is called, results in the error The directory property cannot be found in the cache.
Note that the same error will occur if I trying calling ResetPassword or getting a DirectoryEntry and calling Invoke("SetPassword"... as well
ERROR 3
From my research most seem to indicate this could have to do with needing to access AD LDS using a Secure connection. So, I changed my server to include the port of 636 string server = "myadldsserver.com:636" and I changed the ContextOptions to be ContextOptions.SimpleBind | ContextOptions.SecureSocketLayer.
Making these changes when the PrincipalContext is being constructed I get the following exception The server could not be contacted. with an inner exception of The LDAP server is unavailable., HResult is -2146233087
JAVA and LDP
To add some background to this, we do have similar code written in an older Java application. We are trying to port some of this logic over to .NET side in C#. The code in Java makes use of a Java keystore that contains the certificate that was generated on the AD LDS server. The Java application of course has no issues using the SSL port. We know the server seems to be configured correctly, it's just an issue of how to access it from .NET side.
Is there an equivalent on the .NET side such as the keystore in Java? We know that an SSL connection can be made to server. We have verified this using LDP as well.
GOALS
Be able to create a new user and set their password during creation
Be able to ResetPassword or ChangePassword for a user
Connect to our AD LDS instance from .NET securely

Have you tried using Microsoft Management Console to import the certificate?
Two ways to install the certificate
Either
Open a cmd.exe console and type "MMC"
File > Add/Remove Snap-In...
Select Certificates, click Add
Choose Computer Account and Local Computer when prompted, then OK...
Certificates should now be showing under Console Root
Certificates > Trusted Root Certification Authorities > Certificates > (right-click) > All Tasks > Import Certificate...
Find the certificate you want to import, click Next and choose defaults (Trusted Root Certification Authorities should already be
selected)
Click Next, Finish
(or)
Simply double-click on the .cer file for the certificate in Windows
Explorer, click Install Certificate... > Next > select the option to
"Place all certificates in following store" > Browse... > Select
Trusted Root Certification Authorities. Continue with next until done.
At this point your certificate is installed, and you should be able to communicate securely with your ADLDS server.

Related

Programatically creating an AD account through secure LDAP

Currently I'm successfully creating AD accounts through C# via an LDAP connection using the PrincipalContext. I create a new UserPrincipal, apply the various properties as required and call save()
Essentially something like this
using(var pc = new PrincipalContext(ContextType.Domain))
{
using(var up = new UserPrincipal(pc))
{
up.SamAccountName = "whatever";
up.EmailAddress = "test#example.com";
up.SetPassword(password);
up.Enabled = true;
up.Save();
}
}
All works fine but now we need to do the same thing over a secure LDAP connection and I'm struggling to find any info online regarding the specifics of how to do this. This makes me think that perhaps there's no difference from how I'm currently doing and instead all I need do is make sure the server supports LDAPS and is configured to use it.
Perhaps SO is wrong forum for this, am happy to move the question to a different forum if so.
The default TCP port for LDAP is 389. That's what's used if you don't tell it otherwise. To use LDAPS, you have to specify the LDAPS port of 636. For example:
using(var pc = new PrincipalContext(ContextType.Domain, "example.com:636"))
Where example.com is your domain name.
However, using LDAPS requires that your computer trusts the SSL certificate that the server uses. Sometimes a self-signed cert is used and that will cause this to fail. PrincipalContext doesn't report certificate errors. It reports it as if the server could not be contacted. So if you have an issue there, you can use this PowerShell script to download the certificate and inspect it:
$webRequest = [Net.WebRequest]::Create("https://example.com:636")
try { $webRequest.GetResponse() } catch {}
$cert = $webRequest.ServicePoint.Certificate
$bytes = $cert.Export([Security.Cryptography.X509Certificates.X509ContentType]::Cert)
set-content -value $bytes -encoding byte -path "certificate.cer"
Change the first line to have your domain name. If that works, there will be a file called certificate.cer that you can double-click on and inspect. If your computer doesn't trust it, you will see a message saying so. If that is a problem, you probably just need to install the root certificate as a "Trusted Root Certificate" on your computer.

Identityserver fails to load selfsigned certificate

I'm trying to set a Certificate for identityserver and it keeps failing with a "no access to private key error".
Taking it out of identityserver, the following code throws an access denied error
static X509Certificate2 GetCertificateFromDisk()
{
using (var stream = File.Open(#"patht-to-pfx", FileMode.Open))
{
var cert = new X509Certificate2(ReadStream(stream), "password", X509KeyStorageFlags.MachineKeySet);
return cert;
}
}
When running the code as administrator it works fine, not when running it under my own account. Eventually I want to run it as localsystem.
I even added 'Everyone' under the certificates private key permissions in my local computer certificate store,
screenprint here
... still I get the exception.
What is wrong here? Going Crazy about it
Update
Great tips from CryptoGuy in the answer below. Important note: Opening the file is not correct only Identityserver3 still failed when getting the certificate from the store. What made it work was to regenerate the certificate using Keith Sparkjoy's tool SerfCert. My previous certificate was generated using powershell. So keep in mind that powershell certificates have issues with accessibility of private key. Thanks to Keith for the tool!
There are few things to consider.
1) you are performing write access to Local Machine store. X509KeyStorageFlags.MachineKeySet attempts to save private key to Local Machine store. Therefore, you need administrator permissions to write there. You should remove this flag to perform read-only access
2)
Documentation says that adding permissions in MMC (manage private key-option on a certificate) should allow this, but it doesn't seem to work
it works on an already saved private keys.
What you really should do is to import certificate and private key to Local Machine store and then configure your application to reference installed certificate.
3) if your application runs under unpriveleged account and the key don't need to be shared, then you should use Current User store.

Why active directory is accessible on port 389 even after SSL is enabled?

I basically have three questions here: I need to use .Net 3.5.
I have enabled SSL on my active directory. I have exported the certificate and imported on a different machine. Now when I try to access the active directory using port 389, it allows me to connect . Is this an expected behavior?
Many places I found to use "LDAPS" in my directory path when using SSL. But when I use this I get Unknown COM Exception. Here on MSDN I found there is nothing such "LDAPS"
https://social.msdn.microsoft.com/Forums/vstudio/en-US/723c3908-5806-4515-a5b2-b565e0131a2b/active-directory-connection-ldap-over-ssl
Do I really need to provide the domain name before username (domain\user)? I am able to connect without specifying the domain name this way. All I need to provide the FQDN or the name to which the SSL certificate is issued.
I am using DirectoryEntry class for my implementation.
string path = "LDAP://hostname:port/SearchBase";
DirectoryEntry _directoryEntryObj = new DirectoryEntry(path, userName, password);
if(IsSSL)
_directoryEntryObj.AuthenticationType = AuthenticationTypes.SecureSocketsLayer;
object obj = _directoryEntryObj.NativeObject;
If you don't deactivate plain LDAP, it is an expected behavior, since LDAPS uses a different port, see: https://en.wikipedia.org/wiki/Lightweight_Directory_Access_Protocol
In general I don't believe you will get far with an AD that only runs on LDAPS (port 636), with plain LDAP (389) blocked. I don't think that most appliations implement LDAPS, but I could be wrong.
On top, the certificate has to valid. If you just export to another machine (with another hostname), the certificate won't be trusted. What kind of certificate do you use?
this could be linked with my concern regarding the validity of your certificate
When I played around with ldap/ad in .Net I found that you can also just use the current user. If you don't want to use that user, you'll have to go with domain\user. I also think that you consider that best practice, since you can always determine if the specified user is a local account or a domain account.
--
Hope I could help,
regards

How do I access X.509 certificates stored in a service account?

I'm trying to digitally sign a PDF document using Syncfusion PDF 10.4, like so:
PdfLoadedDocument document = new PdfLoadedDocument(inputStream);
PdfCertificate certificate = PdfCertificate.FindBySubject(certificateStoreType, certificateSubjectName);
PdfSignature signature = new PdfSignature(document, document.Pages[0], certificate, "Signatur");
signature.Bounds = new RectangleF(new PointF(5, 5), new SizeF(100, 100));
This works great for my local user account after installing a suitable certificate using MMC (adding the Certificates snap-in for My user account and storing it in Personal), but not for a service (choosing Service account this time, and picking my service). Running the same code results in no suitable certificate being found, i.e. certificate is null. Furthermore, PdfCertificate.GetCertificates() throws an AccessViolationException, which I assume is a bug on Syncfusion's end.
I can, however, reproduce the same problem without Syncfusion code:
var store = new System.Security.Cryptography.X509Certificates.X509Store("My");
store.Open(System.Security.Cryptography.X509Certificates.OpenFlags.ReadOnly);
foreach (var item in store.Certificates)
{
…
}
Run as my own user, the certificate shows up (as do all the others shown in MMC under Personal), but if I debug the service (by running it, then invoking System.Diagnostics.Debugger.Launch()), I only get a "CN=LOCAL SERVICE" certificate, which doesn't show up in MMC at all.
I'm assuming that I need to A) tell it to open the correct certificate store, or B) change something about the way the service is installed or run, such as giving it a different identity, enabling UserInteraction, etc. Currently, it runs using LocalService and with UserInteraction disabled.
From what I remember, Windows machine accounts (like LocalService) use the machine certificate store. This means that in your code, you have to access the store with StoreLocation.LocalMachine.
var store =
new System.Security.Cryptography.X509Certificates.X509Store(StoreLocation.LocalMachine);
Note that if you decide to run the service under specific identity, you should rather first login as the identity, then import the certificate to the Personal store and then, use StoreLocation.CurrentUser.
The answer appears to be that .NET doesn't support accessing service account certificate stores without P/Invoke or the like:
I don't think that any of the .NET APIs allow access to the Services Certificate store.
However, you can install the certificate into the CurrentUser store of the account that the service runs under.
I've changed the service to run under its own user (which doesn't need admin rights), ran mmc.exe as that user using runas, and imported the certificate to that user's personal store.
I ran into this problem, and to solve it had to allow the "Local Service" account to access the "Local Computer" certificate store using the tool "WinHttpCertCfg"
It is described in detail here:
https://support.microsoft.com/en-us/help/901183/how-to-call-a-web-service-by-using-a-client-certificate-for-authentication-in-an-asp-net-web-application

Accessing Impersonated users key store

I am impersonating a service user account in order to connect to a webservice that requires a cert to connect. I have installed the client cert on the service account on the machine which is running the code however I receive the error System.Security.Cryptography.CryptographicException: The system cannot find the file specified.
using (var ctx = new ImpersonationContext("svcAcctUserName", "domain", "password"))
{
var clientCert = new X509Certificate2("filePath", "certPassword");
}
The impersonation code works, for brevity I have left it out but I check to make sure my context is switched to the svcAcctUserName user by logging the Environment.UserName, which shows that I am running as svcAcctUserName. The filePath is correct, again I left it out, but I open and close the file before I create the X509Certificate2 object to make sure I have both access to the file and that my path is correct.
The error is confusing since I provide the path as a parameter and I know for certain the user running the code has access.
EDIT:
Also tried to do this: How to call a Web service by using a client certificate for authentication in an ASP.NET Web application
Although I am not using an asp.net application, I gave it a try anyway. I added the certificates add-in to the mmc, added the "local computer" certificates add in and then imported the cert into the Personal store of the local machine.
I then ran:
WinHttpCertCfg.exe -g -c LOCAL_MACHINE\My -s issuedToName -a domain\svcAcctUserName
Tried running the operation again, still same problem.
What am I missing?
So, as Alex pointed out, I do not understand the underlying architecture of certificate system in windows. However, after performing the above steps and modifying my code to use the X509Store, I have it working. Hopefully this will help someone:
using (var ctx = new ImpersonationContext("svcAcctUserName", "domain", "password"))
{
var store = new X509Store(StoreLocation.LocalMachine);
store.Open(OpenFlags.ReadOnly | OpenFlags.OpenExistingOnly);
var clientCert = store.Certificates.Find(X509FindType.FindByIssuerName, "IssuerNameHere", false);
var clientCert2 = new X509Certificate2(clientCert[0]);
}

Categories

Resources