How to: Use Separate X.509 Certificates for Signing and Encryption
This topic shows how to configure Windows Communication Foundation (WCF) to use different certificates for message signing and encryption on both the client and service.
To enable separate certificates to be used for signing and encryption, a custom client or service credentials (or both) must be created because WCF does not provide an API to set multiple client or service certificates. Additionally, a security token manager must be provided to leverage the multiple certificates' information and to create an appropriate security token provider for specified key usage and message direction.
The following diagram shows the main classes used, the classes they inherit from (shown by an upward-pointing arrow), and the return types of certain methods and properties.
MyClientCredentials is a custom implementation of ClientCredentials.
Its properties shown in the diagram all return instances of X509Certificate2.
Its method CreateSecurityTokenManager returns an instance of MyClientCredentialsSecurityTokenManager.
MyClientCredentialsSecurityTokenManager is a custom implementation of ClientCredentialsSecurityTokenManager.
- Its method CreateSecurityTokenProvider returns an instance of X509SecurityTokenProvider.
For more information about custom credentials, see Walkthrough: Creating Custom Client and Service Credentials.
In addition, you must create a custom identity verifier, and link it to a security binding element in a custom binding. You must also use the custom credentials instead of the default credentials.
The following diagram shows the classes involved in the custom binding, and how the custom identity verifier is linked. There are several binding elements involved, all of which inherit from BindingElement. The AsymmetricSecurityBindingElement has the LocalClientSecuritySettings property, which returns an instance of IdentityVerifier, from which MyIdentityVerifier is customized.
For more information about creating a custom identity verifier, see How to: How to: Create a Custom Client Identity Verifier.
To use separate certificates for signing and encryption
Define a new client credentials class that inherits from the ClientCredentials class. Implement four new properties to allow multiple certificates specification:
ClientSigningCertificate
,ClientEncryptingCertificate
,ServiceSigningCertificate
, andServiceEncryptingCertificate
. Also override the CreateSecurityTokenManager method to return an instance of the customized ClientCredentialsSecurityTokenManager class that is defined in the next step.Public Class MyClientCredentials Inherits ClientCredentials Private clientSigningCert As X509Certificate2 Private clientEncryptingCert As X509Certificate2 Private serviceSigningCert As X509Certificate2 Private serviceEncryptingCert As X509Certificate2 Public Sub New() End Sub Protected Sub New(ByVal other As MyClientCredentials) MyBase.New(other) Me.clientEncryptingCert = other.clientEncryptingCert Me.clientSigningCert = other.clientSigningCert Me.serviceEncryptingCert = other.serviceEncryptingCert Me.serviceSigningCert = other.serviceSigningCert End Sub Public Property ClientSigningCertificate() As X509Certificate2 Get Return Me.clientSigningCert End Get Set(ByVal value As X509Certificate2) Me.clientSigningCert = value End Set End Property Public Property ClientEncryptingCertificate() As X509Certificate2 Get Return Me.clientEncryptingCert End Get Set(ByVal value As X509Certificate2) Me.clientEncryptingCert = value End Set End Property Public Property ServiceSigningCertificate() As X509Certificate2 Get Return Me.serviceSigningCert End Get Set(ByVal value As X509Certificate2) Me.serviceSigningCert = value End Set End Property Public Property ServiceEncryptingCertificate() As X509Certificate2 Get Return Me.serviceEncryptingCert End Get Set(ByVal value As X509Certificate2) Me.serviceEncryptingCert = value End Set End Property Public Overrides Function CreateSecurityTokenManager() As SecurityTokenManager Return New MyClientCredentialsSecurityTokenManager(Me) End Function Protected Overrides Function CloneCore() As ClientCredentials Return New MyClientCredentials(Me) End Function End Class
public class MyClientCredentials : ClientCredentials { X509Certificate2 clientSigningCert; X509Certificate2 clientEncryptingCert; X509Certificate2 serviceSigningCert; X509Certificate2 serviceEncryptingCert; public MyClientCredentials() { } protected MyClientCredentials(MyClientCredentials other) : base(other) { this.clientEncryptingCert = other.clientEncryptingCert; this.clientSigningCert = other.clientSigningCert; this.serviceEncryptingCert = other.serviceEncryptingCert; this.serviceSigningCert = other.serviceSigningCert; } public X509Certificate2 ClientSigningCertificate { get { return this.clientSigningCert; } set { this.clientSigningCert = value; } } public X509Certificate2 ClientEncryptingCertificate { get { return this.clientEncryptingCert; } set { this.clientEncryptingCert = value; } } public X509Certificate2 ServiceSigningCertificate { get { return this.serviceSigningCert; } set { this.serviceSigningCert = value; } } public X509Certificate2 ServiceEncryptingCertificate { get { return this.serviceEncryptingCert; } set { this.serviceEncryptingCert = value; } } public override SecurityTokenManager CreateSecurityTokenManager() { return new MyClientCredentialsSecurityTokenManager(this); } protected override ClientCredentials CloneCore() { return new MyClientCredentials(this); } }
Define a new client security token manager that inherits from the ClientCredentialsSecurityTokenManager class. Override the CreateSecurityTokenProvider method to create an appropriate security token provider. The requirement parameter (a SecurityTokenRequirement) provides the message direction and key usage.
Friend Class MyClientCredentialsSecurityTokenManager Inherits ClientCredentialsSecurityTokenManager Private credentials As MyClientCredentials Public Sub New(ByVal credentials As MyClientCredentials) MyBase.New(credentials) Me.credentials = credentials End Sub Public Overrides Function CreateSecurityTokenProvider(ByVal requirement As SecurityTokenRequirement) As SecurityTokenProvider Dim result As SecurityTokenProvider = Nothing If requirement.TokenType = SecurityTokenTypes.X509Certificate Then Dim direction = requirement.GetProperty(Of MessageDirection)(ServiceModelSecurityTokenRequirement.MessageDirectionProperty) If direction = MessageDirection.Output Then If requirement.KeyUsage = SecurityKeyUsage.Signature Then result = New X509SecurityTokenProvider(Me.credentials.ClientSigningCertificate) Else result = New X509SecurityTokenProvider(Me.credentials.ServiceEncryptingCertificate) End If Else If requirement.KeyUsage = SecurityKeyUsage.Signature Then result = New X509SecurityTokenProvider(Me.credentials.ServiceSigningCertificate) Else result = New X509SecurityTokenProvider(credentials.ClientEncryptingCertificate) End If End If Else result = MyBase.CreateSecurityTokenProvider(requirement) End If Return result End Function Public Overrides Function CreateSecurityTokenAuthenticator(ByVal tokenRequirement As SecurityTokenRequirement, _ <System.Runtime.InteropServices.Out()> ByRef outOfBandTokenResolver As SecurityTokenResolver) As SecurityTokenAuthenticator Return MyBase.CreateSecurityTokenAuthenticator(tokenRequirement, _ outOfBandTokenResolver) End Function End Class
internal class MyClientCredentialsSecurityTokenManager : ClientCredentialsSecurityTokenManager { MyClientCredentials credentials; public MyClientCredentialsSecurityTokenManager( MyClientCredentials credentials): base(credentials) { this.credentials = credentials; } public override SecurityTokenProvider CreateSecurityTokenProvider( SecurityTokenRequirement requirement) { SecurityTokenProvider result = null; if (requirement.TokenType == SecurityTokenTypes.X509Certificate) { MessageDirection direction = requirement.GetProperty <MessageDirection>(ServiceModelSecurityTokenRequirement. MessageDirectionProperty); if (direction == MessageDirection.Output) { if (requirement.KeyUsage == SecurityKeyUsage.Signature) { result = new X509SecurityTokenProvider( this.credentials.ClientSigningCertificate); } else { result = new X509SecurityTokenProvider(this.credentials. ServiceEncryptingCertificate); } } else { if (requirement.KeyUsage == SecurityKeyUsage.Signature) { result = new X509SecurityTokenProvider(this. credentials.ServiceSigningCertificate); } else { result = new X509SecurityTokenProvider(credentials. ClientEncryptingCertificate); } } } else { result = base.CreateSecurityTokenProvider(requirement); } return result; } public override SecurityTokenAuthenticator CreateSecurityTokenAuthenticator(SecurityTokenRequirement tokenRequirement, out SecurityTokenResolver outOfBandTokenResolver) { return base.CreateSecurityTokenAuthenticator(tokenRequirement, out outOfBandTokenResolver); } }
Define a new service credentials class that inherits from the ServiceCredentials class. Implement four new properties to allow multiple certificates specification:
ClientSigningCertificate
,ClientEncryptingCertificate
,ServiceSigningCertificate
, andServiceEncryptingCertificate
. Also override the CreateSecurityTokenManager method to return an instance of the customized ServiceCredentialsSecurityTokenManager class that is defined in the next step.Public Class MyServiceCredentials Inherits ServiceCredentials Private clientSigningCert As X509Certificate2 Private clientEncryptingCert As X509Certificate2 Private serviceSigningCert As X509Certificate2 Private serviceEncryptingCert As X509Certificate2 Public Sub New() End Sub Protected Sub New(ByVal other As MyServiceCredentials) MyBase.New(other) Me.clientEncryptingCert = other.clientEncryptingCert Me.clientSigningCert = other.clientSigningCert Me.serviceEncryptingCert = other.serviceEncryptingCert Me.serviceSigningCert = other.serviceSigningCert End Sub Public Property ClientSigningCertificate() As X509Certificate2 Get Return Me.clientSigningCert End Get Set(ByVal value As X509Certificate2) Me.clientSigningCert = value End Set End Property Public Property ClientEncryptingCertificate() As X509Certificate2 Get Return Me.clientEncryptingCert End Get Set(ByVal value As X509Certificate2) Me.clientEncryptingCert = value End Set End Property Public Property ServiceSigningCertificate() As X509Certificate2 Get Return Me.serviceSigningCert End Get Set(ByVal value As X509Certificate2) Me.serviceSigningCert = value End Set End Property Public Property ServiceEncryptingCertificate() As X509Certificate2 Get Return Me.serviceEncryptingCert End Get Set(ByVal value As X509Certificate2) Me.serviceEncryptingCert = value End Set End Property Public Overrides Function CreateSecurityTokenManager() As SecurityTokenManager Return New MyServiceCredentialsSecurityTokenManager(Me) End Function Protected Overrides Function CloneCore() As ServiceCredentials Return New MyServiceCredentials(Me) End Function End Class
public class MyServiceCredentials : ServiceCredentials { X509Certificate2 clientSigningCert; X509Certificate2 clientEncryptingCert; X509Certificate2 serviceSigningCert; X509Certificate2 serviceEncryptingCert; public MyServiceCredentials() { } protected MyServiceCredentials(MyServiceCredentials other) : base(other) { this.clientEncryptingCert = other.clientEncryptingCert; this.clientSigningCert = other.clientSigningCert; this.serviceEncryptingCert = other.serviceEncryptingCert; this.serviceSigningCert = other.serviceSigningCert; } public X509Certificate2 ClientSigningCertificate { get { return this.clientSigningCert; } set { this.clientSigningCert = value; } } public X509Certificate2 ClientEncryptingCertificate { get { return this.clientEncryptingCert; } set { this.clientEncryptingCert = value; } } public X509Certificate2 ServiceSigningCertificate { get { return this.serviceSigningCert; } set { this.serviceSigningCert = value; } } public X509Certificate2 ServiceEncryptingCertificate { get { return this.serviceEncryptingCert; } set { this.serviceEncryptingCert = value; } } public override SecurityTokenManager CreateSecurityTokenManager() { return new MyServiceCredentialsSecurityTokenManager(this); } protected override ServiceCredentials CloneCore() { return new MyServiceCredentials(this); } }
Define a new service security token manager that inherits from the ServiceCredentialsSecurityTokenManager class. Override the CreateSecurityTokenProvider method to create an appropriate security token provider given the passed-in message direction and key usage.
Friend Class MyServiceCredentialsSecurityTokenManager Inherits ServiceCredentialsSecurityTokenManager Private credentials As MyServiceCredentials Public Sub New(ByVal credentials As MyServiceCredentials) MyBase.New(credentials) Me.credentials = credentials End Sub Public Overrides Function CreateSecurityTokenProvider(ByVal requirement As SecurityTokenRequirement) As SecurityTokenProvider Dim result As SecurityTokenProvider = Nothing If requirement.TokenType = SecurityTokenTypes.X509Certificate Then Dim direction = requirement.GetProperty(Of MessageDirection)(ServiceModelSecurityTokenRequirement.MessageDirectionProperty) If direction = MessageDirection.Input Then If requirement.KeyUsage = SecurityKeyUsage.Exchange Then result = New X509SecurityTokenProvider(credentials.ServiceEncryptingCertificate) Else result = New X509SecurityTokenProvider(credentials.ClientSigningCertificate) End If Else If requirement.KeyUsage = SecurityKeyUsage.Signature Then result = New X509SecurityTokenProvider(credentials.ServiceSigningCertificate) Else result = New X509SecurityTokenProvider(credentials.ClientEncryptingCertificate) End If End If Else result = MyBase.CreateSecurityTokenProvider(requirement) End If Return result End Function End Class
internal class MyServiceCredentialsSecurityTokenManager : ServiceCredentialsSecurityTokenManager { MyServiceCredentials credentials; public MyServiceCredentialsSecurityTokenManager( MyServiceCredentials credentials) : base(credentials) { this.credentials = credentials; } public override SecurityTokenProvider CreateSecurityTokenProvider( SecurityTokenRequirement requirement) { SecurityTokenProvider result = null; if (requirement.TokenType == SecurityTokenTypes.X509Certificate) { MessageDirection direction = requirement. GetProperty<MessageDirection>( ServiceModelSecurityTokenRequirement. MessageDirectionProperty); if (direction == MessageDirection.Input) { if (requirement.KeyUsage == SecurityKeyUsage.Exchange) { result = new X509SecurityTokenProvider( credentials.ServiceEncryptingCertificate); } else { result = new X509SecurityTokenProvider( credentials.ClientSigningCertificate); } } else { if (requirement.KeyUsage == SecurityKeyUsage.Signature) { result = new X509SecurityTokenProvider( credentials.ServiceSigningCertificate); } else { result = new X509SecurityTokenProvider( credentials.ClientEncryptingCertificate); } } } else { result = base.CreateSecurityTokenProvider(requirement); } return result; } }
To use multiple certificates on the client
Create a custom binding. The security binding element must operate in duplex mode to allow different security token providers to be present for requests and responses. One way to do this is to use a duplex-capable transport or to use the CompositeDuplexBindingElement as shown in the following code. Link the customized IdentityVerifier which is defined in the next step to the security binding element. Replace the default client credentials with the customized client credentials previously created.
Dim serviceEndpoint As New EndpointAddress(New Uri("https://localhost:6060/service")) Dim binding As New CustomBinding() Dim securityBE = SecurityBindingElement.CreateMutualCertificateDuplexBindingElement(MessageSecurityVersion.WSSecurity10WSTrustFebruary2005WSSecureConversationFebruary2005WSSecurityPolicy11BasicSecurityProfile10) ' Add a custom IdentityVerifier because the service uses two certificates ' (one for signing and one for encryption) and an endpoint identity that ' contains a single identity claim. securityBE.LocalClientSettings.IdentityVerifier = New MyIdentityVerifier() binding.Elements.Add(securityBE) Dim compositeDuplex As New CompositeDuplexBindingElement() compositeDuplex.ClientBaseAddress = New Uri("https://localhost:6061/client") With binding.Elements .Add(compositeDuplex) .Add(New OneWayBindingElement()) .Add(New HttpTransportBindingElement()) End With Using factory As New ChannelFactory(Of IMyServiceChannel)(binding, serviceEndpoint) Dim credentials As New MyClientCredentials() SetupCertificates(credentials) With factory.Endpoint.Behaviors .Remove(GetType(ClientCredentials)) .Add(credentials) End With Dim channel = factory.CreateChannel() Console.WriteLine(channel.Hello("world")) channel.Close() End Using
EndpointAddress serviceEndpoint = new EndpointAddress(new Uri("https://localhost:6060/service")); CustomBinding binding = new CustomBinding(); AsymmetricSecurityBindingElement securityBE = SecurityBindingElement.CreateMutualCertificateDuplexBindingElement( MessageSecurityVersion. WSSecurity10WSTrustFebruary2005WSSecureConversationFebruary2005WSSecurityPolicy11BasicSecurityProfile10); // Add a custom IdentityVerifier because the service uses two certificates // (one for signing and one for encryption) and an endpoint identity that // contains a single identity claim. securityBE.LocalClientSettings.IdentityVerifier = new MyIdentityVerifier(); binding.Elements.Add(securityBE); CompositeDuplexBindingElement compositeDuplex = new CompositeDuplexBindingElement(); compositeDuplex.ClientBaseAddress = new Uri("https://localhost:6061/client"); binding.Elements.Add(compositeDuplex); binding.Elements.Add(new OneWayBindingElement()); binding.Elements.Add(new HttpTransportBindingElement()); using (ChannelFactory<IMyServiceChannel> factory = new ChannelFactory<IMyServiceChannel>(binding, serviceEndpoint)) { MyClientCredentials credentials = new MyClientCredentials(); SetupCertificates(credentials); factory.Endpoint.Behaviors.Remove(typeof(ClientCredentials)); factory.Endpoint.Behaviors.Add(credentials); IMyServiceChannel channel = factory.CreateChannel(); Console.WriteLine(channel.Hello("world")); channel.Close(); }
Define a custom IdentityVerifier. The service has multiple identities because different certificates are used to encrypt the request and to sign the response.
Note
In the following sample, the provided custom identity verifier does not perform any endpoint identity checking for demonstration purposes. This is not recommended practice for production code.
Friend Class MyIdentityVerifier Inherits IdentityVerifier Private defaultVerifier As IdentityVerifier Public Sub New() Me.defaultVerifier = IdentityVerifier.CreateDefault() End Sub Public Overrides Function CheckAccess(ByVal identity As EndpointIdentity, ByVal authContext As AuthorizationContext) As Boolean ' The following implementation is for demonstration only, and ' does not perform any checks regarding EndpointIdentity. ' Do not use this for production code. Return True End Function Public Overrides Function TryGetIdentity(ByVal reference As EndpointAddress, <System.Runtime.InteropServices.Out()> ByRef identity As EndpointIdentity) As Boolean Return Me.defaultVerifier.TryGetIdentity(reference, identity) End Function End Class
class MyIdentityVerifier : IdentityVerifier { IdentityVerifier defaultVerifier; public MyIdentityVerifier() { this.defaultVerifier = IdentityVerifier.CreateDefault(); } public override bool CheckAccess(EndpointIdentity identity, AuthorizationContext authContext) { // The following implementation is for demonstration only, and // does not perform any checks regarding EndpointIdentity. // Do not use this for production code. return true; } public override bool TryGetIdentity(EndpointAddress reference, out EndpointIdentity identity) { return this.defaultVerifier.TryGetIdentity(reference, out identity); } }
To use multiple certificates on the service
Create a custom binding. The security binding element must operate in a duplex mode to allow different security token providers to be present for requests and responses. As with the client, use a duplex-capable transport or use CompositeDuplexBindingElement as shown in the following code. Replace the default service credentials with the customized service credentials previously created.
Dim serviceEndpoint As New Uri("https://localhost:6060/service") Using host As New ServiceHost(GetType(Service), serviceEndpoint) Dim binding As New CustomBinding() With binding.Elements .Add(SecurityBindingElement.CreateMutualCertificateDuplexBindingElement(MessageSecurityVersion.WSSecurity10WSTrustFebruary2005WSSecureConversationFebruary2005WSSecurityPolicy11BasicSecurityProfile10)) .Add(New CompositeDuplexBindingElement()) .Add(New OneWayBindingElement()) .Add(New HttpTransportBindingElement()) End With Dim credentials As New MyServiceCredentials() SetupCertificates(credentials) With host.Description.Behaviors .Remove(GetType(ServiceCredentials)) .Add(credentials) End With Dim endpoint = host.AddServiceEndpoint(GetType(IMyService), binding, "") host.Open() Console.WriteLine("Service started, press ENTER to stop...") Console.ReadLine() End Using
Uri serviceEndpoint = new Uri("https://localhost:6060/service"); using (ServiceHost host = new ServiceHost(typeof(Service), serviceEndpoint)) { CustomBinding binding = new CustomBinding(); binding.Elements.Add(SecurityBindingElement. CreateMutualCertificateDuplexBindingElement( MessageSecurityVersion.WSSecurity10WSTrustFebruary2005WSSecureConversationFebruary2005WSSecurityPolicy11BasicSecurityProfile10)); binding.Elements.Add(new CompositeDuplexBindingElement()); binding.Elements.Add(new OneWayBindingElement()); binding.Elements.Add(new HttpTransportBindingElement()); MyServiceCredentials credentials = new MyServiceCredentials(); SetupCertificates(credentials); host.Description.Behaviors.Remove(typeof(ServiceCredentials)); host.Description.Behaviors.Add(credentials); ServiceEndpoint endpoint = host.AddServiceEndpoint( typeof(IMyService), binding, ""); host.Open(); Console.WriteLine("Service started, press ENTER to stop..."); Console.ReadLine(); }
See Also
Reference
ClientCredentials
ServiceCredentials
ClientCredentialsSecurityTokenManager
ServiceCredentialsSecurityTokenManager
IdentityVerifier