Custom X509CertificateValidator Check Requestor Against CN - wcf

I have a custom X509CertificateValidator that currently validates a series of rules against a certificate presented for a WCF SOAP message.
There is a requirement to check the CN name on the certificate against the domain the certificate is being presented by, but I'm not aware that I have access to the request from within the X509CertificateValidator.
Is there any way to check that the certificate matches the request domain?

I haven't found any way to do this from within the X509CertificateValidator, but it is possible within the service.
Here is my first cut - I will be refining it to make it more elegant, but this works.
private static void ValidateRequestIsFromCertificateDomain()
{
RemoteEndpointMessageProperty endpointProperty = OperationContext.Current.IncomingMessageProperties[RemoteEndpointMessageProperty.Name] as RemoteEndpointMessageProperty;
var claimSet = OperationContext.Current.ServiceSecurityContext.AuthorizationContext.ClaimSets[0] as X509CertificateClaimSet;
string domain = claimSet.X509Certificate.GetNameInfo(X509NameType.DnsName, false);
var resolvedAddress = System.Net.Dns.GetHostAddresses(domain);
if (resolvedAddress.Count() == 0 || endpointProperty.Address != resolvedAddress[0].ToString())
{
throw new SecurityException("Client address mismatch");
}
}
This isn't really required because the client encrypts data with its private key that can only be decrypted with its public key - so you know the certificate is being presented by the real client.
However, if you are given this as an integration requirement as I have been, this may be useful to you.

Related

Hangout OAuth - Invalid Scope : Some requested scopes cannot be shown

I am facing the below error while generating token for service account for the Hangout Scope - https://www.googleapis.com/auth/chat.bot.
Where i receive 400 response code after making a post request to this url -
https://www.googleapis.com/oauth2/v4/token
the params are
Content-Type:application/x-www-form-urlencoded
httpMode:POST
body:grant_type=jwt-bearer&assertion=assertion-token
Note:This was completely working fine. Suddenly am facing this issue.
cross verified: jwt generation,service_account_id and etc...
Error Response : { "error": "invalid_scope", "error_description": "Some requested scopes cannot be shown": [https://www.googleapis.com/auth/chat.bot]}
code for generating assertion:
//FORMING THE JWT HEADER
JSONObject header = new JSONObject();
header.put("alg", "RS256");
header.put("typ", "JWT");
//ENCODING THE HEADER
String encodedHeader = new String(encodeUrlSafe(header.toString().getBytes("UTF-8")));
//FORMING THE JWT CLAIM SET
JSONObject claimSet = new JSONObject();
claimSet.put("iss","123#hangout.iam.gserviceaccount.com");
claimSet.put("sub","one#domain.com");
claimSet.put("scope","https://www.googleapis.com/auth/chat.bot");
claimSet.put("aud","https://oauth2.googleapis.com/token");
long time = System.currentTimeMillis() / 1000;
claimSet.put("exp",time+3600);
claimSet.put("iat",time);
//ENCODING THE CLAIM SET
String encodedClaim = new String(encodeUrlSafe(claimSet.toString().getBytes("UTF-8")));
//GENERATING THE SIGNATURE
String password = "secretofkey", alias = "privatekey";
String signInput = encodedHeader + "." + encodedClaim;
Signature signature = Signature.getInstance("SHA256withRSA");
String filepath = "/check/PrivateKeys/hangoutPKEY.p12";
KeyStore kstore = KeyStore.getInstance("PKCS12");
fis = new FileInputStream(filepath);
kstore.load(fis, password.toCharArray());
KeyStore.PrivateKeyEntry pke = (KeyStore.PrivateKeyEntry) kstore.getEntry(alias, new KeyStore.PasswordProtection(password.toCharArray()));
PrivateKey pKey = pke.getPrivateKey();
signature.initSign(pKey);
signature.update(signInput.getBytes("UTF-8"));
String encodedSign = new String(encodeUrlSafe(signature.sign()), "UTF-8");
//JWT GENERATION
String JWT = signInput + "." + encodedSign;
String grant_type = URLEncoder.encode("urn:ietf:params:oauth:grant-type:jwt-bearer");
reqBody = "grant_type=" + grant_type + "&assertion=" + JWT;
public static byte[] encodeUrlSafe(byte[] data) {
Base64 encoder = new Base64();
byte[] encode = encoder.encodeBase64(data);
for (int i = 0; i < encode.length; i++) {
if (encode[i] == '+') {
encode[i] = '-';
} else if (encode[i] == '/') {
encode[i] = '_';
}
}
return encode;
}
Does anyone have any idea, where am going wrong?
Short answer:
You are trying to use domain-wide authority to impersonate a regular account. This is not supported in Chat API.
Issue detail:
You are using the sub parameter when building your JWT claim:
claimSet.put("sub","one#domain.com");
Where sub refers to:
sub: The email address of the user for which the application is requesting delegated access.
I noticed that, if I add the sub parameter to my test code, I get the same error as you.
Solution:
Remove this line from your code in order to authorize with the service account (without impersonation) and handle bot data:
claimSet.put("sub","one#domain.com");
Background explanation:
Chat API can be used for bots to manage their own data, not to manage end-user data. Therefore, you can only use a service account to act as the bot, without impersonating an end-user.
From this Issue Tracker comment:
At the present moment, Chat API can only be used to manage bot-related data (listing the spaces in which the bot is included, etc.). Using domain-wide delegation to manage regular users' data is not currently possible.
Feature request:
If you'd like to be able to access regular users' data with your service account and domain-wide delegation via Chat API, you are not alone. This feature has been requested before in Issue Tracker:
Accessing the Google Chats of regular users using domain-wide delegated permission and service account credentials
I'd suggest you to star the referenced issue in order to keep track of it and to help prioritizing it.
Reference:
Using service accounts
Delegating domain-wide authority to the service account
Preparing to make an authorized API call

Keyset does not exist /r /n

I was tasked to create a integration service between our SharePoint app and one service provider. One requirement of the service provider I'm going to integrate with is to provide them a public key which they will use to verify my request which was signed using our own private key.
Initially I created a console app which reads the certificate store and gets the private key which to use to sign my request and all. The console app works fine so I decided to move it now within our SharePoint application. Unfortunately it fails in this specific part of the code:
key.FromXmlString(privateCert.PrivateKey.ToXmlString(true));
The whole code snippet which gets the certificate and does the signing can be found below:
X509Certificate2 privateCert = null;
X509Store store = new X509Store(StoreName.My, StoreLocation.LocalMachine);
store.Open(OpenFlags.MaxAllowed);
var certs = store.Certificates.Find(X509FindType.FindByThumbprint, "thumbprinthere", true);
if (certs.Count > 0)
{
privateCert = certs[0];
}
RSACryptoServiceProvider key = new RSACryptoServiceProvider();
key.FromXmlString(privateCert.PrivateKey.ToXmlString(true));
byte[] sig = key.SignData(Encoding.ASCII.GetBytes(data), CryptoConfig.MapNameToOID("SHA256"));
string signature = Convert.ToBase64String(sig);
[UPDATE]
I tried following the steps in this link. I first uninstalled my existing private key in the server. I then imported it back to the Certificate store and confirmed that there was a Thumbprint property. After that, I ran findprivatekey.exe and was able to navigate to the MachineKeys folder. From there I added different users ranging from Network Services, IIS_IUSRS and even local accounts I used to login to the server as well as SPFarm admin but I still keep getting the error.
I also made sure that the key I added was exportable so there should be a way for it the application to extract the private key attached to the certificate.
[UPDATE 2]
I updated the code so that it just returns one certificate prior to assigning it to the variable I was using to extract the private key. Still the same issue even if I can see that the certs variable is returning exactly one record.
After much checking I realized I missed one important part in calling the method code block above. I forgot to wrap it an elevate privilege block. After doing that, the code functioned similarly as my console app.
SPSecurity.RunWithElevatedPrivileges(delegate())
{
...
X509Certificate2 privateCert = null;
X509Store store = new X509Store(StoreName.My, StoreLocation.LocalMachine);
store.Open(OpenFlags.MaxAllowed);
var certs = store.Certificates.Find(X509FindType.FindByThumbprint, "<thumbprinthere>", true);
if (certs.Count > 0)
{
privateCert = certs[0];
}
RSACryptoServiceProvider key = new RSACryptoServiceProvider();
key.FromXmlString(privateCert.PrivateKey.ToXmlString(true));
byte[] sig = key.SignData(Encoding.ASCII.GetBytes(data), CryptoConfig.MapNameToOID("SHA256"));
string signature = Convert.ToBase64String(sig);
...
});

Security with RijndaelManaged and ServicePointManager

I have a security question about RijndaelManaged and
ServicePointManager.
I have implemented a system where C# application is encrypting data, such as user credentials and some XML data. Then I use WebClient to send encrypted user credentials with some encrypted XML document containing instructions - to my Tomcat Java Web application. The job of the Java Application: is to decrypt user credentials and XML instructions – perform instructions and respond back to C# with an encrypted XML result.
All connections from my C# application to Tomcat server are with SSL enabled (Self signed certificate for now).
First Question: Given the fact that my C# application by default always connecting to my Server (only) with SSL enabled. Can I simply implement the call back function as:
ServicePointManager.ServerCertificateValidationCallback = delegate { return true; };
As I understand that the call back function is used to Identify and validate certificate used by the Server I’m connecting to. If I were to give that application to – say one of my clients to connect to my Server (with SSL enabled) – is the code above satisfactory? If client uses my application to connect to another server that is not known and I have no Idea about its SSL certificate status – the code above should be replaced with an actual certificate validation function. Does my question make sense?
Second Question: I have encryption/decryption implemented using RijndaelManaged in my C# application. But the key I’m using is part of the C# application – the application is obfuscated. As I understand this is not a secure way.
Is there a reliable way for the C# application to receive the encryption/decryption key from my Web application. Or is there a way for the key to be generated in C# application that can be used by Web application to decrypt the data – if so: how do I generate that key and most important how do I send it to the server in a reliable secure way. Since the connection is SSL – can the key simply be a part of the encrypted stream?
Here is code that I’m using for encryption in my C# app.
private const string KEY = "samplekey";
private const int KEY_SIZE = 128;
private const int KEY_BITS = 16;
private string Encrypt(string textToEncrypt)
{
RijndaelManaged rijndaelCipher = new RijndaelManaged();
rijndaelCipher.Mode = CipherMode.CBC;
rijndaelCipher.Padding = PaddingMode.PKCS7;
rijndaelCipher.KeySize = KEY_SIZE;
rijndaelCipher.BlockSize = KEY_SIZE;
byte[] pwdBytes = Encoding.UTF8.GetBytes(KEY);
byte[] keyBytes = new byte[KEY_BITS];
int len = pwdBytes.Length;
if (len > keyBytes.Length)
{
len = keyBytes.Length;
}
Array.Copy(pwdBytes, 0, keyBytes, 0, len);
rijndaelCipher.Key = keyBytes;
rijndaelCipher.IV = keyBytes;
ICryptoTransform transform = rijndaelCipher.CreateEncryptor();
byte[] plainText = Encoding.UTF8.GetBytes(textToEncrypt);
return System.Convert.ToBase64String(transform.TransformFinalBlock(plainText, 0, plainText.Length));
}

Set callback for System.DirectoryServices.DirectoryEntry to handle self-signed SSL certificate?

I have an application replicating data from a directory service using typical System.DirectoryServices.DirectoryEntry code. I now have a requirement to replicate from Novell eDirectory using SSL with a self-signed certificate. I suspect that the existing code would work with a valid certificate that could be verified, or perhaps if the self-signed cert is added to the local machine keystore. In order to make it work for sure with a self-signed cert however, the only solution I can find is to use the System.DirectoryServices.Protocols namespace and the LdapConnection class, whereby I can wire up a VerifyServerCertificate callback. I can't find any way of applying the same concept to a DirectoryEntry instance, or of connecting with an LdapConnection instance and somehow "converting" that to a DirectoryEntry instance. Maybe it isn't possible, I'd just like to confirm that really. Any other thoughts welcome.
The only pertinent link I've found is at: http://www.codeproject.com/Articles/19097/eDirectory-Authentication-using-LdapConnection-and
This is a phenomenal question.
I've been battling this same issue for a few days now, and I've finally got some definitive proof on why the DirectoryEntry object will not work in this scenario.
This particular Ldap server (running on LDAPS 636) also issues it's own self signed certificate. Using LdapConnection (and monitoring the traffic via Wireshark), I noticed a handshake taking place that does not occur when using DirectoryEntry :
The first sequence is the from the secured ldap server, the second sequence is from my machine. The code that prompts the second sequence is :
ldapConnection.SessionOptions.VerifyServerCertificate += delegate { return true; };
There are others way to "fake out" the callback, but this what I've been using.
Unfortunately, DirectoryEntry does not have an option or method to verify a self signed cert, thus the acceptance of the certificate never happens (second sequence), and the connection fails to initialize.
The only feasible way to accomplish this is by using LdapConnection, in conjunction with a SearchRequest and SearchResponse. This is what I've got so far :
LdapConnection ldapConnection = new LdapConnection("xxx.xxx.xxx:636");
var networkCredential = new NetworkCredential("Hey", "There", "Guy");
ldapConnection.SessionOptions.SecureSocketLayer = true;
ldapConnection.SessionOptions.VerifyServerCertificate += delegate { return true; };
ldapConnection.AuthType = AuthType.Negotiate;
ldapConnection.Bind(networkCredential);
SearchRequest request = new SearchRequest("DC=xxx,DC=xxx,DC=xxx", "(sAMAccountName=3074861)", SearchScope.Subtree);
SearchResponse response = (SearchResponse)ldapConnection.SendRequest(request);
if(response.Entries.Count == 1)
{SearchResultEntry entry = response.Entries[0];
string DN = entry.DistinguishedName;}
From there you can gather AD Properties from the SearchResponse, and process accordingly. This is a total bummer though, because the SearchRequest seems to be much slower then using the DirectoryEntry.
Hope this helps!
I promise, this will be my last post on this particular question. :)
After another week of research and development, I have a nice solution to this, and it has worked exceedingly well for me thus far.
The approach is somewhat different then my first answer, but in general, it's the same concept; using the LdapConnection to force validation of the certificate.
//I set my Domain, Filter, and Root-AutoDiscovery variables from the config file
string Domain = config.LdapAuth.LdapDomain;
string Filter = config.LdapAuth.LdapFilter;
bool AutoRootDiscovery = Convert.ToBoolean(config.LdapAuth.LdapAutoRootDiscovery);
//I start off by defining a string array for the attributes I want
//to retrieve for the user, this is also defined in a config file.
string[] AttributeList = config.LdapAuth.LdapPropertyList.Split('|');
//Delcare your Network Credential with Username, Password, and the Domain
var credentials = new NetworkCredential(Username, Password, Domain);
//Here I create my directory identifier and connection, since I'm working
//with a host address, I set the 3rd parameter (IsFQDNS) to false
var ldapidentifier = new LdapDirectoryIdentifier(ServerName, Port, false, false);
var ldapconn = new LdapConnection(ldapidentifier, credentials);
//This is still very important if the server has a self signed cert, a certificate
//that has an invalid cert path, or hasn't been issued by a root certificate authority.
ldapconn.SessionOptions.VerifyServerCertificate += delegate { return true; };
//I use a boolean to toggle weather or not I want to automatically find and query the absolute root.
//If not, I'll just use the Domain value we already have from the config.
if (AutoRootDiscovery)
{
var getRootRequest = new SearchRequest(string.Empty, "objectClass=*", SearchScope.Base, "rootDomainNamingContext");
var rootResponse = (SearchResponse)ldapconn.SendRequest(getRootRequest);
Domain = rootResponse.Entries[0].Attributes["rootDomainNamingContext"][0].ToString();
}
//This is the filter I've been using : (&(objectCategory=person)(objectClass=user)(&(sAMAccountName={{UserName}})))
string ldapFilter = Filter.Replace("{{UserName}}", UserName);
//Now we can start building our search request
var getUserRequest = new SearchRequest(Domain, ldapFilter, SearchScope.Subtree, AttributeList);
//I only want one entry, so I set the size limit to one
getUserRequest.SizeLimit = 1;
//This is absolutely crucial in getting the request speed we need (milliseconds), as
//setting the DomainScope will suppress any refferal creation from happening during the search
SearchOptionsControl SearchControl = new SearchOptionsControl(SearchOption.DomainScope);
getUserRequest.Controls.Add(SearchControl);
//This happens incredibly fast, even with massive Active Directory structures
var userResponse = (SearchResponse)ldapconn.SendRequest(getUserRequest);
//Now, I have an object that operates very similarly to DirectoryEntry, mission accomplished
SearchResultEntry ResultEntry = userResponse.Entries[0];
The other thing I wanted to note here is that SearchResultEntry will return user "attributes" instead of "properties".
Attributes are returned as byte arrays, so you have to encode those in order to get the string representation. Thankfully, System.Text.Encoding contains a native ASCIIEncoding class that can handle this very easily.
string PropValue = ASCIIEncoding.ASCII.GetString(PropertyValueByteArray);
And that's about it! Very happy to finally have this figured out.
Cheers!
I have used below code to connect with ldaps using DirectoryEntry.
What i understood in my scenerio is directoryEntry does not work when ldaps is specified in server path or authentication type is mentioned as "AuthenticationTypes.SecureSocketsLayer" but if only ldaps port is mentioned at the end of server name it work. After having a look at wireshark log i can see handshake taking place as mentioned in above post.
Handshake:
Code:
public static SearchResultCollection GetADUsers()
{
try
{
List<Users> lstADUsers = new List<Users>();
DirectoryEntry searchRoot = new DirectoryEntry("LDAP://adserver.local:636", "username", "password");
DirectorySearcher search = new DirectorySearcher(searchRoot);
search.PropertiesToLoad.Add("samaccountname");
SearchResult result;
SearchResultCollection resultCol = search.FindAll();
Console.WriteLine("Record count " + resultCol.Count);
return resultCol;
}
catch (Exception ex)
{
Console.WriteLine("exception" + ex.Message);
return null;
}
}

Getting authentication token from IP with nunit

I am working on adding WIF support to my WCF Data Services / ODATA server, and the first thing I'd like to do is create a nUnit test which passes some sort of identity to said server. I believe this falls under the category of an active client: there's no UI; I want to make a call out to a app.config established provider (Google, Yahoo, Windows Live, or some other provider) to get my identity token. Frankly, it doesn't matter what, just that it's more-or-less always accessable and has no administration to get the test running. (If there's some host app that I can include in my solution to act as an IP, I'd be perfectly happy with that.)
All of my existing tests use HttpRequest directly -- I am not using a generated client. While I'm creating my HttpRequest object, I check to see if I already have an authentication token to put in my headers. If not, I am trying something like this:
using (WSTrustChannelFactory factory = new WSTrustChannelFactory(
new UserNameWSTrustBinding(SecurityMode.TransportWithMessageCredential),
new EndpointAddress(new Uri("https://dev.login.live.com/wstlogin.srf"))))
{
factory.Credentials.UserName.UserName = "MYUSERNAME";
factory.Credentials.UserName.Password = "MYPASSWORD";
factory.TrustVersion = TrustVersion.WSTrust13;
WSTrustChannel channel = null;
try
{
var rst = new RequestSecurityToken
{
RequestType = WSTrust13Constants.RequestTypes.Issue,
AppliesTo = new EndpointAddress("http://localhost:60711/Service"),
KeyType = WSTrust13Constants.KeyTypes.Bearer,
};
channel = (WSTrustChannel)factory.CreateChannel();
return channel.Issue(rst);
}
finally
{
if (null != channel)
{
channel.Abort();
}
factory.Abort();
}
}
So to start... I don't even know if I'm aiming at the right URI for the IP, but when I changed it, I got a 404, so I figure maybe I'm on the right track there. At the moment, the channel.Issue method returns a MessageSecurityException with an inner exception of type FaultException, noting "Invalid Request". The FaultException has a Code with Name=Sender and Namespace=http://www.w3.org/2003/05/soap-envelope, which then has a SubCode with Name=InvalidRequest and Namespace=http://schemas.xmlsoap.org/ws/2005/02/trust. I don't know what to do with this information. :)
My apologies if I'm asking a very basic question. I've been looking at authentication for only a couple of days, and don't know my way around yet. Thanks for any help!
EDIT -- SOLUTION
Eugenio is right -- I am doing something a little heavyweight, and it is more of integration testing stuff. I ditched the Google/Yahoo/Live stuff, and found a modified version of SelfSTS, which I cobbled into my project. I don't fully understand what's going on just yet, but I got back a SAML token. Here is final code:
var binding = new WS2007HttpBinding();
binding.Security.Mode = SecurityMode.Message;
binding.Security.Message.ClientCredentialType = MessageCredentialType.UserName;
binding.Security.Message.EstablishSecurityContext = false;
binding.Security.Message.NegotiateServiceCredential = true;
using (var trustChannelFactory = new WSTrustChannelFactory(binding, new EndpointAddress(new Uri("http://localhost:8099/STS/Username"), new DnsEndpointIdentity("adventureWorks"), new AddressHeaderCollection())))
{
trustChannelFactory.Credentials.UserName.UserName = MYUSERNAME;
trustChannelFactory.Credentials.UserName.Password = MYPASSWORD;
trustChannelFactory.Credentials.ServiceCertificate.Authentication.CertificateValidationMode = X509CertificateValidationMode.None;
trustChannelFactory.TrustVersion = TrustVersion.WSTrust13;
WSTrustChannel channel = null;
try
{
var rst = new RequestSecurityToken(WSTrust13Constants.RequestTypes.Issue)
{
AppliesTo = new EndpointAddress("http://localhost:60711/Service"),
KeyType = WSTrust13Constants.KeyTypes.Bearer
};
channel = (WSTrustChannel)trustChannelFactory.CreateChannel();
GenericXmlSecurityToken token = channel.Issue(rst) as GenericXmlSecurityToken;
((IChannel)channel).Close();
channel = null;
trustChannelFactory.Close();
string tokenString = token.TokenXml.OuterXml;
return tokenString;
}
finally
{
if (null != channel)
{
((IChannel)channel).Abort();
}
trustChannelFactory.Abort();
}
}
(This code is also lifted from the same place I got the modified SelfSTS -- thanks, Wade Wegner!)
I'm not sure about the use of "adventureWorks". The version of SelfSTS that I'm using names that as the issuername in the configuration, but I haven't checked to see if there's any correlation.
So... now to hack up my actual server so that it can figure out what to do with the SAML!
Not all of those IPs support active calls. Even if they do, the protocols might not be compatible.
For example, I'm not sure Google implements WS-Trust (what WIF is using under the hood). LiveID might have a WS-Trust endpoint somewhere, but not sure if it is officially supported/documented (for example, likely the error you are getting is because LiveID doesn't know about your RP: http:// localhost:60711/Service; and thus cannot issue a token for it).
For multiple IPs like these, apps typically embed a web browser and use it for all token negotiations (using WS-Federation for example). And often they rely on a specialized STS to deal with protocol transitions (e.g. Windows Azure Access Control Service)
In any case, what you are doing sounds a little bit heavyweight for a Unit test. It sounds more like an integration test you want to automate.
Maybe you could start with a custom (fake) STS of your own. In which you can control everything and simulate different outputs (e.g. different claims, etc.)
This chapter: http://msdn.microsoft.com/en-us/library/hh446528 (and the samples) can give you more information.