I am trying to make a .NET client using WSE 3.0 custom policy. I have local wsdl because service uses https with certificate protection. Request soap message needs to be signed and encrypted with certificates one from client for signing, and one from service provider for encryption. I used custom security assertions found on MSDN, here is how my class looks like:
namespace RegistryProxy
{
class CustomSecurityAssertion : SecurityPolicyAssertion
{
TokenProvider<X509SecurityToken> serviceX509TokenProviderValue;
TokenProvider<X509SecurityToken> clientX509TokenProviderValue;
public TokenProvider<X509SecurityToken> ClientX509TokenProvider
{
get
{
return clientX509TokenProviderValue;
}
set
{
clientX509TokenProviderValue = value;
}
}
public TokenProvider<X509SecurityToken> ServiceX509TokenProvider
{
get
{
return serviceX509TokenProviderValue;
}
set
{
serviceX509TokenProviderValue = value;
}
}
public CustomSecurityAssertion()
: base()
{
}
public override SoapFilter CreateClientOutputFilter(FilterCreationContext context)
{
return new CustomSecurityClientOutputFilter(this);
}
public override SoapFilter CreateClientInputFilter(FilterCreationContext context)
{
return new CustomSecurityClientInputFilter(this);
}
public override SoapFilter CreateServiceInputFilter(FilterCreationContext context)
{
return new CustomSecurityServerInputFilter(this);
}
public override SoapFilter CreateServiceOutputFilter(FilterCreationContext context)
{
return new CustomSecurityServerOutputFilter(this);
}
public override void ReadXml(System.Xml.XmlReader reader, IDictionary<string, Type> extensions)
{
if (reader == null)
throw new ArgumentNullException("reader");
if (extensions == null)
throw new ArgumentNullException("extensions");
bool isEmpty = reader.IsEmptyElement;
base.ReadAttributes(reader);
reader.ReadStartElement("CustomSecurityAssertion");
if (!isEmpty)
{
if (reader.MoveToContent() == XmlNodeType.Element && reader.Name == "clientToken")
{
reader.ReadStartElement();
reader.MoveToContent();
// Get the registed security token provider for X.509 certificate security credentials.
Type type = extensions[reader.Name];
object instance = Activator.CreateInstance(type);
if (instance == null)
throw new InvalidOperationException(String.Format(System.Globalization.CultureInfo.CurrentCulture, "Unable to instantiate policy extension of type {0}.", type.AssemblyQualifiedName));
TokenProvider<X509SecurityToken> clientProvider = instance as TokenProvider<X509SecurityToken>;
// Read the child elements that provide the details about the client's X.509 certificate.
clientProvider.ReadXml(reader, extensions);
this.ClientX509TokenProvider = clientProvider;
reader.ReadEndElement();
}
if (reader.MoveToContent() == XmlNodeType.Element && reader.Name == "serviceToken")
{
reader.ReadStartElement();
reader.MoveToContent();
// Get the registed security token provider for X.509 certificate security credentials.
Type type = extensions[reader.Name];
object instance = Activator.CreateInstance(type);
if (instance == null)
throw new InvalidOperationException(String.Format(System.Globalization.CultureInfo.CurrentCulture, "Unable to instantiate policy extension of type {0}.", type.AssemblyQualifiedName));
TokenProvider<X509SecurityToken> serviceProvider = instance as TokenProvider<X509SecurityToken>;
// Read the child elements that provide the details about the Web service's X.509 certificate.
serviceProvider.ReadXml(reader, extensions);
this.ServiceX509TokenProvider = serviceProvider;
reader.ReadEndElement();
}
base.ReadElements(reader, extensions);
reader.ReadEndElement();
}
}
public override IEnumerable<KeyValuePair<string, Type>> GetExtensions()
{
// Add the CustomSecurityAssertion custom policy assertion to the list of registered
// policy extensions.
List<KeyValuePair<string, Type>> extensions = new List<KeyValuePair<string, Type>>();
extensions.Add(new KeyValuePair<string, Type>("CustomSecurityAssertion", this.GetType()));
if (serviceX509TokenProviderValue != null)
{
// Add any policy extensions that read child elements of the <serviceToken> element
// to the list of registered policy extensions.
IEnumerable<KeyValuePair<string, Type>> innerExtensions = serviceX509TokenProviderValue.GetExtensions();
if (innerExtensions != null)
{
foreach (KeyValuePair<string, Type> extension in innerExtensions)
{
extensions.Add(extension);
}
}
}
if (clientX509TokenProviderValue != null)
{
// Add any policy extensions that read child elements of the <clientToken> element
// to the list of registered policy extensions.
IEnumerable<KeyValuePair<string, Type>> innerExtensions = clientX509TokenProviderValue.GetExtensions();
if (innerExtensions != null)
{
foreach (KeyValuePair<string, Type> extension in innerExtensions)
{
extensions.Add(extension);
}
}
}
return extensions;
}
// </snippet16
}
class RequestState
{
SecurityToken clientToken;
SecurityToken serverToken;
public RequestState(SecurityToken cToken, SecurityToken sToken)
{
clientToken = cToken;
serverToken = sToken;
}
public SecurityToken ClientToken
{
get { return clientToken; }
}
public SecurityToken ServerToken
{
get { return serverToken; }
}
}
class CustomSecurityServerInputFilter : ReceiveSecurityFilter
{
public CustomSecurityServerInputFilter(CustomSecurityAssertion parentAssertion)
: base(parentAssertion.ServiceActor, false)
{
}
public override void ValidateMessageSecurity(SoapEnvelope envelope, Security security)
{
SecurityToken clientToken = null;
SecurityToken serverToken = null;
// Ensure incoming SOAP messages are signed and encrypted.
foreach (ISecurityElement elem in security.Elements)
{
if (elem is MessageSignature)
{
MessageSignature sig = (MessageSignature)elem;
clientToken = sig.SigningToken;
}
if (elem is EncryptedData)
{
EncryptedData enc = (EncryptedData)elem;
serverToken = enc.SecurityToken;
}
}
if (clientToken == null || serverToken == null)
throw new Exception("Incoming message did not meet security requirements");
RequestState state = new RequestState(clientToken, serverToken);
envelope.Context.OperationState.Set(state);
}
}
class CustomSecurityServerOutputFilter : SendSecurityFilter
{
public CustomSecurityServerOutputFilter(CustomSecurityAssertion parentAssertion)
: base(parentAssertion.ServiceActor, false)
{
}
public override void SecureMessage(SoapEnvelope envelope, Security security)
{
RequestState state = envelope.Context.OperationState.Get<RequestState>();
// Sign the message with the Web service's security token.
security.Tokens.Add(state.ServerToken);
security.Elements.Add(new MessageSignature(state.ServerToken));
// Encrypt the message with the client's security token.
security.Elements.Add(new EncryptedData(state.ClientToken));
}
}
class CustomSecurityClientInputFilter : ReceiveSecurityFilter
{
public CustomSecurityClientInputFilter(CustomSecurityAssertion parentAssertion)
: base(parentAssertion.ServiceActor, true)
{
}
public override void ValidateMessageSecurity(SoapEnvelope envelope, Security security)
{
RequestState state;
bool signed = false;
bool encrypted = false;
// Get the request state out of the operation state.
state = envelope.Context.OperationState.Get<RequestState>();
// Make sure the message was signed with the server's security token.
foreach (ISecurityElement elem in security.Elements)
{
if (elem is MessageSignature)
{
MessageSignature sig = (MessageSignature)elem;
if (sig.SigningToken.Equals(state.ServerToken))
signed = true;
}
if (elem is EncryptedData)
{
EncryptedData enc = (EncryptedData)elem;
if (enc.SecurityToken.Equals(state.ClientToken))
encrypted = true;
}
}
if (!signed || !encrypted)
throw new Exception("Response message does not meet security requirements");
}
}
class CustomSecurityClientOutputFilter : SendSecurityFilter
{
SecurityToken clientToken;
SecurityToken serverToken;
public CustomSecurityClientOutputFilter(CustomSecurityAssertion parentAssertion)
: base(parentAssertion.ServiceActor, true)
{
ISecurityTokenManager stm = SecurityTokenManager.GetSecurityTokenManagerByTokenType(WSTrust.TokenTypes.X509v3);
X509SecurityTokenManager x509tm = stm as X509SecurityTokenManager;
x509tm.DefaultSessionKeyAlgorithm = "AES128";
x509tm.DefaultKeyAlgorithm = "RSA15";
// Get the client security token.
clientToken = X509TokenProvider.CreateToken(StoreLocation.LocalMachine, StoreName.My, "serial removed", X509FindType.FindBySerialNumber);
// Get the server security token.
serverToken = X509TokenProvider.CreateToken(StoreLocation.LocalMachine, StoreName.My, "serial removed",X509FindType.FindBySerialNumber);
}
public override void SecureMessage(SoapEnvelope envelope, Security security)
{
ISecurityTokenManager stm = X509SecurityTokenManager.GetSecurityTokenManagerByTokenType(WSTrust.TokenTypes.X509v3);
X509SecurityTokenManager x509tm = stm as X509SecurityTokenManager;
x509tm.DefaultSessionKeyAlgorithm = "AES128";
x509tm.DefaultKeyAlgorithm = "RSA15";
// Sign the SOAP message with the client's security token.
security.Tokens.Add(clientToken);
security.Elements.Add(new MessageSignature(clientToken));
// Encrypt the SOAP message with the client's security token.
security.Elements.Add(new EncryptedData(serverToken));
// Encrypt the client's security token with the server's security token.
security.Elements.Add(new EncryptedData(serverToken, "#" + clientToken.Id));
// Store the client and server security tokens in the request state.
RequestState state = new RequestState(clientToken, serverToken);
// Store the request state in the proxy's operation state.
// This makes these tokens accessible when SOAP responses are
// verified to have sufficient security requirements.
envelope.Context.OperationState.Set(state);
}
}
}
After class in set into project I made a wse3policyCache.config to use certificates:
<policies xmlns="http://schemas.microsoft.com/wse/2005/06/policy">
<extensions>
<extension name="CustomSecurityAssertion" type="GiftRegistryProxy.CustomSecurityAssertion,GiftRegistryProxy" />
</extensions>
<policy name="ClientPolicy">
<CustomSecurityAssertion>
<clientToken>
<x509
storeLocation="LocalMachine"
storeName="My"
findValue="serial number"
findType="FindBySerialNumber" />
</clientToken>
<serviceToken>
<x509
storeLocation="LocalMachine"
storeName="My"
findValue="serial number"
findType="FindBySerialNumber" />
</serviceToken>
</CustomSecurityAssertion>
</policy>
</policies>
And when everything is set I run my client, my client returns message:
The request was aborted: Could not create SSL/TLS secure channel.
When I look Network monitor it says
TLS:TLS Rec Layer-1 HandShake: Client Hello.
TLS:TLS Rec Layer-1 HandShake: Server Hello. Certificate. Certificate.
TLS:TLS Rec Layer-1 HandShake: Certificate. Client Key Exchange.; TLS Rec Layer-2 Cipher Change Spec; TLS Rec Layer-3 HandShake: Encrypted Handshake Message.
TLS:TLS Rec Layer-1 Encrypted Alert --Problem
Can anyone explain me why this happens and type here if possible?
Related
I tried to add the MaxLoginAttempts feature in my ServiceStack project. But it is still allowing login attempts after 5 unsuccessful login attempts. I'm not sure to know what is missing in my code.
AppHost.cs :
public override void Configure(Funq.Container container)
{
ServiceStack.Text.JsConfig.EmitCamelCaseNames = true;
Routes
.Add<Hello>("/hello")
.Add<Hello>("/hello/{Name*}");
var appSettings = new AppSettings();
//Enable a custom authentication
Plugins.Add(new AuthFeature(() => new CustomUserSession(),
new IAuthProvider[] {
new CustomAuthProvider(appSettings),
})
{
MaxLoginAttempts = 5
});
}
CustomAuthProvider.cs
public CustomAuthProvider(AppSettings appSettings) : base(appSettings) {}
public override bool TryAuthenticate(IServiceBase authService, string userName, string password)
{
// authentication logic
if (userName.Equals("username") && password.Equals("password"))
{
return true;
}
else
{
return false;
}
}
public override IHttpResult OnAuthenticated(IServiceBase authService, IAuthSession session, IAuthTokens tokens, Dictionary<string, string> authInfo)
{
//Saving the session!
return base.OnAuthenticated(authService, session, tokens, authInfo);
}
The MaxLoginAttempts gets validated when you validate the Username/Password against the AuthRepository:
public virtual bool TryAuthenticate(IServiceBase authService, string userName, string password)
{
var authRepo = GetUserAuthRepository(authService.Request);
using (authRepo as IDisposable)
{
var session = authService.GetSession();
if (authRepo.TryAuthenticate(userName, password, out var userAuth))
{
if (IsAccountLocked(authRepo, userAuth))
throw new AuthenticationException(ErrorMessages.UserAccountLocked.Localize(authService.Request));
session.PopulateSession(userAuth, authRepo);
return true;
}
return false;
}
}
Since you're overriding TryAuthenticate() and not using an Auth Repository you're going to have to validate it yourself where you'll need to maintain a InvalidLoginAttempts counter wherever you're persisting the User Info.
If it helps this is what all Auth Repositories use to validate invalid password attempts:
public static void RecordInvalidLoginAttempt(this IUserAuthRepository repo, IUserAuth userAuth)
{
var feature = HostContext.GetPlugin<AuthFeature>();
if (feature?.MaxLoginAttempts == null) return;
userAuth.InvalidLoginAttempts += 1;
userAuth.LastLoginAttempt = userAuth.ModifiedDate = DateTime.UtcNow;
if (userAuth.InvalidLoginAttempts >= feature.MaxLoginAttempts.Value)
{
userAuth.LockedDate = userAuth.LastLoginAttempt;
}
repo.SaveUserAuth(userAuth);
}
Note: the recommend way to set Camel Case is to use:
SetConfig(new HostConfig { UseCamelCase = true });
For all other Serialization customization you should use JsConfig.Init(), e.g:
JsConfig.Init(new Config {
DateHandler = DateHandler.ISO8601,
AlwaysUseUtc = true,
TextCase = TextCase.CamelCase,
ExcludeDefaultValues = true,
});
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
I have the following scenario going on:
A windows "fat client" application is connecting to a WCF webservice. Both, client and webservice use exact the same binding, which looks like this:
private static NetTcpBinding Message_Security_UserName_Credentials()
{
NetTcpBinding binding = new NetTcpBinding();
binding.Security.Mode = SecurityMode.Message;
binding.Security.Message.ClientCredentialType = MessageCredentialType.UserName;
binding.Security.Transport.ProtectionLevel = System.Net.Security.ProtectionLevel.EncryptAndSign;
binding.PortSharingEnabled = true;
return binding;
}
The client sends "custom" client credentials to the webservice. The custom client credential class is this:
public class CustomClientCredentials : ClientCredentials
{
public CustomClientCredentials()
{
AuthorizationToken = String.Empty;
this.ClientCertificate.Certificate = Certificates.ClientPFX;
this.ServiceCertificate.Authentication.CertificateValidationMode = System.ServiceModel.Security.X509CertificateValidationMode.Custom;
this.ServiceCertificate.Authentication.CustomCertificateValidator = new CustomClientX509CertificateValidator("CN");
}
private string authorizationtoken;
public string AuthorizationToken
{
get
{
return this.authorizationtoken;
}
set
{
if (value == null)
{
throw new ArgumentNullException("value");
}
this.authorizationtoken = value;
}
}
public String Name
{
set
{
this.UserName.UserName = value;
}
}
public String Password
{
set
{
this.UserName.Password = value;
}
}
protected CustomClientCredentials(CustomClientCredentials other)
: base(other)
{
this.AuthorizationToken = other.AuthorizationToken;
}
protected override ClientCredentials CloneCore()
{
return new CustomClientCredentials(this);
}
}
In short, the process of sending the custom client credentials to the service looks like this:
ChannelFactory<ILoginService> factory = new ChannelFactory<ILoginService> (binding, endpointaddress);
factory.Endpoint.Behaviors.Remove<ClientCredentials>();
CustomClientCredentials credentials = new CustomClientCredentials() {Name = this.User.EMail, Password = this.User.Password, AuthorizationToken = String.Empty};
factory.Endpoint.Behaviors.Add(credentials);
ILoginService client = factory.CreateChannel();
Token result = client.LogIn();
On the server, I use a custom UserPasswordValidator to read out the client credentials. It looks like this:
public class CustomServiceUserNamePasswordValidator : System.IdentityModel.Selectors.UserNamePasswordValidator
{
public override void Validate(string userName, string password)
{
if (null == userName || null == password)
{
throw new ArgumentNullException();
}
}
}
Up to this point everything works fine. As you can see in my custom ClientCredentials class, I want to send more additional information to the server.
My question is: What must I do, to read out the received custom client credentials on the server?
The theory in my head is, that I simply must tell the service endpoint on the server, that he should expect a certain type of credentials and then he can evaluate them.
Validating custom client credentials may not an easy tasks but you can following this link for validation. I would suggest also to follow this link for custom credential implementation.
I am building an OAuth 1.0(a) authorization server using DotNetOpenAuth (NuGet package DotNetOpenAuth.OAuth.ServiceProvider, version = 4.1.4.12333). The server is hosted in an ASP.NET application but that's irrelevant to the question.
My ServiceProvider is configured like this:
private ServiceProvider GetServiceProvider()
{
var baseUri = "http://myauth.com";
return new ServiceProvider(
new ServiceProviderDescription
{
UserAuthorizationEndpoint = new MessageReceivingEndpoint(
new Uri(baseUri + "/get_request_token"),
HttpDeliveryMethods.GetRequest
),
RequestTokenEndpoint = new MessageReceivingEndpoint(
new Uri(baseUri + "/authorize"),
HttpDeliveryMethods.PostRequest
),
AccessTokenEndpoint = new MessageReceivingEndpoint(
new Uri(baseUri + "/get_token"),
HttpDeliveryMethods.PostRequest
),
ProtocolVersion = ProtocolVersion.V10a,
TamperProtectionElements = new ITamperProtectionChannelBindingElement[]
{
new PlaintextSigningBindingElement(),
new HmacSha1SigningBindingElement(),
},
},
tokenManager,
new OAuthServiceProviderMessageFactory(tokenManager)
);
}
The relevant part of my get_request_token endpoint looks like this:
var serviceProvider = GetServiceProvider();
var tokenRequest = serviceProvider.ReadTokenRequest();
Now when a consumer sends the following request to this endpoint:
GET /get_request_token?oauth_nonce=C5657420BCE5F3224914304376B5334696B09B7FFC17C105A7F9629A008869DC&oauth_timestamp=1356006599&oauth_consumer_key=sampleconsumer&oauth_signature_method=plaintext&oauth_signature=samplesecret%26&oauth_version=1.0&oauth_callback=http%3a%2f%2flocalhost%3a30103%2fCustomOAuth1 HTTP/1.1
Host: localhost:8180
Connection: close
(broken for clarity):
oauth_nonce=C5657420BCE5F3224914304376B5334696B09B7FFC17C105A7F9629A008869DC
oauth_timestamp=1356006599
oauth_consumer_key=sampleconsumer
oauth_signature_method=plaintext
oauth_signature=samplesecret%26
oauth_version=1.0
oauth_callback=http%3a%2f%2flocalhost%3a30103%2fCustomOAuth1
The serviceProvider.ReadTokenRequest() method throws an exception:
The UnauthorizedTokenRequest message required protections {All} but the channel could only apply {Expiration, ReplayProtection}.
at DotNetOpenAuth.Messaging.Channel.ProcessIncomingMessage(IProtocolMessage message)
at DotNetOpenAuth.Messaging.Channel.ReadFromRequest(HttpRequestBase httpRequest)
at DotNetOpenAuth.Messaging.Channel.TryReadFromRequest[TRequest](HttpRequestBase httpRequest, TRequest& request)
at DotNetOpenAuth.OAuth.ServiceProvider.ReadTokenRequest(HttpRequestBase request)
at DotNetOpenAuth.OAuth.ServiceProvider.ReadTokenRequest()
at OAuthServers.OAuth1.Services.OAuth1Service.Any(GetRequestTokenRequest request)
at lambda_method(Closure , Object , Object )
at ServiceStack.ServiceHost.ServiceRunner`1.Execute(IRequestContext requestContext, Object instance, TRequest request)
On the other hand if the client sends the following request:
GET /get_request_token?oauth_callback=http%3a%2f%2flocalhost%3a65271%2foauth1%2fHandleAccessToken&oauth_consumer_key=sampleconsumer&oauth_nonce=rGFvxlWm&oauth_signature_method=HMAC-SHA1&oauth_signature=HV%2f5Vq%2b0cF3NrtiISE9k4jmgCrY%3d&oauth_version=1.0&oauth_timestamp=1356007830 HTTP/1.1
Host: localhost:8180
Connection: close
(broken for clarity):
oauth_callback=http%3a%2f%2flocalhost%3a65271%2foauth1%2fHandleAccessToken
oauth_consumer_key=sampleconsumer
oauth_nonce=rGFvxlWm
oauth_signature_method=HMAC-SHA1
oauth_signature=HV%2f5Vq%2b0cF3NrtiISE9k4jmgCrY%3d
oauth_version=1.0
oauth_timestamp=1356007830
it succeeds.
As you can see the only difference between those 2 requests is the oauth_signature_method being used. In the first case PLAINTEXT is used whereas in the second HMAC-SHA1.
Is it possible to make DotNetOpenAuth accept a PLAINTEXT signature method for the request token endpoint along with the GET verb (even if the OAuth 1.0(a) specification recommends POST to be used for this endpoint)? Is there some config option that could relax this requirement on the server?
At the moment modifying the client is not an option for me.
OAuth Authentication is done in three steps:
The Consumer obtains an unauthorized Request Token.
The User authorizes the Request Token.
The Consumer exchanges the Request Token for an Access Token.
So here's what that would look like:
public class InMemoryTokenManager : IConsumerTokenManager, IOpenIdOAuthTokenManager
{
private Dictionary<string, string> tokensAndSecrets = new Dictionary<string, string>();
public InMemoryTokenManager(string consumerKey, string consumerSecret)
{
if (String.IsNullOrEmpty(consumerKey))
{
throw new ArgumentNullException("consumerKey");
}
this.ConsumerKey = consumerKey;
this.ConsumerSecret = consumerSecret;
}
public string ConsumerKey { get; private set; }
public string ConsumerSecret { get; private set; }
#region ITokenManager Members
public string GetConsumerSecret(string consumerKey)
{
if (consumerKey == this.ConsumerKey)
{
return this.ConsumerSecret;
}
else
{
throw new ArgumentException("Unrecognized consumer key.", "consumerKey");
}
}
public string GetTokenSecret(string token)
{
return this.tokensAndSecrets[token];
}
public void StoreNewRequestToken(UnauthorizedTokenRequest request, ITokenSecretContainingMessage response)
{
this.tokensAndSecrets[response.Token] = response.TokenSecret;
}
public void ExpireRequestTokenAndStoreNewAccessToken(string consumerKey, string requestToken, string accessToken, string accessTokenSecret)
{
this.tokensAndSecrets.Remove(requestToken);
this.tokensAndSecrets[accessToken] = accessTokenSecret;
}
/// <summary>
/// Classifies a token as a request token or an access token.
/// </summary>
/// <param name="token">The token to classify.</param>
/// <returns>Request or Access token, or invalid if the token is not recognized.</returns>
public TokenType GetTokenType(string token)
{
throw new NotImplementedException();
}
#endregion
#region IOpenIdOAuthTokenManager Members
public void StoreOpenIdAuthorizedRequestToken(string consumerKey, AuthorizationApprovedResponse authorization)
{
this.tokensAndSecrets[authorization.RequestToken] = string.Empty;
}
#endregion
}
Following block of code may help you to generate plain text signature
public static string GetSignature(OAuthSignatureMethod signatureMethod, AuthSignatureTreatment signatureTreatment, string signatureBase, string consumerSecret, string tokenSecret)
{
if (tokenSecret.IsNullOrBlank())
{
tokenSecret = String.Empty;
}
consumerSecret = UrlEncodeRelaxed(consumerSecret);
tokenSecret = UrlEncodeRelaxed(tokenSecret);
string signature;
switch (signatureMethod)
{
case OAuthSignatureMethod.HmacSha1:
{
var crypto = new HMACSHA1();
var key = "{0}&{1}".FormatWith(consumerSecret, tokenSecret);
crypto.Key = _encoding.GetBytes(key);
signature = signatureBase.HashWith(crypto);
break;
}
case OAuthSignatureMethod.PlainText:
{
signature = "{0}&{1}".FormatWith(consumerSecret, tokenSecret);
break;
}
default:
throw new NotImplementedException("Only HMAC-SHA1 is currently supported.");
}
var result = signatureTreatment == OAuthSignatureTreatment.Escaped
? UrlEncodeRelaxed(signature)
: signature;
return result;
}
-Is it possible to secure a WCF Data Service by using certificate-based authentication ?
-Is there a resource that describes this process ?
-Can we use Message security with a WCF Data service ?
The answer to all your questions is "yes". Below is a very informative link provided by the Patterns and Practices team at Microsoft to accomplish exactly what you are looking for.
http://msdn.microsoft.com/en-us/library/cc949005.aspx
Certificate based Authentication can be done like this:
Server side:
public class ODataService : DataService<Database>
{
public ODataService()
{
ProcessingPipeline.ProcessingRequest += ProcessingPipeline_ProcessingRequest;
}
void ProcessingPipeline_ProcessingRequest(object sender, DataServiceProcessingPipelineEventArgs e)
{
if (!HttpContext.Current.Request.ClientCertificate.IsPresent)
{
throw new DataServiceException(401, "401 Unauthorized");
}
var cert = new X509Certificate2(HttpContext.Current.Request.ClientCertificate.Certificate);
if (!ValidateCertificate(cert))
{
throw new DataServiceException(401, "401 Unauthorized");
}
var identity = new GenericIdentity(cert.Subject, "ClientCertificate");
var principal = new GenericPrincipal(identity, null);
Thread.CurrentPrincipal = principal;
HttpContext.Current.User = principal;
}
private bool ValidateCertificate(X509Certificate2 cert)
{
// do some validation
}
Client side:
Create a partial class for your database service reference (DataServiceContext)
public partial class Database
{
// ref: http://social.msdn.microsoft.com/Forums/en-US/0aa2a875-fd59-4f3e-a459-9f604b374749/how-do-i-use-certificate-based-authentication-with-data-services-client?forum=adodotnetdataservices
private X509Certificate clientCertificate = null;
public X509Certificate ClientCertificate
{
get
{
return clientCertificate;
}
set
{
if (value == null)
{
// if the event has been hooked up before, we should remove it
if (clientCertificate != null)
{
SendingRequest -= OnSendingRequest_AddCertificate;
}
}
else
{
// hook up the event if its being set to something non-null
if (clientCertificate == null)
{
SendingRequest += OnSendingRequest_AddCertificate;
}
}
clientCertificate = value;
}
}
private void OnSendingRequest_AddCertificate(object sender, SendingRequestEventArgs args)
{
if (null != ClientCertificate)
{
(args.Request as HttpWebRequest).ClientCertificates.Add(ClientCertificate);
}
}
Use it like this
Database db = new Database(new Uri(service));
db.ClientCertificate = CertificateUtil.GetCertificateByThumbprint(StoreName.My,
StoreLocation.LocalMachine,
"<a thumbprint>");
Private key stored on client computer, public key stored on server in Local machine/Trusted Root CA
Remember to require/negotiate client sertificate for this Site in IIS.
(Tested on WCF Data Services 5.2, VS 2012)