I need to sign and encrypt a SOAP request with a certificate to access a WS method, but i'm getting the same response all the time:
"A security error was encountered when verifying the message".
I guess there's something wrong with my code rather than any other issue.
Here it is:
cert = new System.Security.Cryptography.X509Certificates.X509Certificate2(sCertificatePath, sCertificatePassword);
userToken = new Microsoft.Web.Services3.Security.Tokens.UsernameToken(sUser, sPass, Microsoft.Web.Services3.Security.Tokens.PasswordOption.SendHashed);
secureToken = new Microsoft.Web.Services3.Security.Tokens.X509SecurityToken(cert);
encDataToken = new Microsoft.Web.Services3.Security.EncryptedData(secureToken);
mSignUsernameToken = new Microsoft.Web.Services3.Security.MessageSignature(userToken);
mSignSecurityToken = new Microsoft.Web.Services3.Security.MessageSignature(secureToken);
wsVehicleInfo = new wsBusiness.VehicleInfoWSImplService();
vehData = new wsBusiness.getVehicleInfoRequest();
vehData.vehicleRegistration = "XXXXYYY";
vehData.language = "es";
requestContext = wsVehicleInfo.RequestSoapContext;
requestContext.Security.Elements.Add(encDataToken);
requestContext.Security.Tokens.Add(secureToken);
requestContext.Security.Elements.Add(mSignSecurityToken);
requestContext.Security.Timestamp.TtlInSeconds = 300;
requestContext.Security.Tokens.Add(userToken);
Is it correct? Actually I got some questions:
I'm signing and encrypting with the same certificate issued by a CA. Don't I need to encrypt with the server's one? How can I get it?
Does order of XML elements generated matter in the request? Which should be the code order?
The algorithm used in the documentation to encrypt the soap body is "aes128-gcm" but I wasn't able to find it and instead using "aes128-cbc". May it cause any trouble?
Is it recommended the usage of WSE in this case? I read this:
"Instead of asymmetrically encrypting the message, WSE use an asymmetric algorithm with a public copy of the recipient's X.509 certificate to encrypt the symmetric key that was actually used to encrypt the message data.". No way to just encrypt the SOAP body from the request with the server's certificate with WSE instead of encrypting the symmetric key?
Related
For the project I am working on I have to convert the following legacy code to use a SHA-384 ECDSA certificate, .NET Framework 4.8 as target.
Is it possible natively in C# or do I need some help like BouncyCastle lib and how?
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
[...]
// Certificate is in PKCS#12 format and contains a private key, certificate is password protected with the session id
var certificateBytes = Convert.FromBase64String(base64Certificate.ToString());
var certificate = new X509Certificate2();
certificate.Import(certificateBytes, sessionId, X509KeyStorageFlags.DefaultKeySet);
var result = (HttpWebRequest)WebRequest.Create( url );
result.Headers.Add( "x-jwt-authorization", $"Bearer {JSONWebToken}" );
result.ClientCertificates.Add( Certificate );
var response = (HttpWebResponse)request.GetResponse();
I tried to find a solution/explanation online but didn't work (keep getting a "Not supported" for the PrivateKey property when trying to import the certificate, and a 403 Forbidden as server response).
Also if I want to keep backward compatibility, how do I distinguish if the Base64 string contains a X509 or a SHA one to support both?
I'm used to implementing pusher client with javascript and having messages decrypted automatically, what we need is to just configure an auth point that authorizes connection and provides a master encryption key. With DotNet I'm experiencing issues, what seems to me is that this approach doesn't work with dotnet pusher client(I'm using Xamarin).
Trying to solve this issue, I have searched and found this code at github.com/pusher/pusher-websocket-dotnet/blob/master/PusherClient/ChannelDataDecrypter.cs but I'm still not achieving to decrypt, It fails and I keep getting Decryption failed for channel.
I'm sure the key I'm passing to DecryptData() is the same of the one at auth endpoint, and to be more specific, I will provide it, plus the relevant part of the code that suppostly should decrypt:
This is the json (json_event.data):
{"data":{"nonce":"Cr3K2CDnVXH1s65jtQpjorV8PnSvy+At","ciphertext":"R+Bg+aJ1VciAGPdhqVH7vB/NjBzsVLcBjLU1Q8LSA5Czd34DLyJaaVpsQCexYt/bYtUo78e0VY2U5lfv7vFna2NGOq7T+6yE0uj9xlvQSHzpfavErQ1XMaZ6LXoFqZakv786BLZ9BsW2jGA4U4+iBzDA1/kPfeJ15w1qbLink8AihtgNAo0v65YUJpmomWlAHwucFCP+psYmPzhk9Sci9RvLxjKIhq13B/G9urSU+EF990Ox05oqGt3+aoIM7XYnqSw69uqskXJE0WOLUbZYrZNbFgoVuZ8BzoUm82OpBCZVSxtlplFFpKA9oz3mh/jOQg7i8jVftLkgF9Ci2pNmjGsKoxuexTp8suwXLygD7Qkffj/cwdEYPglJ+EebCm/FItnL+Lt/ZsXJaAaeSbsGzsDU+gXNbgSwbRgXt38AI/evllhz8g51mhz6W056ON3IKPzpEgiEc5WhcsGb2tNSzpk4rxN41ZCQ3c/TnXXCMySfL0rh54zTAKXfSMWaOXRXAcUgNyD2NnPYgbtLQ6+QCtYRZITxdETqQqyFpDF0TbJueK5eG3CJUCHOoT0WyOlMs1Ry8XEDLA6Mk4M+Xed917b14imgM4lfOiU+o/tp0sg/x271EpMciKbZ8TXNhN+yCJgR2vLJZlnJ5CT7kxC7TaNaap0XBp9h4Hdu4FTevlIu0XLxd9o3EMht1o0COcpvcBO6nZmQZHgDJ/0jJSkSK6BnCEU2snIHqMJUSjtKl5AMvUISjLN+l/CS7T6FIObtSmlDLRVkFBY4IaYDrDfnsxM+b9Zq4vSVT5+nhnbLg+NrFgwjzQQbBi5GObIPxjxhm4HZ9A8nwBmNM8DWQe1o/OZP3XFqGpYYzpx8+05OW1zBB2gk+1lhHlZdA+dFJa9dko83j5Q2BEZc00HL"}}
C# code:
string key = "60e4a9540aa77099695ca4aa4e5a746f"; //got it from auth endpoint server logs
string msgDecrypted = channelDataDecrypter.DecryptData(System.Text.Encoding.ASCII.GetBytes(key), json_event.data);
This is how I'm generating the key at server side(PHP):
require_once 'pusher/autoload.php';
require_once('pem_files/chat_privatekey_salt.php');
$master_key=md5(base64_encode(openssl_digest($salt.$_POST['channel_name'],'SHA256', true)));
//using md5 because pusher was complaining about the key length and saying that the key must have 32 chars, but I think this is not the point since decryption works in javascript client using this very same key.
$options = array('cluster' => 'us2','useTLS' => true,'encrypted' => true,'encryption_master_key'=>$master_key);
$pusher = new Pusher\Pusher('xxxxxxxx','yyyyyyyy','0000000',$options);
echo $pusher->socket_auth($_POST['channel_name'],$_POST['socket_id']);
As I said before, this fails with Decryption failed for channel. What can I do to fix this?
I have a self signed root certificate that I generated in C# using CERTENROLL.dll's CX509CertificateRequest Certificate functionality.
I would like to write a function that generates client certificates signed by my root using the same API. However the only CertEnroll option I can find that does not generate a self signed certificate requires a authenticated CA.
There seems to be a flag for setting a SignerCertificate but it always fails to initialize.
//Initialize cert
var cert = new CX509CertificateRequestCertificate();
//take care of signer
cert.Issuer = issuen;
CSignerCertificate sc = new CSignerCertificate();
var raw = SEScert.GetRawCertData();
var rawStr=Convert.ToBase64String(raw);
sc.Initialize(false, X509PrivateKeyVerify.VerifyNone,
EncodingType.XCN_CRYPT_STRING_BASE64, rawStr); //fails here
cert.SignerCertificate = sc;
Does anyone know how I can generate a client CX509CertificateRequest signed by my root?
Any help or advice would be greatly appreciated.
I was able to solve this.
The encoding of SEScert is a hex string not base64 also the machine context should be set to true not false the correct code looks as follows:
ISignerCertificate signerCertificate = new CSignerCertificate();
signerCertificate.Initialize(true, X509PrivateKeyVerify.VerifyNone,EncodingType.XCN_CRYPT_STRING_HEX, SEScert.GetRawCertDataString());
cert.SignerCertificate = (CSignerCertificate)signerCertificate;
Hope this helps others in the future.
I have a WCF client that is crashing with the error "The EncryptedKey clause was not wrapped with the required encryption token 'System.IdentityModel.Tokens.X509SecurityToken'." for every response.
I've looked around and this blog post seems to indicate that the problem is with my certificate set up, but I'm not sure what I am doing wrong...
My client uses a custom binding with a MutualCertificateBindingElement for security, I am configuring the certificates in code as follows:
client.ClientCredentials.ServiceCertificate.Authentication.CertificateValidationMode = X509CertificateValidationMode.ChainTrust;
client.ClientCredentials.ServiceCertificate.SetDefaultCertificate
(
StoreLocation.CurrentUser,
StoreName.AddressBook,
X509FindType.FindBySerialNumber,
"[serial number 1]"
);
client.ClientCredentials.ClientCertificate.SetCertificate
(
StoreLocation.CurrentUser,
StoreName.My,
X509FindType.FindBySerialNumber,
"[serial number 2]"
);
The serial numbers match the values in the <X509SerialNumber> elements in both the request and the response messages.
One discrepancy I have noticed is the <X509IssuerName> elements in the request and the response are formatted differently:
Request: CN=[CN], O=[O], L=[L], C=[C]
Response: C=[C],L=[L],O=[O],CN=[CN]
Is it possible this is causing the issue?
UPDATE
Turns out it was the certificate name formatting causing the issue. I managed to resolve it by replacing the cert names in the response with what WCF expects by using a custom encoder. Now I have this ugly hack, but it works so I'll live with it!
public override Message ReadMessage(ArraySegment<byte> buffer, BufferManager bufferManager, string contentType)
{
var msgContents = new byte[buffer.Count];
Array.Copy(buffer.Array, buffer.Offset, msgContents, 0, msgContents.Length);
bufferManager.ReturnBuffer(buffer.Array);
var message = Encoding.UTF8.GetString(msgContents);
// Fix certificate issuer name formatting to match what WCF expects.
message = message.Replace
(
"C=[C],L=[L],O=[O],CN=[CN]",
"CN=[CN], O=[O], L=[L], C=[C]"
);
var stream = new MemoryStream(Encoding.UTF8.GetBytes(message));
return ReadMessage(stream, int.MaxValue);
}
The issuer name order that you mentioned is most probably the issue. Since these names are not signed I suggest you write a custom encoder in your client that replaces the names in the response to be formatted as in the request.
Besides obvious cert mismatch and barring miss-configuration... I have seen an issue trying to access private key. Check that client has appropriate permissions to the cert private key. If you right click on the cert in certmanager you should see AllTasks/Manage Private Keys. Add your client's process identity to the list.
Also make sure that the certificate you are using is correct. I used self-signed certificate which was missing Subject Key Identifier.
WCF : The EncryptedKey clause was not wrapped with the required encryption token 'System.IdentityModel.Tokens.X509SecurityToken'
I'm trying to authenticate myself against WebService using my client certificate, but, for some reasons (I explain), I don't want to load certificate from store, rather read it from disc.
The following:
// gw is teh WebService client
X509Certificate cert = new X509Certificate(PathToCertificate);
_gw.ClientCertificates.Add(ClientCertificate());
ServicePointManager.ServerCertificateValidationCallback = (a,b,c,d) => true;
_gw.DoSomeCall();
returns always 403 - the Service doesn't authorize me. But, when I save that certificate into CertStore, it works. (As stated in MSDN.)
Is it possible to use certificate not in store?
(the reason is, that I got windows service(client) sometimes calling webservice(server), and after unspecified amount of time the service 'forgets' my certificates and doesnt authorize against server, with no apparent reason)
What type of file is PathToCertificate? If it's just a .cer file, it will not contain the private key for the certificate and trying to use that certificate for SSL/TLS will fail.
However, if you have a PKCS7 or PKCS12 file that includes the public and private key for the certificate, your code will work (you might need to use the overload that takes a password if the private key has one).
To test this, I went to http://www.mono-project.com/UsingClientCertificatesWithXSP and created my client.p12 file following those instructions. I also created a simple HTTPS server using HttpListener for testing.
Then I compiled the following program into 'client.exe' and run like:
client.exe https://<MYSSLSERVER>/ client.p12 password
where client.p12 is the PKCS12 file generated before and 'password' is the password I set for the private key of the certificate.
using System;
using System.IO;
using System.Net;
using System.Security.Cryptography.X509Certificates;
using System.Text;
public class HttpWebRequestClientCertificateTest : ICertificatePolicy {
public bool CheckValidationResult (ServicePoint sp, X509Certificate certificate,
WebRequest request, int error)
{
return true; // server certificate's CA is not known to windows.
}
static void Main (string[] args)
{
string host = "https://localhost:1234/";
if (args.Length > 0)
host = args[0];
X509Certificate2 certificate = null;
if (args.Length > 1) {
string password = null;
if (args.Length > 2)
password = args [2];
certificate = new X509Certificate2 (args[1], password);
}
ServicePointManager.CertificatePolicy = new HttpWebRequestClientCertificateTest ();
HttpWebRequest req = (HttpWebRequest) WebRequest.Create (host);
if (certificate != null)
req.ClientCertificates.Add (certificate);
WebResponse resp = req.GetResponse ();
Stream stream = resp.GetResponseStream ();
StreamReader sr = new StreamReader (stream, Encoding.UTF8);
Console.WriteLine (sr.ReadToEnd ());
}
}
Let me know if you want me to upload the server code and the certificates used on both sides of the test.
The potential problem could be caching of SSL sessions (Schannel cache). Only first request negotiates the SSL handshake. Subsequent requests will use the same session ID and hope that the server accept it. If the server clears the SessionId, the requests will fail with 403 error. To disable local ssl session caching (and force SSL negotiation for each request) you have to open windows registry folder:
[HKEY_LOCAL_MACHINE][System][CurrentControlSet][Control][SecurityProviders][SCHANNEL]
and add the key named ClientCacheTime (DWORD) with value 0.
This issue is covered here:
http://support.microsoft.com/?id=247658
You have the potential for at least two problems...
First...
Your client certificate file cannot contain a private key unless it's accessed with a password. You should be using a PKCS #12 (*.pfx) certificate with a password so that your client has access to the private key. You client code will have to provide the password when opening the certificate as others have already posted. There are several ways to create this, the easiest is to use the following command-line to first generate the certificate, then use the MMC certificate manager to export the certificates private key:
Process p = Process.Start(
"makecert.exe",
String.Join(" ", new string[] {
"-r",// Create a self signed certificate
"-pe",// Mark generated private key as exportable
"-n", "CN=" + myHostName,// Certificate subject X500 name (eg: CN=Fred Dews)
"-b", "01/01/2000",// Start of the validity period; default to now.
"-e", "01/01/2036",// End of validity period; defaults to 2039
"-eku",// Comma separated enhanced key usage OIDs
"1.3.6.1.5.5.7.3.1," +// Server Authentication (1.3.6.1.5.5.7.3.1)
"1.3.6.1.5.5.7.3.2", // Client Authentication (1.3.6.1.5.5.7.3.2)
"-ss", "my",// Subject's certificate store name that stores the output certificate
"-sr", "LocalMachine",// Subject's certificate store location.
"-sky", "exchange",// Subject key type <signature|exchange|<integer>>.
"-sp",// Subject's CryptoAPI provider's name
"Microsoft RSA SChannel Cryptographic Provider",
"-sy", "12",// Subject's CryptoAPI provider's type
myHostName + ".cer"// [outputCertificateFile]
})
);
Second...
Your next problem is going to be server-side. The server has to allow this certificate. You have the right logic, but on the wrong side of the wire, move this line to the web server handling the request. If you cannot, you must then take the '.cer' file saved above to the server and add it to the server computer's trust list:
ServicePointManager.ServerCertificateValidationCallback = (a,b,c,d) => true;
Do you need a password for the certificate? If so, there is a field for it in the constructor.
X509Certificate cert = new X509Certificate(PathToCertificate,YourPassword);