Validate EC SHA 256 signature in .net without bouncy castle - cryptography

I am implementing Apple's App Attestation service.
As part of the process, i receive a EC key and a signature.
Sample key:
-----BEGIN PUBLIC KEY-----
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEd34IR9wYL76jLyZ148O/hjXo9iaF
z/q/xEMXCwYPy6yxbxYzWDZPegG4FH+snXaXQPYD6QIzZNY/kcMjIGtUTg==
-----END PUBLIC KEY-----
Sample signature:
MEUCIQDXR/22YAi90PUdKrtTHwigrDxWFoiCqPLB/Of1bZPCKQIgNLxFAeUU2x+FSWfhRGX0SOKUIDxPRoigsCHpJxgGXXU=
Sample sha256 hash:
S3i6LAEzew5SDjQbq59/FraEAvGDg9y7fRIfbnhHPf4=
If i put this into a couple of txt files like so:
System.IO.File.WriteAllBytes("/wherever/sig", Convert.FromBase64String(sampleSignature));
System.IO.File.WriteAllBytes("/wherever/hash", Convert.FromBase64String(sampleSha256Hash));
Then i can validate the signature with Openssl like so
openssl dgst -sha256 -verify sampleKey.pem -signature /wherever/sig /wherever/hash
(the above outputs)
Verified OK
I can verify the signature using Bouncy Castle like so:
var bouncyCert = DotNetUtilities.FromX509Certificate(certificate);
var bouncyPk = (ECPublicKeyParameters)bouncyCert.GetPublicKey();
var verifier = SignerUtilities.GetSigner("SHA-256withECDSA");
verifier.Init(false, bouncyPk);
verifier.BlockUpdate(sha256HashByteArray, 0, sha256HashByteArray.Length);
var valid = verifier.VerifySignature(signature); // Happy days, this is true
Since i don't want to share my whole certificate here, the same sample may be achieved as follows:
// these are the values from the sample key shared at the start of the post
// as returned by BC. Note that .Net's Y byte array is completely different.
Org.BouncyCastle.Math.BigInteger x = new Org.BouncyCastle.Math.BigInteger(Convert.FromBase64String("d34IR9wYL76jLyZ148O/hjXo9iaFz/q/xEMXCwYPy6w="));
Org.BouncyCastle.Math.BigInteger y = new Org.BouncyCastle.Math.BigInteger(Convert.FromBase64String("ALFvFjNYNk96AbgUf6yddpdA9gPpAjNk1j+RwyMga1RO"));
X9ECParameters nistParams = NistNamedCurves.GetByName("P-256");
ECDomainParameters domainParameters = new ECDomainParameters(nistParams.Curve, nistParams.G, nistParams.N, nistParams.H, nistParams.GetSeed());
var G = nistParams.G;
Org.BouncyCastle.Math.EC.ECCurve curve = nistParams.Curve;
Org.BouncyCastle.Math.EC.ECPoint q = curve.CreatePoint(x, y);
ECPublicKeyParameters pubkeyParam = new ECPublicKeyParameters(q, domainParameters);
var verifier = SignerUtilities.GetSigner("SHA-256withECDSA");
verifier.Init(false, pubkeyParam);
verifier.BlockUpdate(sha256HashByteArray, 0, sha256HashByteArray.Length);
var valid = verifier.VerifySignature(signature); // again, happy days.
However, i really want to avoid using bouncy castle.
So i am trying to use ECDsa available in .net core:
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
var certificate = new X509Certificate2(cert);
var publicKey = certificate.GetECDsaPublicKey();
var valid = publicKey.VerifyHash(sha256HashByteArray, signature); // FALSE :(
if you want to try to run the above here's the sample that creates the keys without the whole certificate:
using System.Security.Cryptography;
var ecParams = new ECParameters();
ecParams.Curve = ECCurve.CreateFromValue("1.2.840.10045.3.1.7");
ecParams.Q.X = Convert.FromBase64String("d34IR9wYL76jLyZ148O/hjXo9iaFz/q/xEMXCwYPy6w=");
// I KNOW that this is different from BC sample - i got each respective values from
// certificates in respective libraries, and it seems the way they format the coordinates
// are different.
ecParams.Q.Y = Convert.FromBase64String("sW8WM1g2T3oBuBR/rJ12l0D2A+kCM2TWP5HDIyBrVE4=");
var ecDsa = ECDsa.Create(ecParams);
var isValid = ecDsa.VerifyHash(nonce, signature); // FALSE :(
I tried using VerifyData() instead and feeding raw data and HashAlgorithmName.SHA256 with no luck.
I found a response here (https://stackoverflow.com/a/49449863/2057955) that seems to suggest that .net expects the signature as r,s concatenation, so i pulled them out of the DER sequence that i get back from my device (see sample signature) however that had no luck at all, i just can't get that 'true' back.
Question: how can i verify this EC signature using .Net Core on LINUX/MacOs (so unable to use ECDsaCng class)?

SignerUtilities.GetSigner() hashes implicitly, i.e. sha256HashByteArray is hashed again. Therefore instead of ECDsa#VerifyHash() (does not hash implicitly) the method ECDsa#VerifyData() (hashes implicitly) must be used.
Also, SignerUtilities.GetSigner() returns a signature in ASN.1 format, and ECDsa#VerifyData() expects a signature in r|s format (as you already figured out).
If both are taken into account, the verification is successful:
byte[] publicKey = Convert.FromBase64String("MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEd34IR9wYL76jLyZ148O/hjXo9iaFz/q/xEMXCwYPy6yxbxYzWDZPegG4FH+snXaXQPYD6QIzZNY/kcMjIGtUTg==");
byte[] sha256HashByteArray = Convert.FromBase64String("S3i6LAEzew5SDjQbq59/FraEAvGDg9y7fRIfbnhHPf4=");
byte[] signatureRS = Convert.FromBase64String("10f9tmAIvdD1HSq7Ux8IoKw8VhaIgqjywfzn9W2Twik0vEUB5RTbH4VJZ+FEZfRI4pQgPE9GiKCwIeknGAZddQ==");
var ecDsa = ECDsa.Create();
ecDsa.ImportSubjectPublicKeyInfo(publicKey, out _);
var isValid = ecDsa.VerifyData(sha256HashByteArray, signatureRS, HashAlgorithmName.SHA256);
Console.WriteLine(isValid); // True
Regarding the signature formats:
The posted signature in ASN.1 format
MEUCIQDXR/22YAi90PUdKrtTHwigrDxWFoiCqPLB/Of1bZPCKQIgNLxFAeUU2x+FSWfhRGX0SOKUIDxPRoigsCHpJxgGXXU=
is hex encoded
3045022100d747fdb66008bdd0f51d2abb531f08a0ac3c56168882a8f2c1fce7f56d93c229022034bc4501e514db1f854967e14465f448e294203c4f4688a0b021e92718065d75
From this, the signature in r|s format can be derived as (s. here)
d747fdb66008bdd0f51d2abb531f08a0ac3c56168882a8f2c1fce7f56d93c22934bc4501e514db1f854967e14465f448e294203c4f4688a0b021e92718065d75
or Base64 encoded:
10f9tmAIvdD1HSq7Ux8IoKw8VhaIgqjywfzn9W2Twik0vEUB5RTbH4VJZ+FEZfRI4pQgPE9GiKCwIeknGAZddQ==

Related

Signing ECDSA with private key with curve secp224k1

I want to get sign by ECDSA secp224k1. I cannot get the same signature as in the CoinFLEX Authentication Process example from the manual. I am using C# BouncyCastle.
Why can't I get manual page's signature ​​by my code?
// manual page's step 7
byte[] fortyByteMessage = fromHexStringToByteArr("0x00000000 00000001").Concat(fromHexStringToByteArr("0x6b347302 2e6b9b5a f2fe5d1d ae7cf5bf")).Concat(fromHexStringToByteArr("0xf08c98ca f1fd82e8 cea9825d bff04fd0")).ToArray();
// manual page's step 9
byte[] privateKey = fromHexStringToByteArr("0xb89ea7fc d22cc059 c2673dc2 4ff40b97 83074646 86560d0a d7561b83");
// manual page's step 10
X9ECParameters spec = ECNamedCurveTable.GetByName("secp224k1");
ECDomainParameters domain = new ECDomainParameters(spec.Curve, spec.G, spec.N);
ECDsaSigner signer = new ECDsaSigner(new HMacDsaKCalculator(new Sha224Digest()));
signer.Init(true, new ECPrivateKeyParameters(new BigInteger(privateKey), domain));
BigInteger[] signature = signer.GenerateSignature(fortyByteMessage);
byte[] r = signature[0].ToByteArray().SkipWhile(b => b == 0x00).Reverse().ToArray(); // (r) r should be 0x3fb77a9d 7b5b2a68 209e76f6 872078c5 791340d5 989854ad a3ab735e, but not.
Console.WriteLine(BitConverter.ToString(r).Replace("-", string.Empty).ToLower());
Expected byteArr ( step 10 r value ):
r = 0x3fb77a9d 7b5b2a68 209e76f6 872078c5 791340d5 989854ad a3ab735e<br>
My byteArr (this is wrong value, because it is different from step 10 r value )
r = 0x1e3b3f4f 7401ff9d 827b7222 47823919 452d3adb effa7aa4 52a0879e<br>
Another function:
static byte[] fromHexStringToByteArr(string paramHexString)
{
string hexString = paramHexString.Substring(2).Replace(" ", "");
byte[] result = new byte[hexString.Length / 2];
int cur = 0;
for (int i = 0; i < hexString.Length; i = i + 2)
{
string w = hexString.Substring(i, 2);
result[cur] = Convert.ToByte(w, 16);
cur++;
}
return result;
}
According to step 10 of the instructions, not the 40-byte-message should be signed, but the SHA224-hash of this message: The client signs the 28-byte SHA-224 digest of the 40-byte message.... Note, that the data are not automatically hashed by the GenerateSignature-method, i.e. it must be hashed explicitly, see also here and these examples.
The Org.BouncyCastle.Math.BigInteger.ToByteArray-method (which is used in the C#-code) outputs the byte-array in big-endian-format (unlike .Net, whose System.Numerics.BigInteger.ToByteArray-method uses the little-endian-format). Therefore, it is not necessary to reverse the byte-order (using the Reverse-method).
With these modifications, the signature is:
r = 0x1781ff4997b48d389f518df75001c4b6564082956228d74dd0321656
s = 0x0aadc68cf78dc75d44fb300f200465e72a70826ec2d5577d49b62e59
which, however, still differs from the signature shown in the instructions.
In the C#-code, the ECDsaSigner-instance is created with a HMacDsaKCalculator-instance, generating a deterministic signature based on RFC6979. When creating a signature with ECDSA, the k-parameter is chosen randomly for the non-deterministic ECDSA, whereas in the deterministic variant it is created from the message and the private key according to a specific algorithm (described in RFC6979), see here. Thus, the deterministic variant generates the same signature for the same message and private key, while the non-deterministic variant generates different signatures.
Probably the difference between the signatures is caused by the use of the non-deterministic variant by CoinFLEX. Unfortunately, the instructions do not go into detail about the ECDSA-procedure used.
Update:
Both variants, deterministic and non-deterministic, provide valid ECDSA-signatures! Before the deterministic variant (RFC6979 is from August 2013) there was only the non-deterministic variant, see here.
I installed and tested the sign_secp224k1-tool on a Linux (Debian) machine. As suspected, the tool generates different signatures for the same private key and the same message, obviously using the non-deterministic variant. This can also be easily verified from the source-code: The signature is calculated using the ecp_sign-method, which randomly determines the k-value using /dev/urandom.
Thus it is clear that the signature generated by the C#-code using the deterministic variant generally cannot match a signature generated by the sign_secp224k1-tool using the non-deterministic variant.

Generate a key pair in "node-forge" using "exponent", "modulus" and "maxdigits"

I'm using "node-forge" to generate a publicKey to use with my AES symmetric key but I don't know how to use the data provided by my backend to create this publicKey. Currently, I receive from an authentication api the following:
e: "10001"
n:"c7c5dd235568711a943ebbdacac890ca2cf12c1ab539f77726e8874d2ab4220cf06369358b5eff0425fb17d4f696f741cf04c5ea874415e7f67d118a2e763e641e8675b8f42e9277b3f70f14e4de23fe16f51abdc427490f47e4b28ae3e5eb3563ba797fe90f9b70ba878646b1b297c52ba735827682b67309d38b423e31b50b"
maxdigits: "131"
Where "e" is my exponent, "n" is my module and "maxdigits" is the length my BigIntegers are supposed to have.
But when I try something like this:
const keys = forge.pki.rsa.generateKeyPair({ e: res.e, n: res.n });
My backend returns an error. What am I doing wrong?
forge.pki.rsa.generateKeyPair is the wrong method in this context. forge.pki.rsa.generateKeyPair creates a new key pair with random modulus. The first parameter specifies the modulus/key size in bits, the second the exponent ([1] and [2]):
// var forge = require('node-forge'); // in nodejs-context
var pki = forge.pki;
var rsa = forge.pki.rsa;
var keypair = rsa.generateKeyPair({bits: 2048, e: 0x10001});
var pubKeyPEM = pki.publicKeyToPem(keypair.publicKey);
var privKeyPEM = pki.privateKeyToPem(keypair.privateKey);
console.log(pubKeyPEM);
console.log(privKeyPEM);
The forge.pki.rsa.setPublicKey-method is used to generate a public key via modulus and exponent, where the modulus is the first parameter and the exponent is the second parameter ([2]), both of type forge.jsbn.BigInteger ([3]):
var BigInteger = forge.jsbn.BigInteger;
var n = new BigInteger('c7c5dd235568711a943ebbdacac890ca2cf12c1ab539f77726e8874d2ab4220cf06369358b5eff0425fb17d4f696f741cf04c5ea874415e7f67d118a2e763e641e8675b8f42e9277b3f70f14e4de23fe16f51abdc427490f47e4b28ae3e5eb3563ba797fe90f9b70ba878646b1b297c52ba735827682b67309d38b423e31b50b', 16);
var e = new BigInteger('10001', 16);
var pubKey = rsa.setPublicKey(n, e);
var pubKeyPEM = pki.publicKeyToPem(pubKey)
console.log(pubKeyPEM); // Check with e.g. https://lapo.it/asn1js/

JWT public key vs private key signature validation -- what is the difference?

I am using this library, node-jwks-rsa, to fetch JWT keys from my auth0 jwks.json file in order to verify that the id_token my application retrieves after authentication is actually coming from my auth provider.
Under the hood it uses this method to build a public key PEM
export function certToPEM(cert) {
cert = cert.match(/.{1,64}/g).join('\n');
cert = `-----BEGIN CERTIFICATE-----\n${cert}\n-----END CERTIFICATE-----\n`;
return cert;
}
(Using the x50c as argument from the .jwks file).
which I then use in combination with jsonwebtoken to verify that the JWT(id_token) is valid.
How is this method of verification different from generating a private key(RSA) from the modulus and exponent of the jwks.json file and using it for verification instead? (as example see this library)
Additionally here is function as demonstration that generates a PEM from a mod and exponent (taken from http://stackoverflow.com/questions/18835132/xml-to-pem-in-node-js)
export function rsaPublicKeyToPEM(modulusB64, exponentB64) {
const modulus = new Buffer(modulusB64, 'base64');
const exponent = new Buffer(exponentB64, 'base64');
const modulusHex = prepadSigned(modulus.toString('hex'));
const exponentHex = prepadSigned(exponent.toString('hex'));
const modlen = modulusHex.length / 2;
const explen = exponentHex.length / 2;
const encodedModlen = encodeLengthHex(modlen);
const encodedExplen = encodeLengthHex(explen);
const encodedPubkey = '30' +
encodeLengthHex(modlen + explen + encodedModlen.length / 2 + encodedExplen.length / 2 + 2) +
'02' + encodedModlen + modulusHex +
'02' + encodedExplen + exponentHex;
const der = new Buffer(encodedPubkey, 'hex')
.toString('base64');
let pem = `-----BEGIN RSA PUBLIC KEY-----\n`;
pem += `${der.match(/.{1,64}/g).join('\n')}`;
pem += `\n-----END RSA PUBLIC KEY-----\n`;
return pem;
};
The aforementioned jsonwebtoken library can verify a JWT using either -- but why? If both of these verification methods can validate a JWT signature why do they both exist? What are the tradeoffs between them? Is one more secure than the other? Which should I use to verify most fully?
Using a RSA assymetric key pair, the JWT is signed with the private key and verified with the public. You can not verify a digital signature with the private key
Modulus and exponent are the components of the public key and you can use it to build the public key in PEM format, which is a base64 representation of the public key (modulus and exponent) encoded in DER binary format. You can use PEM, DER or modulus and exponent because the contain the same information
But anybody can't build the private key with modulus and exponent. He would need the private RSA elements, which must be kept secret so that no one can sign for you.

TLS 1.2 ECDHE_RSA signature

I'm currently working on a Java TLS server. I'm trying to get the following CipherSuite to work : TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA
When I test it using openssl s_client I get the following error after the ServerKeyExchange message :
140735242416208:error:1414D172:SSL
routines:tls12_check_peer_sigalg:wrong signature type:t1_lib.c:1130:
Here is the TLS message as seen in Wireshark
The Handshake fails on a decode_error fatal error.
So I guess the client doesn't like the signature algorithm chosen.
But I am only using the default SignatureAndHashAlgorithm for now as per RFC 5246 Section-7.4.1.4.1
If the negotiated key exchange algorithm is one of (RSA, DHE_RSA,
DH_RSA, RSA_PSK, ECDH_RSA, ECDHE_RSA), behave as if client had sent
the value {sha1,rsa}.
(I'm still checking if the client do offer theses default values though)
Since I'm doing ECDHE_RSA I believe I should hash and sign the serverECDHparams as per RFC 4492 Section 5.4 (First post here so only 2 links sorry :) )
ServerKeyExchange.signed_params.sha_hash
SHA(ClientHello.random + ServerHello.random +
ServerKeyExchange.params);
struct {
select (KeyExchangeAlgorithm) {
case ec_diffie_hellman:
ServerECDHParams params;
Signature signed_params;
};
} ServerKeyExchange;
And I should do this as per RFC 2246 Section 7.4.3
select (SignatureAlgorithm) {
case rsa:
digitally-signed struct {
opaque md5_hash[16];
opaque sha_hash[20];
};
} Signature;
md5_hash
MD5(ClientHello.random + ServerHello.random + ServerParams);
sha_hash
SHA(ClientHello.random + ServerHello.random + ServerParams);
My Java code regarding signing the serverParams :
private byte[] getSignedParams(ChannelBuffer params)
throws NoSuchAlgorithmException, DigestException,
SignatureException, InvalidKeyException {
byte[] signedParams = null;
ChannelBuffer signAlg = ChannelBuffers.buffer(2);
MessageDigest md5 = MessageDigest.getInstance("MD5");
MessageDigest sha = MessageDigest.getInstance("SHA-1");
switch (session.cipherSuite.sign) {
case rsa:
signAlg.writeByte(2); // 2 for SHA1
sha.update(clientRandom);
sha.update(serverRandom);
sha.update(params.toByteBuffer());
md5.update(clientRandom);
md5.update(serverRandom);
md5.update(params.toByteBuffer());
signedParams = concat(md5.digest(), sha.digest());
break;
}
signAlg.writeByte(session.cipherSuite.sign.value); // for RSA he byte is one
ChannelBuffer signLength = ChannelBuffers.buffer(2);
signLength.writeShort(signedParams.length);
return concat(signAlg.array(),concat(signLength.array(),signedParams));
}
So my question is basically : Am I wrong about all this ? and if so, what am I doing wrong ?
Thank you for your time ! :)
It's me again, I seem to have fixed my particular problem 2 things I noted :
Regarding my Java code, the MessageDigest class only does hashing no signing so I now use the Signature class instead.
It seems I only need to sign using SHA1 in TLS1.2 I don't need to do MD5 at all.
The second item is what I should have found in the RFC but didn't (maybe it is written somewhere, I don't know) I think this could be useful for people even if they're not doing Java ;)
How my code looks now :
private byte[] getSignedParams(ChannelBuffer params)
throws NoSuchAlgorithmException, DigestException,
SignatureException, InvalidKeyException {
byte[] signedParams = null;
Signature signature = Signature.getInstance(selectedSignAndHash.toString());
ChannelBuffer signAlg = ChannelBuffers.buffer(2);
signAlg.writeByte(selectedSignAndHash.hash.value);
signature.initSign(privateKey);
signature.update(clientRandom);
signature.update(serverRandom);
signature.update(params.toByteBuffer());
signedParams = signature.sign();
signAlg.writeByte(session.cipherSuite.sign.value);
ChannelBuffer signLength = ChannelBuffers.buffer(2);
signLength.writeShort(signedParams.length);
return concat(signAlg.array(), concat(signLength.array(), signedParams));
}
The code is different because in between I added a function to choose the SignatureAndHashAlgorithm to use from the list the client provides. But you could modify this to only respond using SHA1withRSA as this seems to be the default HashAndSignatureAlgorithm.

the signature includes en embedded timeStamp but it could not be verified

I've just added timestamp to my pdf. signature is valid. timestamp token is correct too (I checked already). but adobe reader tells me that "signature includes an embedded timestamp but it could not be veridied".
AttributeTable unsigned = signerInformation.getUnsignedAttributes();
Hashtable<ASN1ObjectIdentifier, Attribute> unsignedAttrHash = null;
if (unsigned == null) {
unsignedAttrHash = new Hashtable<ASN1ObjectIdentifier, Attribute>();
} else {
unsignedAttrHash = signerInformation.getUnsignedAttributes().toHashtable();
}
unsignedAttrHash.put(PKCSObjectIdentifiers.id_aa_signatureTimeStampToken, signatureTimeStamp);
SignerInformation newSignertInformation = SignerInformation.replaceUnsignedAttributes(si, new AttributeTable(unsignedAttrHash));
I fount this code at stackowerflow. it works. it's really correct code. finally I have new SignerInformationStore and new CMS Signed Data like this
CMSSignedData.replaceSigners(oldCMSSignedData, newSignerStore);
but maybe something is missing in my PDF? certificate or something like that?
that's sample pdf
The message imprint in the signature-time-stamp seems to be not correct. It is expected to have the SHA256 of the signature value in this message imprint.
SHA256 of the signature value:
1b4532052d612ca32ae96b9a8e7aa6d64ae6c69dc00e1b7b31394ac3b54c4049
The message imprint in the time-stamp token:
E3B0C44298FC1C149AFBF4C8996FB92427AE41E4649B934CA495991B7852B855