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;
}
Related
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"
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.
I got an issue when setting up HTTPs for a self host Owin console application. The browser always shows a connection reset error.
I've tried creating certificates manually according to this link http://chavli.com/how-to-configure-owin-self-hosted-website-with-ssl/ but I still get the connection reset issue on that port.
And I've checked the windows event log and there's no error messages.
The application will create X509 certificate by itself and run netsh command automatically.
Without Ssl, the application can display the web page correctly.
Can any one run my code below and see whether it can work on your computer?
Thanks in advance.
Need to add COM reference CertEnroll 1.0 Type Library to compile the code below (vs2015 already contains this COM reference in Type Libraries)
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Security;
using System.Security.Cryptography.X509Certificates;
using System.Text;
using System.Threading.Tasks;
using CERTENROLLLib;
using Microsoft.Owin.Hosting;
using AppFunc = System.Func<System.Collections.Generic.IDictionary<string, object>, System.Threading.Tasks.Task>;
namespace Owin.Startup
{
class Program
{
static void Main(string[] args)
{
int port = 8888;
string url = $"https://localhost:{port}";
var cert = GetCert("localhost", TimeSpan.FromDays(3650), "devpwd", AppDomain.CurrentDomain.BaseDirectory + "cert.dat");
ActivateCert(cert, port, GetAppId());
using (WebApp.Start<Startup>(url))
{
Console.WriteLine($"Hosted: {url}");
Console.ReadLine();
}
}
static private string GetAppId()
{
Assembly assembly = Assembly.GetExecutingAssembly();
//The following line (part of the original answer) is misleading.
//**Do not** use it unless you want to return the System.Reflection.Assembly type's GUID.
//Console.WriteLine(assembly.GetType().GUID.ToString());
// The following is the correct code.
var attribute = (GuidAttribute)assembly.GetCustomAttributes(typeof(GuidAttribute), true)[0];
var id = attribute.Value;
return id;
}
static public X509Certificate2 GetCert(string cn, TimeSpan expirationLength, string pwd = "", string filename = null)
{
// http://stackoverflow.com/questions/18339706/how-to-create-self-signed-certificate-programmatically-for-wcf-service
// http://stackoverflow.com/questions/21629395/http-listener-with-https-support-coded-in-c-sharp
// https://msdn.microsoft.com/en-us/library/system.security.cryptography.x509certificates.storename(v=vs.110).aspx
// create DN for subject and issuer
var base64encoded = string.Empty;
if (filename != null && File.Exists(filename))
{
base64encoded = File.ReadAllText(filename);
}
else
{
base64encoded = CreateCertContent(cn, expirationLength, pwd);
if (filename != null)
{
File.WriteAllText(filename, base64encoded);
}
}
// instantiate the target class with the PKCS#12 data (and the empty password)
var rlt = new System.Security.Cryptography.X509Certificates.X509Certificate2(
System.Convert.FromBase64String(base64encoded), pwd,
// mark the private key as exportable (this is usually what you want to do)
// mark private key to go into the Machine store instead of the current users store
X509KeyStorageFlags.Exportable | X509KeyStorageFlags.MachineKeySet | X509KeyStorageFlags.PersistKeySet
);
return rlt;
}
private static string CreateCertContent(string cn, TimeSpan expirationLength, string pwd)
{
string base64encoded = string.Empty;
var dn = new CX500DistinguishedName();
dn.Encode("CN=" + cn, X500NameFlags.XCN_CERT_NAME_STR_NONE);
CX509PrivateKey privateKey = new CX509PrivateKey();
privateKey.ProviderName = "Microsoft Strong Cryptographic Provider";
privateKey.Length = 2048;
privateKey.KeySpec = X509KeySpec.XCN_AT_KEYEXCHANGE;
privateKey.KeyUsage = X509PrivateKeyUsageFlags.XCN_NCRYPT_ALLOW_DECRYPT_FLAG |
X509PrivateKeyUsageFlags.XCN_NCRYPT_ALLOW_KEY_AGREEMENT_FLAG;
privateKey.MachineContext = true;
privateKey.ExportPolicy = X509PrivateKeyExportFlags.XCN_NCRYPT_ALLOW_PLAINTEXT_EXPORT_FLAG;
privateKey.Create();
// Use the stronger SHA512 hashing algorithm
var hashobj = new CObjectId();
hashobj.InitializeFromAlgorithmName(ObjectIdGroupId.XCN_CRYPT_HASH_ALG_OID_GROUP_ID,
ObjectIdPublicKeyFlags.XCN_CRYPT_OID_INFO_PUBKEY_ANY,
AlgorithmFlags.AlgorithmFlagsNone, "SHA512");
// Create the self signing request
var cert = new CX509CertificateRequestCertificate();
cert.InitializeFromPrivateKey(X509CertificateEnrollmentContext.ContextMachine, privateKey, "");
cert.Subject = dn;
cert.Issuer = dn; // the issuer and the subject are the same
cert.NotBefore = DateTime.Now.Date;
// this cert expires immediately. Change to whatever makes sense for you
cert.NotAfter = cert.NotBefore + expirationLength;
cert.HashAlgorithm = hashobj; // Specify the hashing algorithm
cert.Encode(); // encode the certificate
// Do the final enrollment process
var enroll = new CX509Enrollment();
enroll.InitializeFromRequest(cert); // load the certificate
enroll.CertificateFriendlyName = cn; // Optional: add a friendly name
string csr = enroll.CreateRequest(); // Output the request in base64
// and install it back as the response
enroll.InstallResponse(InstallResponseRestrictionFlags.AllowUntrustedCertificate,
csr, EncodingType.XCN_CRYPT_STRING_BASE64, pwd); // no password
// output a base64 encoded PKCS#12 so we can import it back to the .Net security classes
base64encoded = enroll.CreatePFX(pwd, // no password, this is for internal consumption
PFXExportOptions.PFXExportChainWithRoot);
return base64encoded;
}
private static void ActivateCert(X509Certificate2 rlt, int port, string appId)
{
X509Store store = new X509Store(StoreName.Root, StoreLocation.LocalMachine);
store.Open(OpenFlags.ReadWrite);
if (!store.Certificates.Contains(rlt))
{
store.Add(rlt);
ProcessStartInfo psi = new ProcessStartInfo();
psi.FileName = "netsh";
psi.Arguments = $"http delete sslcert ipport=0.0.0.0:{port}";
Process procDel = Process.Start(psi);
procDel.WaitForExit();
psi.Arguments = $"http add sslcert ipport=0.0.0.0:{port} certhash={rlt.Thumbprint} appid={{{appId}}}";
Process proc = Process.Start(psi);
proc.WaitForExit();
psi.Arguments = $"http delete sslcert ipport=[::]:{port}";
Process procDelV6 = Process.Start(psi);
procDelV6.WaitForExit();
psi.Arguments = $"http add sslcert ipport=[::]:{port} certhash={rlt.Thumbprint} appid={{{appId}}}";
Process procV6 = Process.Start(psi);
procV6.WaitForExit();
psi.Arguments = $"http add urlacl url=https://+:{port}/ user={Environment.UserDomainName}\\{Environment.UserName}";
Process procAcl = Process.Start(psi);
procAcl.WaitForExit();
}
store.Close();
}
}
public class Startup
{
private IAppBuilder app;
public void Configuration(IAppBuilder app)
{
#if DEBUG
app.UseErrorPage();
#endif
app.Use(new Func<AppFunc, AppFunc>(next => (async env =>
{
Console.WriteLine("Begin Request");
foreach (var i in env.Keys)
{
Console.WriteLine($"{i}\t={(env[i] == null ? "null" : env[i].ToString())}\t#\t{(env[i] == null ? "null" : env[i].GetType().FullName)}");
}
if (next != null)
{
await next.Invoke(env);
}
else
{
Console.WriteLine("Process Complete");
}
Console.WriteLine("End Request");
})));
app.UseWelcomePage("/");
this.app = app;
}
}
}
You should not create certificate with the base64 content which loaded from the cert.dat. Try using cert.Export(X509ContentType.Pfx, pwd) and load it with new X509Certificate2(filename, pwd, X509KeyStorageFlags.Exportable | X509KeyStorageFlags.MachineKeySet | X509KeyStorageFlags.PersistKeySet);
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.
I've written a self-hosted WCF service using WSHttpBindings and I'm trying to implement message-level security using certificates I've generated myself. Unfortunately I'm getting a buried exception (via the Service Trace Viewer) stating "The credentials supplied to the package were not recognized."
A couple notes:
This has to be done in code, not
in configuration
(Server/Client)Cert are certificates
that are in the local machine store
with accessible private keys to my
user while debugging.
I've googled the hell out of this and
found a good resource for setting up
WCF message based security here
I'm not sure what I'm missing. Most of this stuff seems straight forward except for creating the endpoint identities. It fails with the same message whether I use DnsEndpointIdentities, cert based ones, or no identities at all.
Can anyone point me in the right direction?
Server side:
var binding = new WSHttpBinding
{
Security =
{
Mode = SecurityMode.Message,
Message =
{
ClientCredentialType = MessageCredentialType.Certificate,
AlgorithmSuite = SecurityAlgorithmSuite.Basic256Sha256Rsa15
}
}
};
_host = new ServiceHost(this)
{
Credentials =
{
ServiceCertificate =
{
Certificate = ServiceCert
},
ClientCertificate =
{
Certificate = ClientCert,
Authentication =
{
TrustedStoreLocation = StoreLocation.LocalMachine,
RevocationMode = X509RevocationMode.NoCheck,
CertificateValidationMode = X509CertificateValidationMode.PeerOrChainTrust
}
}
}
};
var address = new Uri(string.Format(#"http://serviceaddress"));
var ep = _host.AddServiceEndpoint(typeof (IService), binding, address);
ep.Address = new EndpointAddress(address, EndpointIdentity.CreateX509CertificateIdentity(ServiceCert));
_host.Open();
Client side:
var binding = new WSHttpBinding
{
Security =
{
Mode = SecurityMode.Message,
Message =
{
ClientCredentialType = MessageCredentialType.Certificate,
AlgorithmSuite = SecurityAlgorithmSuite.Basic256Sha256Rsa15
}
}
};
var address = new Uri(#"http://serviceaddress");
var endpoint = new EndpointAddress(address, EndpointIdentity.CreateX509CertificateIdentity(ServerCert));
var channelFactory = new ChannelFactory<IService>(binding, endpoint)
{
Credentials =
{
ServiceCertificate =
{
DefaultCertificate = ServerCert,
Authentication =
{
RevocationMode = X509RevocationMode.NoCheck,
TrustedStoreLocation = StoreLocation.LocalMachine,
CertificateValidationMode = X509CertificateValidationMode.PeerOrChainTrust
}
},
ClientCertificate =
{
Certificate = ClientCert
}
}
};
var channel = channelFactory.CreateChannel();
this msdn article helped tremendously. I think the root of the problem was setting the following message security parameters to false:
httpBinding.Security.Message.NegotiateServiceCredential = false;
httpBinding.Security.Message.EstablishSecurityContext = false;
So now the overall code for the server side looks more like:
var httpBinding = new WSHttpBinding(SecurityMode.Message);
httpBinding.Security.Message.ClientCredentialType = MessageCredentialType.Certificate;
httpBinding.Security.Message.NegotiateServiceCredential = false;
httpBinding.Security.Message.EstablishSecurityContext = false;
var httpUri = new Uri("http://serviceaddress");
_host = new ServiceHost(this, httpUri);
_host.Credentials.ServiceCertificate.SetCertificate(StoreLocation.LocalMachine, StoreName.TrustedPeople, X509FindType.FindByThumbprint, serverThumbprint);
_host.Credentials.ClientCertificate.Authentication.RevocationMode = X509RevocationMode.NoCheck;
_host.Credentials.ClientCertificate.Authentication.TrustedStoreLocation = StoreLocation.LocalMachine;
_host.AddServiceEndpoint(typeof(IMetaService), httpBinding, httpUri);
_host.Open();
and the client side:
var httpBinding = new WSHttpBinding(SecurityMode.Message);
httpBinding.Security.Message.ClientCredentialType = MessageCredentialType.Certificate;
httpBinding.Security.Message.NegotiateServiceCredential = false;
httpBinding.Security.Message.EstablishSecurityContext = false;
var httpUri = new Uri("http://serviceaddress");
var httpEndpoint = new EndpointAddress(httpUri, EndpointIdentity.CreateDnsIdentity("name of server cert"));
var newFactory = new ChannelFactory<IMetaService>(httpBinding, httpEndpoint);
newFactory.Credentials.ClientCertificate.SetCertificate(StoreLocation.LocalMachine, StoreName.TrustedPeople, X509FindType.FindByThumbprint, "client certificate thumbprint");
newFactory.Credentials.ServiceCertificate.SetDefaultCertificate(StoreLocation.LocalMachine, StoreName.TrustedPeople, X509FindType.FindByThumbprint, "server certificate thumbprint");
var channel = newFactory.CreateChannel();