example - WCF SOAP 1.1 y WS-Security 1.0, autenticación de transporte de certificado de cliente, certificado de servicio para firma de cuerpo de mensaje, UsernameToken, Password Digest, Nonce
ws security c# (2)
Pude resolver mi problema y conectarme a DataPower (IBM Xi50) Web Service Gateway utilizando el siguiente WCF CustomBinding (CertificateOverTransport) y CustomCredentials (UsernameToken with Password Digest, cliente cert para autenticación de transporte y servicio cert para cuerpo de mensaje). no estoy seguro de qué es exactamente lo que solucionó el problema, ¡pero aquí está mi código WCF que funciona! Espero que esto ayude a otros que están en una situación similar a la que yo tenía.
Verifique que la puerta de enlace DataPower Xi50 también esté configurada para WCF. De IBM: "Al usar BasicHttpBinding con SSL: puede usar el parámetro disable-ssl-cipher-check para deshabilitar las comprobaciones de cifrado para cualquier aserción de TransportBinding. El encabezado de autenticación básica no es compatible de manera predeterminada en el proxy de servicios web. se requiere una regla de error para inyectar el encabezado WWW-Authenticate para interoperar con WCF ". Para obtener más detalles, vaya aquí: https://publib.boulder.ibm.com/infocenter/ieduasst/v1r1m0/index.jsp?topic=/com.ibm.iea.wdatapower/wdatapower/1.0/xa35/380DataPowerWCFIntegration/player.html .
Asegúrese de haber configurado ProtectionLevel.Sign en su contrato de servicio si desea que su cuerpo de mensaje esté firmado solo (y no encriptado).
Para DNS Identity, con el que tuve problemas anteriormente, ahora pude poner el nombre de mi certificado de cliente del cliente; antes esto no funcionaría.
No tengo ninguna configuración en mi web.config.
Aquí está el Proxy usando CustomBinding:
private ClientProxy GetProxy()
{
XXXServiceClient proxy = new XXXServiceClient(GetCustomBinding(), new EndpointAddress(new Uri("<<GatewayURLHere>>"), EndpointIdentity.CreateDnsIdentity("<<DNS or Client Cert Subject Name>>"), new AddressHeaderCollection()));
proxy.Endpoint.Behaviors.Remove(typeof(ClientCredentials));
proxy.Endpoint.Behaviors.Add(new CustomCredentials(<clientCertHere>, <signingCertHere>));
proxy.ClientCredentials.UserName.UserName = @"XXX";
proxy.ClientCredentials.UserName.Password = "yyy";
return proxy;
}
private Binding GetCustomBinding()
{
TransportSecurityBindingElement secBE = SecurityBindingElement.CreateCertificateOverTransportBindingElement(MessageSecurityVersion.WSSecurity10WSTrustFebruary2005WSSecureConversationFebruary2005WSSecurityPolicy11BasicSecurityProfile10);
secBE.EndpointSupportingTokenParameters.Signed.Add(new UserNameSecurityTokenParameters { InclusionMode = SecurityTokenInclusionMode.Never, RequireDerivedKeys = false });
secBE.EnableUnsecuredResponse = true;
secBE.IncludeTimestamp = true;
TextMessageEncodingBindingElement textEncBE = new TextMessageEncodingBindingElement(MessageVersion.Soap11WSAddressingAugust2004, System.Text.Encoding.UTF8);
HttpsTransportBindingElement httpsBE = new HttpsTransportBindingElement();
httpsBE.RequireClientCertificate = true;
CustomBinding myBinding = new CustomBinding();
myBinding.Elements.Add(secBE);
myBinding.Elements.Add(textEncBE);
myBinding.Elements.Add(httpsBE);
return myBinding;
}
Aquí está mi clase CustomCredentials que reuní de varias fuentes, incluida la biblioteca UsernameToken mencionada anteriormente: establece el certificado del cliente para la autenticación (mutua) en la capa de transporte, el certificado de servicio / firma para firmar el cuerpo del mensaje y UsernameToken con Password Digest en el SOAP encabezamiento:
using System;
using System.IdentityModel.Selectors;
using System.IdentityModel.Tokens;
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
using System.ServiceModel;
using System.ServiceModel.Channels;
using System.ServiceModel.Description;
using System.ServiceModel.Security;
using System.Text;
namespace XXX_WCF
{
public class CustomCredentials : ClientCredentials
{
private X509Certificate2 clientAuthCert;
private X509Certificate2 clientSigningCert;
public CustomCredentials() : base() { }
public CustomCredentials(CustomCredentials other)
: base(other)
{
clientSigningCert = other.clientSigningCert;
clientAuthCert = other.clientAuthCert;
}
protected override ClientCredentials CloneCore()
{
CustomCredentials scc = new CustomCredentials(this);
return scc;
}
public CustomCredentials(X509Certificate2 ClientAuthCert, X509Certificate2 ClientSigningCert)
: base()
{
clientAuthCert = ClientAuthCert;
clientSigningCert = ClientSigningCert;
}
public X509Certificate2 ClientAuthCert
{
get { return clientAuthCert; }
set { clientAuthCert = value; }
}
public X509Certificate2 ClientSigningCert
{
get { return clientSigningCert; }
set { clientSigningCert = value; }
}
public override SecurityTokenManager CreateSecurityTokenManager()
{
return new CustomTokenManager(this);
}
}
public class CustomTokenManager : ClientCredentialsSecurityTokenManager
{
private CustomCredentials custCreds;
public CustomTokenManager(CustomCredentials CustCreds)
: base(CustCreds)
{
custCreds = CustCreds;
}
public override SecurityTokenProvider CreateSecurityTokenProvider(SecurityTokenRequirement tokenRequirement)
{
if (tokenRequirement.TokenType == SecurityTokenTypes.X509Certificate)
{
x509CustomSecurityTokenProvider prov;
object temp = null;
TransportSecurityBindingElement secBE = null;
if (tokenRequirement.Properties.TryGetValue("http://schemas.microsoft.com/ws/2006/05/servicemodel/securitytokenrequirement/SecurityBindingElement", out temp))
{
secBE = (TransportSecurityBindingElement)temp;
}
if (secBE == null)
prov = new x509CustomSecurityTokenProvider(custCreds.ClientAuthCert);
else
prov = new x509CustomSecurityTokenProvider(custCreds.ClientSigningCert);
return prov;
}
return base.CreateSecurityTokenProvider(tokenRequirement);
}
public override System.IdentityModel.Selectors.SecurityTokenSerializer CreateSecurityTokenSerializer(System.IdentityModel.Selectors.SecurityTokenVersion version)
{
return new CustomTokenSerializer(System.ServiceModel.Security.SecurityVersion.WSSecurity10);
}
}
class x509CustomSecurityTokenProvider : SecurityTokenProvider
{
private X509Certificate2 clientCert;
public x509CustomSecurityTokenProvider(X509Certificate2 cert)
: base()
{
clientCert = cert;
}
protected override SecurityToken GetTokenCore(TimeSpan timeout)
{
return new X509SecurityToken(clientCert);
}
}
public class CustomTokenSerializer : WSSecurityTokenSerializer
{
public CustomTokenSerializer(SecurityVersion sv) : base(sv) { }
protected override void WriteTokenCore(System.Xml.XmlWriter writer, System.IdentityModel.Tokens.SecurityToken token)
{
if (writer == null)
{
throw new ArgumentNullException("writer");
}
if (token == null)
{
throw new ArgumentNullException("token");
}
if (token.GetType() == new UserNameSecurityToken("x", "y").GetType())
{
UserNameSecurityToken userToken = token as UserNameSecurityToken;
if (userToken == null)
{
throw new ArgumentNullException("userToken: " + token.ToString());
}
string tokennamespace = "o";
DateTime created = DateTime.Now;
string createdStr = created.ToString("yyyy-MM-ddThh:mm:ss.fffZ");
string phrase = Guid.NewGuid().ToString();
string nonce = GetSHA1String(phrase);
string password = GetSHA1String(nonce + createdStr + userToken.Password);
//string password = userToken.Password;
writer.WriteStartElement(tokennamespace, "UsernameToken", "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd");
writer.WriteAttributeString("u", "Id", "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd", token.Id);
writer.WriteElementString(tokennamespace, "Username", "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd", userToken.UserName);
writer.WriteStartElement(tokennamespace, "Password", "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd");
writer.WriteAttributeString("Type", "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordDigest");
writer.WriteValue(password);
writer.WriteEndElement();
writer.WriteStartElement(tokennamespace, "Nonce", "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd");
writer.WriteAttributeString("EncodingType", "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-soap-message-security-1.0#Base64Binary");
writer.WriteValue(nonce);
writer.WriteEndElement();
writer.WriteElementString(tokennamespace, "Created", "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd", createdStr);
writer.WriteEndElement();
writer.Flush();
}
else
{
base.WriteTokenCore(writer, token);
}
}
protected string GetSHA1String(string phrase)
{
SHA1CryptoServiceProvider sha1Hasher = new SHA1CryptoServiceProvider();
byte[] hashedDataBytes = sha1Hasher.ComputeHash(Encoding.UTF8.GetBytes(phrase));
return Convert.ToBase64String(hashedDataBytes);
}
}//CustomTokenSerializer
}
¡Buena suerte!
Resumen: estoy trabajando en un cliente .NET 4.0 WCF para consumir un servicio web (DataPower, servicio Java en el otro extremo) usando SOAP 1.1 y WS-Security 1.0. El cliente de WCF debe implementar un certificado de cliente para la autenticación mutua en la capa de transporte. El cuerpo del mensaje debe firmarse utilizando un certificado de servicio / firma por separado. El encabezado SOAP también debe contener un Token de nombre de usuario con Password Digest e incluir etiquetas Nonce y Created.
Puedo consumir este servicio web usando WSE 3.0 con BasicHTTPBinding. Pero hasta ahora no he tenido éxito implementando lo mismo con WCF usando WSHttpBinding o CustomBinding. Probé todos los elementos de enlace de seguridad y hasta ahora no he tenido suerte.
También estoy usando la biblioteca usernametoken desde aquí ( http://blogs.msdn.com/b/aszego/archive/2010/06/24/usernametoken-profile-vs-wcf.aspx ) para poder agregar la contraseña digest / nonce / creado en UsernameToken en el encabezado SOAP.
Actualmente estoy usando SecurityBindingElement.CreateMutualCertificateBindingElement. También he probado varios otros como AsymmetricSecurityBindingElement, TransportSecurityBindingElement, etc. (comentados en el código a continuación)
CERTS: Tengo el certificado de cliente y el certificado de servicio cargados en el almacén de certificados usando MMC (estoy en Windows 7 por cierto). Tanto el certificado de cliente como el certificado de servicio tienen claves privadas. Cargué ambos archivos PFX en LocalMachine / Personal, LocalMachine / Root y LocalMachine / TrustedPeople. También he ejecutado FindPrivateKey / ICACLS para dar permiso a la cuenta "IIS App Pool / DefaultAppPool". Aunque nada de esto debería importar ya que puedo ejecutar el código WSE 3.0 desde mi máquina y funciona sin ningún problema de certificación.
Comandos ejecutados:
FindPrivateKey.exe My LocalMachine -t "thumbprint of client cert"
FindPrivateKey.exe My LocalMachine -t "thumbprint of service cert"
icacls C:/ProgramData/Microsoft/Crypto/RSA/MachineKeys/{privateKeyOfClientCert} /grant "IIS AppPool/DefaultAppPool":R <<Successfully processed 1 files; Failed processing 0 files>>
icacls C:/ProgramData/Microsoft/Crypto/RSA/MachineKeys/{privateKeyOfServiceCert} /grant "IIS AppPool/DefaultAppPool":R <<Successfully processed 1 files; Failed processing 0 files>>
EDICIÓN DE WCF: actualmente recibo un mensaje de "No se pudo establecer un canal seguro para SSL / TLS con autorización ''xxcom''" desde la puerta de enlace de DataPower. Me imagino que esto podría deberse a que la puerta de enlace toma el certificado de servicio y lo utiliza para autenticar al cliente en lugar de usar el certificado de cliente que estoy enviando. Lo digo porque cuando no especifico la identidad DNS para el punto final, recibo un mensaje que dice que la puerta de enlace espera que la identidad del DNS sea "{nombre del servicio / certificado de firma}".
Aquí está la solicitud SOAP generada por WCF que está dando el error anterior. La solicitud WCF SOAP es muy similar a la solicitud WAP SOAP. Es muy probable que el error anterior ocurra debido a la emisión del certificado en la capa SSL / Transporte.
Solicitud SOAP de WCF:
<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/" xmlns:u="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd">
<s:Header>
<o:Security s:mustUnderstand="1" xmlns:o="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd">
<u:Timestamp u:Id="uuid-8533d9a5-865e-4a4b-a750-fadb7c1ce36c-1">
<u:Created>2013-02-06T20:53:04.679Z</u:Created>
<u:Expires>2013-02-06T20:58:04.679Z</u:Expires>
</u:Timestamp>
<o:BinarySecurityToken u:Id="uuid-0bab08ce-3e3b-4360-a44b-694b06a3dd67-2" ValueType="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-x509-token-profile-1.0#X509v3">Removed Service Cert Encoded Value</o:BinarySecurityToken>
<wsse:UsernameToken wsu:Id="7843ab92-f69a-4d00-a5ba-117e32a74f49" xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd"
xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd">
<wsse:Username>USER_Removed</wsse:Username>
<wsse:Password Type="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordDigest">XXX=</wsse:Password>
<wsse:Nonce>XXX==</wsse:Nonce>
<wsu:Created>2013-02-06T20:53:04Z</wsu:Created>
</wsse:UsernameToken>
<Signature xmlns="http://www.w3.org/2000/09/xmldsig#">
<SignedInfo>
<CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"></CanonicalizationMethod>
<SignatureMethod Algorithm="http://www.w3.org/2000/09/xmldsig#rsa-sha1"></SignatureMethod>
<Reference URI="#_1">
<Transforms>
<Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"></Transform>
</Transforms>
<DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"></DigestMethod>
<DigestValue>XXX=</DigestValue>
</Reference>
<Reference URI="#uuid-8533d9a5-865e-4a4b-a750-fadb7c1ce36c-1">
<Transforms>
<Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"></Transform>
</Transforms>
<DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"></DigestMethod>
<DigestValue>XXX=</DigestValue>
</Reference>
<Reference URI="#7843ab92-f69a-4d00-a5ba-117e32a74f49">
<Transforms>
<Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"></Transform>
</Transforms>
<DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"></DigestMethod>
<DigestValue>XXX=</DigestValue>
</Reference>
</SignedInfo>
<SignatureValue>XXXLongXXX=</SignatureValue>
<KeyInfo>
<o:SecurityTokenReference>
<o:Reference URI="#uuid-0bab08ce-3e3b-4360-a44b-694b06a3dd67-2"></o:Reference>
</o:SecurityTokenReference>
</KeyInfo>
</Signature>
</o:Security>
</s:Header>
<s:Body u:Id="_1" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<ping xmlns="https://x.x.com/xxx/v1">
<pingRequest xmlns="">hello</pingRequest>
</ping>
</s:Body>
Solicitud WAP 3.0 SOAP (esto funciona):
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:wsa="http://schemas.xmlsoap.org/ws/2004/08/addressing"
xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd" xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd">
<soap:Header>
<wsa:Action wsu:Id="Id-4271fb72-464a-467d-ab1f-4d32542e20f0"/>
<wsa:MessageID wsu:Id="Id-11657f64-d856-47d8-b600-d5379fb91a0d">urn:uuid:ff8becb7-74c2-4844-ab46-8ae23f1355a7</wsa:MessageID>
<wsa:ReplyTo wsu:Id="Id-40b2e6e8-e67b-4a6c-a545-071ce0f0107a">
<wsa:Address>http://schemas.xmlsoap.org/ws/2004/08/addressing/role/anonymous</wsa:Address>
</wsa:ReplyTo>
<wsa:To wsu:Id="Id-d5e0b488-6f8a-479c-940d-2b85833dbc66">https://x.x.com/xxx/v1</wsa:To>
<wsse:Security soap:mustUnderstand="1">
<wsu:Timestamp wsu:Id="Timestamp-68476551-5c58-4a47-967b-54ec18257b1b">
<wsu:Created>2013-02-06T19:38:39Z</wsu:Created>
<wsu:Expires>2013-02-06T19:43:39Z</wsu:Expires>
</wsu:Timestamp>
<wsse:UsernameToken wsu:Id="SecurityToken-e5f65166-a825-48cb-a939-8e515a637e01">
<wsse:Username>USER_Removed</wsse:Username>
<wsse:Password Type="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordDigest">XXX=</wsse:Password>
<wsse:Nonce>XXX==</wsse:Nonce>
<wsu:Created>2013-02-06T19:38:39Z</wsu:Created>
</wsse:UsernameToken>
<Signature xmlns="http://www.w3.org/2000/09/xmldsig#">
<SignedInfo>
<ds:CanonicalizationMethod xmlns:ds="http://www.w3.org/2000/09/xmldsig#" Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
<SignatureMethod Algorithm="http://www.w3.org/2000/09/xmldsig#rsa-sha1"/>
<Reference URI="#Id-4271fb72-464a-467d-ab1f-4d32542e20f0">
<Transforms>
<Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
</Transforms>
<DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"/>
<DigestValue>XXX=</DigestValue>
</Reference>
<Reference URI="#Id-11657f64-d856-47d8-b600-d5379fb91a0d">
<Transforms>
<Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
</Transforms>
<DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"/>
<DigestValue>XXX=</DigestValue>
</Reference>
<Reference URI="#Id-40b2e6e8-e67b-4a6c-a545-071ce0f0107a">
<Transforms>
<Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
</Transforms>
<DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"/>
<DigestValue>XXX=</DigestValue>
</Reference>
<Reference URI="#Id-d5e0b488-6f8a-479c-940d-2b85833dbc66">
<Transforms>
<Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
</Transforms>
<DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"/>
<DigestValue>XXX=</DigestValue>
</Reference>
<Reference URI="#Timestamp-68476551-5c58-4a47-967b-54ec18257b1b">
<Transforms>
<Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
</Transforms>
<DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"/>
<DigestValue>XXX=</DigestValue>
</Reference>
<Reference URI="#Id-6f76e50e-932c-4878-bbc0-3ef4c8a36990">
<Transforms>
<Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
</Transforms>
<DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"/>
<DigestValue>XXX=</DigestValue>
</Reference>
</SignedInfo>
<SignatureValue>XXXLongXXX=</SignatureValue>
<KeyInfo>
<wsse:SecurityTokenReference>
<wsse:KeyIdentifier ValueType="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-x509-token-profile-1.0#X509SubjectKeyIdentifier"
EncodingType="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-soap-message-security-1.0#Base64Binary">XXX=</wsse:KeyIdentifier>
</wsse:SecurityTokenReference>
</KeyInfo>
</Signature>
</wsse:Security>
</soap:Header>
<soap:Body wsu:Id="Id-6f76e50e-932c-4878-bbc0-3ef4c8a36990">
<ping xmlns="https://x.x.com/xxx/v1">
<pingRequest xmlns="">hello</pingRequest>
</ping>
</soap:Body>
Aquí está toda la configuración, por favor, hágame saber lo que estoy haciendo mal!
WCF web.config: eliminé todo de la web.config ya que estoy haciendo toda la configuración en el código.
WCF config en el código:
var proxy = GetProxy();
pingResponseMessage resp = proxy.ping("hello");
lblStatus.Text = resp.status.ToString();
private XXXClient GetProxy()
{
System.Net.ServicePointManager.ServerCertificateValidationCallback += (se, cert, chain, sslerror) => { return true; };
XXXClient proxy = new XXXClient(GetCustomBinding(), new EndpointAddress(new Uri("https://xxx"), EndpointIdentity.CreateDnsIdentity("I am forced to put the signing cert subject here, nothing else works"), new AddressHeaderCollection()));
proxy.Endpoint.Behaviors.Remove(typeof(ClientCredentials));
proxy.Endpoint.Behaviors.Add(new UsernameClientCredentials(new UsernameInfo(@"USER_Removed", "X")));
proxy.ClientCredentials.ClientCertificate.SetCertificate(StoreLocation.LocalMachine, StoreName.My, X509FindType.FindByThumbprint, "REMOVED");
proxy.ClientCredentials.ServiceCertificate.SetDefaultCertificate(StoreLocation.LocalMachine, StoreName.My, X509FindType.FindByThumbprint, "REMOVED");
proxy.ClientCredentials.ServiceCertificate.Authentication.CertificateValidationMode = System.ServiceModel.Security.X509CertificateValidationMode.None;
return proxy;
}
private Binding GetCustomBinding()
{
//TransportSecurityBindingElement secBE = SecurityBindingElement.CreateCertificateOverTransportBindingElement(MessageSecurityVersion.WSSecurity10WSTrust13WSSecureConversation13WSSecurityPolicy12BasicSecurityProfile10);
//AsymmetricSecurityBindingElement secBE = (AsymmetricSecurityBindingElement)SecurityBindingElement.CreateMutualCertificateBindingElement(MessageSecurityVersion.WSSecurity10WSTrustFebruary2005WSSecureConversationFebruary2005WSSecurityPolicy11BasicSecurityProfile10);
//secBE.InitiatorTokenParameters = new System.ServiceModel.Security.Tokens.X509SecurityTokenParameters { InclusionMode = SecurityTokenInclusionMode.AlwaysToRecipient, RequireDerivedKeys = false, X509ReferenceStyle = X509KeyIdentifierClauseType.SubjectKeyIdentifier };
//secBE.RecipientTokenParameters = new System.ServiceModel.Security.Tokens.X509SecurityTokenParameters { InclusionMode = SecurityTokenInclusionMode.AlwaysToInitiator, RequireDerivedKeys = false, X509ReferenceStyle = X509KeyIdentifierClauseType.SubjectKeyIdentifier };
//secBE.MessageProtectionOrder = System.ServiceModel.Security.MessageProtectionOrder.SignBeforeEncrypt;
//secBE.EndpointSupportingTokenParameters.Signed.Add(new UserNameSecurityTokenParameters() { InclusionMode = SecurityTokenInclusionMode.AlwaysToRecipient, RequireDerivedKeys = false });
//secBE.EndpointSupportingTokenParameters.Signed.Add(new X509SecurityTokenParameters(X509KeyIdentifierClauseType.SubjectKeyIdentifier, SecurityTokenInclusionMode.Never) { InclusionMode = SecurityTokenInclusionMode.Never, RequireDerivedKeys = false, X509ReferenceStyle = X509KeyIdentifierClauseType.SubjectKeyIdentifier });
//secBE.ProtectionTokenParameters = new System.ServiceModel.Security.Tokens.X509SecurityTokenParameters { InclusionMode = SecurityTokenInclusionMode.AlwaysToRecipient };
//secBE.DefaultAlgorithmSuite = new CustomSecurityAlgorithm();
SecurityBindingElement secBE = SecurityBindingElement.CreateMutualCertificateBindingElement(MessageSecurityVersion.WSSecurity10WSTrustFebruary2005WSSecureConversationFebruary2005WSSecurityPolicy11BasicSecurityProfile10);
secBE.MessageSecurityVersion = MessageSecurityVersion.WSSecurity10WSTrustFebruary2005WSSecureConversationFebruary2005WSSecurityPolicy11BasicSecurityProfile10;
secBE.EndpointSupportingTokenParameters.Signed.Add(new UsernameTokenParameters() { InclusionMode= SecurityTokenInclusionMode.AlwaysToRecipient, ReferenceStyle = SecurityTokenReferenceStyle.External, RequireDerivedKeys = false });
secBE.SecurityHeaderLayout = SecurityHeaderLayout.Strict;
//secBE.AllowInsecureTransport = false;
//secBE.AllowSerializedSigningTokenOnReply = false;
secBE.EnableUnsecuredResponse = true;
secBE.IncludeTimestamp = true;
secBE.SetKeyDerivation(false);
TextMessageEncodingBindingElement textEncBE = new TextMessageEncodingBindingElement(MessageVersion.Soap11, System.Text.Encoding.UTF8);
HttpsTransportBindingElement httpsBE = new HttpsTransportBindingElement();
httpsBE.RequireClientCertificate = true;
//httpsBindingElement.AllowCookies = false;
//httpsBindingElement.AuthenticationScheme = System.Net.AuthenticationSchemes.Basic;
httpsBE.BypassProxyOnLocal = false;
httpsBE.HostNameComparisonMode = HostNameComparisonMode.StrongWildcard;
//httpsBindingElement.KeepAliveEnabled = false;
httpsBE.TransferMode = TransferMode.Buffered;
httpsBE.UseDefaultWebProxy = true;
CustomBinding myBinding = new CustomBinding();
myBinding.Elements.Add(secBE);
myBinding.Elements.Add(textEncBE);
myBinding.Elements.Add(httpsBE);
return myBinding;
}
He agregado ProtectionLevel.Sign en ServiceContract y OperationContracts ya que solo necesito firmar el cuerpo del mensaje. Sin embargo, no he llegado tan lejos para verificarlo.
[System.ServiceModel.ServiceContractAttribute(Namespace = "https://x.x.com/xxx/v1", ConfigurationName = "x.x", ProtectionLevel = System.Net.Security.ProtectionLevel.Sign)]
public interface XXXService {
[System.ServiceModel.OperationContractAttribute(Action = "", ReplyAction = "*", ProtectionLevel = System.Net.Security.ProtectionLevel.Sign)]
[System.ServiceModel.XmlSerializerFormatAttribute(SupportFaults=true)]
[return: System.ServiceModel.MessageParameterAttribute(Name="return")]
XXX.pingResponse ping(XXX.ping request);
[System.ServiceModel.ServiceContractAttribute(Namespace = "https://x.x.com/xxx/v1", ProtectionLevel = System.Net.Security.ProtectionLevel.Sign)]
public partial class XXXClient : System.ServiceModel.ClientBase<XXXService> {
[System.ServiceModel.OperationContractAttribute(Action = "", ReplyAction = "*", ProtectionLevel = System.Net.Security.ProtectionLevel.Sign)]
public XXX.pingResponseMessage ping(string pingRequest) {
He agregado lo siguiente a continuación a web.config para permitir el registro de todo el jabón, incluidos los datos de pii
(for pii, also added <machineSettings enableLoggingKnownPii="true" /> under <system.serviceModel> to C:/Windows/Microsoft.NET/Framework/vX/CONFIG/machine.config)
<system.serviceModel>
<diagnostics>
<messageLogging logKnownPii="true" logEntireMessage="true" logMalformedMessages="true" logMessagesAtServiceLevel="true" logMessagesAtTransportLevel="true" maxMessagesToLog="3000"/>
</diagnostics>
</system.serviceModel>
<system.diagnostics>
<sources>
<source name="System.ServiceModel.MessageLogging" logKnownPii="true">
<listeners>
<add initializeData="C:/trace.log" type="System.Diagnostics.XmlWriterTraceListener" name="messages"/>
</listeners>
</source>
</sources>
</system.diagnostics>
===============
WSE 3.0 (configuración de trabajo y código): web.config:
<system.serviceModel>
<bindings>
<basicHttpBinding>
<binding name="myBinding" closeTimeout="00:01:00" openTimeout="00:01:00" receiveTimeout="00:10:00" sendTimeout="00:01:00" allowCookies="false" bypassProxyOnLocal="false" hostNameComparisonMode="StrongWildcard" maxBufferSize="65536" maxBufferPoolSize="524288" maxReceivedMessageSize="65536" messageEncoding="Text" textEncoding="utf-8" transferMode="Buffered" useDefaultWebProxy="true">
<readerQuotas maxDepth="32" maxStringContentLength="8192" maxArrayLength="16384" maxBytesPerRead="4096" maxNameTableCharCount="16384"/>
<security mode="Transport">
<transport clientCredentialType="None" proxyCredentialType="None" realm=""/>
<message clientCredentialType="UserName" algorithmSuite="Default"/>
</security>
</binding>
</basicHttpBinding>
</bindings>
<client>
<endpoint address="https://x.x.com/xxx/v1" binding="basicHttpBinding" bindingConfiguration="myBinding" contract="XXXService" name="XXX"/>
</client>
</system.serviceModel>
<appSettings>
<add key="XXXImplService" value="https://x.x.com/xxx/v1"/>
</appSettings>
... y código WSE3:
var proxy = new XXXImplServiceWse();
UsernameToken usernameToken = new UsernameToken(@"USER_Removed", "X");
proxy.RequestSoapContext.Security.Tokens.Add(usernameToken);
X509Certificate2 mutualCert = LoadCertFromStore(StoreLocation.LocalMachine, StoreName.My, "Client Cert Subject Name");
proxy.ClientCertificates.Add(mutualCert);
X509Certificate2 signCert = LoadCertFromStore(StoreLocation.LocalMachine, StoreName.My, "Service Cert Subject Name");
X509SecurityToken signatureToken = new X509SecurityToken(signCert);
MessageSignature signature = new MessageSignature(signatureToken); // <!-- IS THIS SAME AS THIS STEP IN WCF: secBE.EndpointSupportingTokenParameters.Signed.Add(new UsernameTokenParameters()) -->
proxy.RequestSoapContext.Security.Elements.Add(signature);
==========
Entonces, ¿cómo convierto el código anterior de WSE 3.0 a WCF?
Revisé tu código y parece correcto. Hay una pequeña diferencia en los mensajes WSE y WCF, pero la diferencia radica solo en la forma en que se hace referencia al certificado utilizado para firmar el mensaje.
Creo que el problema central aquí es el uso incorrecto de los certificados. Está utilizando seguridad mutua de transporte y mensaje. En teoría, esto requiere cuatro certificados. Necesitas
- Certificado de servicio para la seguridad del transporte: el servidor utiliza este certificado para crear una conexión SSL. Para construir correctamente la conexión, el cliente debe confiar en el certificado (debe confiar en la autoridad que emitió el certificado o el certificado del servidor debe colocarse en la tienda de personas de confianza).
- Certificado de cliente para la seguridad del transporte: este certificado se utiliza para autenticar al cliente en el servidor a nivel de transporte. Debe tener un certificado y su clave privada en su tienda personal.
- Certificado de servicio para la seguridad del mensaje: este certificado se utiliza para encriptar la solicitud y la respuesta de firma (cuando se usa WS-Security 1.0). Debe tener este certificado en algún lugar de su máquina (depende de usted la ubicación que se utilizará para cargar el certificado).
- Certificado de cliente para la seguridad del mensaje: este certificado se utiliza para cifrar la respuesta y la solicitud de firma (cuando se usa WS-Security 1.0). Debe tener este certificado y su clave privada en algún lugar de su máquina (depende de usted la ubicación que se utilizará para cargar el certificado).
Parece que solo tiene dos certificados: un cliente y un servidor. En tal caso, probablemente deberían usarse tanto para el transporte como para la seguridad de los mensajes. Pero aquí viene un problema interesante: su certificado de "firma" en el lado del cliente en el ejemplo de WSE es en realidad un certificado de servicio. Si es realmente el caso, significa que el cliente debe tener acceso a la clave privada del servidor; eso nunca debería suceder. Esa es la peor violación de la infraestructura de PKI. La infraestructura de PKI se basa en la confianza de las autoridades de certificación y en la seguridad de claves privadas donde cada participante tiene su propia clave privada a la que no puede acceder nadie más. Compartir claves privadas reduce la seguridad. En el peor de los casos, puede ser igual a la falta de seguridad porque cualquier persona con acceso a la clave privada puede interceptar la comunicación o la firma falsa en el mensaje.
Si estoy en lo cierto, debes usar WSE 3.0 y estar feliz con eso. Solo forzar a WCF a utilizar un certificado de cliente diferente para HTTPS y la seguridad de los mensajes puede ser bastante difícil. Tiene una propiedad de ClientCertificate
único, pero necesita cargar un certificado diferente para HTTPS y seguridad de mensaje. Requiere crear ClientCredentials
personalizadas con dos propiedades y SecurityTokenManager
personalizado para devolver el proveedor de certificado correcto (implementando para cada uso (es una teoría, nunca lo he intentado).
Por cierto. su problema con EndpointIdentity
se basa en el hecho de que su servicio está expuesto en algunos DNS y si el tema del certificado de servicio (que en su caso también es certificado de firma) es diferente, debe crear una nueva identidad DNS para su endpoint. De lo contrario, WCF no confiará en el certificado. El certificado del servidor debe emitirse con el nombre del sujeto que coincide con el nombre DNS utilizado para acceder al servidor.