I'm currently learning about how the Certificates Signing Requests (csr) work. The contents of my csr looks like this
openssl req -text -noout -verify -in bobsbooks.com.csr
verify OK
Certificate Request:
Data:
Version: 0 (0x0)
Subject: C=UK, ST=Hampshire, L=Southampton, O=Bob's books Ltd, OU=IT Department, CN=bobsbooks.com/emailAddress=admin#bobsbooks.com
Subject Public Key Info:
Public Key Algorithm: rsaEncryption
Public-Key: (2048 bit)
Modulus:
00:94:e6:c2:33:ee:e0:67:c9:aa:e8:ff:82:07:40:
0e:91:ca:19:ea:b2:98:2a:87:b7:1a:ce:c7:d3:3b:
e3:a9:39:9d:8d:7f:e6:26:84:70:4a:a8:49:97:e9:
37:72:07:98:58:77:98:03:a7:fd:0f:9e:0d:7f:f6:
d7:84:ac:40:79:2b:bd:62:18:da:75:f3:e8:5e:33:
48:82:e4:c2:91:0b:81:74:11:cd:d4:3c:f2:60:f6:
de:a2:32:97:fd:8c:73:53:6e:fe:33:6c:28:2b:d0:
e9:9f:af:dc:b3:c6:30:04:bb:e0:e5:41:d8:4b:78:
b9:0b:53:b2:32:c4:33:62:e0:11:cc:b7:59:26:96:
95:82:ca:0e:22:37:70:ca:06:9f:1d:27:41:6d:b4:
64:c6:1b:09:8e:85:72:e0:72:54:1e:eb:ee:d7:54:
d6:b9:98:99:61:46:1b:1c:da:2d:35:8a:0a:59:bf:
fe:e9:bd:92:5b:52:74:44:ab:1d:a0:6c:2d:6d:a2:
6b:d1:ce:ed:ca:ce:a6:0a:d4:4a:14:22:4c:bb:f9:
1b:e8:8f:74:32:f2:12:4b:6c:54:e4:35:b0:bf:e1:
3b:83:f0:57:da:55:be:5e:38:03:c2:2c:36:8c:19:
e0:e5:af:91:67:38:4d:7a:10:04:3d:e2:72:c9:3e:
eb:ed
Exponent: 65537 (0x10001)
Attributes:
a0:00
Signature Algorithm: sha256WithRSAEncryption
67:0a:00:b3:62:36:13:e6:c6:ef:04:10:5b:ec:1f:54:fe:55:
c1:50:30:bc:ca:ae:c6:61:a4:44:9d:98:d5:9b:84:a0:93:60:
6b:83:02:62:a3:73:96:10:55:f3:90:9e:85:00:22:78:0a:1f:
45:1c:d0:e6:03:8a:35:72:ce:44:66:08:19:65:44:d7:12:5d:
00:0a:b9:db:3b:1f:a7:a6:fa:a9:84:f5:3a:61:5f:14:48:89:
37:37:b4:b0:1f:51:48:2d:02:6b:f4:ff:6b:4d:d3:56:c5:2e:
43:46:67:6a:cc:be:07:86:b3:82:12:4c:06:67:33:35:5e:63:
b0:76:33:13:1f:85:70:d9:b6:1e:b3:76:ee:f3:54:40:09:a8:
60:bc:35:21:cd:1e:bc:bb:49:c8:10:ae:11:03:c9:d2:fa:7c:
40:9a:53:08:d6:7e:08:9b:be:43:9a:60:79:25:14:7f:5e:6d:
81:45:2f:39:89:91:3d:8b:8e:3b:84:9c:50:e4:40:88:e1:82:
cb:5d:3f:af:13:2c:d0:34:3d:c3:e7:35:11:f3:ac:70:1d:b5:
2e:ae:d6:35:5c:45:75:5c:b5:81:82:a5:c4:73:4e:8a:09:1f:
5c:44:b7:73:42:d7:68:a0:e2:30:91:4d:29:04:32:e2:1e:bc:
12:37:74:00
What I'm trying to figure out is how the signature block (Signature Algorithm: sha256WithRSAEncryption) has been generated. My understanding is that it is the hash of the public key that that has been encrypted by the private key, i.e.:
$ sha256sum bobsbooks.com.public.key | openssl rsautl -inkey bobsbooks.com.private.key -sign | hexdump
0000000 574e 10f5 3a49 5784 25cc 7b06 c234 6ecf
0000010 adb2 9011 9332 e9e7 8dbf 7b10 c7d1 707e
0000020 aaf3 10b1 9497 32a6 d943 5b68 caba 8e5c
0000030 507d 24c7 76bb ab19 d5d7 e653 51e1 f54a
0000040 2a56 7f14 360d 15a9 767b 736a 16bc c3d8
0000050 8cb9 be7a 5ebe 701f 86ef ca5d cfd9 f035
0000060 0b5a 71ac fec4 c5cd 015e 643b 541e 6e5c
0000070 266b 5c33 98d6 db99 2431 d98e d591 04d1
0000080 4479 c85b e180 648b e71c b164 de14 6f33
0000090 fcb1 c2f6 3cc7 61ea 6f0f ab93 a1ad 5ba2
00000a0 af61 addd 4e5e 949e f38b e454 45f7 d12a
00000b0 5dfe 8bc3 b166 e4a0 8075 aa55 2089 49e4
00000c0 1a1d f137 d82b cb47 c0aa 0b14 a897 7879
00000d0 13e2 7c0a 6455 92a6 f9c7 b607 d52b ec07
00000e0 1c66 b9cb d055 ee0b 27b5 fabb 9191 8eb9
00000f0 104c 8ee2 cd81 02b9 d2a9 7502 99bd dbe1
0000100
But that output of this looks nothing like the signature. I must be
The best way to solve your doubts is to refer to RFC 2986 - PKCS#10 Certification Request Syntax
CertificationRequest ::= SEQUENCE {
certificationRequestInfo CertificationRequestInfo,
signatureAlgorithm AlgorithmIdentifier{{ SignatureAlgorithms }},
signature BIT STRING
}
The signature process consists of two steps:
The value of the certificationRequestInfo component is DER
encoded, yielding an octet string.
The result of step 1 is signed with the certification request
subject's private key under the specified signature
algorithm, yielding a bit string, the signature.
So it is signed the entire CSR, not only the public key.
Related
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==
Is there a Microsoft API equivalent of the following OpenSSL (extracts private key from .pfx file and saves as a new file) ?
openssl pkcs12 -in mycert.pfx -nocerts -out mycert.key -passin pass:Password -passout pass:Password
Try this:
RSACryptoServiceProvider rsa = (RSACryptoServiceProvider)YOUR_CERTIFICATE.PrivateKey;
MemoryStream memoryStream = new MemoryStream();
TextWriter streamWriter = new StreamWriter(memoryStream);
PemWriter pemWriter = new PemWriter(streamWriter);
AsymmetricCipherKeyPair keyPair = DotNetUtilities.GetRsaKeyPair(rsa);
pemWriter.WriteObject(keyPair.Private);
streamWriter.Flush();
string output = Encoding.ASCII.GetString(memoryStream.GetBuffer()).Trim();
int index_of_footer = output.IndexOf("-----END RSA PRIVATE KEY-----");
memoryStream.Close();
streamWriter.Close();
string PrivKey = output.Substring(0, index_of_footer + 29);
I’m implementing RSA-OAEP with windows CNG libraries. So far I’ve been able to have a full (encryption/decryption) flow with CNG library and able to verify results with OpenSSL. However, this only works if the hashing function is the same as the MGF1. If these two are different my implementation with CNG fails, For example, if OpenSSL command is changed from:
pkeyutl -encrypt -in test.txt -pubin -inkey keypair.pem -out out.bin -pkeyopt rsa_padding_mode:oaep -pkeyopt rsa_oaep_md:sha256 -pkeyopt rsa_mgf1_md:sha256
To:
pkeyutl -encrypt -in test.txt -pubin -inkey keypair.pem -out out.bin -pkeyopt rsa_padding_mode:oaep -pkeyopt rsa_oaep_md:sha256 -pkeyopt rsa_mgf1_md:sha1
CNG will fail to decrypt (Note the change from SHA256 to SHA1 in mgf1 parameter). My guess is that I need to specify the use of SHA1 as the mask generation function to CNG APIs, but I'm unable to figure out how to do so. So far my research has pointed the existence of the CRYPT_RSAES_OAEP_PARAMETERS struct which allows to specify the mask generation function. But I haven’t been able to find a sample on how to use this parameters with CNG.
Any help, deeply appreciated.
Here is my CNG code:
BCRYPT_OAEP_PADDING_INFO paddingParams = { BCRYPT_SHA256_ALGORITHM, NULL, 0 };
///Encryption
status = BCryptEncrypt(hKey, pbInput, cbInput, &paddingParams, NULL /*pbIV*/, 0 /*cbIV*/, NULL /*pbOutput*/, 0 /*cbOutput*/, &cbBuffer, BCRYPT_PAD_OAEP);
if (!NT_SUCCESS(status))
{
printf("Failed to get required size of buffer..status : %08x\n", status);
}
pbBuffer = (PUCHAR) LocalAlloc(0, cbBuffer);
status = BCryptEncrypt(hKey, pbInput, cbInput, &paddingParams, NULL /*pbIV*/, 0 /*cbIV*/, pbBuffer, cbBuffer, &cbBuffer, BCRYPT_PAD_OAEP);
if (!NT_SUCCESS(status))
{
printf("Failed encrypt data..status : %08x\n", status);
}
//Decryption
status = BCryptDecrypt(hKey, pbBuffer, cbBuffer, &paddingParams, NULL/*pbIV*/, 0/*cbIV*/, NULL, 0, &cbBufferRaw, BCRYPT_PAD_OAEP);
if (!NT_SUCCESS(status))
{
printf("Failed to get required size of buffer..status : %08x\n", status);
}
pBufferRaw = (PUCHAR) LocalAlloc(0, cbBufferRaw);
status = BCryptDecrypt(hKey, pbBuffer, cbBuffer, &paddingParams, NULL/*pbIV*/, 0/*cbIV*/, pBufferRaw, cbBufferRaw, &cbBufferRaw, BCRYPT_PAD_OAEP);
if (!NT_SUCCESS(status))
{
printf("Failed to get required size of buffer..status : %08x\n", status);
}
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.
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.