How to programmatically sign HCK submission with Extended Validation certificate - c#

We have a small app written in C# that we use to sign *.hckx files before they are submitted to Microsoft for signing.
The application code look +/- like this:
var workDirectory = new System.IO.DirectoryInfo(args[0]);
var filesToSign = from item in workDirectory.GetFiles("*.hckx", System.IO.SearchOption.TopDirectoryOnly) select item.FullName;
X509Certificate2 certificate = getCerticifate();
foreach (var item in filesToSign)
{
Console.WriteLine("Signing: {0}", item);
Microsoft.Windows.Kits.Hardware.ObjectModel.Submission.PackageManager.Sign(item, certificate);
Console.WriteLine("Signing finished");
var manager = new Microsoft.Windows.Kits.Hardware.ObjectModel.Submission.PackageManager(item);
Console.WriteLine("Verifying the signature.");
var signResult = manager.VerifySignature();
if (signResult != System.IO.Packaging.VerifyResult.Success)
{
throw new Exception(String.Format("Verification failed. Expected: {0}, but the result was: {1}.", System.IO.Packaging.VerifyResult.Success, signResult));
}
}
That code works with previous, "regular", certificate.
With new EV certificate there is an additional window displayed that asks for PIN to the certificate.
So the question is:
is there an interface/class that allows to make full sign with EV certificate programmatically?
I would expect PackageManager.Sign method with possibility to provide the PIN as a parameter.

This works for us: in our system there is the EV certificate USB token related "authentication agent" and there in the advanced client settings there is a setting for "single logon" for the agent to ask the USB token password only once and keeps the password until the desktop session is closed. For the desktop check also that no screensaver closes the open desktop. There is also an additional timer setting for "automatic logoff" in the agent settings. We set it to "never". We only need to give the EV token password again if we need to remove the token and attach it back to the system. Then we just run a small script to do a test sign and give the password in the opening dialog.

Related

Not able to Set Password and Enable Account using C# and admin User

Using WPF & C#, I can set all the attributes in Active Directory, but can't do the following :
1) Can't Set User Password
2) Can't Enable User
However, I can do the same thing manually!
Approach Tried:
1.
DirectoryEntry directoryEntry=
directoryEntry.Invoke("SetPassword", new object[] {myPass#x6712}); // To set password
directoryEntry.Properties["userAcountControl"].Value=0x0200; //To Enable User
2.
DirectoryEntry uEntry = new DirectoryEntry(userDn);
uEntry.Invoke("SetPassword", new object[] { password });
uEntry.Properties["LockOutTime"].Value = 0; //unlock account
3.
using (var context = new PrincipalContext( ContextType.Domain ))
{
using (var user = UserPrincipal.FindByIdentity( context, IdentityType.SamAccountName, userName ))
{
user.SetPassword( "newpassword" );
// or
user.ChangePassword( "oldPassword", "newpassword" );
user.Save();
}
}
ERROR ON PASSWORD SET: Exception has been thrown by the target invocation.
ERROR ON ENABLE USER: Access is denied.
NOTE: I'm using a Domain Admin User.
The program gives the exception in these above lines.
Please, Advice! Thanks in Advance !!
Maybe this is just a mistake in your question, but the code you show in your first example wouldn't compile because the password is not in quotes. It should be:
directoryEntry.Invoke("SetPassword", new object[] {"myPass#x6712"});
That code invokes IADsUser.SetPassword. The 'Remarks' in the documentation point to some prerequisites for it to work, namely, that it must be a secure connection. So it may have failed in setting up a secure connection. It would usually try Kerberos to do that, so something might have gone wrong there.
You can try specifically connecting via LDAPS (LDAP over SSL) by pointing it at port 636 (new DirectoryEntry("LDAP://example.com:636/CN=whatever,DC=example,DC=com")), but that requires that you trust the certificate that is served up. Sometimes it's a self-signed cert, so you would need to add the cert to the trusted certs on whichever computer you run this from.
Or, the account you are running it with does not have the 'Reset Password' permission on the account.
For enabling, the userAccountControl attribute is a bit flag, so you don't want to set it to 2, mostly because 2 (or more accurately, the second bit) means that it's disabled. So you want to unset the second bit. You would do that like this:
directoryEntry.Properties["userAcountControl"].Value =
(int) directoryEntry.Properties["userAcountControl"].Value & ~2;
Most of the time that will result in a value of 512 (NORMAL_ACCOUNT), but not necessarily. The account could have other bits set that you don't want to inadvertently unset.
You also need to call .CommitChanges() for the changes to userAcountControl to take effect:
directoryEntry.CommitChanges();

LDAP search fails on server, not in Visual Studio

I'm creating a service to search for users in LDAP. This should be fairly straightforward and probably done a thousand times, but I cannot seem to break through properly. I thought I had it, but then I deployed this to IIS and it all fell apart.
The following is setup as environment variables:
ldapController
ldapPort
adminUsername 🡒 Definitely a different user than the error reports
adminPassword
baseDn
And read in through my Startup.Configure method.
EDIT I know they are available to IIS, because I returned them in a REST endpoint.
This is my code:
// Connect to LDAP
LdapConnection conn = new LdapConnection();
conn.Connect(ldapController, ldapPort);
conn.Bind(adminUsername, adminPassword);
// Run search
LdapSearchResults lsc = conn.Search(
baseDn,
LdapConnection.SCOPE_SUB,
lFilter,
new string[] { /* lots of attributes to fetch */ },
false
);
// List out entries
var entries = new List<UserDto>();
while (lsc.hasMore() && entries.Count < 10) {
LdapEntry ent = lsc.next(); // <--- THIS FAILS!
// ...
}
return entries;
As I said, when debugging this in visual studio, it all works fine. When deployed to IIS, the error is;
Login failed for user 'DOMAIN\IIS_SERVER$'
Why? The user specified in adminUsername should be the user used to login (through conn.Bind(adminUsername, adminPassword);), right? So why does it explode stating that the IIS user is the one doing the login?
EDIT I'm using Novell.Directory.Ldap.NETStandard
EDIT The 'user' specified in the error above, is actually NOT a user at all. It is the AD registered name of the computer running IIS... If that makes any difference at all.
UPDATE After consulting with colleagues, I set up a new application pool on IIS, and tried to run the application as a specified user instead of the default passthrough. Exactly the same error message regardless of which user I set.
Try going via Network credentials that allows you to specify domain:
var networkCredential = new NetworkCredential(userName, password, domain);
conn.Bind(networkCredential);
If that does not work, specify auth type basic (not sure that the default is) before the call to bind.
conn.AuthType = AuthType.Basic;

Reading all certificates from asp.net

I have a problem with reading certificates. I have a web service that has to get a certificate serial number using part of the subject. Everything works fine if I'm doing it from a form but when I try it from a web service it seems that it cannot find any certificate. I'm using this code to read all of the the certificates:
X509Store store = new X509Store();
store.Open(OpenFlags.ReadOnly);
if (args.Parameters["CertificateName"].ToString() != "")
{
foreach (X509Certificate2 mCert in store.Certificates)
{
if (mCert.Subject.Contains("OU=" + args.Parameters["CertificateName"].ToString()))
{
SerialNum = mCert.SerialNumber;
break;
}
}
if (SerialNum == String.Empty)
{
throw new Exception("Certificate not found with name: " + args.Parameters["CertificateName"].ToString() + " ;" + " OU=" + args.Parameters["CertificateName"]);
}
}
else
{
foreach (X509Certificate2 mCert in store.Certificates)
{
if (mCert.Subject.Contains("OU=Eua"))
{
SerialNum = mCert.SerialNumber;
break;
}
}
if (SerialNum == String.Empty)
{
throw new Exception("Haven't found default certificate ;");
}
}
store=null;
You are using the parameterless constructor for X509Store which according to the documentation will open the cert store for the current user. Well, the current user for your forms application is probably not the same as the current user for your web application, which most likely runs within an AppDomain configured to use a service account. So that means the web application won't be able to find it.
To fix this, you have two options
Option 1
First store your certificate in the machine store (not the user store). Then, in your code, open the store using a different constructor that lets you specify the store location, and specify that you want the machine store. Like this:
var store = new X509Store(StoreLocation.MachineStore);
Option 2
Maintain two copies of the certificate. Follow these steps:
Export the certificate from your current user's cert store
Start certificate manager using "RunAs" to impersonate the service account for the app domain, e.g. runas /user:MyDomain\MyServiceAccount "cmd /c start /B certmgr.msc". When prompted make sure you tell it you want to work with the current user's cert store, not the machine store.
Import the certificate there
Open the cert up and make sure its chain of trust is intact; if any intermediate or root certs are missing, you may have to import those as well.
Remember when this cert expires, you will have to replace both copies.

UWP app HttpClient HTTPS client certificate problems

I'm writing a UWP app in C# that is eventually destined for IoT, but right now I've only been debugging locally. I'm using Windows.Web.Http.HttpClient to connect to a self-hosted WCF REST web service that I've also written and have running as a Console app on the same machine for testing. The service requires mutual authentication with certificates, so I have a CA cert, service cert, and client cert.
My UWP code works like this:
Check app cert store for client cert and CA cert installed.
If not, install from PFX file and CER file, respectively.
Attach the Certificate to the HttpBaseProtocolFilter and add the filter to the HttpClient
Call the HttpClient.PostAsync
After I call PostAsync I get the following error: An Error Occurred in the Secure Channel Support. After plenty of searching online, and by common sense, I'm pretty sure HttpClient is barfing because of a problem establishing the mutually-authenticated SSL connection. But based on my troubleshooting I can't figure why.
To troublshoot further, I've written a plain old Console app using System.Net.Http.HttpClient, attached the client certificate to the request and everything works great. Sadly, System.Net isn't fully supported on UWP. I've also tried NOT attaching the certificate to the UWP HttpClient and the app prompts me with a UI to select an installed certificate. I select the correct cert and still get the same exception (this at least lets me know the cert is installed correctly and validating properly with the CA from the app's perspective). In additon, I hit the GET on the web service from a browser, select the client cert when prompted, and am able to download a file.
I've tried using Fiddler and, I assume because of the way it proxies traffic, it seems to work a little bit further, except my web service rejects the request as Forbidden (presumably because Fiddler is not including the correct client cert in the request). I haven't hit up Wireshark yet because it's a pain to get Wireshark to work using localhost on Windows.
My next step is to start changing the web service to not require client authentication and see if that is the problem.
Two questions: Why is Windows.Web.Http.HttClient not working in this case? And, less important, any recommendations on good HTTP monitoring tools to help me debug this further?
This MSDN post proved to have the answer. Seems like an oversight on MS part requiring a separate, meaningless call to the API beforehand. Oh well.
http://blogs.msdn.com/b/wsdevsol/archive/2015/03/26/how-to-use-a-shared-user-certificate-for-https-authentication-in-an-enterprise-application.aspx
Excerpt from the article:
However, the security subsystem requires user confirmation before allowing access to a certificates private key of a certificate stored in the shared user certificates store. To complicate matters, if a client certificate is specified in code then the lower level network functions assume the application has already taken care of this and will not prompt the user for confirmation.
If you look at the Windows Runtime classes related to certificates you won’t find any method to explicitly request access to the certificate private key, so what is the app developer to do?
The solution is to use the selected certificate to 'Sign' some small bit of data. When an application calls CryptographicEngine.SignAsync, the underlying code requests access to the private key to do the signing at which point the user is asked if they want to allow the application to access the certificate private key. Note that you must call 'Async' version of this function because the synchronous version of the function: Sign, uses an option that blocks the display of the confirmation dialog.
For example:
public static async Task<bool> VerifyCertificateKeyAccess(Certificate selectedCertificate)
{
bool VerifyResult = false; // default to access failure
CryptographicKey keyPair = await PersistedKeyProvider.OpenKeyPairFromCertificateAsync(
selectedCertificate, HashAlgorithmNames.Sha1,
CryptographicPadding.RsaPkcs1V15);
String buffer = "Data to sign";
IBuffer Data = CryptographicBuffer.ConvertStringToBinary(buffer, BinaryStringEncoding.Utf16BE);
try
{
//sign the data by using the key
IBuffer Signed = await CryptographicEngine.SignAsync(keyPair, Data);
VerifyResult = CryptographicEngine.VerifySignature(keyPair, Data, Signed);
}
catch (Exception exp)
{
System.Diagnostics.Debug.WriteLine("Verification Failed. Exception Occurred : {0}", exp.Message);
// default result is false so drop through to exit.
}
return VerifyResult;
}
You can then modify the earlier code example to call this function prior to using the client certificate in order to ensure the application has access to the certificate private key.
Add the Certificate file your Project
Add the Certificate to the Manifested file (give file path in attachment)
the Frist Service Call of in Ur Project use to ignore the certificate validation Following Code is most Suitable for Login Function.
try
{
var filter = new HttpBaseProtocolFilter();
filter.IgnorableServerCertificateErrors.Add(ChainValidationResult.Expired);
filter.IgnorableServerCertificateErrors.Add(ChainValidationResult.Untrusted);
filter.IgnorableServerCertificateErrors.Add(ChainValidationResult.InvalidName);
filter.IgnorableServerCertificateErrors.Add(ChainValidationResult.RevocationFailure);
filter.IgnorableServerCertificateErrors.Add(ChainValidationResult.RevocationInformationMissing);
filter.IgnorableServerCertificateErrors.Add(ChainValidationResult.WrongUsage);
filter.IgnorableServerCertificateErrors.Add(ChainValidationResult.IncompleteChain);
Windows.Web.Http.HttpClient client = new Windows.Web.Http.HttpClient(filter);
TimeSpan span = new TimeSpan(0, 0, 60);
var cts = new CancellationTokenSource();
cts.CancelAfter(span);
var request = new Windows.Web.Http.HttpRequestMessage()
{
RequestUri = new Uri(App.URL + "/oauth/token"),
Method = Windows.Web.Http.HttpMethod.Post,
};
//request.Properties. = span;
string encoded = System.Convert.ToBase64String(System.Text.Encoding.GetEncoding("ISO-8859-1").GetBytes(Server_Username + ":" + Server_Password));
var values = new Dictionary<string, string>
{ { "grant_type", "password" },{ "username", Uname}, { "password", Pwd }};
var content = new HttpFormUrlEncodedContent(values);
request.Headers.Add("Authorization", "Basic " + encoded);
request.Content = content;
User root = new User();
using (Windows.Web.Http.HttpResponseMessage response = await client.SendRequestAsync(request).AsTask(cts.Token))
{
HttpStatusCode = (int)response.StatusCode;
if (HttpStatusCode == (int)HttpCode.OK)
{
using (IHttpContent content1 = response.Content)
{
var jsonString = await content1.ReadAsStringAsync();
root = JsonConvert.DeserializeObject<User>(jsonString);
App.localSettings.Values["access_token"] = root.Access_token;
App.localSettings.Values["refresh_token"] = root.Refresh_token;
App.localSettings.Values["expires_in"] = root.Expires_in;
var json = JsonConvert.SerializeObject(root.Locations);
App.localSettings.Values["LocationList"] = json;
App.localSettings.Values["LoginUser"] = Uname;
}
}
}
}
catch (Exception ex)
{
ex.ToString();
}

Enroll new user in 2-Step verification through the Google API

We are using Google API to create new google accounts (users and their emails).
New requirement is that we should support 2-Step authentication enabled in admin.google.com (for sub-organization) and we need to enforce the rule.
Now comes the problem: If we create new user in this sub-org it will try to enforce 2-Step authentication and, as it is not setup, user will not be able to login to set it up. And admin cannot setup 2-step verification for the user.
Even more... I need to be able to setup users 2-step verification through the API.
Does workaround for this exist, or does anyone have any idea how to do it?
Any suggestions are welcome,
thanks
UPDATE 1
Thanks to Jay Lee's answer I am expanding a bit with working C# code using Google.Apis.Admin.Directory.directory_v1 SDK
private string GenerateVerificationCode(string userKey)
{
var _service = new DirectoryService(new BaseClientService.Initializer()
{
HttpClientInitializer = credential,
ApplicationName = _applicationName,
});
var generateVerificationCodesRequest = _service.VerificationCodes.Generate(userKey);
generateVerificationCodesRequest.Execute();
var verificationCodesRequest = _service.VerificationCodes.List(userKey);
var verificationCodes = verificationCodesRequest.Execute();
var verificationCode = verificationCodes.Items[0].VerificationCodeValue;
return verificationCode;
}
You can:
Make sure user is created in an OU where 2SV is forced. Set the orgUnitPath attribute when calling users.create()
Call VerificationCodes.generate() for the new user to create backup codes to get backup 2SV codes for the user.
Share the backup codes with the new user along with their password and instructions for first login and setup of 2SV.
User will be able to pass 2SV with the backup codes for first login. Then they can setup normal 2SV via SMS or app. You'll want to provide new users with a good set of detailed instructions for this process as it does complicate onboarding but it means they are secure on day one.

Categories