CertEnroll 509PrivateKey KeyProtection password not working when using client certificate in W8 W10 - x509certificate

We have a process where our clients sign up for X509 client certificate through CertEnroll.
It works, but now one of our clients like to add one extra layer of security, so we added password to the certificate.
User is asked for password when creating the certificate and then have use the password every time the certificate is being used.
It works both ways in Windows 7, but in Windows 8.1 / 10
Browsers are all IE 11.
In Windows 8.1 / 10 the password is asked for when user is applying for the certificate, but when the certificate then is going to be used the password is not asked for.
Hope someone have a clue what is going on here.
Here is the javascript creating the certificate request.
function doSubmit() {
var PublicKeyInfo =''
var request;
request = document.forms(0)
//
// other stuff
//
try {
// Variables
var objCSP = request.Enroll.CreateObject("X509Enrollment.CCspInformation");
var objCSPs = request.Enroll.CreateObject("X509Enrollment.CCspInformations");
var objPrivateKey = request.Enroll.CreateObject("X509Enrollment.CX509PrivateKey");
var objRequest = request.Enroll.CreateObject("X509Enrollment.CX509CertificateRequestPkcs10")
var objObjectIds = request.Enroll.CreateObject("X509Enrollment.CObjectIds");
var objObjectId = request.Enroll.CreateObject("X509Enrollment.CObjectId");
var objX509ExtensionEnhancedKeyUsage = request.Enroll.CreateObject("X509Enrollment.CX509ExtensionEnhancedKeyUsage");
var objExtensionTemplate = request.Enroll.CreateObject("X509Enrollment.CX509ExtensionTemplateName")
var objDn = request.Enroll.CreateObject("X509Enrollment.CX500DistinguishedName")
var objEnroll = request.Enroll.CreateObject("X509Enrollment.CX509Enrollment")
// Initialize the csp object using the desired Cryptograhic Service Provider (CSP)
objCSP.InitializeFromName("Microsoft Enhanced Cryptographic Provider v1.0");
// Add this CSP object to the CSP collection object
objCSPs.Add(objCSP);
objPrivateKey.Length = "2048";
objPrivateKey.KeySpec = 1;
//objPrivateKey.ExportPolicy = 1; // Possible to export PrivateKey
//Force password when request for cert and password when cert is used
objPrivateKey.KeyProtection = 2; // XCN_NCRYPT_UI_FORCE_HIGH_PROTECTION_FLAG
// Provide the CSP collection object (in this case containing only 1 CSP object)
// to the private key object
objPrivateKey.CspInformations = objCSPs;
// Initialize P10 based on private key
objRequest.InitializeFromPrivateKey(1, objPrivateKey, ""); // context user = 1
// 1.3.6.1.5.5.7.3.2 Oid - Extension
objObjectId.InitializeFromValue("1.3.6.1.5.5.7.3.2");
objObjectIds.Add(objObjectId);
objX509ExtensionEnhancedKeyUsage.InitializeEncode(objObjectIds);
objRequest.X509Extensions.Add(objX509ExtensionEnhancedKeyUsage);
objDn.Encode("CN=xxxxxx", 0); // XCN_CERT_NAME_STR_NONE = 0
objRequest.Subject = objDn;
// Enroll
objEnroll.InitializeFromRequest(objRequest);
var pkcs10 = objEnroll.CreateRequest(3); // XCN_CRYPT_STRING_BASE64REQUESTHEADER = 3
request.PublicKeyInfo.value = pkcs10
} catch (ex) {
alert( ex.description + "\n" + ex.error );
return false;
}
request.submit()
}

Problem solved.
When installing the issued certificates it did not work if I installed them as "Local Computer", but if installed as "Current User" it worked for W8.1 / W10 also.

Related

Is it possible in a .NET Core application to retrieve a certificate from AWS Certificate Manager and use it in a HttpClient post?

My .Net core application makes a post request to an external web service using HttpClient. The external web service requires a certificate to validate against.
The certificates are installed in AWS and I have an ARN that points to the certificate.
Is it possible to get the certificate programitically from AWS Certificate Manager and use this in my HtppClient, for example this is the code I would use normally to add a certificate but I need to get it from AWS.
private HttpClientHandler HttpClientHandler()
{
var handler = new HttpClientHandler
{
ClientCertificateOptions = ClientCertificateOption.Manual,
SslProtocols = SslProtocols.Tls12
};
handler.ClientCertificates.Add(new X509Certificate2("cert.crt")); //TODO: fetch from AWS.
return handler;
}
So, it's possible.
I installed AWSSDK.Core and AWSSDK.CertificateManager from NuGet.
Then, I created a credentials file for AWS, see instructions from Amazon
https://docs.aws.amazon.com/cli/latest/userguide/cli-config-files.html
Next, I used the AmazonCertificateManagerClient to get the certificate.
AmazonCertificateManagerClient client = new AmazonCertificateManagerClient();
var certificates = client.GetCertificateAsync(arn).Result;
I then converted the certificate from string to bytes and then add to the handler.
var handler = new HttpClientHandler{
ClientCertificateOptions = ClientCertificateOption.Manual,
SslProtocols = SslProtocols.Tls12
};
byte[] toBytes = Encoding.ASCII.GetBytes(certificates.Certificate);
var cert = new X509Certificate2(toBytes);
handler.ClientCertificates.Add(cert);
var httpClient = new HttpClient(handler);
Obviously, not production worthy code, hope it helps.
As mentioned by Zack the accepted answer does not work. It does retrieve a certificate out of ACM but it is not usable as a client certificate for HttpClient because it has no private key.
As far as I can tell there is no way to get the private key out of ACM so I ended up putting it in SecretsManager and doing something like:
var certManagerClient = new AmazonCertificateManagerClient();
var awsCert = certManagerClient.GetCertificateAsync(arn).Result;
byte[] awsBytes = Encoding.ASCII.GetBytes(awsCert.Certificate);
var cert = new X509Certificate2(awsBytes);
var secretsManagerClient = new AmazonSecretsManagerClient();
var privateKey = secretsManagerClient.GetSecretValueAsync(new GetSecretValueRequest { SecretId = secretId }).Result.SecretString;
byte[] privateKeyBytes = Convert.FromBase64String(privateKey);
var privateKey = RSA.Create();
privateKey.ImportRSAPrivateKey(new ReadOnlySpan<byte>(privateKeyBytes), out _);
var certWithPrivateKey = cert.CopyWithPrivateKey(privateKey);
And then using certWithPrivateKey in my HttpClientHandler:
var handler = new HttpClientHandler { ClientCertificateOptions = ClientCertificateOption.Manual };
handler.ClientCertificates.Add(certWithPrivateKey);
If you use the AWS SDK you can get certificates using the AmazonCertificateManagerClient. See the AWS SDK documentation for details. (select Amazon.CertificateManager > AmazonCertificateManagerClient)

QRS API call returns "The client certificate credentials were not recognized"

Exported Qlik Sense certificate using QMC (client.pfx, root.cer, server.pfx).
Imported certificates into IIS web server using MMC. Server and client certificates to store Personal->Certificates, root to store Trusted Root Certification Authorities.
Requested QRS API from ASP.NET controller using QlikClient certificate from store (code below). Tried various user IDs and directories, including INTERNAL/sa_repository, but in all cases got an error "An error occurred while sending the request. The client certificate credentials were not recognized".
Endpoint for test : https://server:4242/qrs/about
I've searched the web but I haven't managed to find what I'm doing wrong, what credentials I should provide.
On the other hand, as I converted exported certificates to separate .key/.crt files (using https://www.markbrilman.nl/2011/08/howto-convert-a-pfx-to-a-seperate-key-crt-file/) and used them in the Postman from web server, it worked without any problem, actually with any UserId in header (i guess it's ignored in that case).
ASP.NET controller:
public X509Certificate2 LoadQlikCertificate()
{
X509Certificate2 certificate = null;
try
{
// Open certification store (MMC)
X509Store store = new X509Store(StoreName.My, StoreLocation.LocalMachine);
store.Open(OpenFlags.ReadOnly);
// Get certiface based on the friendly name
certificate = store.Certificates.Cast<X509Certificate2>().FirstOrDefault(c => c.FriendlyName == "QlikClient");
// Logging for debugging purposes
if (certificate != null)
{
logger.Log(LogLevel.Warning, $"Certificate: {certificate.FriendlyName} {certificate.GetSerialNumberString()}");
}
else
{
logger.Log(LogLevel.Warning, $"Certificate: No certificate");
}
// Close certification store
store.Close();
// Return certificate
return certificate;
}
catch (Exception e)
{
...
}
}
/* Get Qlik API response
***********************/
[HttpGet("getqlikapi")]
public IActionResult GetQlikAPI()
{
// Get Qlik certificate
var certificate = this.LoadQlikCertificate();
try
{
ServicePointManager.ServerCertificateValidationCallback = delegate { return true; };
// Set server name
string server = "server";
// HARDCODED USER AND DIRECTORY FOR TESTING
string userID = "sa_repository"; // tried also other user ids
string userDirectory = "INTERNAL";
// Set Xrfkey header to prevent cross-site request forgery
string xrfkey = "abcdefg123456789";
// Create URL to REST endpoint
string url = $"https://{server}:4242/qrs/about?xrfkey={xrfkey}";
// The JSON object containing the UserId and UserDirectory
string body = $"{{ 'UserId': '{userID}', 'UserDirectory': '{userDirectory}', 'Attributes': [] }}";
// Encode the json object and get the bytes
byte[] bodyBytes = Encoding.UTF8.GetBytes(body);
// Create the HTTP Request
HttpWebRequest request = (HttpWebRequest)WebRequest.Create(url);
// Add the method to authentication the user
request.ClientCertificates.Add(certificate);
// POST request will be used
request.Method = "POST";
// The request will accept responses in JSON format
request.Accept = "application/json";
// A header is added to validate that this request contains a valid cross-site scripting key (the same key as the one used in the url)
request.Headers.Add("X-Qlik-Xrfkey", xrfkey);
request.ContentType = "application/json";
request.ContentLength = bodyBytes.Length;
Stream requestStream = request.GetRequestStream();
requestStream.Write(bodyBytes, 0, bodyBytes.Length);
requestStream.Close();
// Make the web request and get response
HttpWebResponse response = (HttpWebResponse)request.GetResponse();
Stream stream = response.GetResponseStream();
// Return string in response
//return new OkObjectResult(stream != null ? new StreamReader(stream).ReadToEnd() : string.Empty);
return new OkObjectResult("test");
}
catch (Exception e)
{
...
}
}
I ran into this issue on a system we are building.
The problem was that the user did not have rights to the certificate.
Open certificate manager (Start > Manage Computer Certificates)
Find the required certificate.
Right-click cert > All Tasks > Manage Private Keys > Add > [Select the appropriate user]
Note: Manage User Certificates does not have the Manage Private Keys option.

Complete ADFS login programmatically

I need to perform a complete AFDS login action in code. I cannot redirect the user to the ADFS login page. The user has already authenticated using a custom authentication mechanism and I use the same credentials to authenticate to ADFS, this to enable SSO to a SAP EP.
I can successfully retrieve a SAML token from the ADFS but SAP apparently can only handle the out of the box authentication. So I will need to authenticate the entire session.
This is what I have right now:
Retrieve the token:
var binding = new WS2007HttpBinding();
binding.Security.Message.EstablishSecurityContext = false;
binding.Security.Message.NegotiateServiceCredential = false;
binding.Security.Transport.ClientCredentialType = HttpClientCredentialType.None;
binding.Security.Message.ClientCredentialType = MessageCredentialType.UserName;
binding.Security.Mode = SecurityMode.TransportWithMessageCredential;
var trustChannelFactory = new WSTrustChannelFactory(binding, new EndpointAddress(AppSettings.AdfsUrl));
trustChannelFactory.TrustVersion = TrustVersion.WSTrust13;
trustChannelFactory.Credentials.UserName.UserName = user.UserName;
trustChannelFactory.Credentials.UserName.Password = PasswordService.Decrypt(user.UserPassword, user.UserID.ToString(CultureInfo.InvariantCulture));
trustChannelFactory.ConfigureChannelFactory();
// Create issuance issuance and get security token
var requestToken = new RequestSecurityToken(WSTrust13Constants.RequestTypes.Issue);
requestToken.AppliesTo = new EndpointAddress(AppSettings.ServicePortalUrl);
requestToken.KeyType = WSTrust13Constants.KeyTypes.Bearer;
var tokenClient = (WSTrustChannel) trustChannelFactory.CreateChannel();
var token = tokenClient.Issue(requestToken) as GenericXmlSecurityToken;
return token;
And an attempt to get the claims so I could perhaps put the user principal in the HttpContext before redirecting to the SAP Portal. (Long shot)
var tokenHandlers = new SecurityTokenHandlerCollection(new SecurityTokenHandler[] { new SamlSecurityTokenHandler() });
tokenHandlers.First().Configuration.AudienceRestriction.AudienceMode = AudienceUriMode.Never;
tokenHandlers.First().Configuration.CertificateValidationMode = X509CertificateValidationMode.None;
tokenHandlers.Configuration.CertificateValidationMode = X509CertificateValidationMode.None;
var trusted = new TrustedIssuerNameRegistry("*.domain.com");
tokenHandlers.Configuration.IssuerNameRegistry = trusted;
var samlToken = tokenHandlers.ReadToken(new XmlTextReader(new StringReader(token.TokenXml.OuterXml)));
var claimsPrincipal = new ClaimsPrincipal(tokenHandlers.ValidateToken(samlToken).First());
HttpContext.Current.User = claimsPrincipal;
This does not work, as I keep on getting X509 certificate validation errors.
What I've tried:
Providing the SAML signature as MYSAPSSO2 token (long shot, did not work)
Putting the user principal in the HTTP context as I saw that SAP looks for an IPrincipal in the HTTP context. (Can't get it to work)
Set the MSISAuthenticated cookie, but have not idea how to get the value (base64 timestamp of moment of authentication?)
Is there any obvious way that I'm overseeing? Basically, I just want to perform the same authentication the ADFS login page does, but in code, so the user doesn't see a second login page.
Try it like this ...
// ######################### TOKEN HANDLER ########################################################################################################################
var genericToken = token as GenericXmlSecurityToken;
var handlers = FederatedAuthentication.FederationConfiguration.IdentityConfiguration.SecurityTokenHandlerCollectionManager.SecurityTokenHandlerCollections.First();
// ######################## HANDLE AudienceRestriction programatically.############################################################################################
//handlers.Configuration.AudienceRestriction.AudienceMode.Equals(0);
handlers.Configuration.AudienceRestriction.AllowedAudienceUris.Add(new Uri (svcEndpoint)); // porgramatically validate AllowedAudienceUris. This is your RP. https://learn.microsoft.com/en-us/dotnet/api/system.identitymodel.selectors.audienceurimode?view=netframework-4.8#fields
// ##### HANDLE STS SIGNING CERTIFICATE VALIDATIONS. Set to none for ADFS autocertificaterollover.
handlers.Configuration.CertificateValidationMode = X509CertificateValidationMode.None; //Also set in App.config / web.config
// ########################## HANDLE IssuerNameRegistry automatically.##############################################################################################
// ######################## READ METADATA OF ADFS TO EXTRACT SIGNING CERTIFICATE ###################################################################################
AdfsMetadataService svc = new AdfsMetadataLoader("https://" + opts.Farmname + "/FederationMetadata/2007-06/FederationMetadata.xml");
var metadata = svc.Get();
string IdP = metadata.Result.Identity;
string stringSigningCert = metadata.Result.SigningCertificateString;
// ####################### CONVERT FROM BASE64 TO READ SIGNING CERTIFICATE THUMBPRINT AND SUBJECT TO USE IN ISSUERNAMEREGISTRY #####################################
byte[] bytes = Convert.FromBase64String(stringSigningCert);
var AdfsSigncert = new X509Certificate2(bytes);
//Console.WriteLine(IdP + stringSigningCert + cert.Thumbprint + cert.Subject);
var registry = new ConfigurationBasedIssuerNameRegistry();
registry.AddTrustedIssuer(AdfsSigncert.Thumbprint, AdfsSigncert.Subject);
handlers.Configuration.IssuerNameRegistry = registry;
var cToken = handlers.ReadToken(new XmlTextReader(new StringReader(genericToken.TokenXml.OuterXml)));
var identity = handlers.ValidateToken(cToken).First();
var userIdenity = new ClaimsPrincipal(identity);
Console.WriteLine("Successfully Authenticated with identity type userIdenity.Identity.Name with value ~~~ " + userIdenity.Identity.Name);
foreach (var c in userIdenity.Claims)
{
Console.WriteLine("Claim Type = " + c.Type + " ~~~ Claim Value = " + c.Value);
}

Creating Google Drive DriveService with existing access token

I am using ASP.NET Web API and Google.Apis.Drive.v2 Client Library for .NET to upload files to users Drive.
All examples of using the Drive Client Library for .NET require a authentication flow. But how should I create the DriveService when I already know the access token?
Despite the fact that have been 2 years since the question has been asked, today I've encountered the same situation and my solution is:
var valid_token = "Pass_the_valid_token_here";
var token = new Google.Apis.Auth.OAuth2.Responses.TokenResponse()
{
AccessToken = valid_token,
ExpiresInSeconds = 3600,
Issued = DateTime.Now
};
var fakeflow = new GoogleAuthorizationCodeFlow(new GoogleAuthorizationCodeFlow.Initializer
{
ClientSecrets = new ClientSecrets
{
ClientId = "fakeClientId",
ClientSecret = "fakeClientSecret"
}
});
UserCredential credential = new UserCredential(fakeflow, "fakeUserId", token);
var serviceInitializer = new BaseClientService.Initializer()
{
//ApplicationName = "Storage Sample",
HttpClientInitializer = credential
};
DriveService service = new DriveService(serviceInitializer);
Update
You could create your own custom token but the issue with this is going to be that the client library will not be able to refresh your access without the refresh token.
var token = new Google.Apis.Auth.OAuth2.Responses.TokenResponse()
{
AccessToken = valid_token,
ExpiresInSeconds = 3600,
Issued = DateTime.Now
};
var authorization = new GoogleAuthorizationCodeFlow(new GoogleAuthorizationCodeFlow.Initializer
{
ClientSecrets = new ClientSecrets
{
ClientId = "lientId",
ClientSecret = "ClientSecret"
}
});
var credential = new UserCredential(authorization, "user", token);
The issue you are going to have with this is that the client library is not going to be able refersh the access token after it has expired since you are not supplying a refresh token its only going to work for an hour.
The answer from Svetoslav Georgiev has so far worked well for me - Can't thank you enough. Google really don't help themselves with the lack of .Net (Asp Core) samples etc. Anway, one problem I did run into was that of referer restriction, so a addition/slight modification to the answer - Once you have the "service" and want to say upload a file, you need to set the referer on a buried HttpClient property...
FilesResource.CreateMediaUpload uploadRequest;
byte[] byteArray = Encoding.UTF8.GetBytes(html);
using (var stream = new MemoryStream(byteArray))
{
uploadRequest = service.Files.Create(fileMetadata, stream, "text/html");
uploadRequest.Service.HttpClient.DefaultRequestHeaders.Referrer = new Uri($"{baseUrl}");
uploadRequest.Fields = "id";
var progress = uploadRequest.Upload();
if (progress.Exception != null)
{
throw progress.Exception;
}
var file = uploadRequest.ResponseBody;
.... do what you will with file ....
}

how to skip facebook app permissions dialog

Here, I am trying to authenticate user via login and after that I want to skip permissions dialog. But I am unable to achieve this, as it always asking for permissions for app to the user. My intention is if user is not logged into the facebook he/she should be prompted for facebook login and then I will fetch public information by using method Get("/me"). Let me know what I am doing wrong here.
public string GetFBAccessToken(string strAppID, string strAppSecret, string strUrl)
{
// Declaring facebook client type
var vFB = new FacebookClient();
string strAccessTok = string.Empty;
try
{
if (!string.IsNullOrEmpty(strAppID) && !string.IsNullOrEmpty(strAppSecret) && !string.IsNullOrEmpty(strUrl))
{
// Getting login url for facebook
var loginUrl = vFB.GetLoginUrl(new
{
client_id = strAppID,
client_secret = strAppSecret,
redirect_uri = strUrl,
response_type = "code",
state = "returnUrl",
//scope = "",
display = "popup"
});
// Redirecting the page to login url
if (HttpContext.Current.Request.QueryString["code"] == null)
{
HttpContext.Current.Response.Redirect(loginUrl.AbsoluteUri);
}
// Fetching the access token from query string
if (HttpContext.Current.Request.QueryString["code"] != null)
{
dynamic result = vFB.Post("oauth/access_token", new
{
client_id = strAppID,
client_secret = strAppSecret,
redirect_uri = strUrl,
code = HttpContext.Current.Request.QueryString["code"]
});
// Getting access token and storing in a variable
strAccessTok = result.access_token;
}
}
return strAccessTok;
}
catch (Exception ex)
{
//if (HttpContext.Current.Request.QueryString["response_type"] == "code")
//{
// var fb = new FacebookClient();
// var details = fb.Get("/me");
//}
return strAccessTok;
}
}
Regardless to the platform/ language you are using; solution can be as follows.
check use's logged in status. https://developers.facebook.com/docs/reference/javascript/FB.getLoginStatus/
based on Response status, forcefully call your action (i.e. Log in, Get Permission or any additional action if user is already connected). For Log in check this reference document from FB. https://developers.facebook.com/docs/facebook-login/login-flow-for-web/
No. You cannot skip the Login Dialog.
In fact, it is really important for an APP owner to build a trust relationship with your users. I would recommend you to follow the Login Best Practices while authenticating the users using your APP.

Resources