client certificate with NSUrlSessionDelegate in xamarin - c#

I want to implement client certificate authentication in my xamarin app.
On top of that I am using a custom Certificate Authority (CA) and TLS 1.2.
Until now I managed to get it running using android, UWP and WPF. The only platform missing is ios.
Here is my NSUrlSessionDelegate:
public class SSLSessionDelegate : NSUrlSessionDelegate, INSUrlSessionDelegate
{
private NSUrlCredential Credential { get; set; }
private SecIdentity identity = null;
private X509Certificate2 ClientCertificate = null;
private readonly SecCertificate CACertificate = null;
public SSLSessionDelegate(byte[] caCert) : base()
{
if (caCert != null)
{
CACertificate = new SecCertificate(new X509Certificate2(caCert));
}
}
public void SetClientCertificate(byte[] pkcs12, char[] password)
{
if (pkcs12 != null)
{
ClientCertificate = new X509Certificate2(pkcs12, new string(password));
identity = SecIdentity.Import(ClientCertificate);
SecCertificate certificate = new SecCertificate(ClientCertificate);
SecCertificate[] certificates = { certificate };
Credential = NSUrlCredential.FromIdentityCertificatesPersistance(identity, certificates, NSUrlCredentialPersistence.ForSession);
}
else
{
ClientCertificate = null;
identity = null;
Credential = null;
}
}
public override void DidReceiveChallenge(NSUrlSession session, NSUrlAuthenticationChallenge challenge, Action<NSUrlSessionAuthChallengeDisposition, NSUrlCredential> completionHandler)
{
if (challenge.ProtectionSpace.AuthenticationMethod == NSUrlProtectionSpace.AuthenticationMethodClientCertificate)
{
NSUrlCredential c = Credential;
if (c != null)
{
completionHandler.Invoke(NSUrlSessionAuthChallengeDisposition.UseCredential, c);
return;
}
}
if (challenge.ProtectionSpace.AuthenticationMethod == NSUrlProtectionSpace.AuthenticationMethodServerTrust)
{
SecTrust secTrust = challenge.ProtectionSpace.ServerSecTrust;
secTrust.SetAnchorCertificates(new SecCertificate[] {
CACertificate
});
secTrust.SetAnchorCertificatesOnly(true);
}
completionHandler.Invoke(NSUrlSessionAuthChallengeDisposition.PerformDefaultHandling, null);
}
}
This works if no client certificate is configured DidReceiveChallenge is called once with AuthenticationMethodServerTrust and the custom CA is accepted.
But as soon as a client certificate is configured DidReceiveChallenge gets called 4 times (twice for each AuthenticationMethod) and I am getting NSURLErrorDomain (-1200) error.
Anyone any idea what I am doing wrong?
Update
The SSLSessionDelegate is used like this:
public class HttpsServer : AbstractRemoteServer, IRemoteServer
{
private static readonly Logger LOG = LogManager.GetLogger();
private SSLSessionDelegate sSLSessionDelegate;
private NSUrlSession session;
private NSUrl baseAddress;
public HttpsServer()
{
sSLSessionDelegate = new SSLSessionDelegate(SSLSupport.GetTruststoreRaw());
NSUrlSessionConfiguration configuration = NSUrlSessionConfiguration.DefaultSessionConfiguration;
configuration.HttpShouldSetCookies = true;
configuration.TimeoutIntervalForRequest = 30;
configuration.TLSMinimumSupportedProtocol = SslProtocol.Tls_1_2;
configuration.TimeoutIntervalForResource = 30;
NSMutableDictionary requestHeaders;
if (configuration.HttpAdditionalHeaders != null)
{
requestHeaders = (NSMutableDictionary)configuration.HttpAdditionalHeaders.MutableCopy();
}
else
{
requestHeaders = new NSMutableDictionary();
}
AppendHeaders(requestHeaders, SSLSupport.GetDefaultHeaders());
configuration.HttpAdditionalHeaders = requestHeaders;
session = NSUrlSession.FromConfiguration(configuration, (INSUrlSessionDelegate)sSLSessionDelegate, NSOperationQueue.MainQueue);
baseAddress = NSUrl.FromString(SSLSupport.GetBaseAddress());
}
public void SetClientCertificate(byte[] pkcs12, char[] password)
{
sSLSessionDelegate.SetClientCertificate(pkcs12, password);
}
public override async Task<string> GetString(string url, Dictionary<string, string> headers, CancellationToken cancellationToken)
{
NSData responseContent = await GetRaw(url, headers, cancellationToken);
return NSString.FromData(responseContent, NSStringEncoding.UTF8).ToString();
}
private async Task<NSData> GetRaw(string url, Dictionary<string, string> headers, CancellationToken cancellationToken)
{
NSMutableUrlRequest request = GetRequest(url);
request.HttpMethod = "GET";
request.Headers = AppendHeaders(request.Headers, headers);
Task<NSUrlSessionDataTaskRequest> taskRequest = session.CreateDataTaskAsync(request, out NSUrlSessionDataTask task);
cancellationToken.Register(() =>
{
if (task != null)
{
task.Cancel();
}
});
try
{
task.Resume();
NSUrlSessionDataTaskRequest taskResponse = await taskRequest;
if (taskResponse == null || taskResponse.Response == null)
{
throw new Exception(task.Error.Description);
}
else
{
NSHttpUrlResponse httpResponse = (NSHttpUrlResponse)taskResponse.Response;
if (httpResponse.StatusCode == 303)
{
if (!httpResponse.AllHeaderFields.TryGetValue(new NSString("Location"), out NSObject locationValue))
{
throw new Exception("redirect received without Location-header!");
}
return await GetRaw(locationValue.ToString(), headers, cancellationToken);
}
if (httpResponse.StatusCode != 200)
{
throw new Exception("unsupported statuscode: " + httpResponse.Description);
}
return taskResponse.Data;
}
}
catch (Exception ex)
{
throw new Exception("communication exception: " + ex.Message);
}
}
}
And here my Info.plist
<key>NSAppTransportSecurity</key>
<dict>
<key>NSExceptionDomains</key>
<dict>
<key>XXXXXXXXXX</key>
<dict>
<key>NSExceptionAllowsInsecureHTTPLoads</key>
<true/>
<key>NSIncludesSubdomains</key>
<true/>
</dict>
</dict>
</dict>
Update 2
Neither I found the solution nor could anyone give me a hint, so I finally dropped client-certificates for now. I switched to OAuth2 for authorization and use my own certificate-authority (no self-signed certificate) for server -authentication which works well.
But still I am interested in this issue and glad for every idea in how to make it work.

I would suggest using ModernHttpClient. It supports ClientCertificates for Android and iOS.
It's opensource so you could always check out their github for reference if you want to finish your own implementation.
ModernHttpClient

Related

Authenticate an EWS application by using OAuth within custom class

I have code to authenticate with EWS using oAuth working fine if I call it from winform button click, but not working if I place my code inside custom class and call it inside constructor I don't know what is the problem ?
Authenticate Code function :
public async Task<AuthenticationResult> oAuthLoginRequest()
{
System.Net.ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12;
var cca = ConfidentialClientApplicationBuilder
.Create(Settings.Default.appId)
.WithClientSecret(Settings.Default.clientSecret)
.WithTenantId(Settings.Default.tenantId)
.Build();
var ewsScopes = new string[] { "https://outlook.office365.com/.default" };
try
{
_authenticationResult = await cca.AcquireTokenForClient(ewsScopes)
.ExecuteAsync();
return _authenticationResult;
}
catch (Exception ex)
{
string.Format("oAuthLoginRequest: Exception= {0}", ex.Message).LogIt(TLogType.ltError);
return _authenticationResult;
}
}
Working well and I got access token :
private async void button1_Click(object sender, EventArgs e)
{
oAuthLoginRequest();
//Access Token Available here
var accessToken = _authenticationResult.AccessToken ; //Working fine
}
NOT WORKING :
public class TServiceController
{
private bool _started = false;
public bool Started { get { return _started; } }
TEWSService mailService = null;
public ExchangeService _service = null;
public AuthenticationResult _authenticationResult = null;
public DateTimeOffset TokenExpiresOn { get; set; }
public async Task<AuthenticationResult> oAuthLoginRequest()
{
System.Net.ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12;
var cca = ConfidentialClientApplicationBuilder
.Create(Settings.Default.appId)
.WithClientSecret(Settings.Default.clientSecret)
.WithTenantId(Settings.Default.tenantId)
.Build();
// "https://outlook.office365.com/.default" ,"https://outlook.office365.com/EWS.AccessAsUser.All" , "https://graph.microsoft.com/Mail.Send"
// "https://ps.outlook.com/full_access_as_app"
var ewsScopes = new string[] { "https://outlook.office365.com/.default" };
try
{
_authenticationResult = await cca.AcquireTokenForClient(ewsScopes).ExecuteAsync();
TokenExpiresOn = _authenticationResult.ExpiresOn;
("AccessToken:" + _authenticationResult.AccessToken).LogIt(TLogType.ltDebug);
}
catch (Exception ex)
{
string.Format("oAuthLoginRequest: Exception= {0}", ex.Message).LogIt(TLogType.ltError);
}
return _authenticationResult;
}
public TServiceController()
{
var auth = oAuthLoginRequest().Result; //STUCK HERE
"Service controller started.".LogIt();
} //end constructore
} //END CLASS
Any explanation ?
I tried two methods one of them work just fine in winform click button and other solution not working within my class constructor .

'System.Net.Http.HttpRequestException Connection Refused' Xamarin

I have my API that is running correctly, then I have my application in which I have:
public partial class MainPage : ContentPage
{
HttpClientHandler insecureHandler;
HttpClient client;
Uri uri = new Uri("http://172.20.10.2:3000/api/operatori/getoperatori");
public MainPage()
{
InitializeComponent();
insecureHandler = GetInsecureHandler();
client = new HttpClient(insecureHandler);
client.Timeout = TimeSpan.FromSeconds(200); // this is double the default
}
async void Button_Clicked(object sender, EventArgs e)
{
bool login = false;
try
{
Task<String> result = getOperation();
string result2 = await result;
var Item = JsonConvert.DeserializeObject<List<Operatore>>(result2);
foreach (Operatore o in Item)
{
if (o.oP_NOME == user.Text && o.oP_PASSWD == pass.Text)
{
login = true;
}
}
}
catch (Exception ex)
{
welcome.Text += ex.ToString();
}
if (login == true)
{
await Navigation.PushAsync(new HomePage(user.Text));
}
else
{
await DisplayAlert("Login fallito!", "Username o Password Errati", "OK");
}
}
private async Task<String> getOperation()
{
HttpResponseMessage response = await client.GetAsync(uri).ConfigureAwait(false);
response.EnsureSuccessStatusCode();
var risposta = await response.Content.ReadAsStringAsync().ConfigureAwait(false);
return risposta;
}
public HttpClientHandler GetInsecureHandler()
{
HttpClientHandler handler = new HttpClientHandler();
handler.ServerCertificateCustomValidationCallback = (message, cert, chain, errors) =>
{
if (cert.Issuer.Equals("CN=localhost"))
return true;
return errors == System.Net.Security.SslPolicyErrors.None;
};
return handler;
}
}
172.20.10.2 is my IP address. If I launch Google Chrome on my PC (where API is running) it work, but when I try yo make this HTTP request on my Android device (not emulator) I have this error:
System.Net.Http.HttpRequestException Connection refused ---> System.Net.Sockets.SocketException: Connection refused
Even if I make the request from Chrome browser on phone, I have the same error.
Can I solve it?

Unable to get mvc webapi bearer token after hosting on server

I'm working on the securing the web API by OAuth bearer token as this working fine in local able to generate token, After hosting on server code is not working to generate token hosted on the server.
when am trying to the server sample Url "api.somedemo.com/token" showing the request responce as method not found ,
but in local "http://local:1234/token" same code working generated token.
I can't able find where am missing on the server to able generate bearer token.
here the code.
From Controller:
private BearerToken GetBearerToken(string userName, string password)
{
BearerToken token = null;
FormUrlEncodedContent bearerTokenContent = OAuthClientWrapper.CreateBearerToken(userName, password);
Uri tokenUri = new Uri("api.somedemo.com/token");
token = OAuthClientWrapper.GetJwtToken(tokenUri, bearerTokenContent);
return token;
}
OAuthClientWrapper Class:
public static FormUrlEncodedContent CreateBearerToken(string userName, string password)
{
var bearerTokenContent = new List<KeyValuePair<string, string>>
{
new KeyValuePair<string, string>("username", userName),
new KeyValuePair<string, string>("password", password),
new KeyValuePair<string, string>("grant_type", "password")
};
return new FormUrlEncodedContent(bearerTokenContent);
}
GetJwtToken method:
public static BearerToken GetJwtToken(Uri uri, FormUrlEncodedContent bearerTokenContent)
{
BearerToken token = null;
try
{
using (var httpClient = new HttpClient())
{
//Set the headers
httpClient.DefaultRequestHeaders.Accept.Clear();
httpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
using (HttpResponseMessage response = httpClient.PostAsync(uri, bearerTokenContent).Result)
{
if (response.IsSuccessStatusCode)
{
token = response.Content.ReadAsAsync<BearerToken>().Result;
}
else
{
var reasonPhrase = response.ReasonPhrase;
var result = response.Content.ReadAsStringAsync();
var errorMessage = string.Format("Error: {0} {1} for uri: {2}", reasonPhrase, result, uri.AbsoluteUri);
//Log the error message
token = null;
} //else
} //using
}//using
}//try
catch (AggregateException aex)
{
throw aex;
}//catch
return token;
}
As WebAPI Startup Class:
public partial class Startup
{
public void Configuration(IAppBuilder app)
{
// For more information on how to configure your application, visit http://go.microsoft.com/fwlink/?LinkID=316888
ConfigureAuth(app);
app.UseOAuthAuthorizationServer(OAuthOptions);
app.UseJwtBearerAuthentication(new MyJwtOptions());
}
}
Startupoverride :
public partial class Startup
{
public static OAuthAuthorizationServerOptions OAuthOptions { get; private set; }
// For more information on configuring authentication, please visit http://go.microsoft.com/fwlink/?LinkId=301864
public void ConfigureAuth(IAppBuilder app)
{
int tokenExpiration = Convert.ToInt32(ConfigurationManager.AppSettings["TokenExpiration"]);
OAuthOptions = new OAuthAuthorizationServerOptions
{
AllowInsecureHttp = true,
TokenEndpointPath = new PathString("/Token"),
Provider = new ApplicationOAuthProvider(),
//AccessTokenFormat = new JwtFormat(audience, new SymmetricKeyIssuerSecurityTokenProvider(issuer, signingKey)),
AccessTokenFormat = new MyJwtFormat(),
RefreshTokenProvider = new ApplicationRefreshTokenProvider(),
AccessTokenExpireTimeSpan = TimeSpan.FromMinutes(tokenExpiration)
};
// Token Generation
app.UseOAuthAuthorizationServer(OAuthOptions);
app.UseOAuthBearerAuthentication(new OAuthBearerAuthenticationOptions());
}
}
Application OAuthProviderClass:
public class ApplicationOAuthProvider : OAuthAuthorizationServerProvider
{
private readonly string _publicClientId;
IHttpClientWrapper _HttpClientWrapper = null;
private IEndPoints _ConfigsProviders = null;
public ApplicationOAuthProvider()
{
_HttpClientWrapper = new HttpClientWrapper();
_ConfigsProviders = new EndPoints();
}
public override async Task GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentialsContext context)
{
try
{
var isValidUser = false;
context.OwinContext.Response.Headers.Add("Access-Control-Allow-Origin", new[] { "*" });
var url = string.Format("{0}/{1}/{2}", "someAPIBaseUrl", context.UserName, context.Password);
//Getting user information if login is validated in json format.
var returnValue = _HttpClientWrapper.GetStringAsync(url);
var responseObject = JsonConvert.DeserializeObject<ResponseObject>(returnValue);
if (responseObject.Status==true)
{
isValidUser = true;
}
if (!isValidUser)
{
context.SetError("invalid_grant", "The user name or password is incorrect.");
return;
}
var identity = new ClaimsIdentity(context.Options.AuthenticationType);
identity.AddClaim(new Claim("sub", context.UserName));
context.Validated(identity);
}
catch (Exception ex)
{
throw ex;
}
}
public override async Task ValidateClientAuthentication(OAuthValidateClientAuthenticationContext context)
{
context.Validated();
}
public override Task TokenEndpoint(OAuthTokenEndpointContext context)
{
foreach (KeyValuePair<string, string> property in context.Properties.Dictionary)
{
context.AdditionalResponseParameters.Add(property.Key, property.Value);
}
return Task.FromResult<object>(null);
}
public override Task ValidateClientRedirectUri(OAuthValidateClientRedirectUriContext context)
{
if (context.ClientId == _publicClientId)
{
Uri expectedRootUri = new Uri(context.Request.Uri, "/");
if (expectedRootUri.AbsoluteUri == context.RedirectUri)
{
context.Validated();
}
}
return Task.FromResult<object>(null);
}
public static AuthenticationProperties CreateProperties(string userName)
{
IDictionary<string, string> data = new Dictionary<string, string>
{
{ "userName", userName }
};
return new AuthenticationProperties(data);
}
}
public MyJwtFormat()
{
_tokenIssuer = ConfigurationManager.AppSettings["TokenIssuer"];
_tokenExpiration = Convert.ToInt32(ConfigurationManager.AppSettings["TokenExpiration"]);
}
ConfigurationManager.AppSettings["TokenIssuer"] given "localhost" in sever also.
Please help to fix this issue
Advance thanks you.

'Access Denied' on Web API with security Authorization

I'm trying to create a dummy web api with authentication
by following this link : YouTube Video Tutorial Link
Controller Code :
MySecurityClient msc = new MySecurityClient();
ViewBag.result1 = msc.Demo()==null ?"Access Denied": msc.Demo();
return View();
In Model:
public class MySecurityClient
{
private string BASE_URL = "http://localhost:3513/api/MySecurity/";
private object convert;
public string Demo()
{
try
{
HttpClient Client = new HttpClient();
var authInfo = Convert.ToBase64String(Encoding.Default.GetBytes("acc1:123"));
Client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic", authInfo);
Client.BaseAddress = new Uri(BASE_URL);
HttpResponseMessage response = Client.GetAsync("Work2").Result;
if (response.IsSuccessStatusCode)
return response.Content.ReadAsStringAsync().Result;
return null;
}
catch (Exception ex)
{
return null;
}
}
}
Server Controller :
[HttpGet]
[Route("Work2")]
[MyAuthorize(Roles="SuperAdmin")]
public string Work2()
{
return "Work2";
}
Authorization Override:
public override void OnAuthorization(HttpActionContext actionContext)
{
try
{
AuthenticationHeaderValue authValue = actionContext.Request.Headers.Authorization;
if (authValue != null && !string.IsNullOrWhiteSpace(authValue.Parameter)
&& authValue.Scheme == BasicAuthResponseHeaderValue)
{
Credential parsedCredentials = ParseAuthorizationHeader(authValue.Parameter);
var MyPrincipal = new MyPrincipal(parsedCredentials.UserName);
if (!MyPrincipal.IsInRole(Roles))
{
actionContext.Response = actionContext.Request.CreateResponse(HttpStatusCode.Unauthorized);
actionContext.Response.Headers.Add(BasicAuthResponseHeader, BasicAuthResponseHeaderValue);
}
else
{
actionContext.Response = actionContext.Request.CreateResponse(HttpStatusCode.OK);
actionContext.Response.Headers.Add(BasicAuthResponseHeader, BasicAuthResponseHeaderValue);
//return;
}
}
}
catch (Exception ex)
{
actionContext.Response = actionContext.Request.CreateResponse(HttpStatusCode.OK);
actionContext.Response.Headers.Add(BasicAuthResponseHeader, BasicAuthResponseHeaderValue);
}
}
response.IsSuccessStatusCode is true,
but ViewBag.result1 is empty if we use return response.Content.ReadAsAsync<string>().Result;
and Access Denied on return response.Content.ReadAsAsync<string>().Result;
Thanks in advance

Store user token from Dropbox using DropNet

I'm trying to store a user token once my application has been authorized with Dropbox so that when I open a different form (which will have drag and drop functionality), the user won't have to authorize the app again and will be able to perform upload functions to Dropbox.
My class for Authorizing Dropbox with DropNet is:
I have declared two properties; public string UserToken { get; set; } and public string UserSecret { get; set; }
string appKey = "APP_KEY";
string appSecret = "APP_SECRET";
public bool LinkDrpbox()
{
bool dropboxLink = false;
Authenticate(
url =>
{
var proc = Process.Start("iexplore.exe", url);
proc.WaitForExit();
Authenticated(
() =>
{
dropboxLink = true;
},
exc => ShowException(exc));
},
ex => dropboxLink = false);
if (dropboxLink)
{
return true;
}
else
{
return false;
}
}
private DropNetClient _Client;
public DropNetClient Client
{
get
{
if (_Client == null)
{
_Client = new DropNetClient(appKey, appSecret);
if (IsAuthenticated)
{
_Client.UserLogin = new UserLogin
{
Token = UserToken,
Secret = UserSecret
};
}
_Client.UseSandbox = true;
}
return _Client;
}
}
public bool IsAuthenticated
{
get
{
return UserToken != null &&
UserSecret != null;
}
}
public void Authenticate(Action<string> success, Action<Exception> failure)
{
Client.GetTokenAsync(userLogin =>
{
var url = Client.BuildAuthorizeUrl(userLogin);
if (success != null) success(url);
}, error =>
{
if (failure != null) failure(error);
});
}
public void Authenticated(Action success, Action<Exception> failure)
{
Client.GetAccessTokenAsync((accessToken) =>
{
UserToken = accessToken.Token;
UserSecret = accessToken.Secret;
if (success != null) success();
},
(error) =>
{
if (failure != null) failure(error);
});
}
private void ShowException(Exception ex)
{
string error = ex.ToString();
}
}
I am able to authorize my app but am unsure how to save the access token. I'm presuming in app.config file but not sure.
Any help would be appreciated!
This is more of a .net question rather than a DropNet specific thing.
Have a look at this answer https://stackoverflow.com/a/3032538/75946 I don't agree with using the registry but the other 2 are good.
You will just need to load it from where ever you store it when you start up the app and set it on your DropNetClient instance.

Categories

Resources