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
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==
I have a certificate that I need to send in the header of an http request. This is how I acquired the cert:
PCCERT_CONTEXT cert = nullptr;
wstring store = // store name
wstring subjectName = // subject name
HCERTSTORE hStoreHandle = CertOpenStore(
CERT_STORE_PROV_SYSTEM,
0,
NULL,
CERT_SYSTEM_STORE_CURRENT_USER,
store.c_str());
cert = CertFindCertificateInStore(
hStoreHandle,
X509_ASN_ENCODING,
0,
CERT_FIND_SUBJECT_STR,
subjectName.c_str(),
NULL);
I need to send it as a custom header, as the load balancer that sits in front of my service strips off the certificate header ["X-ARR-CLIENTCERT"] before forwarding the request. I believe I need to send the cert->pbCertEncoded, but on the server, I can't decode it and convert it back to an X509Certificate2.
This is what I tried on the client:
request.headers().add("client-cert", cert->pbCertEncoded);
On the server:
var headerCert = Request.Headers["client-cert"];
byte[] certdata = Convert.FromBase64String(headerCert);
X509Certificate2 cert = new X509Certificate2(certdata);
The request header on the server is non-null. But it cannot parse it back to an X509Certificate2.
I tried another thing on the client. After getting the cert, I converted it to a string
DWORD size = 0;
CryptBinaryToString(cert->pbCertEncoded, cert->cbCertEncoded, CRYPT_STRING_BASE64, NULL, &size);
LPWSTR outstring = new TCHAR[size];
CryptBinaryToString(cert->pbCertEncoded, cert->cbCertEncoded, CRYPT_STRING_BASE64, outstring, &size);
If I try to send outstring in the header, it complains:
WinHttpAddRequestHeaders: 87: The parameter is incorrect.
But when I take the contents of outstring and try to parse it on the server, it decodes back to the right certificate. This tells me that I'm not doing something right when passing cert->pbCertEncoded in the header. Maybe I need to re-encode it or transform it somehow so the server can correctly parse it? I'd appreciate any help. Thanks!
My client is in c++ and server in .NET. I'm using cpprestsdk to send the certificate in the http request.
The pbCertEncoded is the ASN.1 encoded representation of the certificate. Look for instance here.
So you must encode the bytes to base64 for instance like this:
#include <Wincrypt.h>
#pragma comment (lib, "Crypt32.lib")
int ToBase64Crypto(const BYTE* pSrc, int nLenSrc, char* pDst, int nLenDst )
{
DWORD nLenOut = nLenDst;
BOOL fRet = CryptBinaryToString(
(const BYTE*)pSrc,
nLenSrc,
CRYPT_STRING_BASE64 | CRYPT_STRING_NOCRLF,
pDst, &nLenOut
);
if (!fRet) {
nLenOut=0; // failed
}
return (nLenOut);
}
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.
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.
I'm opening the certificate store using the "CertOpenStore" API and get the certificates using the "CertEnumCertificatesInStore" API.
The CERT_CONTEXT data returned by the API gives the issuer name in CERT_NAME_BLOB type.
How to get the CERT_RDN or CERT_NAME_INFO from the certificate.?
My requirement is to get the issuer name attributes (O, OU, etc.). I do not want to parse the string returned by the CertNameToStr API.
The above comment is correct, you do need to decode the ASN.1 encoded data in the CERT_NAME_BLOB. However, the CryptoAPI has a function to do this for you - CryptDecodeObject.
If you have a PCCERT_CONTEXT handle pCertContext, you can decode it to a CERT_NAME_INFO structure as follows:
BOOL success = CryptDecodeObject(
X509_ASN_ENCODING,
X509_NAME,
pCertContext->pCertInfo->Issuer.pbData,
pCertContext->pCertInfo->Issuer.cbData,
0,
NULL,
&dwNameInfoSize);
// (check that CryptDecodeObject succeeded)
PCERT_NAME_INFO pCertNameInfo = (PCERT_NAME_INFO) malloc(dwNameInfoSize);
// (check that malloc succeeded)
CryptDecodeObject(
X509_ASN_ENCODING,
X509_NAME,
pCertContext->pCertInfo->Issuer.pbData,
pCertContext->pCertInfo->Issuer.cbData,
0,
pCertNameInfo,
&dwNameInfoSize);
Now you can loop through the different components of the RDN like this:
for (DWORD i = 0; i < pCertNameInfo->cRDN; i++)
{
for (DWORD j = 0; j < pCertNameInfo->rgRDN[i].cRDNAttr; j++)
{
CERT_RDN_ATTR &rdnAttribute = pCertNameInfo->rgRDN[i].rgRDNAttr[j];
//
// Do stuff with the RDN attribute
//
}
}
With each iteration, rdnAttribute will be set to a different component of the issuer name like you want.
Finally, free the memory when you're done:
free(pCertNameInfo);