GetDiscoveryDocumentAsync can not find the authority url - IdentiyServer4 - asp.net-core

I was trying to replace obsolete IdentityServer methods and types and as I had been warned about, I tried to replace DiscoveryClient() and TokenClient() with appropiate methods like in the examples of the latest identity server docs. When I try to get related endpoints by GetDiscoveryDocumentAsync it returns null even though I could read those values with current code and also get those values on browser.
Besides, when I by-pass the step of discovery and supplying the direct token endpoint RequestTokenAsync returns null because of Not Found exception.
For the sake of clearity of the question I should say that I have not changed anything (its settings or endpoints) in my Identity server project (from which I try to get access token).
Followings are my previous and updated code to achieve what I've described. Any help or suggestion is appreciated. Thanks in advance.
Previous Code (Working):
var testServer = new TestServer(builder);
var client = testServer.CreateClient();
client.BaseAddress = new Uri("http://localhost:5000");
var discoClient = new DiscoveryClient(AuthorityUrl) {Policy = {RequireHttps = false}};
var disco = discoClient.GetAsync().Result;
var tokenClient = new TokenClient(disco.TokenEndpoint, ClientId, ClientSecret);
var tokenResponse = tokenClient.RequestClientCredentialsAsync(Scope).Result;
client.SetBearerToken(tokenResponse.AccessToken);
Updated Code (Not Working):
var testServer = new TestServer(builder);
var client = testServer.CreateClient();
client.BaseAddress = new Uri("http://localhost:5000");
//var discoClient = new DiscoveryClient(AuthorityUrl) {Policy = {RequireHttps = false}};
var disco = client.GetDiscoveryDocumentAsync(new DiscoveryDocumentRequest()
{ Address = AuthorityUrl, Policy = new DiscoveryPolicy() { RequireHttps = false, Authority = AuthorityUrl} }).Result;
;
if (disco.IsError)
{
throw new Exception(disco.Error);
}
//var tokenClient = new TokenClient(disco.TokenEndpoint, ClientId, ClientSecret);
var tokenClient = client.RequestTokenAsync(new TokenRequest()
{ Address = disco.TokenEndpoint, ClientId = ClientId, ClientSecret = ClientSecret, GrantType = GrantType}).Result;
//var tokenResponse = tokenClient.RequestClientCredentialsAsync(Scope).Result;
client.SetBearerToken(tokenClient.AccessToken);
return client;
Edit:
Updated my code as shown below and still getting the same error.
var testServer = new TestServer(builder);
var client = testServer.CreateClient();
client.BaseAddress = new Uri("http://localhost:5000");
//var discoClient = new DiscoveryClient(AuthorityUrl) {Policy = {RequireHttps = false}};
var disco = await client.GetDiscoveryDocumentAsync(new DiscoveryDocumentRequest()
{ Address = AuthorityUrl, Policy = new DiscoveryPolicy() { RequireHttps = false, Authority = AuthorityUrl } });
;
if (disco.IsError)
{
throw new Exception(disco.Error);
}
//var tokenClient = new TokenClient(disco.TokenEndpoint, ClientId, ClientSecret);
var tokenClient =await client.RequestTokenAsync(new ClientCredentialsTokenRequest()
{ Address = disco.TokenEndpoint, ClientId = ClientId, ClientSecret = ClientSecret, GrantType = GrantType , Scope = Scope});
client.SetBearerToken(tokenClient.AccessToken);
return client;
Error:
"Error connecting to AuthorityUrl/.well-known/openid-configuration: Not Found"

Related

JWT Encryption Error when using EncryptingCredentials in token generation method

I implemented JWT token in my ASP.net core 3.1 web api.
In the token generation method when I try to use EncryptingCredentials I got the below error :
IDX10662: The KeyWrap algorithm 'System.String' requires a key size of 'System.Int32' bits. Key '', is of size:'System.Int32'. (Parameter 'Length')
This is my Token generation method :
public async Task<string> GenerateAsync(User user)
{
var secretKey = Encoding.UTF8.GetBytes(_siteSetting.JwtSettings.SecretKey); //"SampleSecretKeyChange"
var signingCredentials = new SigningCredentials(new SymmetricSecurityKey(secretKey), SecurityAlgorithms.HmacSha256Signature);
var encryptionkey = Encoding.UTF8.GetBytes(_siteSetting.JwtSettings.Encryptkey); //"AbcdefgHijklmnopk123456"
var encryptingCredentials = new EncryptingCredentials(new SymmetricSecurityKey(encryptionkey), SecurityAlgorithms.Aes128KW, SecurityAlgorithms.Aes128CbcHmacSha256);
var descriptor = new SecurityTokenDescriptor
{
Issuer = _siteSetting.JwtSettings.Issuer,
Audience = _siteSetting.JwtSettings.Audience,
IssuedAt = DateTime.Now,
NotBefore = DateTime.Now.AddMinutes(_siteSetting.JwtSettings.NotBeforeMinutes),
Expires = DateTime.Now.AddMinutes(_siteSetting.JwtSettings.ExpirationMinutes),
SigningCredentials = signingCredentials,
EncryptingCredentials = encryptingCredentials,
Subject = new ClaimsIdentity(_getClaims(user))
};
var tokenHandler = new JwtSecurityTokenHandler();
var securityToken = tokenHandler.CreateToken(descriptor);
var jwt = tokenHandler.WriteToken(securityToken);
return await Task.FromResult(jwt);
}
After of all research that I did, I found the problem. When you want to use EncryptingCredentials with the conditions that I described the Encryptkey key length must be equal 16 characters.

register user to .net core from mobile

i try to register user from xamarin in .net core with enabled Identity and membership system it return true but no user registered in database.
also i check with MVC web site and all things is Ok
my code is
using (var client = new HttpClient())
{
var model = new RegisterBindingModel
{
Email = email,
Password = password,
ConfirmPassword = confirmPassword
};
var json = JsonConvert.SerializeObject(model);
HttpContent httpContent = new StringContent(json);
httpContent.Headers.ContentType = new MediaTypeHeaderValue("application/json");
var response = await client.PostAsync(
Constants.SignalrEndpointAddress + "Account/Register", httpContent);
if (response.IsSuccessStatusCode)
{
return true;
}
}
problem is in StringContent and my code ended up to this:
using (var client = new HttpClient())
{
var model = new Dictionary<string, string>
{
{"Email", email },
{"Password", password},
{"ConfirmPassword", confirmPassword},
};
var httpContent = new FormUrlEncodedContent(model);
var response = await client.PostAsync(
Constants.EndpointAddress + "Account/Register", httpContent);
if (response.IsSuccessStatusCode)
{
return true;
}
}
thanks

NSUrlSession: Challenge NSURLAuthenticationMethodServerTrust fails only when client certificate is also needed

we want to connect our app to our IIS webservice. We use self signed certificates and also a client certificate for authentication.
When the webservice doesn't require client certificate authentication, everything works fine, NSURLAuthenticationMethodServerTrust gets called and the request continues.
But when I activate client certificate authentication on our server, after DidReceiveChallenge with NSURLAuthenticationMethodServerTrust as the challenge, DidCompleteWithError gets called. Error message is: "The certificate for this server is invalid. You might be connecting to a server that is pretending to be "192.168.221.118" which could put your confidential information at risk.
Note: "NSURLAuthenticationMethodClientCertificate" never gets called, the app crashes before that.
The client certificate is signed by the intermediate certificate, so I don't understand why the ServerTrust Challenge fails.
Also: in my opinion it should not be necessary, but I also tried adding the client certificate to the collection of AnchorCertificates of the Sectrust.
Thanks in advance for your help.
Here is my code:
private class SessionDelegate : NSUrlSessionDataDelegate, INSUrlSessionDelegate
{
private Action<bool, string> completed_callback;
private string antwortCache;
private int status_code;
public SessionDelegate(Action<bool, string> completed)
{
completed_callback = completed;
antwortCache = "";
}
public override void DidReceiveChallenge(NSUrlSession session, NSUrlSessionTask task, NSUrlAuthenticationChallenge challenge, Action<NSUrlSessionAuthChallengeDisposition, NSUrlCredential> completionHandler)
{
if (challenge.PreviousFailureCount == 0)
{
if (challenge.ProtectionSpace.AuthenticationMethod.Equals("NSURLAuthenticationMethodServerTrust"))
{
// GetParent is correct, because I'm too lazy to copy the certs into to the correct folders...
var path = Directory.GetParent(GlobaleObjekte.SSLZertifikatePath);
var caPath = Path.Combine(path.FullName, "ca.cert.der");
var caByteArray = File.ReadAllBytes(caPath);
var caCert = new X509Certificate2(caByteArray);
var interPath = Path.Combine(path.FullName, "intermediate.cert.der");
var interByteArray = File.ReadAllBytes(interPath);
var interCert = new X509Certificate2(interByteArray);
var secTrust = challenge.ProtectionSpace.ServerSecTrust;
var certCollection = new X509CertificateCollection();
certCollection.Add(caCert);
certCollection.Add(interCert);
secTrust.SetAnchorCertificates(certCollection);
var credential = new NSUrlCredential(secTrust);
completionHandler(NSUrlSessionAuthChallengeDisposition.UseCredential, credential);
return;
}
if (challenge.ProtectionSpace.AuthenticationMethod.Equals("NSURLAuthenticationMethodClientCertificate"))
{
var path = Directory.GetParent(GlobaleObjekte.SSLZertifikatePath);
var certPath = Path.Combine(path.FullName, "client.pfx");
var certByteArray = File.ReadAllBytes(certPath);
var cert = new X509Certificate2(certByteArray, Settings.WSClientCertPasswort);
var ident = SecIdentity.Import(certByteArray, Settings.WSClientCertPasswort);
var credential = new NSUrlCredential(ident, new SecCertificate[] { new SecCertificate(cert) }, NSUrlCredentialPersistence.ForSession);
completionHandler(NSUrlSessionAuthChallengeDisposition.UseCredential, credential);
return;
}
if (challenge.ProtectionSpace.AuthenticationMethod.Equals("NSURLAuthenticationMethodHTTPBasic"))
{
var credential = new NSUrlCredential(Settings.WebserviceBenutzer, Settings.WebservicePasswort, NSUrlCredentialPersistence.ForSession);
completionHandler(NSUrlSessionAuthChallengeDisposition.UseCredential, credential);
return;
}
completed_callback(false, "Unbekannte Authentifizierungsanfrage: " + challenge?.ProtectionSpace?.AuthenticationMethod);
}
else
{
completed_callback(false, "Authentifizierung fehlgeschlagen: " + challenge?.ProtectionSpace?.AuthenticationMethod);
}
}
}
I finally found a solution. I had to create the credential object in a different way. Instead of adding the certificates to the SecTrust and create the credential with the SecTrust as a parameter, I had to create a identity from the client certificate and then create the credential with the identity and the other certificates as parameters:
if (challenge.ProtectionSpace.AuthenticationMethod.Equals("NSURLAuthenticationMethodServerTrust"))
{
var path = Directory.GetParent(GlobaleObjekte.SSLZertifikatePath);
var caPath = Path.Combine(path.FullName, "ca.cert.der");
var caByteArray = File.ReadAllBytes(caPath);
var caCert = new SecCertificate(caByteArray);
var interPath = Path.Combine(path.FullName, "intermediate.cert.der");
var interByteArray = File.ReadAllBytes(interPath);
var interCert = new SecCertificate(interByteArray);
var clientPath = Path.Combine(path.FullName, "client.pfx");
var clientByteArray = File.ReadAllBytes(clientPath);
var clientCert = new X509Certificate2(clientByteArray, Settings.WSClientCertPasswort);
//var secTrust = challenge.ProtectionSpace.ServerSecTrust;
//var certCollection = new X509CertificateCollection();
//certCollection.Add(caCert);
//certCollection.Add(interCert);
//certCollection.Add(cert);
//secTrust.SetAnchorCertificates(certCollection);
//var credential = new NSUrlCredential(secTrust);
var identity = SecIdentity.Import(clientCert);
var credential = new NSUrlCredential(identity, new SecCertificate[] { caCert, interCert }, NSUrlCredentialPersistence.ForSession);
completionHandler(NSUrlSessionAuthChallengeDisposition.UseCredential, credential);
return;
}

How to renew a SAML assertion with ADFS?

I am able to succesfully request a SAML SecurityToken from ADFS with a user name and password using the following code:
private GenericXmlSecurityToken IssueToken(string userName, string password)
{
var relyingPartyIdentifier = new EndpointReference("https://mywebsite.net");
var stsAddress = "https://myadfs.com/adfs/services/trust/13/usernamemixed";
var wsBinding = new WS2007HttpBinding(SecurityMode.TransportWithMessageCredential);
wsBinding.Security.Transport.ClientCredentialType = HttpClientCredentialType.Ntlm;
wsBinding.Security.Message.EstablishSecurityContext = false;
wsBinding.Security.Message.ClientCredentialType = MessageCredentialType.UserName;
var trustChannelFactory = new WSTrustChannelFactory(wsBinding, stsAddress);
trustChannelFactory.Credentials.SupportInteractive = false;
trustChannelFactory.TrustVersion = TrustVersion.WSTrust13;
trustChannelFactory.Credentials.UserName.Password = password;
trustChannelFactory.Credentials.UserName.UserName = userName;
var tokenRequest = new RequestSecurityToken()
{
RequestType = RequestTypes.Issue,
AppliesTo = relyingPartyIdentifier,
KeyType = KeyTypes.Symmetric,
TokenType = "http://docs.oasis-open.org/wss/oasis-wss-saml-token-profile-1.1#SAMLV1.1"
};
tokenRequest.Claims.Add(new RequestClaim(ClaimTypes.Name));
tokenRequest.Claims.Add(new RequestClaim(ClaimTypes.Email));
var channel = trustChannelFactory.CreateChannel();
var token = (GenericXmlSecurityToken)channel.Issue(tokenRequest);
return token;
}
However, that token will expire in an hour. So my question is, how can I issue a new token based on the initial token, such that I don't need to remember the username and password?
I tried the following code:
public GenericXmlSecurityToken RenewToken(SecurityToken token)
{
var relyingPartyIdentifier = new EndpointReference("https://mywebsite.net");
var stsAddress = "https://myadfs.com/adfs/services/trust/13/usernamemixed";
var wsBinding = new WS2007FederationHttpBinding(WSFederationHttpSecurityMode.TransportWithMessageCredential);
wsBinding.Security.Message.EstablishSecurityContext = false;
wsBinding.Security.Message.IssuerAddress = new EndpointAddress(new Uri(stsAddress));
wsBinding.Security.Message.IssuerBinding = new WS2007HttpBinding(SecurityMode.TransportWithMessageCredential);
wsBinding.Security.Message.IssuerMetadataAddress = new EndpointAddress("https://myadfs.com/adfs/services/trust/mex");
var trustChannelFactory = new WSTrustChannelFactory(wsBinding, stsAddress);
trustChannelFactory.Credentials.SupportInteractive = false;
trustChannelFactory.Credentials.UserName.UserName = Settings.UserName;
trustChannelFactory.TrustVersion = TrustVersion.WSTrust13;
var tokenRequest = new RequestSecurityToken()
{
RequestType = RequestTypes.Renew,
RenewTarget = new SecurityTokenElement(token),
AppliesTo = relyingPartyIdentifier,
KeyType = KeyTypes.Symmetric,
TokenType = "http://docs.oasis-open.org/wss/oasis-wss-saml-token-profile-1.1#SAMLV1.1",
};
tokenRequest.Claims.Add(new RequestClaim(ClaimTypes.Name));
tokenRequest.Claims.Add(new RequestClaim(ClaimTypes.Email));
var channel = trustChannelFactory.CreateChannel();
// This call fails with:
// SecurityNegotiationException: Secure channel cannot be opened because security negotiation with the remote endpoint has failed. This may be due to absent or incorrectly specified EndpointIdentity in the EndpointAddress used to create the channel. Please verify the EndpointIdentity specified or implied by the EndpointAddress correctly identifies the remote endpoint. '
// Inner Exception: FaultException: An error occurred when verifying security for the message.
var newToken = (GenericXmlSecurityToken)channel.Issue(tokenRequest);
return newToken;
}
But the call to channel.Issue fails with the following exception:
SecurityNegotiationException: Secure channel cannot be opened because security negotiation with the remote endpoint has failed. This may be due to absent or incorrectly specified EndpointIdentity in the EndpointAddress used to create the channel. Please verify the EndpointIdentity specified or implied by the EndpointAddress correctly identifies the remote endpoint. '
Inner Exception: FaultException: An error occurred when verifying security for the message.
I am completely clueless as how to get a new token using the initial token.

How to pass a certificate to WSTrust to get Saml Token

Here is an example of getting tokem using WSTrustChannelFactory. From here.
var stsBinding = new WS2007HttpBinding();
stsBinding.Security.Mode = SecurityMode.TransportWithMessageCredential;
stsBinding.Security.Message.EstablishSecurityContext = false;
stsBinding.Security.Message.NegotiateServiceCredential = false;
stsBinding.Security.Message.ClientCredentialType = MessageCredentialType.Certificate;
WSTrustChannelFactory trustChannelFactory = new WSTrustChannelFactory(
stsBinding
, new EndpointAddress(tokenurl)
);
trustChannelFactory.TrustVersion = System.ServiceModel.Security.TrustVersion.WSTrust13;
X509Store myStore = new X509Store(StoreName.My, StoreLocation.LocalMachine);
myStore.Open(OpenFlags.ReadOnly);
X509Certificate2Collection coll = myStore.Certificates.Find(X509FindType.FindBySerialNumber, "MycertSerialNumber", true);
X509Certificate2 cert = coll[0];
trustChannelFactory.Credentials.ClientCertificate.Certificate = cert;
WSTrustChannel channel = (WSTrustChannel)trustChannelFactory.CreateChannel();
RequestSecurityToken rst = new RequestSecurityToken(RequestTypes.Issue, keyType);
rst.AppliesTo = new EndpointAddress(realm);
RequestSecurityTokenResponse rstr = null;
rst.TokenType = SecurityTokenTypes.Saml;
SecurityToken token = channel.Issue(rst, out rstr);
Now I don't have a username/password but the provider has given me certificate .pfx file.
How do I pass it to the WSTrushChannelFactory? I have tried using CertificateBinding but no success.
Updated Code above: 11/05/2014:
Getting this error: ID3242: The security token could not be authenticated or authorized.
Use the ClientCertificate property:
var stsBinding = new WS2007HttpBinding();
stsBinding.Security.Mode = SecurityMode.TransportWithMessageCredential;
stsBinding.Security.Message.EstablishSecurityContext = false;
stsBinding.Security.Message.NegotiateServiceCredential = false;
// select the authentication mode of Client Certificate
stsBinding.Security.Message.ClientCredentialType = MessageCredentialType.Certificate;
var wifChannelFactory = new WSTrustChannelFactory(stsBinding, stsEndpoint);
wifChannelFactory.TrustVersion = TrustVersion.WSTrust13;
// Supply the credentials
wifChannelFactory.Credentials.ClientCertificate.Certificate = config.Certificate;
The PFX you can import to your certificate store via the certmgr.msc snapin. Make sure that the account your application is running as has access to the private key. You can reference it in the store using the x509certificate2 classes.
Here you go.
private static SecurityToken RequestSecurityToken()
{
// set up the ws-trust channel factory
var factory = new WSTrustChannelFactory(
new UserNameWSTrustBinding(
SecurityMode.TransportWithMessageCredential),
_idpAddress);
factory.TrustVersion = TrustVersion.WSTrust13;
var authCertificate = X509.LocalMachine.My.Thumbprint.Find(Properties.Settings.Default.RassCertificateThumbprint).FirstOrDefault();
if (authCertificate == null)
throw new InternalException(String.Format("No atuhentication certificate found in store with thumbprint {0}.", Properties.Settings.Default.ClientCertificateThumbprint));
// overenie je na zaklade certifikatu RASS
factory.Credentials.ClientCertificate.Certificate = authCertificate;
// create token request
var rst = new RequestSecurityToken
{
RequestType = RequestTypes.Issue,
KeyType = KeyTypes.Symmetric,
AppliesTo = new EndpointReference(_serviceAddress.AbsoluteUri)
};
// request token and return
return factory.CreateChannel().Issue(rst);
}
BTW: #Mitch is right about access to the private key. I just took your method and replaced few lines of code.