Reputation: 113
I need help to implement EWP (https://developers.erasmuswithoutpaper.eu/). I have set up some API's and use an online validator tool by EWP to test them. Everything is working now, except for 1 thing: the signature verification.
I have a few methods to go through the request headers to check if they are valid. Only method remaining is VerifySignature. I have implemented logging and have a valid signature along with the public key I should use to decrypt it, but whatever I try, I don't get any useable value out of it.
The signature I receive is:
UiBgtTc4hIYeZehhP8RWKvPIvWFXeh7ERFEvJr43v87YE7I4dqAHbD8l5DwZW3jezVfIcBflBS7ezjFDOH0/43T21ZCxwa/0qKhQRTjXoWQSETap5fXI9MCtWcGcP5iNmnBang7zfIsr+DBqQU5N3vlCBLORGqGVM0eMEv8nwBanAM2J7ZjbIVg7gou22eHau9751M4OoQM5FCo3nBTRepf2XA0K4W00TJg55chjMW/s91rw2ryJSPLUlrhvl5kWUYozGO56SzRjNhW+/HawcNZRa+OioWvlLp4bYi8mFyjlWWNAXqKPbqROQILhJnlHjGeMMq2qZTbd3t8aGHpeDg==
The public key is found in an xml file managed by EWP, using the keyId I receive in the request. Since I can find the public key by the keyId, I'm confident it is correct:
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAkH+X94XHM5RDyTNETNmt8vuu1Q5leSpb
EMIru1WE/vte5Co8k9lluffhvGWeyZRCKH4kzIBHf5n/kKjftTvvT/mPwArnJFZXcRbT4ebZ66RE
hi/+uJkJbIQ2md43MJBjM6fSXZAUiNw8VP29yVhbVPV5UUIA2ddJfygk/4ZNOmxdgnEJdc3aJTsK
99dEJ6BYqbcxA0Bk19Fv3/azV0jGZgrC4Y2hx3A+NvIYC05QYoqNkcfDExRVrKwduVWzqz6XDt7C
9ERl5Ss2bsgm4gbDouJC+k+WoCcxkUO2tnxrsKxQtZetZZSvrkst/5ELBaJAvKpcq12CvnB09dY1
MBV2XwIDAQAB
Now I need to decrypt the signature with only this public key (it was encrypted with the private key matching this public key) and the result should be a signing string, looking like this:
(request-target): get /api/institutions?hei_id_param=uhasselt.be
date: Tue, 08 Dec 2020 15:27:05 GMT
digest: SHA-256=47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU=
host: staging-ewp.uhasselt.be
x-request-id: 0e9b58f8-f98e-41a2-ae30-c09f566f437c
EWP documentation states that the signature is formed like this: signature="Base64(RSA-SHA256(signing string))
Can anyone show me code to decrypt this signature with this public key, resulting in above signing string? (or at least something similar) I've been struggling with this for a long time now...
Edit: So I understand that it is not possible to get the original message from the signature and public key, but only a hash generated with the original message and I should reproduce that hash to see if it matches. Ok for me, I have all the data needed to do so, just no working code to get a match.
Some more documentation about standards used by EWP:
https://github.com/erasmus-without-paper/ewp-specs-sec-cliauth-httpsig/tree/v1.0.1 https://datatracker.ietf.org/doc/html/draft-cavage-http-signatures-07#section-2.5
About the test data I use: I implemented a lot of logging in my API for testing, and these are just values I receive from the official EWP online validator. The signature is directly coming from the request, the public key is found in the EWP registry xml. Only thing I created myself is the signing string, as explained here: https://datatracker.ietf.org/doc/html/draft-cavage-http-signatures-07#section-3.1.2
My code after testing some more (requestContext.ClientPublicKey being the complete string MIIBIj...IDAQAB):
requestContext.SigningString = GetSigningString(request, ref requestContext);
log.Info("Signing string: " + requestContext.SigningString);
string x509Pem = @"-----BEGIN PUBLIC KEY-----" + requestContext.ClientPublicKey + "-----END PUBLIC KEY-----";
byte[] message = Encoding.UTF8.GetBytes(requestContext.SigningString);
byte[] signature = Convert.FromBase64String(requestContext.Signature);
byte[] Sha256Message = SHA256.Create().ComputeHash(message);
string x = Convert.ToBase64String(Sha256Message);
log.Info("x = " + x);
PemReader pr = new PemReader(new StringReader(x509Pem));
AsymmetricKeyParameter publicKey = (AsymmetricKeyParameter)pr.ReadObject();
RSAParameters rsaParams = DotNetUtilities.ToRSAParameters((RsaKeyParameters)publicKey);
RSACng rsaCng = new RSACng();
rsaCng.ImportParameters(rsaParams);
bool verified = rsaCng.VerifyData(message, signature, HashAlgorithmName.SHA256, RSASignaturePadding.Pss);
log.Info(verified);
log.Info(rsaCng.VerifyHash(Sha256Message, signature, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1));
log.Info(rsaCng.VerifyHash(Sha256Message, signature, HashAlgorithmName.SHA256, RSASignaturePadding.Pss));
log.Info(rsaCng.VerifyHash(message, signature, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1));
log.Info(rsaCng.VerifyHash(message, signature, HashAlgorithmName.SHA256, RSASignaturePadding.Pss));
string publicKeyXml = "<RSAKeyValue><Modulus>" + requestContext.ClientPublicKey + "</Modulus><Exponent>AQAB</Exponent></RSAKeyValue>";
log.Info("public key: " + publicKeyXml);
RSACryptoServiceProvider rsaCryptoServiceProvider = new RSACryptoServiceProvider();
rsaCryptoServiceProvider.FromXmlString(publicKeyXml);
log.Info(rsaCryptoServiceProvider.VerifyHash(Sha256Message, signature, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1));
log.Info(rsaCryptoServiceProvider.VerifyHash(Sha256Message, signature, HashAlgorithmName.SHA256, RSASignaturePadding.Pss));
log.Info(rsaCryptoServiceProvider.VerifyHash(message, signature, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1));
log.Info(rsaCryptoServiceProvider.VerifyHash(message, signature, HashAlgorithmName.SHA256, RSASignaturePadding.Pss));
Edit2: I found a working test in my logs! But, strange as it is, it only worked once... I can reproduce the successful verification with these same data:
private void test()
{
string SigningString = "";
string keyId = "769c7a9c8bac71a238ea9e78dda3e49bcabbaf695f56fbc99dd4d6e8f777756a";
SigningString += "(request-target): get /api/institutions?hei_id=tul.edu\n";
SigningString += "original-date: Wed, 09 Dec 2020 08:36:58 GMT\n";
SigningString += "x-request-id: bc8da180-1217-4fe6-ac22-fc4919e96718\n";
SigningString += "digest: SHA-256=47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU=\n";
SigningString += "host: staging-ewp.uhasselt.be";
string Signature = "iqd5ZLHbzshwm3uClkKPpTniLqdQvodZAexEnywDEir9mfytcf8NDWBYi5bLrwf7jiZ29GO5F1CtlhstcqgO2Ia3LvLFbl4Dru58TzYasspsZtEr6r0HDFNNzfuVhWXSa349yJADGCDYbvAo3rBS7SM2lMZ9dpf8QZDOUL9wTUgpzt2X9IO83nmUxUQWYC9LQZJ5x/95RQsbXN4bY8lDFjfUAC2nW30g2SVtMKlpVXAZwLv7MoYlVXpFl56DuoOIGPmeH6LDumA6krU28EwK4vs1hbDr+GKpzQRvSKiabraUkTq3ITGeXhZgZy+0Av5KxCKNQOVjYiK3Hf0k8sAgpQ==";
string ClientPublicKey = LookUpTheKey(keyId);
Console.WriteLine("ClientPublicKey: " + ClientPublicKey);
Console.WriteLine("Signing string: " + SigningString);
string x509Pem = @"-----BEGIN PUBLIC KEY-----" + ClientPublicKey + "-----END PUBLIC KEY-----";
byte[] message = Encoding.UTF8.GetBytes(SigningString);
byte[] signature = Convert.FromBase64String(Signature);
byte[] Sha256Message = SHA256.Create().ComputeHash(message);
string x = Convert.ToBase64String(Sha256Message);
Console.WriteLine("x = " + x);
PemReader pr = new PemReader(new StringReader(x509Pem));
AsymmetricKeyParameter publicKey = (AsymmetricKeyParameter)pr.ReadObject();
RSAParameters rsaParams = DotNetUtilities.ToRSAParameters((RsaKeyParameters)publicKey);
RSACng rsaCng = new RSACng();
rsaCng.ImportParameters(rsaParams);
Console.WriteLine(rsaCng.VerifyHash(Sha256Message, signature, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1));
}
protected string LookUpTheKey(string keyId)
{
string clientPublicKey = "";
string registryPath = "registry.xml";
if ((!File.Exists(registryPath)) || (Math.Abs((File.GetLastWriteTime(registryPath) - DateTime.Now).TotalMinutes) > minutesToChacheRegistry))
{
using (var handler = new HttpClientHandler())
using (var client = new HttpClient(handler))
{
var request = new HttpRequestMessage(HttpMethod.Get, "https://dev- registry.erasmuswithoutpaper.eu/catalogue-v1.xml");
request.Headers.Add(HttpRequestHeader.Accept.ToString(), "application/xml");
var response = client.SendAsync(request).Result;
var returnedXml = response.Content.ReadAsStringAsync().Result;
File.WriteAllText(registryPath, returnedXml);
}
}
try
{
XmlDocument doc = new XmlDocument();
doc.Load(registryPath);
var namespaceManager = new XmlNamespaceManager(doc.NameTable);
namespaceManager.AddNamespace("r", "https://github.com/erasmus-without- paper/ewp-specs-api-registry/tree/stable-v1");
XmlNode root = doc.DocumentElement;
var XPathExpr = @"//r:binaries/r:rsa-public-key[@sha-256=""" + keyId + @"""]";
XmlNode node = root.SelectSingleNode(XPathExpr, namespaceManager);
if (node == null) { }
else { clientPublicKey = node.InnerText; }
}
catch
{
}
return clientPublicKey;
}
But in my API, with new data, other keyId, public key, signing string and signature, it is failing again... how is this possible?
Upvotes: 0
Views: 6746
Reputation: 61
It seems like your initial confusion lies in this statement:
Now I need to decrypt the signature with only this public key (it was encrypted with the > private key matching this public key) and the result should be a signing string, looking > like this:
So a message digest (SHA256 is a message digest or hashing algorithm) or even a signature is not reversibly encrypted data. It is indeed a one-way function meaning the "hash" or message digest produced both by the SHA256 algorithm as well as the RSA signing algorithm is not reversible back into the data from which it was generated.
See here: https://www.cs.cornell.edu/courses/cs5430/2015sp/notes/rsa_sign_vs_dec.php
So you really are not "decrypting" that signature. I believe what you would want to do is take the known public key and create an instance of the RSACryptoServiceProvider
and then pass the "signature" and the "sha256 hash" into the VerifyHash()
function to get back a boolean of whether the signature matches the hash. You should probably re-compute the SHA256 hash on your end as well to ensure what was sent in the header actually matches the "payload" you received. Just running the check on the HASH passed in the header against the signature in the header doesn't guarantee that they payload was not altered/tampered.
So I used the values you provided:
Digest (SHA256 Hash):
47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU=
Signature (RSA):
UiBgtTc4hIYeZehhP8RWKvPIvWFXeh7ERFEvJr43v87YE7I4dqAHbD8l5DwZW3jezVfIcBflBS7ezjFDOH0/43T21ZCxwa/0qKhQRTjXoWQSETap5fXI9MCtWcGcP5iNmnBang7zfIsr+DBqQU5N3vlCBLORGqGVM0eMEv8nwBanAM2J7ZjbIVg7gou22eHau9751M4OoQM5FCo3nBTRepf2XA0K4W00TJg55chjMW/s91rw2ryJSPLUlrhvl5kWUYozGO56SzRjNhW+/HawcNZRa+OioWvlLp4bYi8mFyjlWWNAXqKPbqROQILhJnlHjGeMMq2qZTbd3t8aGHpeDg==
Public Key (PEM format):
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAkH+X94XHM5RDyTNETNmt8vuu1Q5leSpb
EMIru1WE/vte5Co8k9lluffhvGWeyZRCKH4kzIBHf5n/kKjftTvvT/mPwArnJFZXcRbT4ebZ66RE
hi/+uJkJbIQ2md43MJBjM6fSXZAUiNw8VP29yVhbVPV5UUIA2ddJfygk/4ZNOmxdgnEJdc3aJTsK
99dEJ6BYqbcxA0Bk19Fv3/azV0jGZgrC4Y2hx3A+NvIYC05QYoqNkcfDExRVrKwduVWzqz6XDt7C
9ERl5Ss2bsgm4gbDouJC+k+WoCcxkUO2tnxrsKxQtZetZZSvrkst/5ELBaJAvKpcq12CvnB09dY1
MBV2XwIDAQAB
And following the RFC draft for the Authorization header: https://datatracker.ietf.org/doc/html/draft-cavage-http-signatures-07#section-3.1
The following C# code should attempt to Verify the signature:
using System;
using System.Text;
using System.Security.Cryptography;
public static class SHA256WithRSATest
{
/*
Public Key:
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAkH+X94XHM5RDyTNETNmt8vuu1Q5leSpb
EMIru1WE/vte5Co8k9lluffhvGWeyZRCKH4kzIBHf5n/kKjftTvvT/mPwArnJFZXcRbT4ebZ66RE
hi/+uJkJbIQ2md43MJBjM6fSXZAUiNw8VP29yVhbVPV5UUIA2ddJfygk/4ZNOmxdgnEJdc3aJTsK
99dEJ6BYqbcxA0Bk19Fv3/azV0jGZgrC4Y2hx3A+NvIYC05QYoqNkcfDExRVrKwduVWzqz6XDt7C
9ERl5Ss2bsgm4gbDouJC+k+WoCcxkUO2tnxrsKxQtZetZZSvrkst/5ELBaJAvKpcq12CvnB09dY1
MBV2XwIDAQAB
Converted to an RSAParameters struct using Openssl:
echo MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAkH+X94XHM5RDyTNETNmt8vuu1Q5leSpbEMIru1WE/vte5Co8k9lluffhvGWeyZRCKH4kzIBHf5n/kKjftTvvT/mPwArnJFZXcRbT4ebZ66REhi/+uJkJbIQ2md43MJBjM6fSXZAUiNw8VP29yVhbVPV5UUIA2ddJfygk/4ZNOmxdgnEJdc3aJTsK99dEJ6BYqbcxA0Bk19Fv3/azV0jGZgrC4Y2hx3A+NvIYC05QYoqNkcfDExRVrKwduVWzqz6XDt7C9ERl5Ss2bsgm4gbDouJC+k+WoCcxkUO2tnxrsKxQtZetZZSvrkst/5ELBaJAvKpcq12CvnB09dY1MBV2XwIDAQAB | base64 -d | openssl rsa -pubin -inform DER -noout -text
RSA Public-Key: (2048 bit)
Modulus:
00:90:7f:97:f7:85:c7:33:94:43:c9:33:44:4c:d9:
ad:f2:fb:ae:d5:0e:65:79:2a:5b:10:c2:2b:bb:55:
84:fe:fb:5e:e4:2a:3c:93:d9:65:b9:f7:e1:bc:65:
9e:c9:94:42:28:7e:24:cc:80:47:7f:99:ff:90:a8:
df:b5:3b:ef:4f:f9:8f:c0:0a:e7:24:56:57:71:16:
d3:e1:e6:d9:eb:a4:44:86:2f:fe:b8:99:09:6c:84:
36:99:de:37:30:90:63:33:a7:d2:5d:90:14:88:dc:
3c:54:fd:bd:c9:58:5b:54:f5:79:51:42:00:d9:d7:
49:7f:28:24:ff:86:4d:3a:6c:5d:82:71:09:75:cd:
da:25:3b:0a:f7:d7:44:27:a0:58:a9:b7:31:03:40:
64:d7:d1:6f:df:f6:b3:57:48:c6:66:0a:c2:e1:8d:
a1:c7:70:3e:36:f2:18:0b:4e:50:62:8a:8d:91:c7:
c3:13:14:55:ac:ac:1d:b9:55:b3:ab:3e:97:0e:de:
c2:f4:44:65:e5:2b:36:6e:c8:26:e2:06:c3:a2:e2:
42:fa:4f:96:a0:27:31:91:43:b6:b6:7c:6b:b0:ac:
50:b5:97:ad:65:94:af:ae:4b:2d:ff:91:0b:05:a2:
40:bc:aa:5c:ab:5d:82:be:70:74:f5:d6:35:30:15:
76:5f
Exponent: 65537 (0x10001)
*/
private static readonly RSAParameters PUBLIC_KEY = new RSAParameters
{
Modulus = new byte[]
{
0x90, 0x7f, 0x97, 0xf7, 0x85, 0xc7, 0x33, 0x94, 0x43, 0xc9,
0x33, 0x44, 0x4c, 0xd9, 0xad, 0xf2, 0xfb, 0xae, 0xd5, 0x0e,
0x65, 0x79, 0x2a, 0x5b, 0x10, 0xc2, 0x2b, 0xbb, 0x55, 0x84,
0xfe, 0xfb, 0x5e, 0xe4, 0x2a, 0x3c, 0x93, 0xd9, 0x65, 0xb9,
0xf7, 0xe1, 0xbc, 0x65, 0x9e, 0xc9, 0x94, 0x42, 0x28, 0x7e,
0x24, 0xcc, 0x80, 0x47, 0x7f, 0x99, 0xff, 0x90, 0xa8, 0xdf,
0xb5, 0x3b, 0xef, 0x4f, 0xf9, 0x8f, 0xc0, 0x0a, 0xe7, 0x24,
0x56, 0x57, 0x71, 0x16, 0xd3, 0xe1, 0xe6, 0xd9, 0xeb, 0xa4,
0x44, 0x86, 0x2f, 0xfe, 0xb8, 0x99, 0x09, 0x6c, 0x84, 0x36,
0x99, 0xde, 0x37, 0x30, 0x90, 0x63, 0x33, 0xa7, 0xd2, 0x5d,
0x90, 0x14, 0x88, 0xdc, 0x3c, 0x54, 0xfd, 0xbd, 0xc9, 0x58,
0x5b, 0x54, 0xf5, 0x79, 0x51, 0x42, 0x00, 0xd9, 0xd7, 0x49,
0x7f, 0x28, 0x24, 0xff, 0x86, 0x4d, 0x3a, 0x6c, 0x5d, 0x82,
0x71, 0x09, 0x75, 0xcd, 0xda, 0x25, 0x3b, 0x0a, 0xf7, 0xd7,
0x44, 0x27, 0xa0, 0x58, 0xa9, 0xb7, 0x31, 0x03, 0x40, 0x64,
0xd7, 0xd1, 0x6f, 0xdf, 0xf6, 0xb3, 0x57, 0x48, 0xc6, 0x66,
0x0a, 0xc2, 0xe1, 0x8d, 0xa1, 0xc7, 0x70, 0x3e, 0x36, 0xf2,
0x18, 0x0b, 0x4e, 0x50, 0x62, 0x8a, 0x8d, 0x91, 0xc7, 0xc3,
0x13, 0x14, 0x55, 0xac, 0xac, 0x1d, 0xb9, 0x55, 0xb3, 0xab,
0x3e, 0x97, 0x0e, 0xde, 0xc2, 0xf4, 0x44, 0x65, 0xe5, 0x2b,
0x36, 0x6e, 0xc8, 0x26, 0xe2, 0x06, 0xc3, 0xa2, 0xe2, 0x42,
0xfa, 0x4f, 0x96, 0xa0, 0x27, 0x31, 0x91, 0x43, 0xb6, 0xb6,
0x7c, 0x6b, 0xb0, 0xac, 0x50, 0xb5, 0x97, 0xad, 0x65, 0x94,
0xaf, 0xae, 0x4b, 0x2d, 0xff, 0x91, 0x0b, 0x05, 0xa2, 0x40,
0xbc, 0xaa, 0x5c, 0xab, 0x5d, 0x82, 0xbe, 0x70, 0x74, 0xf5,
0xd6, 0x35, 0x30, 0x15, 0x76, 0x5f
},
Exponent = new byte[] { 0x01,0x00,0x01 }
};
public static readonly byte[] theSignature =
Convert.FromBase64String("UiBgtTc4hIYeZehhP8RWKvPIvWFXeh7ERFEvJr43v87YE7I4dqAHbD8l5DwZW3jezVfIcBflBS7ezjFDOH0/43T21ZCxwa/0qKhQRTjXoWQSETap5fXI9MCtWcGcP5iNmnBang7zfIsr+DBqQU5N3vlCBLORGqGVM0eMEv8nwBanAM2J7ZjbIVg7gou22eHau9751M4OoQM5FCo3nBTRepf2XA0K4W00TJg55chjMW/s91rw2ryJSPLUlrhvl5kWUYozGO56SzRjNhW+/HawcNZRa+OioWvlLp4bYi8mFyjlWWNAXqKPbqROQILhJnlHjGeMMq2qZTbd3t8aGHpeDg==");
public static readonly byte[] SHA256HASH =
Convert.FromBase64String(
"47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU=");
public static bool VerifyDigestSignature(
byte[] digest, byte[] digestSignature)
{
using(RSACryptoServiceProvider rsaProvider =
new RSACryptoServiceProvider())
{
rsaProvider.ImportParameters(PUBLIC_KEY);
return rsaProvider.VerifyHash(
digest, digestSignature, HashAlgorithmName.SHA256,
RSASignaturePadding.Pkcs1);
}
}
public static void Main(string[] args)
{
Console.Error.WriteLine(
VerifyDigestSignature(SHA256HASH,theSignature));
}
}
However, the output of that code returns "False." So it would appear either something was lost in your post or wherever that example came from may have a misprint?
As a pure proof of concept here is similar code that computes a hash and signature and then verifies the signature successfully using the SHA256WithRSA method:
A new public/private key pair was generated using openssl genrsa 2048
...
using System;
using System.Text;
using System.Security.Cryptography;
public static class SHA256WithRSATest
{
private static readonly RSAParameters PUBLIC_KEY = new RSAParameters
{
Modulus = new byte[]
{
0xc2, 0xdb, 0x06, 0xc3, 0xe0, 0x43, 0x3b, 0x38, 0x72,
0x43, 0xc1, 0x87, 0xa4, 0x8b, 0x8c, 0x31, 0x64, 0x8a, 0xa4,
0xf3, 0x0d, 0x96, 0xc9, 0x3c, 0x46, 0x5e, 0x16, 0x8a, 0x57,
0xb6, 0x8c, 0x62, 0x0d, 0x3b, 0x78, 0x2f, 0xd8, 0x23, 0x6d,
0x1a, 0x74, 0x0b, 0x1e, 0x7e, 0xd9, 0x44, 0xec, 0x74, 0x99,
0xaf, 0x83, 0xe1, 0x84, 0x5c, 0x8b, 0x31, 0xac, 0x83, 0xe3,
0x09, 0xc9, 0xff, 0xee, 0x29, 0xfd, 0xcd, 0x64, 0x34, 0x5b,
0x25, 0xc7, 0xab, 0xf9, 0x76, 0x49, 0xcd, 0x1c, 0x53, 0xc4,
0x82, 0xcb, 0x61, 0xfa, 0x87, 0xf5, 0xa7, 0xe4, 0x63, 0x03,
0xbb, 0xbb, 0xb6, 0xc0, 0x2e, 0x5b, 0x1c, 0x28, 0xe6, 0xb8,
0xba, 0x6e, 0x89, 0xf9, 0x5a, 0x15, 0xf7, 0x49, 0x63, 0x6b,
0xb4, 0x90, 0x9c, 0xd5, 0xe8, 0xad, 0x5e, 0xa0, 0x95, 0x4b,
0xf2, 0x9a, 0xaa, 0x29, 0x1e, 0x04, 0xfe, 0xc3, 0x8d, 0xea,
0x41, 0xaf, 0xf8, 0x24, 0x7d, 0xf7, 0x3d, 0x24, 0x4d, 0xdd,
0xcb, 0xad, 0x84, 0x94, 0xba, 0x32, 0xb8, 0x26, 0x3b, 0x48,
0x55, 0xe2, 0x07, 0x13, 0x4d, 0x58, 0x1f, 0x61, 0x01, 0x92,
0x40, 0x53, 0x23, 0x8f, 0x3d, 0x7d, 0x6a, 0x65, 0xe3, 0xb1,
0xd3, 0xe3, 0x39, 0xd1, 0xd8, 0x77, 0xcd, 0x7d, 0x37, 0x9f,
0x54, 0x76, 0x3f, 0xc3, 0x3f, 0x38, 0xe4, 0xab, 0xaf, 0x99,
0x09, 0x1e, 0x96, 0x9d, 0x6e, 0x8e, 0x66, 0xb2, 0x5c, 0x39,
0xee, 0xf7, 0x7e, 0x65, 0x89, 0x69, 0x9d, 0xdc, 0x20, 0xe7,
0x63, 0x20, 0x75, 0x60, 0x75, 0x34, 0x22, 0x00, 0x2f, 0x74,
0x5c, 0x4c, 0x0a, 0xdf, 0xb5, 0x12, 0xfa, 0x9d, 0xc1, 0xed,
0x7f, 0x2e, 0xfc, 0xef, 0xd3, 0xb1, 0x62, 0x8a, 0xd7, 0x68,
0x3c, 0xa6, 0x61, 0xa9, 0x3e, 0x9e, 0x27, 0x3c, 0x6e, 0x02,
0xd1, 0x78, 0x0e, 0xe7, 0x8b, 0xb7, 0x91
},
Exponent = new byte[] { 0x01,0x00,0x01 }
};
private static readonly RSAParameters PRIVATE_KEY = new RSAParameters
{
Modulus = new byte[]
{
0xc2, 0xdb, 0x06, 0xc3, 0xe0, 0x43, 0x3b, 0x38, 0x72,
0x43, 0xc1, 0x87, 0xa4, 0x8b, 0x8c, 0x31, 0x64, 0x8a, 0xa4,
0xf3, 0x0d, 0x96, 0xc9, 0x3c, 0x46, 0x5e, 0x16, 0x8a, 0x57,
0xb6, 0x8c, 0x62, 0x0d, 0x3b, 0x78, 0x2f, 0xd8, 0x23, 0x6d,
0x1a, 0x74, 0x0b, 0x1e, 0x7e, 0xd9, 0x44, 0xec, 0x74, 0x99,
0xaf, 0x83, 0xe1, 0x84, 0x5c, 0x8b, 0x31, 0xac, 0x83, 0xe3,
0x09, 0xc9, 0xff, 0xee, 0x29, 0xfd, 0xcd, 0x64, 0x34, 0x5b,
0x25, 0xc7, 0xab, 0xf9, 0x76, 0x49, 0xcd, 0x1c, 0x53, 0xc4,
0x82, 0xcb, 0x61, 0xfa, 0x87, 0xf5, 0xa7, 0xe4, 0x63, 0x03,
0xbb, 0xbb, 0xb6, 0xc0, 0x2e, 0x5b, 0x1c, 0x28, 0xe6, 0xb8,
0xba, 0x6e, 0x89, 0xf9, 0x5a, 0x15, 0xf7, 0x49, 0x63, 0x6b,
0xb4, 0x90, 0x9c, 0xd5, 0xe8, 0xad, 0x5e, 0xa0, 0x95, 0x4b,
0xf2, 0x9a, 0xaa, 0x29, 0x1e, 0x04, 0xfe, 0xc3, 0x8d, 0xea,
0x41, 0xaf, 0xf8, 0x24, 0x7d, 0xf7, 0x3d, 0x24, 0x4d, 0xdd,
0xcb, 0xad, 0x84, 0x94, 0xba, 0x32, 0xb8, 0x26, 0x3b, 0x48,
0x55, 0xe2, 0x07, 0x13, 0x4d, 0x58, 0x1f, 0x61, 0x01, 0x92,
0x40, 0x53, 0x23, 0x8f, 0x3d, 0x7d, 0x6a, 0x65, 0xe3, 0xb1,
0xd3, 0xe3, 0x39, 0xd1, 0xd8, 0x77, 0xcd, 0x7d, 0x37, 0x9f,
0x54, 0x76, 0x3f, 0xc3, 0x3f, 0x38, 0xe4, 0xab, 0xaf, 0x99,
0x09, 0x1e, 0x96, 0x9d, 0x6e, 0x8e, 0x66, 0xb2, 0x5c, 0x39,
0xee, 0xf7, 0x7e, 0x65, 0x89, 0x69, 0x9d, 0xdc, 0x20, 0xe7,
0x63, 0x20, 0x75, 0x60, 0x75, 0x34, 0x22, 0x00, 0x2f, 0x74,
0x5c, 0x4c, 0x0a, 0xdf, 0xb5, 0x12, 0xfa, 0x9d, 0xc1, 0xed,
0x7f, 0x2e, 0xfc, 0xef, 0xd3, 0xb1, 0x62, 0x8a, 0xd7, 0x68,
0x3c, 0xa6, 0x61, 0xa9, 0x3e, 0x9e, 0x27, 0x3c, 0x6e, 0x02,
0xd1, 0x78, 0x0e, 0xe7, 0x8b, 0xb7, 0x91
},
Exponent = new byte[] { 0x01,0x00,0x01 },
D = new byte[]
{
0x01, 0xe9, 0x6a, 0x38, 0x93, 0xc3, 0xb5, 0x1a, 0x09, 0xac,
0xf6, 0x82, 0x21, 0x30, 0x29, 0x50, 0xf6, 0xbe, 0x91, 0x7d,
0xbc, 0xfd, 0x64, 0xbe, 0x0d, 0xa6, 0xb0, 0xab, 0xec, 0xce,
0x62, 0xb4, 0x37, 0x93, 0x04, 0xcb, 0xdb, 0x60, 0x05, 0x9b,
0x03, 0xd6, 0x74, 0x17, 0x24, 0x84, 0x93, 0x99, 0x55, 0x44,
0xae, 0x93, 0x90, 0xdb, 0xe3, 0x95, 0xba, 0x2e, 0x95, 0x14,
0xac, 0x81, 0xb1, 0x51, 0x82, 0x26, 0xf8, 0xbb, 0xb6, 0xc5,
0x39, 0x1f, 0x4b, 0xd4, 0x48, 0x47, 0x15, 0xe7, 0x10, 0x7f,
0x84, 0x05, 0x53, 0x12, 0xf0, 0x6b, 0x14, 0x47, 0x90, 0x6a,
0xd0, 0x1d, 0xab, 0xe7, 0x08, 0x87, 0xcf, 0x32, 0xec, 0x4f,
0x0f, 0xf4, 0x94, 0x98, 0xb8, 0xac, 0x73, 0x70, 0xe1, 0x46,
0xa5, 0x40, 0x94, 0xac, 0xb2, 0xbe, 0xc1, 0xee, 0x95, 0x0f,
0x2d, 0x4b, 0x4c, 0x19, 0xfa, 0x4c, 0x91, 0x98, 0x97, 0x75,
0x79, 0x6e, 0x02, 0x9d, 0xe7, 0x96, 0xe5, 0x71, 0x74, 0x67,
0x6b, 0x80, 0xc1, 0x5c, 0x30, 0x28, 0x78, 0xe6, 0x31, 0xdd,
0x09, 0x2d, 0x55, 0xd3, 0x1c, 0xbf, 0x12, 0x39, 0xe1, 0x3b,
0xf0, 0xd0, 0x92, 0xa0, 0x8e, 0xe4, 0x69, 0x2f, 0xb4, 0xe0,
0x7e, 0x6d, 0x62, 0x00, 0xd4, 0xf4, 0x80, 0x0a, 0x5e, 0xcf,
0xf3, 0x16, 0x59, 0x15, 0x0f, 0x96, 0xb5, 0x3b, 0xcd, 0xb3,
0x4d, 0x3b, 0x0f, 0x58, 0xc1, 0xaf, 0xde, 0x7d, 0x11, 0x6f,
0x53, 0x37, 0x24, 0x29, 0x12, 0xc6, 0xeb, 0x2e, 0x11, 0x74,
0x93, 0x66, 0xbd, 0x42, 0xc2, 0x28, 0x35, 0x32, 0x1f, 0xef,
0x95, 0x6b, 0x92, 0xe9, 0x7f, 0x9e, 0xc5, 0xbf, 0xf1, 0xfc,
0x48, 0x07, 0x2f, 0xda, 0xe0, 0x6b, 0xaa, 0xdd, 0x02, 0xc9,
0x4f, 0xe4, 0xff, 0x56, 0xc5, 0xfb, 0xe7, 0x1e, 0x63, 0x47,
0xa1, 0x0d, 0x44, 0x0e, 0x22, 0x1d
},
P = new byte[]
{
0xf1, 0x50, 0x52, 0x7e, 0x2b, 0xa7, 0xc3, 0x30, 0x26,
0xda, 0x14, 0xaf, 0xf9, 0xc5, 0x21, 0x49, 0xab, 0xa8, 0x09,
0x1f, 0x36, 0xe5, 0x1f, 0x0f, 0x52, 0x4d, 0x82, 0x41, 0x46,
0x7e, 0x75, 0x43, 0x80, 0x8e, 0x58, 0x6f, 0xdf, 0x9d, 0xee,
0x77, 0x4c, 0x9c, 0xff, 0x94, 0xf6, 0x90, 0xaa, 0xaf, 0x0f,
0xba, 0xb6, 0x08, 0xe6, 0x3f, 0xaa, 0xf6, 0x37, 0xc5, 0xe3,
0xf7, 0x6a, 0xcd, 0x1b, 0xb7, 0xa8, 0x78, 0x15, 0xfc, 0xd0,
0xce, 0xe0, 0x67, 0xf5, 0xd7, 0xa2, 0xa7, 0x37, 0x6f, 0x95,
0xeb, 0xe0, 0x13, 0x93, 0xc9, 0x65, 0xd8, 0x8b, 0x4e, 0x1c,
0x46, 0xd8, 0x4f, 0x50, 0x6b, 0x02, 0x87, 0xb4, 0x4e, 0x55,
0x6c, 0x21, 0xc3, 0xf5, 0xc7, 0xd9, 0x1d, 0x2a, 0xcd, 0x25,
0x9a, 0xb4, 0x79, 0x28, 0x29, 0x03, 0x45, 0x11, 0x26, 0xce,
0x76, 0x6c, 0xcb, 0x17, 0xb2, 0xc3, 0x58, 0x85, 0xe3
},
Q = new byte[]
{
0xce, 0xb6, 0xe2, 0x42, 0x9c, 0xe6, 0xd7, 0xc3, 0x52,
0x83, 0x5f, 0xa7, 0x16, 0x04, 0x61, 0xf1, 0xb6, 0xf9, 0x65,
0xb3, 0xda, 0x02, 0x10, 0x74, 0xc4, 0x67, 0x85, 0x5f, 0x1c,
0x1f, 0xcb, 0x71, 0x0e, 0xe5, 0x10, 0x21, 0xd0, 0x5f, 0xbe,
0xa4, 0x81, 0xcc, 0xdd, 0x52, 0xab, 0x6b, 0x40, 0x72, 0x09,
0x9b, 0xa0, 0x4f, 0x2f, 0x88, 0x96, 0x54, 0xae, 0x66, 0x83,
0xd5, 0x45, 0x48, 0xc7, 0x67, 0xaf, 0x5c, 0xc6, 0xfe, 0xb6,
0x94, 0x26, 0x02, 0xf7, 0x1d, 0x41, 0x67, 0x98, 0x81, 0x1a,
0x6a, 0xeb, 0xfb, 0x33, 0xc3, 0x34, 0x8a, 0x93, 0xbd, 0x74,
0x4c, 0x7d, 0x1c, 0xc4, 0x38, 0x9d, 0x72, 0x6e, 0xe3, 0x79,
0xd0, 0xec, 0xaf, 0xac, 0x14, 0xe2, 0xee, 0x27, 0x9c, 0x72,
0xe6, 0xd8, 0x43, 0x6b, 0xc8, 0xae, 0x07, 0x5b, 0x5d, 0x27,
0x61, 0xba, 0xa5, 0x7f, 0x74, 0xc8, 0x78, 0x66, 0xfb
},
DP = new byte[]
{
0x86, 0x46, 0x6e, 0x90, 0x9c, 0x54, 0x0e, 0x4d, 0x55,
0xe1, 0x15, 0x8f, 0xd2, 0x08, 0xb9, 0xfc, 0x17, 0x53, 0x3a,
0x38, 0x2f, 0x40, 0x90, 0xe6, 0xe2, 0xa2, 0x14, 0x6f, 0xa3,
0xfd, 0x2b, 0xdc, 0xf2, 0xc4, 0xc2, 0x3b, 0x06, 0x10, 0x08,
0x28, 0x43, 0xee, 0x3c, 0x5d, 0x34, 0x51, 0xcd, 0x57, 0xfa,
0x05, 0xa7, 0xd3, 0x0d, 0xe3, 0xb1, 0x8a, 0xae, 0x00, 0x24,
0x58, 0x81, 0x0a, 0x3e, 0x79, 0x14, 0x7a, 0x35, 0xa9, 0xe6,
0xba, 0xa6, 0xad, 0xd2, 0x63, 0x39, 0xb3, 0x98, 0x2a, 0x34,
0x1e, 0xfb, 0x21, 0x89, 0xa3, 0x90, 0x53, 0x4d, 0x38, 0x9a,
0x8d, 0x65, 0x41, 0xc4, 0xfa, 0xb6, 0x7e, 0xb2, 0x7a, 0xc0,
0x17, 0x9a, 0x36, 0x43, 0x26, 0x00, 0x0e, 0xb5, 0xc9, 0x4f,
0x3a, 0x65, 0x5f, 0xe1, 0x53, 0xe8, 0xe8, 0xde, 0xa1, 0x5c,
0x53, 0x13, 0x38, 0x73, 0x28, 0x5a, 0x80, 0x80, 0x87
},
DQ = new byte[]
{
0xc7, 0xe5, 0x24, 0x91, 0x84, 0x06, 0xdb, 0x19, 0x1f,
0x9e, 0xb9, 0x0d, 0xeb, 0x85, 0x9b, 0x6d, 0x52, 0x22, 0x84,
0x4d, 0xd2, 0x80, 0xf2, 0x86, 0xe8, 0x32, 0xaf, 0x4f, 0x94,
0xf3, 0xce, 0x18, 0xeb, 0x6d, 0x69, 0x17, 0x39, 0xd8, 0x8c,
0x93, 0xaa, 0x8d, 0x80, 0x6c, 0xe4, 0x25, 0x57, 0xf1, 0xaf,
0x06, 0xd6, 0x94, 0x1c, 0x84, 0x39, 0xd3, 0x73, 0xbe, 0xe0,
0xb7, 0x89, 0x43, 0x62, 0xc9, 0x0a, 0x54, 0x6e, 0x7e, 0x7b,
0xf2, 0x71, 0x7b, 0xa6, 0x99, 0x9c, 0xd8, 0xe0, 0x29, 0xe0,
0x71, 0x0a, 0xf8, 0x25, 0x4b, 0x1c, 0x70, 0xf1, 0x83, 0x60,
0x86, 0x62, 0xea, 0x41, 0x79, 0xfa, 0x0f, 0x61, 0xda, 0x09,
0xbf, 0x96, 0x52, 0x1e, 0xd7, 0x27, 0xc7, 0x63, 0x78, 0xaf,
0xc8, 0x39, 0xd3, 0xa0, 0xd4, 0x34, 0x2e, 0x1b, 0x14, 0xce,
0xf3, 0x7b, 0xb9, 0x74, 0xb2, 0x6a, 0xf5, 0xbb, 0xa7
},
InverseQ = new byte[]
{
0x83, 0x84, 0x1d, 0xa5, 0x91, 0x6a, 0xac, 0x17, 0xa3,
0xaf, 0xb5, 0x1f, 0x36, 0x77, 0xcb, 0x9a, 0x05, 0x7a, 0x79,
0xa3, 0x25, 0x77, 0xd9, 0x0e, 0x64, 0xdc, 0x18, 0xfd, 0xec,
0xc6, 0xa4, 0xdc, 0x0f, 0x6a, 0x50, 0x91, 0x97, 0x12, 0xfe,
0x05, 0x0b, 0xcd, 0x64, 0xf8, 0x6e, 0xbf, 0x12, 0x6b, 0xd2,
0x0e, 0x7c, 0x94, 0xc2, 0x5b, 0x2d, 0x53, 0x0f, 0x45, 0x41,
0xfb, 0x6b, 0x22, 0x64, 0xbb, 0x36, 0xc0, 0x24, 0x01, 0xe5,
0x1b, 0xf7, 0x76, 0xa0, 0x3d, 0x67, 0x66, 0x2f, 0x6b, 0x80,
0xb2, 0x35, 0x87, 0x68, 0xdb, 0x75, 0x4d, 0x3d, 0xc1, 0x21,
0xa8, 0xff, 0x0d, 0xe7, 0xea, 0x2c, 0x87, 0xe1, 0xe5, 0xc1,
0x81, 0x45, 0xd0, 0x2f, 0x6f, 0xbb, 0xe9, 0xbf, 0x1f, 0x93,
0x62, 0x06, 0xf5, 0x2c, 0x78, 0xb0, 0x2a, 0x22, 0xb8, 0xeb,
0xf8, 0xae, 0xdd, 0x1d, 0x25, 0x53, 0x66, 0xab, 0x94
}
};
public const string SAMPLE_PAYLOAD =
@"{ 'name': 'value', 'name2' : 'value2' }";
public static byte[] GetDigest(byte[] message)
{
using (SHA256 digestAlg = SHA256.Create())
return digestAlg.ComputeHash(message);
}
public static byte[] GetDigestSignature(byte[] digest)
{
using(RSACryptoServiceProvider rsaProvider =
new RSACryptoServiceProvider())
{
rsaProvider.ImportParameters(PRIVATE_KEY);
return rsaProvider.SignHash(digest, HashAlgorithmName.SHA256,
RSASignaturePadding.Pkcs1);
}
}
public static bool VerifyDigestSignature(
byte[] digest, byte[] digestSignature)
{
using(RSACryptoServiceProvider rsaProvider =
new RSACryptoServiceProvider())
{
rsaProvider.ImportParameters(PUBLIC_KEY);
return rsaProvider.VerifyHash(
digest, digestSignature, HashAlgorithmName.SHA256,
RSASignaturePadding.Pkcs1);
}
}
public static void Main(string[] args)
{
byte[] dataForSignature = Encoding.UTF8.GetBytes(SAMPLE_PAYLOAD);
byte[] digest = GetDigest(dataForSignature);
byte[] hashSignature = GetDigestSignature(digest);
Console.WriteLine("The Sample Payload: {0}", SAMPLE_PAYLOAD);
Console.WriteLine("SHA256 of Payload: {0}",
Convert.ToBase64String(digest));
Console.WriteLine("Signature of SHA256 of Payload: {0}",
Convert.ToBase64String(hashSignature));
Console.WriteLine("Verify RSA Signature of SHA256 Hash: {0}",
VerifyDigestSignature(digest,hashSignature));
}
}
Results of Execution:
The Sample Payload: { 'name': 'value', 'name2' : 'value2' }
SHA256 of Payload: Pw3YzEtDseyloxZGsfrFN1q8g39ubwlnRKJzal2sUb4=
Signature of SHA256 of Payload: rVdEq158Z8h9PHinlQ6I7lnUUcXDal25LZ+MIyW0Uvf8HpH6wRoQzqJhygVwHmeVTkZn2mZS644B/9Ey1WVbgq56zzk2XDiHyNnS1IeZvfaTkaXqiBszVQ/hTSjrDOARzrU2NDRo2/gI/UUXC2v2fx3eyd4NbztvghTKfoUBp+xZjyBHWlCwAtEmDfVT0/Di0LkrII80txrlHQpSNXEpejmrhJ10WsskUuOCOloQmcl8A5d0ibkXXqL6TLLWKpLS0wXI6+GZnlc6b3qL0rU1iDLQsYMHLvel6+DJzCvhV8FHn/yJNKwjNpFiSNektZBQovd4NgO9aBRNgxIOGEEqEA==
Verify RSA Signature of SHA256 Hash: True
One thing to notice in the above C# samples is that the Public Key and Private Keys are defined using large byte arrays and the RSAParameters struct. If you would like to read (with C# code) the base64 PEM encoded Public Key you provided you will either need to be using .NET 5.0 (as it appears Microsoft has added functions to load Keys from PEM format) or you'll probably want to reference something like the BouncyCastle libraries via NuGet. More info here: http://www.bouncycastle.org/csharp/
Otherwise once you have the RSACryptoServiceProvider instantiated and configured with the public key a simple call to the VerifyHash() or VerifyData() methods should give you the boolean answer you need: https://learn.microsoft.com/en-us/dotnet/api/system.security.cryptography.rsacryptoserviceprovider.verifydata?view=net-5.0
The VerifyData()
method would first Calculate the SHA256 hash (assuming you specify CryptoConfig.MapNameToOID("SHA256")
for the "halg" parameter) and then verify that the SHA256 generated matches that from which the signature was generated. If this is a web app, I personally would probably approach it the following way: Calculate the SHA256 hash of the payload. Compare that to the SHA256 passed in the HTTP Header. If they don't match, you can stop there (thus saving compute power as RSA / asymmetric encryption is a bit on the expensive side). If they do match then continue on to verify said SHA256 hash with the signature using RSA. Keep in mind the above examples assume the Padding Method for the signature is PKCS1 (which I believe is more common, but PSS is another option). I didn't fully read the RFC draft or other RFCs mentioned on the EWS site. You will definitely want to verify. However, when I ran the first code sample with PSS padding it generated an exception so that definitely lead me to believe it was PKCS1.
Upvotes: 1