How to renew a SAML assertion with ADFS? - wcf

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.

Related

GetDiscoveryDocumentAsync can not find the authority url - IdentiyServer4

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"

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;
}

RequestSecurityToken from ADFS using windows authentication

My client should request the security token from the ADFS server using the currently logged on user's context. I have been successfully able to request the security token from the adfs/services/trust/13/usernamemixed endpoint using username and password from the client and post it to my website.
Here's a snippet of my code.
WS2007HttpBinding binding = new WS2007HttpBinding(SecurityMode.TransportWithMessageCredential);
binding.Security.Message.EstablishSecurityContext = false;
binding.Security.Transport.ClientCredentialType = HttpClientCredentialType.None;
EndpointAddress EpAddress;
if (!bWindowsAuth)
{
binding.Security.Message.ClientCredentialType = MessageCredentialType.UserName;
EpAddress = new EndpointAddress(".../adfs/services/trust/13/usernamemixed");
}
else
{
binding.Security.Message.ClientCredentialType = MessageCredentialType.Windows;
EpAddress = new EndpointAddress(".../adfs/services/trust/13/windowsmixed");
}
WSTrustChannelFactory trustChannelFactory = new WSTrustChannelFactory(binding, EpAddress);
trustChannelFactory.TrustVersion = TrustVersion.WSTrust13;
if (!bWindowsAuth)
{
trustChannelFactory.Credentials.UserName.UserName = username;
trustChannelFactory.Credentials.UserName.Password = password;
}
else
{
trustChannelFactory.Credentials.Windows.ClientCredential = CredentialCache.DefaultNetworkCredentials;
}
trustChannelFactory.ConfigureChannelFactory();
// Create issuance issuance and get security token
RequestSecurityToken requestToken = new RequestSecurityToken(WSTrust13Constants.RequestTypes.Issue, WSTrust13Constants.KeyTypes.Bearer);
requestToken.AppliesTo = new EndpointAddress(appliesTo);
WSTrustChannel tokenClient = (WSTrustChannel)trustChannelFactory.CreateChannel();
SecurityToken token = tokenClient.Issue(requestToken, out rsts);
CredentialCache.DefaultNetworkCredentials is empty!!
Any other configurations to be done?
Thanks in advance.

Get claims for service using Windows validation/authentication

I have the following code which was validation using username & password.
How can I use Windows credentials instead?
private static SecurityToken GetClaims(string serviceurl, string username, string password)
{
bool isincurrentusercontext = String.IsNullOrEmpty(username);
WS2007HttpBinding binding = new WS2007HttpBinding();
binding.Security.Mode = SecurityMode.TransportWithMessageCredential;
binding.Security.Message.EstablishSecurityContext = false;
EndpointAddress endpoint = new EndpointAddress(new Uri(GetStsUrl(isincurrentusercontext)));
WSTrustChannelFactory trustChannelFactory;
if (!isincurrentusercontext)
{
binding.Security.Transport.ClientCredentialType = HttpClientCredentialType.None;
binding.Security.Message.ClientCredentialType = MessageCredentialType.UserName;
trustChannelFactory = new WSTrustChannelFactory(binding, endpoint);
trustChannelFactory.Credentials.UserName.UserName = username;
trustChannelFactory.Credentials.UserName.Password = password;
}
else
trustChannelFactory = new WSTrustChannelFactory(new KerberosWSTrustBinding(SecurityMode.TransportWithMessageCredential), endpoint);
trustChannelFactory.TrustVersion = TrustVersion.WSTrust13;
trustChannelFactory.Credentials.UseIdentityConfiguration = true;
WSTrustChannel channel = null;
RequestSecurityToken rst = new RequestSecurityToken(RequestTypes.Issue);
rst.AppliesTo = new EndpointReference(serviceurl);
rst.KeyType = KeyTypes.Bearer;
channel = (WSTrustChannel)trustChannelFactory.CreateChannel();
SecurityToken token = channel.Issue(rst);
((IChannel)channel).Close();
channel = null;
trustChannelFactory.Close();
trustChannelFactory = null;
return token;
}
If I change,
binding.Security.Message.ClientCredentialType = MessageCredentialType.UserName;
to
binding.Security.Message.ClientCredentialType = MessageCredentialType.Windows;
and remove these two statements,
trustChannelFactory.Credentials.UserName.UserName = username;
trustChannelFactory.Credentials.UserName.Password = password;
and when i run this statement,
SecurityToken token = channel.Issue(rst);
I get the following error,
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

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.