PHP validate client signature using client public key - c#

Hi I'm building REST service and planning to erase language restriction for developing both client and server from this project, the REST server currently written using PHP.
For security I'm validating their hash code, and also their client app signature. Currently the hash is successful but for the signature it always failed.
Every client will have their own private key and on the server we will have their public key to verify the signature, each request to server will send a signature.
If the client is a web server there will be only one private key for all user. If the client is a native app ( C# ) then each installed client app will have their unique private key.
PHP client to PHP REST server -> calculate hash ok, verify client signature ok
C# client (winapp) to PHP REST Server -> calculate hash ok, verify client signature failed
In the future I want to try with JAVA and vice versa.
I use easy way to create a self sign certificate
openssl genrsa -des3 -out netclient.key 2048
openssl req -new -x509 -key netclient.key -out netclient.crt
openssl pkcs12 -export -inkey netclient.key -in netclient.crt -out netclient.p12
Here is my code to sign data using C#
public static string (string Base64EncryptedData ) {
X509Certificate2 my;
my = new X509Certificate2("cert/netclient.p12", "abcdefg", X509KeyStorageFlags.Exportable | X509KeyStorageFlags.PersistKeySet);
RSACryptoServiceProvider csp = null;
csp = (RSACryptoServiceProvider)my.PrivateKey;
SHA1Managed sha1 = new SHA1Managed();
UnicodeEncoding encoding = new UnicodeEncoding();
byte[] data = encoding.GetBytes(Base64EncryptedData);
byte[] hash = sha1.ComputeHash(data);
return Convert.ToBase64String(csp.SignHash(hash, CryptoConfig.MapNameToOID("SHA1")));
}
It return a signature (base64) which I send to PHP REST server using GET method.
On PHP Server I recount the hash and verify but it never return 1. Currently I copy manually all the key to the server.
set_include_path(get_include_path() . PATH_SEPARATOR ."lib");
include("Crypt/RSA.php");
$p12cert = array();
$fp=fopen("cert/netclient.p12","r");
$priv_key=fread($fp,8192);
fclose($fp);
openssl_pkcs12_read($priv_key, $p12cert, "abcdefg");
//try using the phpseclib library from http://phpseclib.sourceforge.net
//req() will get all the $_POST, $_GET data req("signature") = $_GET["signature"];
//$hash_request is an base64_encode string same exact value as the one in C#
$rsa = new Crypt_RSA();
$rsa->setHash("sha1");
$rsa->setSignatureMode(CRYPT_RSA_SIGNATURE_PKCS1);
$rsa->loadKey($p12cert["cert"]);
$verified_phpseclib = $rsa->verify($hash_request, base64_decode(req("signature"))) ? "verified" : "diff";
//try using php built in openssl
$verified_builtin = openssl_verify($hash_request, base64_decode(req('signature')), $p12cert["cert");
Non of the result return "verified" or "1"
Could it be because when C# sign the data, the format of the data is byte[] ?
Or is there other reason ?
I'm running all the code in linux
php - using apache2
c# - using monodevelop
Here is my pk12 array after reading using openssl_pkcs12_read you can download it here
Array
(
[cert] => -----BEGIN CERTIFICATE-----
MIIDXTCCAkWgAwIBAgIJANjuyBraeAN0MA0GCSqGSIb3DQEBBQUAMEUxCzAJBgNV
BAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEwHwYDVQQKDBhJbnRlcm5ldCBX
aWRnaXRzIFB0eSBMdGQwHhcNMTMwMzE5MDA0ODI0WhcNMTMwNDE4MDA0ODI0WjBF
MQswCQYDVQQGEwJBVTETMBEGA1UECAwKU29tZS1TdGF0ZTEhMB8GA1UECgwYSW50
ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB
CgKCAQEAojE1Md/DET9mvKpoNJvb+mbGdTfSYuXQurGxb5pU0ba+gVeARbc8fjaK
D/2EKhUP/7240a31e5qI/2UlgiHO+ghQKDPo+5W5CetDVR1oMbWVA1fp7/gv8DEV
AvNV9nVkYzxnoVPs+W0MhMlWPrhCNSqQ28BbbL0EzXwXESnMw0I2VAhGWDNG0zSl
U03l5zh8jzzRKKK+gw4gIKo0u4lEUqB52MaXPzdwQkOEXRUdT+l1vK3DGfQycMrm
gZEN6IXDvpSFcQfHJlICK06+JNgDM0tYbif2mMfDEPKeFIjGC8S2ZDTZlR92LCk1
/WUGhdDFobbLAOx40M/etOcaEN1bgQIDAQABo1AwTjAdBgNVHQ4EFgQUTr0r49JA
DUKKiIQkRVlog4T952AwHwYDVR0jBBgwFoAUTr0r49JADUKKiIQkRVlog4T952Aw
DAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQUFAAOCAQEAO2IwjeIEVy8aLCOTjCkt
fTyLYuoiex+Xa/Dy2Xdl51Q44uAjCeSVsm1mXko+cZSypngGDu8W97sifzeR+pmW
7r6r+Rj+5pZcQiLQOCUQrXVpqHeiCS8QpOhpxGF+TOLy1O9zcqQwEiohKmzJB++d
1NM8//OBx1/Nyu7eLTKrTqfBdhuXfee963HVF9uBxwV8oa3UTQfhKu2vwJ64pcbU
5OVQw4rfhfSYhB1uu+zMQhCZ3hsroj2rC6Kfb8t3BNzngQwiqDBprmJznmJY38pW
kw0NIx+GrO1NUu+Ydl/7MXChujQajqSkG65Z9iJ8G7eegrY988INPVIfitIopWF+
jg==
-----END CERTIFICATE-----
[pkey] => -----BEGIN PRIVATE KEY-----
MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCiMTUx38MRP2a8
qmg0m9v6ZsZ1N9Ji5dC6sbFvmlTRtr6BV4BFtzx+NooP/YQqFQ//vbjRrfV7moj/
ZSWCIc76CFAoM+j7lbkJ60NVHWgxtZUDV+nv+C/wMRUC81X2dWRjPGehU+z5bQyE
yVY+uEI1KpDbwFtsvQTNfBcRKczDQjZUCEZYM0bTNKVTTeXnOHyPPNEoor6DDiAg
qjS7iURSoHnYxpc/N3BCQ4RdFR1P6XW8rcMZ9DJwyuaBkQ3ohcO+lIVxB8cmUgIr
Tr4k2AMzS1huJ/aYx8MQ8p4UiMYLxLZkNNmVH3YsKTX9ZQaF0MWhtssA7HjQz960
5xoQ3VuBAgMBAAECggEAB/TgBi1S9XKlyJWXfRU0SmlmTPPLF1zsy2vSJ4ZrqMoN
OG0hdsoRZqOoTDaEmEfmPAaDnY2qIEEpfVXp7CNacvubaw143Xav2CO5buB9bwrY
X4ydhk8nkuHlhPqI+gkyPogFEW37jxTha1YxK+yAGvmWl6EtGv1+0dHHk+j4CZAO
9DH1gZRBOXyeAJAhYuBMxxKfZhBNgGHzLtf9SGoOd2v9ya/h4CuqYh/CoxHGM/w1
uE6M2CKxSuZtqdtiJnoYcmAnDLACDmVHQNbFMEyC3ALrixgTj1p9aYPi8P/9BVKv
+T6cCmRQIl4VY4Ir0PCbai6BQaP6TIbAo0lt9cefUQKBgQDV2KKuc/TsZgrl+ijV
vW1kf46X1Squ8AqPgJnXYM5M4kG+17AecV25sm1oHmzN0ChwvWYotfhLMSqdH+1l
5zMYMwjTle9BkQXELyAvKiKND5I42DtTE04YnHi7HDlaJmOCa3WTTPjJiyIZC+9S
Lpc3gDwP8VSBcq2V2t/SMw9+nQKBgQDCKfIUJWW0WHkESmc8rJDBUWueUVfEWh/d
9ZG/gIapSXnJcTp/fRDZsaqS0pI7c8bvdPJPkQKO0M7Sy2E/OjyKOT5X4+4FguCd
XlwaUhVRycMxGjb1Q4pTlPCnQkoBVKQ5oW4IVQW/ILtT6wuJQPmqkwiXN6CSHzmF
w1RW+pUpNQKBgDXEeHLgmO5vYcIdOfMz47NnFxU59bdyh1U5gnTS1Ewkf19an9+n
pWcxY6zQKY8+DUz7cho+VqWhQROsmWYL0Z7+BfQdOMEFk6uWJcN2FqXdCmjchV4H
9pTdksWI/SqbiF2cYz2cFtml7/bYN140dLTxuyhPB25cxSRumeQiDn1JAoGBAJDf
w2UM0mpSaVmuOoGnMQtNuUMT5qz3ojd3eByvxcqirGCGP+PIab5FNsT+oWYC6Tja
xcJgrMvrOadHYXRP+8QXGlFyHLO4B+jj800gWhAAv8fvi3pNvvTGeRoT+Cwt/6uQ
rA1Dg1otDhl7k8wB00hXFV3ff8wHyF/qcw/DQXDRAoGBAJGvJSl9yIr5v3ooZ6BV
FtDcDTPFGxQCyqvfi5RzKW65Wqu1ZL7jSiOPcY4+418Zifnfyfr/9IZjl9EKPjhC
/fNqptnuA7DKPDKkQIeQSwtHF9HGVoqG0JGOmYCeCZxQK6eZhiuU8VohAAbzsdu8
PgxyS/PRbCsXP4p+OjR1i+3C
-----END PRIVATE KEY-----
)

Try this:
<?php
set_include_path(get_include_path() . PATH_SEPARATOR ."lib");
include('File/X509.php');
$p12cert = array();
$fp=fopen("cert/netclient.p12","r");
$priv_key=fread($fp,8192);
fclose($fp);
openssl_pkcs12_read($priv_key, $p12cert, "abcdefg");
$x509 = new File_X509();
$x509->loadX509($p12cert["cert"]);
$pubkey = $x509->getPublicKey();
$pubkey->setSignatureMode(CRYPT_RSA_SIGNATURE_PKCS1);
$verified_phpseclib = $pubkey->verify($hash_request, base64_decode(req("signature"))) ? "verified" : "diff";

Related

HttpClient - The message received was unexpected or badly formatted

I am trying to connect to an API that requires two way SSL configured. I have the the certificate and the private key which I configured in postman as shown in the below image.
This works fine in Postman, but SSL fails when I try to implement it in C# using HttpClient. The error that I get is "The message received was unexpected or badly formatted.". I believe it has something to do with incorrect configuration.
I have referred this StackOverflow post to implement my code: Associate a private key with the X509Certificate2 class in .net
Below is what I have tried:
byte[] publicCertificateBytes = File.ReadAllBytes("<Public Certificate>");
var publicCertificate = new X509Certificate2(publicCertificateBytes);
byte[] privateKey = Convert.FromBase64String(File.ReadAllText("<private key file>").Replace("-----BEGIN PRIVATE KEY-----", "").Replace("-----END PRIVATE KEY-----", ""));
using (var rsa = RSA.Create())
{
rsa.ImportPkcs8PrivateKey(privateKey, out _);
publicCertificate = publicCertificate.CopyWithPrivateKey(rsa);
publicCertificate = new X509Certificate2(publicCertificate.Export(X509ContentType.Pkcs12));
}
var httpService = new GenericUtilityManager().ResolveUtility<IHttpService>();
var handler = new HttpClientHandler();
handler.SslProtocols = SslProtocols.Tls12;
handler.ClientCertificateOptions = ClientCertificateOption.Manual;
handler.ClientCertificates.Add(publicCertificate);
handler.ServerCertificateCustomValidationCallback = (sender, cert, chain, sslPolicyErrors) => true;
httpService.InitializeHttpClientHandler(handler);
Please help.
Try to load the certicate using the CreateFromPem static function, this method returns a new certificate with the private key.
var certificateCrt = File.ReadAllText("<Public Certificate>");
var privateKey = File.ReadAllText("<private key file>");
using var certificate = X509Certificate2.CreateFromPem(certificateCrt, privateKey);
If that doesn't work, try to creating a .pfx certificate using OpenSSL:
openssl pkcs12 -export -out certificate.pfx -inkey privateKey.key -in certificate.crt
I had this problem when I needed to use a certificate on a Windows OS, to work I had to create a .pfx certificate, but on other OS like Mac and Linux it worked fine without a .pfx certificate.

Using MimeKit to cms sign and verify

I am using Mimekit to do rsa pss cms sign, to simulate this openssl command
openssl cms -sign -in keys.zip -binary -nodetach -signer selfsigned.crt -inkey keypair.pem -out keys.zip.signed -keyopt rsa_padding_mode:pss
public byte[] Sign(byte[] data, byte[] signCert, byte[] privateKey, CmsKeyOpt cmsKeyOpt)
{
MimeMessage message = new MimeMessage
{
Body = new MimePart()
{
Content = new MimeContent(new MemoryStream(data)),
ContentTransferEncoding = ContentEncoding.Binary,
},
};
// Load private key from byte array
StreamReader stream = new StreamReader(new MemoryStream(privateKey), Encoding.Default);
AsymmetricCipherKeyPair keyPair = (AsymmetricCipherKeyPair)new PemReader(stream).ReadObject();
// load certifacte from byte array
X509CertificateParser parser = new X509CertificateParser();
X509Certificate certificate = parser.ReadCertificate(signCert);
// Create RSA PSS CMS signer
CmsSigner signer = new CmsSigner(certificate, keyPair.Private)
{
RsaSignaturePadding = RsaSignaturePadding.Pss,
DigestAlgorithm = DigestAlgorithm.Sha256,
};
// Create BouncyCastle Secure MimeContext
BouncyCastleSecureMimeContext ctx = new TemporarySecureMimeContext();
ctx.EncapsulatedSign(signer, new MemoryStream(data));
// Get signed message body and override it
message.Body = MultipartSigned.Create(ctx, signer, message.Body);
byte[] singedData;
using (var memory = new MemoryStream())
{
message.WriteTo(memory);
singedData = memory.ToArray();
}
return singedData;
}
And everything works fine, my question how I can achieve verify by Mimekit/BouncyCastle, to simulate this openssl command
openssl cms -verify -in keys.zip.signed.dec -CAfile selfsigned.crt -out keys_dec_unsigned.zip
I tried this but I got exception System.NotSupportedException: 'SQLite is not available on pkcs7.Verify(out original) line
public byte[] Verify(byte[] data, byte[] signCert, byte[] privateKey)
{
bool valid = false;
// Load private key from byte array
StreamReader stream = new StreamReader(new MemoryStream(privateKey), Encoding.Default);
AsymmetricCipherKeyPair keyPair = (AsymmetricCipherKeyPair)new PemReader(stream).ReadObject();
// load certifacte from byte array
X509CertificateParser parser = new X509CertificateParser();
X509Certificate certificate = parser.ReadCertificate(signCert);
MimeMessage message = MimeMessage.Load(new MemoryStream(data));
// Create BouncyCastle Secure MimeContext
MimeEntity original;
ApplicationPkcs7Mime pkcs7 = message.Body as ApplicationPkcs7Mime;
if (pkcs7 != null && pkcs7.SecureMimeType == SecureMimeType.SignedData)
{
foreach (var signature in pkcs7.Verify(out original))
{
try
{
valid = signature.Verify();
}
catch (DigitalSignatureVerifyException)
{
// There was an error verifying the signature.
}
}
}
byte[] res = { 0 };
return res;
}
Is there any guide I can follow to verify and return the data to it's origin forum, or an example?
By default, MimeKit tries to instantiate a S/MIME verify context based on its SQLite backend for certificate storage.
If you don't have SQLite installed and/or don't have your certificates stored in the SQLite database that MimeKit will maintain for you, then you need to either register a different backend for certificate storage/retrieval or instantiate your own S/MIME context (like you did for signing).
MimeKit comes with 2 options:
TemporarySecureMimeContext which stores certificates in memory and is what you used to sign the message in your code snippet above.
WindowsSecureMimeContext which uses the Windows OS X.509 certificate store.
Once you decide on which of those to use, you will need to import your certificate(s) into the context (which will import them into the appropriate backend storage location).
Then, after that, you can do this:
using (BouncyCastleSecureMimeContext ctx = new TemporarySecureMimeContext()) {
ctx.Import (...);
foreach (var signature in pkcs7.Verify(ctx, out original)) {
// ...
}
}
Update:
This devolved into misuse of the openssl cms sign command and an inability to verify the signature using MimeKit because MimeKit expects the encapsulated signed data to be in MIME format as it is supposed to be according to the S/MIME specifications.
Here's the deal:
The openssl cms sign command can be used to sign arbitrary data and the corresponding openssl cms verify command can be used to verify such signed output. HOWEVER, it is only valid S/MIME if the original content signed by the openssl cms sign command is/was in MIME format.
MimeKit expects valid S/MIME because it is... surprise, surprise... a MIME library.
As you might have noticed, the Verify() method has an output parameter (out MimeEntity originalEntity). This means that the Verify() method extracts the encapsulated content from within the signed data and parses it into a MIME entity and returns it to you, the caller, in the form of said output parameter.
If the encapsulated content is not in MIME format, then, obviously, the parser will fail to parse it.
Additional background with how CMS signatures work:
When you sign content using the CMS signing routine (e.g. openssl cms sign ...), it produces something that looks a bit like this:
MIME-Version: 1.0
Content-Disposition: attachment; filename="smime.p7m"
Content-Type: application/pkcs7-mime; smime-type=signed-data; name="smime.p7m"
Content-Transfer-Encoding: base64
MIIQgAYJKoZIhvcNAQcCoIIQcTCCEG0CAQExDTALBglghkgBZQMEAgEwggkPBgkq...
The base64 content in the above MIME part includes both the original content and the CMS signature data (i.e. a list of signatures).
Therefor, MimeKit's Verify() method needs to separate the signatures from the original content after base64 decoding it. MimeKit then returns to you, the caller, the original content (which it expects to be in MIME format) and the list of signatures that you can then independently verify the authenticity of.
When I kept repeating myself that it mattered what format the file.bin file was in when you signed the content, I was not saying that openssl or MimeKit needed to parse file.bin when verifying the signature, I was saying that file.bin has exactly the same content as what would be extracted from the base64 blob in the S/MIME output produced by openssl cms sign ... and therefore, it needs to be in MIME format.

C# signature verification using ECDSA with SHA256 certificate

I'm trying to use C# and the built in Crypto libraries to verify a signature created using an EC key + SHA256. Here's what I'm doing.
I've created a private key and corresponding certificate using openssl:
$ openssl ecparam -genkey -name prime256v1 -out ca.key
$ openssl req -x509 -new -SHA256 -nodes -key ca.key -days 36500 -out ca.crt
Here are the keys I'm using (don't worry, they're not important):
$ cat ca.key
-----BEGIN EC PARAMETERS-----
BggqhkjOPQMBBw==
-----END EC PARAMETERS-----
-----BEGIN EC PRIVATE KEY-----
MHcCAQEEIHd3OvRV1nEnoDxGzzemX1x8l2rHasWH3L/LflUGg5vloAoGCCqGSM49
AwEHoUQDQgAE7f1xwQL5m/UcN4zL+zsly6V1g3/wNcL5TdCfWt0XfnUfg0x+RsIf
1uerBnhrmhH0cN9o0xfXg5B3hURFlXVuEQ==
-----END EC PRIVATE KEY-----
$ cat ca.crt
-----BEGIN CERTIFICATE-----
MIIB4TCCAYegAwIBAgIUKt0WdaKI2eRXBO2nVk+OF6AZqHMwCgYIKoZIzj0EAwIw
RTELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoMGElu
dGVybmV0IFdpZGdpdHMgUHR5IEx0ZDAgFw0yMDA1MDcxMzM2MTNaGA8yMTIwMDQx
MzEzMzYxM1owRTELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAf
BgNVBAoMGEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDBZMBMGByqGSM49AgEGCCqG
SM49AwEHA0IABO39ccEC+Zv1HDeMy/s7JculdYN/8DXC+U3Qn1rdF351H4NMfkbC
H9bnqwZ4a5oR9HDfaNMX14OQd4VERZV1bhGjUzBRMB0GA1UdDgQWBBRGuUmsyB2h
JCXMRTVMRTcdoWZQaDAfBgNVHSMEGDAWgBRGuUmsyB2hJCXMRTVMRTcdoWZQaDAP
BgNVHRMBAf8EBTADAQH/MAoGCCqGSM49BAMCA0gAMEUCIGG8tQZlh7aJaI34Y7jq
44SmSc/ule9MgjIX+Gg+i5vwAiEA9Jb/304KO4t9OMqFMQeWZXIHdzhDFBwx7FWz
78+UsnY=
-----END CERTIFICATE-----
I then have a simple data file containing the string "Hello". I then sign that file using openssl as follows:
$ openssl dgst -sha256 -sign ca.key data.txt > sig
$ base64 sig
MEUCIQD5593C/NBhHA1DILT72gjhGj/lKjom9vYP+JbuypBrxQIgNAjYT1LihEpPbUhe1n9ccUHQ
vw676bGqOTEU/25qcRQ=
I can then verify the signature by first extracting the public key from the certificate and then using that to verify:
$ openssl x509 -pubkey -noout -in ca.crt > ca.pub
$ cat ca.pub
-----BEGIN PUBLIC KEY-----
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE7f1xwQL5m/UcN4zL+zsly6V1g3/w
NcL5TdCfWt0XfnUfg0x+RsIf1uerBnhrmhH0cN9o0xfXg5B3hURFlXVuEQ==
-----END PUBLIC KEY-----
$ openssl dgst -verify ca.pub -sha256 -signature sig data.txt
Verified OK
I then try and use C# (.NET Core 3.1) to verify the signature. The code is as follows:
using System;
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
using System.Text;
namespace security_sandbox
{
class Program
{
static void Main(string[] args)
{
var certData = Encoding.ASCII.GetBytes(
#"-----BEGIN CERTIFICATE-----
MIIB4TCCAYegAwIBAgIUKt0WdaKI2eRXBO2nVk+OF6AZqHMwCgYIKoZIzj0EAwIw
RTELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoMGElu
dGVybmV0IFdpZGdpdHMgUHR5IEx0ZDAgFw0yMDA1MDcxMzM2MTNaGA8yMTIwMDQx
MzEzMzYxM1owRTELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAf
BgNVBAoMGEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDBZMBMGByqGSM49AgEGCCqG
SM49AwEHA0IABO39ccEC+Zv1HDeMy/s7JculdYN/8DXC+U3Qn1rdF351H4NMfkbC
H9bnqwZ4a5oR9HDfaNMX14OQd4VERZV1bhGjUzBRMB0GA1UdDgQWBBRGuUmsyB2h
JCXMRTVMRTcdoWZQaDAfBgNVHSMEGDAWgBRGuUmsyB2hJCXMRTVMRTcdoWZQaDAP
BgNVHRMBAf8EBTADAQH/MAoGCCqGSM49BAMCA0gAMEUCIGG8tQZlh7aJaI34Y7jq
44SmSc/ule9MgjIX+Gg+i5vwAiEA9Jb/304KO4t9OMqFMQeWZXIHdzhDFBwx7FWz
78+UsnY=
-----END CERTIFICATE-----");
var cert = new X509Certificate2(certData);
var ecdsa = cert.GetECDsaPublicKey();
var data = Encoding.ASCII.GetBytes("Hello");
var signature = Convert.FromBase64String("MEUCIQD5593C/NBhHA1DILT72gjhGj/lKjom9vYP+JbuypBrxQIgNAjYT1LihEpPbUhe1n9ccUHQvw676bGqOTEU/25qcRQ=");
var success = ecdsa.VerifyData(data, signature, HashAlgorithmName.SHA256);
if (success)
{
Console.WriteLine("Verified");
} else
{
Console.WriteLine("Failed");
}
}
}
}
Unfortunately, it always fails the verification. Where is the mistake?
The lack of PEM/OpenSSL-compatible manipulation tools in .NET proved to be extremely frustrating. I ended up using Bouncy Castle to load the certificate or public key and then use that to verify my ASN signature. Here's a full working code sample demonstrating how to perform the signature verification using both the certificate and the PEM public key and working with an ASN-encoded signature.
using System;
using System.IO;
using System.Text;
using Org.BouncyCastle.Crypto;
using Org.BouncyCastle.OpenSsl;
using Org.BouncyCastle.Security;
using Org.BouncyCastle.X509;
namespace security_sandbox
{
class Program
{
static void Main(string[] args)
{
var certificateString = #"-----BEGIN CERTIFICATE-----
MIIB4TCCAYegAwIBAgIUKt0WdaKI2eRXBO2nVk+OF6AZqHMwCgYIKoZIzj0EAwIw
RTELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoMGElu
dGVybmV0IFdpZGdpdHMgUHR5IEx0ZDAgFw0yMDA1MDcxMzM2MTNaGA8yMTIwMDQx
MzEzMzYxM1owRTELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAf
BgNVBAoMGEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDBZMBMGByqGSM49AgEGCCqG
SM49AwEHA0IABO39ccEC+Zv1HDeMy/s7JculdYN/8DXC+U3Qn1rdF351H4NMfkbC
H9bnqwZ4a5oR9HDfaNMX14OQd4VERZV1bhGjUzBRMB0GA1UdDgQWBBRGuUmsyB2h
JCXMRTVMRTcdoWZQaDAfBgNVHSMEGDAWgBRGuUmsyB2hJCXMRTVMRTcdoWZQaDAP
BgNVHRMBAf8EBTADAQH/MAoGCCqGSM49BAMCA0gAMEUCIGG8tQZlh7aJaI34Y7jq
44SmSc/ule9MgjIX+Gg+i5vwAiEA9Jb/304KO4t9OMqFMQeWZXIHdzhDFBwx7FWz
78+UsnY=
-----END CERTIFICATE-----";
var pemreader = new PemReader(new StringReader(certificateString));
var cert = (X509Certificate)pemreader.ReadObject();
// Alternatively, load the public key directly
var pubkeyString =
#"-----BEGIN PUBLIC KEY-----
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE7f1xwQL5m/UcN4zL+zsly6V1g3/w
NcL5TdCfWt0XfnUfg0x+RsIf1uerBnhrmhH0cN9o0xfXg5B3hURFlXVuEQ==
-----END PUBLIC KEY-----";
pemreader = new PemReader(new StringReader(pubkeyString));
var pubkey = (AsymmetricKeyParameter)pemreader.ReadObject();
var data = "Hello";
var signature = Convert.FromBase64String("MEUCIQD5593C/NBhHA1DILT72gjhGj/lKjom9vYP+JbuypBrxQIgNAjYT1LihEpPbUhe1n9ccUHQvw676bGqOTEU/25qcRQ=");
// Verify using the public key
var signer = SignerUtilities.GetSigner("SHA-256withECDSA");
signer.Init(false, pubkey);
signer.BlockUpdate(Encoding.ASCII.GetBytes(data), 0, data.Length);
var success = signer.VerifySignature(signature);
if (success) {
Console.WriteLine("Signature verified successfully using public key");
} else {
Console.WriteLine("Failed to verify signature using public key");
}
// Verify using the certificate - the certificate's public key is extracted using the GetPublicKey method.
signer.Init(false, cert.GetPublicKey());
signer.BlockUpdate(Encoding.ASCII.GetBytes(data), 0, data.Length);
success = signer.VerifySignature(signature);
if (success) {
Console.WriteLine("Signature verified successfully using certificate");
} else {
Console.WriteLine("Failed to verify signature using certificate");
}
}
}
}

Cryptography: Why am I getting different RSA signatures depending on which certificate store the certificate was loaded from?

I have some working code which produces a correct signature of a string if I load a certificate from a file or from the current user's store. However, if I load the exact same certificate (same .p12 and same thumbprint) from the Machine certificate store, it behaves differently. When loaded from that store, the signatures generated by my C# code are half the length (1024 bits instead of 2048) and are incorrect. The private key appears to be loading properly in both cases.
Why does which store the certificate is loaded from make any difference to which signature is generated? And why would the signature be half the length?
Loaded from CurrentUser:
Thumbprint: FBBE05A1C5F2AEF637CDE20A7985CD1011861651
Has private key:True
rsa.KeySize (bits) =2048
Signature Length (bits): 2048
Signature: kBC2yh0WCo/AU8aVo+VUbRoh67aIJ7SWM4dRMkNvt...
(correct)
Loaded from LocalMachine:
Thumbprint: FBBE05A1C5F2AEF637CDE20A7985CD1011861651
Has private key: True
rsa.KeySize (bits) = 1024
Signature Length (bits): 1024
Signature: RijmdQ73DXHK1IUYkOzov2R+WRdHW8tLqsH....
(incorrect - and note the 1024 bit key size and signature length)
Here's the C# I'm using:
string s = "AE0DE01564,1484821101811,http://localhost:8080/example_site/CallBack";
var inputData = Encoding.UTF8.GetBytes(s);
var store = new X509Store(StoreName.My, StoreLocation.LocalMachine);
store.Open(OpenFlags.ReadOnly | OpenFlags.OpenExistingOnly);
string thumbprint = CleanThumbPrint("fb be 05 a1 c5 f2 ae f6 37 cd e2 0a 79 85 cd 10 11 86 16 51");
X509Certificate2Collection col = store.Certificates.Find(X509FindType.FindByThumbprint, thumbprint, false);
// TODO: close store.
X509Certificate2 certificate = null;
Console.WriteLine("Cert count: " + col.Count);
if (col.Count == 1)
{
certificate = col[0];
RSACryptoServiceProvider rsa = (RSACryptoServiceProvider)col[0].PrivateKey;
// Force use of the Enhanced RSA and AES Cryptographic Provider with openssl-generated SHA256 keys
var enhCsp = new RSACryptoServiceProvider().CspKeyContainerInfo;
var cspparams = new CspParameters(enhCsp.ProviderType, enhCsp.ProviderName, rsa.CspKeyContainerInfo.KeyContainerName);
rsa = new RSACryptoServiceProvider( cspparams);
Console.WriteLine("Name: " + certificate.SubjectName.Name);
Console.WriteLine("Thumbprint: " + certificate.Thumbprint);
Console.WriteLine("Has private key: " + certificate.HasPrivateKey);
Console.WriteLine("Sig algorithm: " + certificate.SignatureAlgorithm);
Console.WriteLine("rsa.KeySize (bits) =" + rsa.KeySize);
var sha256 = CryptoConfig.CreateFromName("SHA256");
byte[] signature = rsa.SignData(inputData, sha256);
Console.WriteLine("Signature Length (bits): " + signature.Length * 8);
Console.WriteLine("Signature: " + System.Convert.ToBase64String(signature));
Console.WriteLine();
}
It turns out it was something to do with the file format of the certificate I was using which I created with OpenSSL and the fact that the crypto provider wasn't set. The critical command is number 5 below:
Here are the commands I used to create the working certificate:
Generate a keypair:
openssl genrsa -out private_key.pem 2048
Extract the public key:
openssl rsa -pubout -in private_key.pem -out public_key.pem
Create a CSR Certificate Signing Request from private key:
openssl req -new -key private_key.pem -out csr.csr
Generate a self signed certificate:
openssl x509 -req -days 1095 -in csr.csr -signkey private_key.pem -out certificate.crt
Create a PFX format certificate with the specified CSP:
openssl pkcs12 -export -in certificate.crt -inkey private_key.pem -CSP "Microsoft Enhanced RSA and AES Cryptographic Provider" -out TEST_pfx.pfx

Azure IOT Hub - Device Security Token

so we are using MQTT to connect device/server. I have everything working using a mock client using the M2Mqtt library. What im really struggling with is how to in code generate the signature used in the password field.
I followed this https://azure.microsoft.com/en-us/documentation/articles/iot-hub-sas-tokens/ however im battling around the HMAC side of things. What is the "** signingKey**" they talk of? Is that the devices shared access key? For now just getting the mock client to create its own signature in code (not through the device explorer) is essential before we even worry if our products in the field can compute this (Finding this really over complicated for field devices). Is there a C# example somewhere I can follow other than the node.js - what does this line mean "hmac.update(toSign);"
Is there any simpler way to authenticate a device to the server? maybe just using its shared access key?
Sorry for all the questions :/ Probably I just need a step by step guide on what/when to do URI encode/Base64 encode/decode, HMAC 256 etc as I believe the documentation is far from sufficient.
"{signature} An HMAC-SHA256 signature string of the form: {URL-encoded-resourceURI} + "\n" + expiry. Important: The key is decoded from base64 and used as key to perform the HMAC-SHA256 computation."
This will be helpful for someone someday:
Construct authorization header for Azure IoT Hub
https://github.com/snobu/Azure-IoT-Hub/blob/master/make-token.sh
#!/usr/bin/env bash
#
# GitHub repo:
# https://github.com/snobu/Azure-IoT-Hub
#
# Construct authorization header for Azure IoT Hub
# https://azure.microsoft.com/en-us/documentation/articles/iot-hub-devguide/#security
#
# The security token has the following format:
# SharedAccessSignature sig={signature-string}&se={expiry}&skn={policyName}&sr={URL-encoded-resourceURI}
#
# Author:
# Adrian Calinescu (a-adcali#microsoft.com), Twitter: #evilSnobu, github.com/snobu
#
# Many things borrowed from:
# http://stackoverflow.com/questions/20103258/accessing-azure-blob-storage-using-bash-curl
#
# Prereq:
# OpenSSL
# npm install underscore -g (for the tidy JSON colorized output) - OPTIONAL
# Python 2.6 (Might work with 2.5 too)
# curl (a build from this century should do)
urlencodesafe() {
# Use urllib to safely urlencode stuff
python -c "import urllib, sys; print urllib.quote_plus(sys.argv[1])" $1
}
iothub_name="heresthething"
apiversion="2015-08-15-preview"
req_url="${iothub_name}.azure-devices.net/devices?top=100&api-version=${apiversion}"
sas_key="eU2XXXXXXXXXXXXXXXXXXXXXXXXXXXXX="
sas_name="iothubowner"
authorization="SharedAccessSignature"
# 259200 seconds = 72h (Signature is good for the next 72h)
expiry=$(echo $(date +%s)+259200 | bc)
req_url_encoded=$(urlencodesafe $req_url)
string_to_sign="$req_url_encoded\\n$expiry"
# Create the HMAC signature for the Authorization header
#
# In pseudocode:
# BASE64_ENCODE(HMAC_SHA256($string_to_sign))
#
# With OpenSSL it's a little more work (StackOverflow thread at the top for details)
decoded_hex_key=$(printf %b "$sas_key" | base64 -d -w0 | xxd -p -c256)
signature=$(printf %b "$string_to_sign" | openssl dgst -sha256 -mac HMAC -macopt "hexkey:$decoded_hex_key" -binary | base64 -w0)
# URLencode computed HMAC signature
sig_urlencoded=$(urlencodesafe $signature)
# Print Authorization header
authorization_header="Authorization: $authorization sr=$req_url_encoded&sig=$sig_urlencoded&se=$expiry&skn=$sas_name"
echo -e "\n$authorization_header\n"
# We're ready to make the GET request against azure-devices.net REST API
curl -s -H "$authorization_header" "https://$req_url" | underscore print --color
echo -e "\n"
And a sample MQTT user/pass combo for Azure IoT Hub (yes the password is brutal and includes a whitespace):
https://github.com/Azure/azure-content/blob/master/articles/iot-hub/iot-hub-devguide.md#example
Username (DeviceId is case sensitive): iothubname.azure-devices.net/DeviceId
Password (Generate SAS with Device Explorer): SharedAccessSignature sr=iothubname.azure-devices.net%2fdevices%2fDeviceId&sig=kPszxZZZZZZZZZZZZZZZZZAhLT%2bV7o%3d&se=1487709501
The page https://azure.microsoft.com/en-us/documentation/articles/iot-hub-sas-tokens/ includes a Node.js function that generates a SAS token from the given inputs.
From what you have said, you're using the token to enable a device to connect to your IoT Hub, so the inputs to the Node function should be:
resource URI: {IoT hub name}.azure-devices.net/devices/{device id}.
signing key: any symmetric key for the {device id} identity. You can obtain this key from the IoT Hub device identity registry - for example by using the DeviceExplorer tool.
no policy name.
any expiration time.
Finally got it :)
public static string getSaSToken()
{
TimeSpan fromEpochStart = DateTime.UtcNow - new DateTime(1970, 1, 1);
string expiry = Convert.ToString((int)fromEpochStart.TotalSeconds + 3600);
string baseAddress = "XYZABCBLAH.azure-devices.net/devices/12345".ToLower();
string stringToSign = WebUtility.UrlEncode(baseAddress).ToLower() + "\n" + expiry;
byte[] data = Convert.FromBase64String("y2moreblahblahblah=");
HMACSHA256 hmac = new HMACSHA256(data);
byte[] poo = hmac.ComputeHash(Encoding.UTF8.GetBytes(stringToSign));
string signature = Convert.ToBase64String(hmac.ComputeHash(Encoding.UTF8.GetBytes(stringToSign)));
string token = String.Format(CultureInfo.InvariantCulture, "SharedAccessSignature sr={0}&sig={1}&se={2}",
WebUtility.UrlEncode(baseAddress).ToLower(), WebUtility.UrlEncode(signature), expiry);
return token;
}
"12345" is our device's serial number.
the key of y2z.... will be a base64 combination of our serial with something else fancy (as long as its in the base64 format to make the hub happy ;) )
Here is how the SAS token can be generated in Java:
import java.io.UnsupportedEncodingException;
import java.net.MalformedURLException;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.Base64;
import java.util.Date;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
public class AzureSasTokenCreator
{
public static void main(String[] args) throws InvalidKeyException, UnsupportedEncodingException,
MalformedURLException, NoSuchAlgorithmException
{
String token = generateSasTokenForIotDevice("myiothub.azure-devices.net/devices/mydevice",
"ZNILSsz4ke0r5DQ8rfB/PBWf6QqWGV7aaT/iICi9WTc=", 3600);
System.out.println(token);
}
private static String generateSasTokenForIotDevice(String uri, String devicePrimaryKey, int validtySeconds)
throws UnsupportedEncodingException, MalformedURLException, NoSuchAlgorithmException,
InvalidKeyException
{
Date now = new Date();
Date previousDate = new Date(1970);
long tokenExpirationTime = ((now.getTime() - previousDate.getTime()) / 1000) + validtySeconds;
String signature = getSignature(uri, tokenExpirationTime, devicePrimaryKey);
String token = String.format("SharedAccessSignature sr=%s&sig=%s&se=%s", uri, signature,
String.valueOf(tokenExpirationTime));
return token;
}
private static String getSignature(String resourceUri, long expiryTime, String devicePrimaryKey)
throws InvalidKeyException, NoSuchAlgorithmException, UnsupportedEncodingException
{
byte[] textToSign = new String(resourceUri + "\n" + expiryTime).getBytes();
byte[] decodedDeviceKey = Base64.getDecoder().decode(devicePrimaryKey);
byte[] signature = encryptHmacSha256(textToSign, decodedDeviceKey);
byte[] encryptedSignature = Base64.getEncoder().encode(signature);
String encryptedSignatureUtf8 = new String(encryptedSignature, StandardCharsets.UTF_8);
return URLEncoder.encode(encryptedSignatureUtf8, "utf-8");
}
private static byte[] encryptHmacSha256(byte[] textToSign, byte[] key)
throws NoSuchAlgorithmException, InvalidKeyException
{
SecretKeySpec secretKey = new SecretKeySpec(key, "HmacSHA256");
Mac hMacSha256 = Mac.getInstance("HmacSHA256");
hMacSha256.init(secretKey);
return hMacSha256.doFinal(textToSign);
}
}
See also: https://github.com/Breitmann/AzureSasTokenCreator

Categories

Resources