certificate is not validating while calling external api from azure function - https

i am working on an azure function (app service plan) which calls and external api. that api is secured by a certificate.
i have the certificate uploaded in azure function SSL settings. i also have relevant thumbprint in Azure Functions settings.
i am able to pickup the exact same certificate by using its thumbprint.
X509Store certStore = new X509Store(StoreName.My, StoreLocation.CurrentUser);
certStore.Open(OpenFlags.ReadOnly);
var cert1 = certStore.Certificates.Find(
X509FindType.FindByThumbprint,
"xxxxxxxxxxxxxx",
false)[0];
log.LogInformation(cert1.Subject);
but when i make a call using HttpClient i get and SSL error
var _clientHandler = new HttpClientHandler();
_clientHandler.UseDefaultCredentials = false;
_clientHandler.ClientCertificateOptions = ClientCertificateOption.Automatic;
using (var client = new HttpClient(_clientHandler))
{
try
{
client.DefaultRequestHeaders.Accept.Clear();
var resp = await client.GetAsync("https://xxxxxxxxxxxx");
i dont want to bypass validation but to check whats going on i added this code and the chain status is "UntrustedRoot"
_clientHandler.ServerCertificateCustomValidationCallback =
(sender, cert, chain, sslPolicyErrors) =>
{
log.LogInformation(chain.ChainStatus[0].Status.ToString());
return true;
};
what is that i am doing wrong ?

what is that i am doing wrong ?
Nothing. Your client certificate looks like it's probably being properly attached to the request. The server cert validation callback says that the machine executing the request doesn't trust the certificate chain from the server it sent the request to.
If you compare the certificate chain that you see in Azure (e.g. foreach (var elem in chain.ChainElements) { log.LogInformation(elem.Certificate.Subject) }) to one you see from another source and they're the same, then you can make your callback something more like
_clientHandler.ServerCertificateCustomValidationCallback =
(sender, cert, chain, sslPolicyErrors) =>
{
if (sslPolicyErrors == SslPolicyErrors.RemoteCertificateChainErrors)
{
X509ChainStatusFlags flags = chain.ChainStatus.Aggregate(
X509ChainStatusFlags.NoError,
(f, s) => f | s.Status);
if (flags == X509ChainStatusFlags.UntrustedRoot)
{
X509Certificate2 presentedRoot =
chain.ChainElements[chain.ChainElements.Length - 1].Certificate;
return presentedRoot.RawData.SequenceEqual(s_pinnedRootBytes);
}
}
return sslPolicyErrors == SslPolicyErrors.None;
};
Which will only return true if either:
It would have succeeded without a custom callback
The only thing wrong with the chain is that it had an untrusted root and the root for the chain is byte-for-byte equal to a copy of the root certificate you've already saved. (Hard coded, external resource, whatever.)

Related

Implementing private_key_jwt and client_secret_jwt with Identity Server 4; providing client credentials using a JWT token

Using Identity Server 4, how do you hook into the exchange between the client and server when using the authorization_code flow to provide Client credentials to the Identity Server using a JWT Token?
Below is the solution:
In ConfigureServices the is key to hook into the Identity Server pipeline and provide a call-back for the event OnAuthorizationCodeReceived. This event is called at the point in the pipeline where the authorization code is received back from Identity server during the normal exchange between the client and server as described by https://www.ietf.org/rfc/rfc6750.txt.
Doing this give you the opportunity to create the JWT token and make it available from that point on in the pipeline.
Configuration on the client
services.AddAuthentication(options =>
...
.AddOpenIdConnect(OpenIdConnectDefaults.AuthenticationScheme, options =>
{
options.SignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.RemoteAuthenticationTimeout = TimeSpan.FromMinutes(10);
options.UseTokenLifetime = false;
options.RequireHttpsMetadata = false;
options.Authority = "http://localhost:44320/";
options.ClientId = "cliend-id";
options.ClientSecret = "client-secret";
options.ResponseType = "code";
options.GetClaimsFromUserInfoEndpoint = true;
options.SaveTokens = true;
options.Events.OnAuthorizationCodeReceived = delegate (AuthorizationCodeReceivedContext context)
{
var clientassertion = new ClientAssertion("client-id", "http://localhost:44320/connect/token");
var assertion = clientassertion.CreateJwtClientAssertionAsymmetric("localhost");
context.TokenEndpointRequest.ClientAssertion = assertion.ClientAssertion;
context.TokenEndpointRequest.ClientAssertionType = assertion.ClientAssertionType;
return Task.CompletedTask;
};
...
Configuration on the server
As http://docs.identityserver.io/en/release/topics/secrets.html?highlight=beyond indicates under the section beyond shared secrets.
The important bit here is to ensure the type and value are aligned as in the example below.
var client = new Client
{
...
ClientSecrets =
{
new Secret
{
Type = IdentityServerConstants.SecretTypes.X509CertificateBase64,
Value = "MIIDATCC..."
}
},
AllowedGrantTypes = GrantTypes.CodeAndClientCredentials,
AllowedScopes = { "api1", "api2" }
};
Implementation
Implement the interfaces ISecretParser and ISecretValidator
Then add to implementations to the DI system in ConfigureServices.
Eg.
builder.AddSecretParser()
builder.AddSecretValidator()
If your Validator is not getting called, ensure RequireClientSecret is set to true.
Take the appropriate action in the parser, and validator (from the parse return success for failure).
This approach work for both private_key_jwt and client_secret_jwt.

Node Express as proxy for a server that requires basic authentication

So I'm using express and express-http-proxy as a proxy to an API web server that requires basic authentication. Then in my app, I'll be issuing Ajax calls to these APIs. After some effort I got this working but I'm sure there's a better way out there, hence this post.
Initially I set up the express proxy as follows:
var express = require('express'),
app = express(),
proxy = require('express-http-proxy');
app.use('/apis', proxy("https://myserver", {
forwardPath: function(req, res) {
return "/apis" + require('url').parse(req.url).path;
}
}));
When calling a URL directly in the browser (not via Ajax), eg. https://myserver/apis/myapi.ashx, I would see the authentication dialog asking me for my credentials, and I could authenticate and see the result.
However, when accessing the same URL via an Ajax call in my app, I was not getting a popup. Why this difference of behavior?
So I decided I needed to add my own basic authentication middleware using request and basic-auth as follows:
var express = require('express'),
app = express(),
proxy = require('express-http-proxy'),
request = require('request'),
basicAuth = require('basic-auth');
var myAuth = function (req, res, next) {
function unauthorized(res) {
res.set('WWW-Authenticate', 'Basic realm=Rimes');
return res.sendStatus(401);
};
var user = basicAuth(req);
if (!user || !user.name || !user.pass) {
return unauthorized(res);
};
var connectUrl = 'https://'+user.name+':'+user.pass+'#myserver/apis/connect.ashx';
request.get(connectUrl, function(error, response, body) {
if (!error && response.statusCode == 200) {
return next();
} else {
return unauthorized(res);
}
});
};
app.use('/apis', proxy("https://myserver", {
forwardPath: function(req, res) {
return "/apis" + require('url').parse(req.url).path;
}
}));
This worked fine, showing me the authentication popup during the Ajax call.
The obvious disadvantage here is:
Credential verification for every API request, although there may be a way to cache valid credentials. But in its defence, this is only on the development environment.
So is there a better way of doing this? Would a different middleware package do a better job?

How to implement SIGNED Authorization Type in Gmail Contextual Gadget

I'm deploying an in-house Gmail contextual gadget from an existing code. Have deployed the gadget in my google apps domain by refering this document : https://developers.google.com/apps-marketplace/preparing
function makeRequest(){
var params = {};
params[gadgets.io.RequestParameters.METHOD] = gadgets.io.MethodType.GET;
params[gadgets.io.RequestParameters.CONTENT_TYPE] = gadgets.io.ContentType.TEXT;
params[gadgets.io.RequestParameters.AUTHORIZATION] = gadgets.io.AuthorizationType.SIGNED;
params[gadgets.io.RequestParameters.OAUTH_SERVICE_NAME] = "HMAC";
params[gadgets.io.RequestParameters.REFRESH_INTERVAL] = 3600;
var url = "https://applicationid.appspot.com/user/" + sender_name;
gadgets.io.makeRequest(url, onResponse, params);
}
function onResponse(response) {
if (response.rc != 200) {
document.getElementById('profile-container').innerHTML = 'Service temporarily unavailable.';
gadgets.window.adjustHeight();
} else {
document.getElementById('profile-container').innerHTML = response.text;
gadgets.window.adjustHeight();
}
}
But here response.rc always returning status code as 500. And gadgets.io.makeRequest() function doesn't make any request to the url (application hosted on appengine). It seems like the issue with SIGNED Authorization. How to implement SIGNED Authorization in Gmail Gadget?
#NNJ - the OAUTH_SERVICE_NAME = HMAC option is not supported.
You'll need to use the RSA-SHA1 signature method, you can get the certificate from http://opensocialresources.appspot.com/static/igoogle.cert

RestSharp on Windows Phone no set-cookie in response

I have a problem with using the RestSharp client on the Windows Phone device. Starting form the beginning, I have the ASP.NET Web Api service hosted online. I have a request user address: POST: http://my-service-url.com/token where I send Email and Password as a body parameters and I get 201 status code and a cookie in the response. When I do it in fiddler everything works fine. I also have the functional test for my API which is using the RestSharp which is also working correctly:
[Given(#"I fill email and password with correct data and I click log in button")]
public void GivenIFillEmailAndPasswordWithCorrectDataAndIClickLogInButton()
{
//Delete user if he exists
_testUserHelper.DeleteTestUser(TestEmail);
//Create new activated user
Assert.IsTrue(_testUserHelper.CreateTestUser(TestEmail, TestPassword));
//Prepare client
var client = new RestClient(CarRentalsConstants.HostAddress);
var restRequest = new RestRequest("api/token", Method.POST) { RequestFormat = DataFormat.Json };
//Add parameters to request
restRequest.AddBody(new { Email = TestEmail, Password = TestPassword });
//Perform request
_response = client.Execute(restRequest);
}
[When(#"the log in login process finishes")]
public void WhenTheLogInLoginProcessFinishes()
{
Assert.AreEqual(HttpStatusCode.Created, _response.StatusCode, _response.Content);
Assert.IsNotNull(_response.Cookies.SingleOrDefault(q => q.Name == ".ASPXAUTH"););
}
The one above works properly, and the cookie is in the response object.
Now what I try to do on my windows phone looks like this:
var restRequest = new RestRequest("token", Method.POST) {RequestFormat = DataFormat.Json};
restRequest.AddBody(new {Email = email, Password = password});
myWebClient.ExecuteAsync(restRequest, (restResponse, handle) =>
{
switch (restResponse.StatusCode)
{
case HttpStatusCode.Created:
{
var cookie = restResponse.Cookies.SingleOrDefault(q => q.Name == ".ASPXAUTH");
successLogicDelegate(cookie);
}
break;
case HttpStatusCode.BadRequest:
{
HandleBadRequest(restResponse.Content, failureLogicDelegate);
}
break;
default:
msgBox.Show(StringResources.ServerConnectionError);
failureLogicDelegate(null);
break;
}
});
And in this case, the response returns the "Created" status code, but the cookie is then set to null. I have no idea what is happening here, but I am fairly certain that server is sending this cookie, so where does it get lost?
Any help will be really appreciated.

Restrict Login Email with Google OAuth2.0 to Specific Domain Name

I can't seem to find any documentation on how to restrict the login to my web application (which uses OAuth2.0 and Google APIs) to only accept authentication requests from users with an email on a specific domain name or set of domain names. I would like to whitelist as opposed to blacklist.
Does anyone have suggestions on how to do this, documentation on the officially accepted method of doing so, or an easy, secure work around?
For the record, I do not know any info about the user until they attempt to log in through Google's OAuth authentication. All I receive back is the basic user info and email.
So I've got an answer for you. In the OAuth request you can add hd=example.com and it will restrict authentication to users from that domain (I don't know if you can do multiple domains). You can find hd parameter documented here
I'm using the Google API libraries from here: http://code.google.com/p/google-api-php-client/wiki/OAuth2 so I had to manually edit the /auth/apiOAuth2.php file to this:
public function createAuthUrl($scope) {
$params = array(
'response_type=code',
'redirect_uri=' . urlencode($this->redirectUri),
'client_id=' . urlencode($this->clientId),
'scope=' . urlencode($scope),
'access_type=' . urlencode($this->accessType),
'approval_prompt=' . urlencode($this->approvalPrompt),
'hd=example.com'
);
if (isset($this->state)) {
$params[] = 'state=' . urlencode($this->state);
}
$params = implode('&', $params);
return self::OAUTH2_AUTH_URL . "?$params";
}
I'm still working on this app and found this, which may be the more correct answer to this question. https://developers.google.com/google-apps/profiles/
Client Side:
Using the auth2 init function, you can pass the hosted_domain parameter to restrict the accounts listed on the signin popup to those matching your hosted_domain. You can see this in the documentation here: https://developers.google.com/identity/sign-in/web/reference
Server Side:
Even with a restricted client-side list you will need to verify that the id_token matches the hosted domain you specified. For some implementations this means checking the hd attribute you receive from Google after verifying the token.
Full Stack Example:
Web Code:
gapi.load('auth2', function () {
// init auth2 with your hosted_domain
// only matching accounts will show up in the list or be accepted
var auth2 = gapi.auth2.init({
client_id: "your-client-id.apps.googleusercontent.com",
hosted_domain: 'your-special-domain.example'
});
// setup your signin button
auth2.attachClickHandler(yourButtonElement, {});
// when the current user changes
auth2.currentUser.listen(function (user) {
// if the user is signed in
if (user && user.isSignedIn()) {
// validate the token on your server,
// your server will need to double check that the
// `hd` matches your specified `hosted_domain`;
validateTokenOnYourServer(user.getAuthResponse().id_token)
.then(function () {
console.log('yay');
})
.catch(function (err) {
auth2.then(function() { auth2.signOut(); });
});
}
});
});
Server Code (using googles Node.js library):
If you're not using Node.js you can view other examples here: https://developers.google.com/identity/sign-in/web/backend-auth
const GoogleAuth = require('google-auth-library');
const Auth = new GoogleAuth();
const authData = JSON.parse(fs.readFileSync(your_auth_creds_json_file));
const oauth = new Auth.OAuth2(authData.web.client_id, authData.web.client_secret);
const acceptableISSs = new Set(
['accounts.google.com', 'https://accounts.google.com']
);
const validateToken = (token) => {
return new Promise((resolve, reject) => {
if (!token) {
reject();
}
oauth.verifyIdToken(token, null, (err, ticket) => {
if (err) {
return reject(err);
}
const payload = ticket.getPayload();
const tokenIsOK = payload &&
payload.aud === authData.web.client_id &&
new Date(payload.exp * 1000) > new Date() &&
acceptableISSs.has(payload.iss) &&
payload.hd === 'your-special-domain.example';
return tokenIsOK ? resolve() : reject();
});
});
};
When defining your provider, pass in a hash at the end with the 'hd' parameter. You can read up on that here. https://developers.google.com/accounts/docs/OpenIDConnect#hd-param
E.g., for config/initializers/devise.rb
config.omniauth :google_oauth2, 'identifier', 'key', {hd: 'yourdomain.com'}
Here's what I did using passport in node.js. profile is the user attempting to log in.
//passed, stringified email login
var emailString = String(profile.emails[0].value);
//the domain you want to whitelist
var yourDomain = '#google.com';
//check the x amount of characters including and after # symbol of passed user login.
//This means '#google.com' must be the final set of characters in the attempted login
var domain = emailString.substr(emailString.length - yourDomain.length);
//I send the user back to the login screen if domain does not match
if (domain != yourDomain)
return done(err);
Then just create logic to look for multiple domains instead of just one. I believe this method is secure because 1. the '#' symbol is not a valid character in the first or second part of an email address. I could not trick the function by creating an email address like mike#fake#google.com 2. In a traditional login system I could, but this email address could never exist in Google. If it's not a valid Google account, you can't login.
Since 2015 there has been a function in the library to set this without needing to edit the source of the library as in the workaround by aaron-bruce
Before generating the url just call setHostedDomain against your Google Client
$client->setHostedDomain("HOSTED DOMAIN")
For login with Google using Laravel Socialite
https://laravel.com/docs/8.x/socialite#optional-parameters
use Laravel\Socialite\Facades\Socialite;
return Socialite::driver('google')
->with(['hd' => 'pontomais.com.br'])
->redirect();

Resources