How do I read the Common Name from the client certificate? - c#

Our application needs a piece of data that it is included in the client cert's common name. Currently, I'm trying to get it from HttpContext.Current.Request.ClientCertificate. How do I read this out? Sadly, I'm trying to code this blind while I figure out why SoapUI isn't sending the cert, so I haven't tried much other than reading about the object on MSDN and poking through the empty properties but I'm not sure what I'm looking for. So to recap, what do I need to do to pull out the common name from this cert? TIA

I am maybe too late to answer your question but i hope this would help others who are looking for the way to get common name from certificate.
If you use 'Subject', you might need to trim away other unnecessary information.
For example, CN = localhost,OU = DepartmentName,O = CompanyName,L = Location,S = State,C = Country
Dim store As New X509Store(StoreName.My, StoreLocation.LocalMachine)
store.Open(OpenFlags.ReadOnly)
store.Certificates(0).Subject
But if you use the code below, you will get 'localhost' which directly give you the common name of the certificate.
Dim store As New X509Store(StoreName.My, StoreLocation.LocalMachine)
store.Open(OpenFlags.ReadOnly)
store.Certificates(0).GetNameInfo(X509NameType.SimpleName, False)
Here's the link for reference:-
https://msdn.microsoft.com/en-us/library/system.security.cryptography.x509certificates.x509certificate2.getnameinfo(v=vs.110).aspx

I know a tiny bit about certificates. Here was my workflow:
I started at:
HttpRequest.ClientCertificate
Which lead me to:
HttpClientCertificate (as the return type).
It appears to have a few properties, but none that explicitly are named common name.
Googled: HttpClientCertificate Common Name:
Problem with extracting X509 certificate from Context on web service side
Which had some code:
//extracting Common name from certificate
Subject = cert.Subject.ToString();
Then went to:
HttpClientCertificate.Subject
which remarks:
If String is specified without a subfield, the HttpClientCertificate collection returns a comma-separated list of subfields. For example, C=US, O= Msft.
And with the extreme limited knowledge I do have, I know the Common Name = is in this list. I have no actual way to test this at the moment, but it shouldn't be hard to parse this value for the name you are looking for.
It's good question (+1), I am happy you asked it as it will probably be useful for future readers.
I created a DotNetFiddle Example and even though it uses HttpWebRequest to get an X509Certificate class, it does have a Subject property that returned the following value for https on www.google.com:
CN=www.google.com, O=Google Inc, L=Mountain View, S=California, C=US
So I'm inclined to believe that Subject on the HttpClientCertificate would be the same values (knowing that CN means CommonName).

Just a oneliner in Linq.
var kvs = cert.Subject.Split(',').Select(x => new KeyValuePair<string, string>(x.Split('=')[0], x.Split('=')[1])).ToList();
Returns a generic list. Dont use dictionary here, because Subject can contain duplicate fields.

You may have to adjust due to differences in certificate formats.
Here is some code to do this:
HttpClientCertificate theHttpCertificate = HttpContext.Current.Request.ClientCertificate;
// split the subject into its parts
string[] subjectArray = theHttpCertificate.Subject.Split(',');
string[] nameParts;
string CN = string.Empty;
string firstName = string.Empty;
string lastName = string.Empty;
foreach (string item in subjectArray)
{
string[] oneItem = item.Split('=');
// Split the Subject CN information
if (oneItem[0].Trim() == "CN")
{
CN = oneItem[1];
if (CN.IndexOf(".") > 0)
{// Split the name information
nameParts = CN.Split('.');
lastName = nameParts[0];
firstName = nameParts[1];
}
}
}

The best thing to do is to use the build-in GetNameInfo methods using the name type and the boolean flag
Lets assume you use this extension method:
[return:MaybeNull]
public static X509Certificate2? GetCodeSignCertificate(this Assembly asm)
{
if (asm is null)
{
throw new ArgumentNullException(nameof(asm));
}
if (!File.Exists(asm.Location))
{
return null;
}
using var cert=System.Security.Cryptography.X509Certificates.X509Certificate.CreateFromSignedFile(asm.Location);
if (cert is null)
return null;
return new X509Certificate2(cert);
}
You can then use a type in the class to get the certificate like so:
[TestMethod()]
public void TryGetCodeSigning()
{
//var item = new Walter.CallStack();
var item = new List<string>();
using var cert = item.GetType().Assembly.GetCodeSignCertificate();
Assert.IsNotNull(cert.GetNameInfo(X509NameType.SimpleName,true));
}
to get the name of the certificate signing authority
cert.GetNameInfo(X509NameType.SimpleName,true)
to get the name for who the certificate was signed
cert.GetNameInfo(X509NameType.SimpleName,false)
Have a look at X509NameType and look if this works for you.

I created this extension to handle all individual elements of a subject name.
public enum SubjectNameElements {CN,O,L,OU,S,C,}
public static readonly Dictionary<SubjectNameElements, string> SubNameSybms =
new Dictionary<SubjectNameElements, string>{
{ SubjectNameElements.CN,"CN="},
{ SubjectNameElements.O ,"O="},
{ SubjectNameElements.L ,"L="},
{ SubjectNameElements.OU,"OU="},
{ SubjectNameElements.S ,"S="},
{ SubjectNameElements.C ,"C="},
};
/// <summary>
/// Retrieve CN from subject Name of a certificate
/// </summary>
/// <param name="subjName"></param>
/// <param name="symb"></param>
/// <remarks>
/// Example:
/// GetOneElementFromSubjectName("C=IT, S=Italy, L=Torino, O=Example Italy S.p.A., OU=Example-Client, CN=www.google.com",SubjectNameElements.CN) => www.google.com
/// </remarks>
/// <returns> a string value or empty string in case of invalid options </returns>
public static string GetOneElementFromSubjectName(this X500DistinguishedName subjName, SubjectNameElements symb=SubjectNameElements.CN)
{
string subjNameString = subjName.Name;
try
{
string Symb = SubNameSybms[symb];
string CName = subjNameString.Substring(subjNameString.IndexOf(Symb)).Split(",").First().Replace(Symb, string.Empty).Trim();
return CName;
}
catch (Exception ex)
{
Log.Error("Error in GetOneElementFromSubjectName. Ex.Message: '" + ex.Message + "'. Ex.StackTrace: '" + ex.StackTrace + "'");
return string.Empty;
}
}

Related

The "SignName" property of class PdfPKCS7 always return null in Itext 5.5.13.2 using the C#

I signed the pdf successfully and also verified the pdf successfully but the "SignName" property of class PdfPKCS7 always returns null.
I don't know why? Am I missing something during the pdf signing process or verification process?
//res.SignName always returns null.
validite = "Validated : " + res.SignName;
Pdf signing code see here: "Invalid algorithm specified" when pdf signing with Itext 5.5.13.2
and Verification Code is given below.
private void Button1_Click(object sender, EventArgs e)
{
PdfReader reader;
reader = new PdfReader(pdfFilePath.Text);
try
{
AcroFields acf = reader.AcroFields();
List<string> sgnoms = acf.GetSignatureNames();
List<string> sgnoms2 = acf.GetBlankSignatureNames();
Org.BouncyCastle.X509.X509Certificate cert;
if (sgnoms.Count > 0)
{
foreach (object obj in sgnoms)
{
PdfPKCS7 res = acf.VerifySignature(obj.ToString());
string validite = "Not Validate";
DateTime cal = res.SignDate;
if (res.SigningCertificate.IsValid(DateTime.Now) && res.Verify())
//res.SignName` Always returns null
validite = "Validated : " + res.SignName;
res = null;
validite = null;
cal = default(DateTime);
}
}
else
throw new Exception("Document not sign!");
reader = null;
acf = null;
sgnoms = null;
sgnoms2 = null;
}
catch (Exception ex)
{
}
}
Is any alternative to get the Signer Name? Or anything missing in the code?
Please check it, Unbale to get the reason. Am I missing something during the pdf signing process? or Am I missing something during the pdf verification process?
Any ideas, working code and suggestions are welcome.
Thanks in advance.
The SignName property is the value of the Name entry of the signature dictionary. This entry is specified as:
Key
Type
Value
Name
text string
(Optional) The name of the person or authority signing the document. This value should be used only when it is not possible to extract the name from the signature.EXAMPLE 1 From the certificate of the signer.
(ISO 32000-1, Table 252 – Entries in a signature dictionary)
This entry is optional, so the value you retrieve may well be null.
There is also a hint where you can look for the signer name here: in the signer certificate! Thus, consider inspecting the SigningCertificate property of your PdfPKCS7 object.

Exception has been thrown by the target of an invocation ERROR when trying to changind AD user with ASP.net C#

I've tryed a lot of others solutions, and I still didn't make it work. Can someone help me please.
my code is like that:
I saw something about secureString, I tryed to use it but it still didn't work.
I saw too another solution that says to use var rather than string in the variables. Didn't work
I dont know if I'm doing something wrong or if those solutions that dosoen't work.
public bool RedefinirSenha(string pUsuario, string pSenhaAtual, string pNovaSenha)
{
var NovaSenha = pNovaSenha;
var SenhaAtual = pSenhaAtual;
var Usuario = pUsuario;
//string Pwd = String.Format(#"""{0}""", NovaSenha);
//byte[] pwdCerto = System.Text.Encoding.Unicode.GetBytes(Pwd);
try
{
string LDAP = myLDAPpath;
DirectoryEntry ADcon = new DirectoryEntry(LDAP, Usuario, SenhaAtual, AuthenticationTypes.Secure);
if (ADcon != null)
{
DirectorySearcher search = new DirectorySearcher(ADcon);
search.Filter = "(SAMAccountName=" + Usuario + ")";
SearchResult result = search.FindOne();
if (result != null)
{
DirectoryEntry userEntry = result.GetDirectoryEntry();
if (userEntry != null)
{
try
{
userEntry.Invoke("ChangePassword", new object[] { SenhaAtual, NovaSenha }, AuthenticationTypes.Secure);
userEntry.Properties["LockOutTime"].Value = 0;
userEntry.CommitChanges();
userEntry.Close();
return true;
}
catch (Exception INex)
{
this.Erro = INex.Message + "COD:\r\n" + INex.InnerException;
userEntry.Close();
return false;
}
}
}
}
return true;
}
catch (Exception ex)
{
this.Erro = ex.Message;
return false;
}
}
First, there will be no difference at runtime if you declare the variables as var or string. Using the var keyword lets the compiler decide what the type is. Because you're assigning a string to it, then it is a string too. In most cases, var is fine. There are only very rare cases when you need to explicitly specify the type.
Second, DirectoryEntry.Invoke is defined like this:
public object Invoke (string methodName, params object[] args);
That may seem like you need to pass an object array, but that is not the case. The params keyword is a way to allow you to pass multiple parameters that get used inside the method as an array. So when you call it like this:
userEntry.Invoke("ChangePassword", new object[] { SenhaAtual, NovaSenha }, AuthenticationTypes.Secure);
The first parameter is an object array and the second parameter is AuthenticationTypes.Secure, then both of those get put inside the args array for use inside the Invoke method. But that is not what ChangePassword looks for. If this doesn't make sense to you, read the documentation for the params keyword and it should help.
When you call .Invoke("ChangePassword", ...), it calls the native Windows IADsUser.ChangePassword method. That takes two parameters: a string with the old password and a string with the new password - not an object array and an AuthenticationTypes value. So you should be calling it like this:
userEntry.Invoke("ChangePassword", SenhaAtual, NovaSenha);
You don't need to worry about the authentication because the password can only be changed over a secure connection. In the documentation, it says it behaves the same way as (IADsUser.SetPassword](https://learn.microsoft.com/en-ca/windows/win32/api/iads/nf-iads-iadsuser-setpassword), where it attempts several different ways to achieve a secure connection for you.
There is another way to change the password if the DirectoryEntry connection is already over a secure connection. A secure connection can either be using Kerberos, which can be done using AuthenticationTypes.Sealing (this is best if you are on the same network as the domain controller):
var ADcon = new DirectoryEntry(LDAP, Usuario, SenhaAtual, AuthenticationTypes.Secure | AuthenticationTypes.Sealing);
Or if by using LDAPS (LDAP over SSL), which you can use just by specifying port 636 in the LDAP path (this is the only way if you are not on the same network as the domain controller):
var ADcon = new DirectoryEntry("LDAP://example.com:636", Usuario, SenhaAtual);
If you do that, then you can change the password by updating the unicodePwd attribute directly, in the very specific way it wants it (enclosed in quotes and encoded in UTF-16), like this:
userEntry.Properties["unicodePwd"].Remove(Encoding.Unicode.GetBytes($"\"{SenhaAtual}\""));
userEntry.Properties["unicodePwd"].Add(Encoding.Unicode.GetBytes($"\"{NovaSenha}\""));
This should perform slightly faster since all of the work (changing the password and setting lockOutTime) is done over one network request instead of two.

Coinspot REST API - C#

I'm trying to hit the Coinspot REST API, but I'm getting an error returned. I'm having no trouble talking to Bittrex and Independent Reserve, but Coinspot is a bit different. This is my code:
protected override RESTClient RESTClient { get; } = new RESTClient(new NewtonsoftSerializationAdapter(), new Uri("https://www.coinspot.com.au/api"));
public class postdata
{
public string nonce { get; set; }
}
public string CalculateMD5Hash(string input)
{
//step 1, calculate MD5 hash from input
MD5 md5 = MD5.Create();
var inputBytes = Encoding.ASCII.GetBytes(input);
var hash = md5.ComputeHash(inputBytes);
// step 2, convert byte array to hex string
var sb = new StringBuilder();
for (int i = 0; i < hash.Length; i++)
{
sb.Append(hash[i].ToString("X2"));
}
return sb.ToString();
}
/// <summary>
/// Private IR Call: GetAccounts
/// </summary>
/// <returns></returns>
private async Task<List<AccountHolding>> Balances()
{
//https://github.com/geekpete/py-coinspot-api/blob/master/coinspot/coinspot.py
//var nonce = new Date().getTime();
//var postdata = postdata || { };
//postdata.nonce = nonce;
//var stringmessage = JSON.stringify(postdata);
//var signedMessage = new hmac("sha512", self.secret);
//signedMessage.update(stringmessage);
// 'sign': sign,
//'key': self.key
var nonce = APIHelpers.GetNonce();
var postdata = new postdata { nonce = nonce };
var json = JsonConvert.SerializeObject(postdata);
System.Diagnostics.Debug.WriteLine(json);
var sign = APIHelpers.GetHMACSHAHash(ApiSecret, json, APIHelpers.HMACSHAType.NineBit);
//Do we do this?
//The JavaScript samples seem to hash with MD5 afterwards for double encryption?
sign = CalculateMD5Hash(sign);
RESTClient.Headers.Clear();
RESTClient.Headers.Add("sign", sign);
RESTClient.Headers.Add("key", ApiKey);
try
{
var retVal = await RESTClient.PostAsync<string, postdata>(postdata, "/my/balances");
System.Diagnostics.Debug.WriteLine(retVal);
}
catch (Exception ex)
{
}
throw new NotImplementedException();
}
The doco is very scant! I'm stuck.
https://www.coinspot.com.au/api
I don't have the error handy right now, but it was a completely non-descript error with information about what went wrong. It was something like "invalid call". But, I know that it is accepted my posted data to some extent, because if I change the name of the property "nonce" to "noncey", I get a meaningful error back that says "no nonce".
Did you ever manage to get this API working. CoinSpot are not very supportive of this. I can only get 3 of the coins API working which isn't much help
I managed to get it working recently and put together a simple SDK in .NET
https://github.com/QuintinHumphreys/CoinspotAPI
tl:dr It's undocumented but you need to use port 443, I found it by digging through their node SDK.
I was having the same issue, getting the very non-descriptive {status: invalid} response, in my case using Elixir not C#. I got it to work by peeking into their node SDK - my details worked using their SDK so I knew it had to be something I wasn't doing properly (although their documentation is pretty shocking). They use port 443 and as soon as I set that it worked.
I tried 2 things, I'm 90% sure it was the port number but half way through my getting it to work I printed the sha512 sign created by their node sdk and compared it to the one I generating using Cryptex I saw that they were generating the same sha512 signature, but my one was in capital letters while the node one was in lowercase - this may or may not end up mattering but I did use String.downcase() on mine in the end.

The domain account is locked out when certificate key is accessed with local account

When I access X509Certificate2.PublicKey or X509Certificate2.PrivateKey initialized from an object that was generated with BouncyCastle, I'm getting my domain account locked out (if I do it multiple times). It happens if I run the program on behalf of a local account with the same name but different password. Here is the code:
using Org.BouncyCastle.OpenSsl;
using Org.BouncyCastle.Security;
using System.IO;
using System.Security.Cryptography.X509Certificates;
namespace TestCertificateConversion
{
class Program
{
static void Main(string[] args)
{
var certString = GetCertificateString();
var textReader = new StringReader(certString);
var pemReader = new PemReader(textReader);
var bcCert = pemReader.ReadObject() as Org.BouncyCastle.X509.X509Certificate;
var netCert = DotNetUtilities.ToX509Certificate(bcCert);
var netCert2 = new X509Certificate2(netCert);
var publicKey = netCert2.PublicKey; // this locks out domain account
}
private static string GetCertificateString()
{
return #"-----BEGIN CERTIFICATE-----
MIICvDCCAaSgAwIBAgIQANDHl0sFjYUG3j76dYTadzANBgkqhkiG9w0BAQsFADAQ
MQ4wDAYDVQQDDAVwYWNlbTAgFw0xNjAxMDExMjQ4MzdaGA8yMjAwMDEwMTIyNTg0
N1owEDEOMAwGA1UEAwwFcGFjZW0wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEK
AoIBAQC5AKAkYnerRUmeAX0Z+aZsX39LXTVZiUd8U6waw7Hzd9E0YA50tHWizfEI
w7IBZwXS0aiXwHqJvrslc3NNs0grwu/iYQl+FGdudKmgXVE7Riu0uFAHo6eFr0dO
o0IP3LS+dPSWarXEBLbymaXRiPJDyHLefvslcSM9UQ2BHOe7dnHh9K1h+XMKTw3Z
/3szAyABBX9hsJU/mc9XjrMNXHJXALSxTfLIPzDrfh+aJtlopWpnb6vQcXwKksyk
4hyVUfw1klhglJgN0LgBGU7Ps3oxCbOqns7fB+tzkBV1E5Q97otgvMR14qLZgc8k
NQrdMl57GaWQJl6mAP1NR1gZt2f1AgMBAAGjEDAOMAwGA1UdEwQFMAMBAf8wDQYJ
KoZIhvcNAQELBQADggEBAAEz3vJOfqao9QXPWSz8YCjbWG1FeVG0NdYpd422dC2V
Vrzlo5zrkRv5XPhBOY3o81OhUe7iByiiM9suYfXLNxxd29TBGB5amO8Yv1ZX0hS/
zvVF6QS0+zZvOiujVhfHGiJxKypqgaigI6NM80ZDKPzsRPwFLIJiZYwQ7eQUlrpt
WGgFkZC23/mSOkY6VMmO5zugeMoiXRyFq33uWLlaAr+zJtRh1IPRmkA1lJv0bkC1
SslO0oSDoT2lcvZkQ5odFKX5i1z7T/wioQqG62i8nsDSz+iZOqUyDx7bL8fIEHog
qgwizgr2aAPLO/VQKU9pRTyRNFl/GL5bi7w8NN+rLxE=
-----END CERTIFICATE-----";
}
}
}
I'm not sure what I'm doing wrong, are there any security settings I might need to change to prevent it from locking out domain accounts?
Can you check and confirm if the service account is coming in this format
I checked the .net source code and found what causes an authentication problem in X509Certificate2.PublicKey. It is a creation of a new OID object:
public PublicKey PublicKey {
[SecuritySafeCritical]
get {
if (m_safeCertContext.IsInvalid)
throw new CryptographicException(SR.GetString(SR.Cryptography_InvalidHandle), "m_safeCertContext");
if (m_publicKey == null) {
string friendlyName = this.GetKeyAlgorithm();
byte[] parameters = this.GetKeyAlgorithmParameters();
byte[] keyValue = this.GetPublicKey();
Oid oid = new Oid(friendlyName, OidGroup.PublicKeyAlgorithm, true); // this line
m_publicKey = new PublicKey(oid, new AsnEncodedData(oid, parameters), new AsnEncodedData(oid, keyValue));
}
return m_publicKey;
}
}
The OID constructor is called with lookupFriendlyName set to 'true', which leads to FindOidInfoWithFallback function:
// Try to find OID info within a specific group, and if that doesn't work fall back to all
// groups for compatibility with previous frameworks
internal static string FindOidInfoWithFallback(uint key, string value, OidGroup group)
{
string info = FindOidInfo(key, value, group);
// If we couldn't find it in the requested group, then try again in all groups
if (info == null && group != OidGroup.All)
{
info = FindOidInfo(key, value, OidGroup.All);
}
return info;
}
The first FindOidInfo returns null and then it is called second time with OidGroup.All. Eventually it results in cryptAPI call:
CAPIMethods.CryptFindOIDInfo(dwKeyType, pvKey, dwGroupId);
From documentation:
The CryptFindOIDInfo function performs a lookup in the active
directory to retrieve the friendly names of OIDs under the following
conditions:
The key type in the dwKeyType parameter is set to CRYPT_OID_INFO_OID_KEY or CRYPT_OID_INFO_NAME_KEY.
No group identifier is specified in the dwGroupId parameter or the GroupID refers to EKU OIDs, policy OIDs or template OIDs.
It then attempts to authentication with local user account and as a result I'm getting my domain account locked. From the comments to the code I see that the second FindOidInfo call was added for compatibility with older frameworks and potentially I can remove it. Unfortunately there is no easy was to change the code since it is in the framework itself. I may try to inherit the X509Certificate2 object and rewrite PublicKey and PrivateKey, but I don't really like that idea.

How to strip http:// and www. from an entered domain name using c#

In the application I am working on, we allow user to enter a list of domain names, we expect the user to enter the domain names in any on the following formats
stackoverflow.com
www.stackoverflow.com
http://stackoverflow.com
http://www.stackoverflow.com
but when storing this domain names back into our databases, we want to store the domain name in the following format only
Format : stackoverflow.com
So wanted to know if there is a ready made helper that I can use to get this job done, or any suggestions to do this in an efficient manner.
What have I tried ?
This is what I came up with,
public static string CleanDomainName(string domain)
{
domain = domain.Trim();
if (domain.Split('.').Count() > 2)
{
domain = domain.Split('.')[1] + "." + domain.Split('.')[2];
}
return domain;
}
Please help me out on this.
use Regex to replace the expressions in the beginning of the string:
Regex.Replace(input, #"^(?:http(?:s)?://)?(?:www(?:[0-9]+)?\.)?", string.Empty, RegexOptions.IgnoreCase);
this will replace:
an eventual "http://" in the beginning (or "https://") followed by
an eventual "www." (also with a number following ie: www8 or www23)
Use the Uri class.
string url = "http://www.testsite.com/path/file.html";
Uri uri = new Uri(url);
There are various properties do retrieve different parts of the url.
Use uri.Host to get the www.testsite.com portion of the url. A little string manipulation can remove the www.;
string domain = uri.Host;
if (domain.StartsWith("www."))
{
domain = domain.Substring(4);
}
Use the System.Uri class.
System.Uri uri = new Uri("http://stackoverflow.com/search?q=something");
string uriWithoutScheme = uri.Host.Replace("www.","") + uri.PathAndQuery;
This will give you: stackoverflow.com/search?q=something
public static string CleanDomainName(string url)
{
var uri = url.Contains("http://") ? new Uri(url) : new Uri("http://" + url);
var parts = uri.Host.Split('.');
return string.Join(".", parts.Skip(parts.Length - 2));
}
/// <summary>
/// Clears the sub domains from a URL.
/// </summary>
/// <param name="url">The URL.</param>
/// <returns>String for a URL without any subdomains</returns>
private static string ClearUrlSubDomains(Uri url)
{
var host = url.Host.ToLowerInvariant();
if (host.LastIndexOf('.') != host.IndexOf('.'))
{
host = host.Remove(0, host.IndexOf('.') + 1); // Strip out the subdomain (i.e. remove en-gb or www etc)
}
return host;
}
You can create a new Uri from your domain so calling it will be:
ClearUrlSubDomains(new Uri(domain))
This doesn't address urls like http://www.something.somethingelse.com but can be modified to do so with a while construct of equivalent.

Categories