How to authenticate a console application with Exchange Online/EWS Managed API? - c#

I'm developing a console app for personal use that uses the Exchange Web Services Managed API as an alternative to Redemption (which I, unfortunately, cannot use). My ultimate goal is to use the app in a Windows scheduled task to connect to my Exchange mailbox and carry out some tasks like creating folders at specific times during the year, moving emails to those folders, set retention policies on items, etc.
The app is coming along nicely, but I'm curious about how best to implement secure authentication for the app so I can use this at work. I know Exchange Online won't let me connect automatically with default credentials (not sure if this includes Modern Authentication...see below) and I'll have to pass them explicitly. While in development, I'm explicitly passing my user ID and password to Exchange using the following code:
string userId = UserPrincipal.Current.EmailAddress;
SecureString securePwd = new SecureString();
Console.Write("Current User ID/Email Address: {0}\n", userId);
Console.Write("Enter Password: ");
do
{
key = Console.ReadKey(true);
// Ignore the Backspace and Enter keys.
if (key.Key != ConsoleKey.Backspace && key.Key != ConsoleKey.Enter)
{
// Append the character to the password.
securePwd.AppendChar(key.KeyChar);
Console.Write("*");
}
else
{
if (key.Key == ConsoleKey.Backspace & securePwd.Length > 0)
{
securePwd.RemoveAt(securePwd.Length - 1);
Console.Write("\b \b");
}
else if (key.Key == ConsoleKey.Enter)
{
break;
}
}
} while (true);
service.Credentials = new WebCredentials(new NetworkCredential(userId, securePwd));
I was hoping, since I will be creating my Windows scheduled task with the Run whether user is logged on or not option selected and will enter my password, that the app could use and pass the same credentials that the scheduled task is using so I don't have to store my password at all, but I haven't found anything that would allow this yet.
My work has enabled Modern Authentication with Exchange Online and that appears to pass my Windows credentials to Exchange when I'm using the Outlook client without requiring me to explicitly enter my credentials for the Outlook client. Is there something similar in EWS Managed API that will allow my app to login with my Windows credentials without explicitly passing them? I know you can register your app and use OAuth, but I wanted to avoid that since this is for my personal use and I'm not sure I'll be able to register my app at work.
If the above isn't possible, I have seen a number of articles, like this one, that mention using the Windows Credential Manager and that looks promising, but it appears as though I may still need to do something additional with my password prior to storing it. If I create a credential in Windows Credential Manager and implement the code at the link above using the code below (and using the CredentialManagement NuGet package), the password is displayed in plain text.
private const string PasswordName = "CredentialTest";
public static void Main(string[] args)
{
LoadCredential();
Console.ReadKey();
}
public static void LoadCredential()
{
using (var cred = new CredentialManagement.Credential())
{
cred.Target = PasswordName;
cred.Load();
Console.WriteLine("Password: {0}", cred.Password);
}
}
Is it recommended to read in the password as a secure string, encrypt it using a key that is stored in an encrypted section of the app.config file, and store the encrypted password in Windows Credential Manager?
Another interesting item I came across was the ClientCertificateCredentials class in the EWS Managed API. Unfortunately, I haven't seen any examples of it in use. Is this an alternate method of authentication? Can a self-signed certificate be used for this method? If so, does it require storing a private key on the Exchange Online server?
I'm open to other ideas and appreciate the help.
Thanks!

Windows Credentials Manager is of course an option (that is how Outlook still stores POP3/IMAP4/SMTP passwords and used to store Exchange credentials).
Keep in mind that Office 365 will turn Basic authentication off in October 2020.
OAuth might be an option, but that means you'd still need to somehow prompt the user for the credentials and store the access and refresh tokens somehow.
The preferred solution is to use certificate based authentication - see for example https://developermessaging.azurewebsites.net/2018/09/11/authenticating-against-exchange-web-services-using-certificate-based-oauth2-tokens/
But that requires importing certificate on the server side, which requires admin privileges.

Related

Two factor authentication with ssh.net

I am trying to connect from a Windows app to a unix server, to run some commands (automating checks on server status). I am using the ssh.net library, and have this code to connect to the server:
using (SshClient ssh = new SshClient("myserver.univ.edu", "myusername", "My!maginaryPa55wrd"))
{
ssh.Connect();
var result = ssh.RunCommand("df -h");
LogText.Text = result.Result; // puts result of command into multiline textbox
ssh.Disconnect();
}
But when I run this, it times out on the connect. I am guessing this is because the server requires two factor authentication. When I log into it with Putty, it will prompt for username and password, it then prompts for a choice of factors ... enter a passcode or "1" for Duo push to xxx-xxx-1212 or "2" for phone call to xxx-xxx-1212.
The SSH.net library says it supports two-factor authentication, but I have searched all over for a way to do it, and I am coming up empty. Any clues as to how to solve this, I'd appreciate it.
The problem is that you don't have two factor authentication, you have single factor and a bunch of interactive options, which is non-standard and not supported by ssh.net. It's not expecting to have to make choices.
To make this work, you would need to modify the ssh.Net code, or better, setup Public Key Authentication with the remote servers, in which case it won't need a password or 2FA.
Any reasonable size organization almost certainly has PKI setup. You would need to check with the admins and see about getting your Public Key added to the servers you'll be checking.
ssh.Net supports public key auth and would eliminate all the problems you're having.

Event when smart card of the X509Certificate2 object is removed

We have a feature where we use Windows' personal certificate store to authenticate using certificates after the user chooses a valid certificate. It simply signs a challenge with following:
public static byte[] SignDataSHA512RSA(X509Certificate2 certificate, byte[] data)
{
using (var rsa = certificate.PrivateKey as RSACryptoServiceProvider)
{
if (rsa == null)
{
return null;
}
return rsa.SignData(data, CryptoConfig.MapNameToOID("SHA512"));
}
}
It works pretty well using the RSACryptoServiceProvider class. The feature allows both regular certificates and smart card certificates. If there is a smart card and it requires PIN, windows prompts a dialog for it and all.
Now, there is an extra security requirement in this feature where if a smartcard is used for this operation (which we can find out by rsa.CspKeyContainerInfo.Removable, and HardwareDevice if you want to enforce hardware), we want to make sure that it's accessible at all times during the session. In other words, we need an event when the smart card is removed so we can log out automatically.
One crude way would be having a timer job which checks certificate.PrivateKey is accessible every minute or so, but that doesn't throw before prompting user to insert the smart card and user presses Cancel.
The feature supports Windows 7 as well, so using the UWP libraries is not an option. Any way to accomplish this?
You could use PC/SC to check for card removal. In windows it is implemented in WinSCard library. See this SO answer.
There is also a C# implementation pcsc-sharp.

The LDAP Server is Unavailable using PrincipalContext and ADLDS

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.

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

How to read credentials from a SmartCard in c#

In my organization, users must use SmartCard for interactive login to a Windows stations (95,Vista and 7). almost daily, we need to read the credentials stored in the SmartCard and compaire them with the ActiveDirectory, without implementing a custom credentials manager. The fields we compare are: userPrincialName and sAMAccountName.
Can you please show me a code that demonstrates how to read the credentials from the SmartCard or guide me to an article / code on the internet?
A search over internet suggeted implementing credentials manager or using other languages (like C, C++).
Also, I came across this article : http://www.codeproject.com/Articles/17013/Smart-Card-Framework-for-NET written by orouit, which is a framework for working with SmartCards - but I think this too much for my simple task. What do you think?
Well if developing under windows, once you insert smart card windows will fetch all certificates from the smart card place them to the My certificate store.
var smartCardCerts = new List<X509Certificate2>();
var myStore = new X509Store(StoreName.My, StoreLocation.CurrentUser);
myStore.Open(OpenFlags.ReadOnly);
foreach(X509Certificate2 cert in myStore.Certificates)
{
if( !cert.HasPrivateKey ) continue; // not smartcard for sure
var rsa = cert.PrivateKey as RSACryptoServiceProvider;
if( rsa==null ) continue; // not smart card cert again
if( rsa.CspKeyContainerInfo.HardwareDevice ) // sure - smartcard
{
// inspect rsa.CspKeyContainerInfo.KeyContainerName Property
// or rsa.CspKeyContainerInfo.ProviderName (your smartcard provider, such as
// "Schlumberger Cryptographic Service Provider" for Schlumberger Cryptoflex 4K
// card, etc
var name = cert.Name;
rsa.SignData(); // to confirm presence of private key - to finally authenticate
}
}
basically a lot of crypto API is available via .NET nowdays. But you could also use API directly Crypto API
for example you could access smart card directly via
CryptAcquireContext(&hProv,"\\.\<Reader Name>\<Container Name>",...)
where reader name is card reader name and container name is whatever rsa.KeyContainerName in code snippet above. There are multiple ways to access information like that and Crypto API is not very consistent or straightforward. as a hint .NET version of CryptAcquireContext is RSACryptoServiceProvider with CspParameters where you can specify container name if needed.
Well finding user in ActiveDirectory may be done via System.DirectoryServices.DirectoyEntry and System.DirectoryServices.DirectorySearcher, but do not forget System.DirectoryServices.ActiveDirectory.Forest and related API which makes some things a lot easier to figure out.
You would be able to get

Categories

Resources