UPDATE
I didn't realize this question was still getting eyeballs.
I found and fixed the problem some time ago, and it was basically me not understanding how to do things properly. I wasn't using the right file type for the certificate, I was not reading the private key properly (should have been using PEM_read_RSAPrivateKey
, not PEM_read_PrivateKey
), I was not computing the signature properly, etc.
It just took me more time than it should have to figure it out.
Thanks for everyone who took the time to answer.
ORIGINAL
I'm working on some code to digitally sign some requests, but the signature I am generating from the code is incorrect, and it doesn't jibe with a signature generated from the command line. I suspect the problem is that I am not reading the private key in properly, but I would expect to get some errors back if that were the case, and I'm not.
This is the first time I've worked with openssl, and I'm sure I'm doing things incorrectly, but I don't yet understand enough to ask intelligent questions.
I've created a MessageSigner
class to handle the libcrypto chores. The private key and certificate are attributes of the class:
class MessageSigner
{
...
private:
EVP_PKEY *private_key;
X509 *certificate;
};
I initialize these to NULL
when an instance is created:
MessageSigner::MessageSigner() :
private_key( NULL ),
certificate( NULL )
{
}
I load the private key as follows:
void MessageSigner::addKeyFile( const std::string& filename, const std::string& passphrase )
{
FILE *fp = ::fopen( keyfile.c_str(), "r" );
if ( !fp )
// throw exception
private_key = PEM_read_PrivateKey( fp, NULL, NULL, passphrase.c_str() );
::fclose( fp );
if ( !private_key )
// throw exception
}
And I am generating the signature as
void MessageSigner::signMessage( const std::vector< unsigned char >& msg, std::vector< unsigned char >& signature )
{
unsigned char *msgbuf = new unsigned char [msg.size()];
std::copy( msg.begin(), msg.end(), msgbuf );
unsigned char *sigbuf;
size_t sigbuf_length;
EVP_MD_CTX *ctx = EVP_MT_CTX_create();
if ( !ctx )
// throw exception
if ( EVP_DigestSignInit( ctx, NULL, EVP_sha256(), NULL, private_key ) != 1)
// throw exception
if ( EVP_DigestSignUpdate( ctx, msgbuf, msg.size() ) != 1 )
// throw exception
if ( EVP_DigestSignFinal( ctx, NULL, &sigbuf_length ) != 1 )
// throw exception
sigbuf = (unsigned char *)OPENSSL_malloc( sizeof *sigbuf * sigbuf_length );
if ( !sigbuf )
// throw exception
if ( EVP_DigestSignFinal( ctx, sigbuf, &sigbuf_length ) != 1 )
// throw exception
std::copy( sigbuf, sigbuf + sigbbuf_length, std::back_inserter( signature ) );
EVP_MD_CTX_destroy( ctx );
OPENSSL_free( sigbuf ); // yes, there's potential for a memory leak, but I'm just trying to get this bastard to work.
}
Again, the signature generated from this code does not jibe with the result of
openssl rsautl -sign -inkey keyfile.pem -keyform PEM -in msg.txt -out signature
I'm convinced the problem is with how I'm loading the private key, but all the examples I've seen so far indicate that this should work.
I've been staring at this and pulling my hair our for two days. Any suggestions, hints, guidance, rude remarks, etc., will be much appreciated.
Thanks.
EDIT
Some examples should illustrate what I'm up against.
Here's the SignedInfo
generated for a given message:
<ds:SignedInfo xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
<ds:CanonicalizationMethod Algorithm="http://www.w3.org/2006/12/xml-exc-c14n#">
<ec:InclusiveNamespaces xmlns:ec="http://www.w3.org/2001/10/xml-exc-c14n#" PrefixList="alws soapenv"/>
</ds:CanonicalizationMethod>
<ds:SignatureMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"/>
<ds:Reference URI="#TS-5b171864-232b-11e9-846f-00505695541c">
<ds:Transforms>
<ds:Transform Algorithm="http://www.w3.org/2006/12/xml-exc-c14n#">
<ec:InclusiveNamespaces xmlns:ec="http://www.w3.org/2001/10/xml-exc-c14n#" PrefixList="wsse alws soapenv"/>
</ds:Transform>
</ds:Transforms>
<ds:DigestValue>eeLn6ak1glbbbWE48q7olsxO0CO/fL85bZ+8hzcjrvE=
</ds:DigestValue>
<ds:DigestMethod Algorithm="https//www.w3.org/2001/04/xmlenc#sha256"/>
</ds:Reference>
</ds:SignedInfo>
The digest value is calculated from the timestamp:
<wsu:Timestamp xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd" wsu:Id="TS-5b171864-232b-11e9-846f-00505695541c">
<wsu:Created>2019-01-28T18:34:33Z</wsu:Created>
<wsu:Expires>2019-01-28T18:34:38Z</wsu:Expires>
</wsu:Timestamp>
When I calculate the digest for that timestamp from the command line as so:
$ openssl dgst -sha256 -binary timestamp > timestamp.dgst
I get the same digest value:
$ openssl base64 -in timestamp.dgst
eeLn6ak1glbbbWE48q7olsxO0CO/fL85bZ+8hzcjrvE=
So far so good. That hash is added to SignedInfo
, then I take the hash of SignedInfo
:
cQaWLGHi8D/c1kXPG9i49xzAupeBuypvMvuMQlzA/wo=
And use it to generate the signature. Now, here's where things go completely off the rails. When I generate the signature using the above code, I get:
HtQ4LkYq4Eao4bMOpV4SBpMxHi2a+0ilxDXS9jIQZWdCC8HCNlpvVU4rWMZG2Zd/
LplCWmUHIaB35FKv6uKjCjJPVDAJT2agyp7FnSKxaBI44Y/YsdvKyxJTAMiAlF8i
dd1MB8ljYsfayrzq5e76kt2cbHlYkT/RM3SvwJtjZiYsNpfcXD0Bi6JhRshHxQ8s
6/errruOe7jUqbKh7UOPJokadCX0OTSSwRgcs+sm7VjnS9MYILaGzFFT3Js9xI6d
TL4B6A/JGIkEqLO+GA1lrokAeIBr9OVUu7OEzaBb7DaiP9Gv1diu0j1sbZ4uT5Cf
CjYJPYU72Xx8F+MKdSJteg==
When I use the command-line tool, I get:
PvfCDqPl86/8USbFU0XR5r1Dhl5JbWd2va3L4W1IW1zw6xdes04F4lYjol6gMKio
jyr8DdmWBquroVlo4vW8kmhr6760qMcpK6mfsZ26ftu7XRC+Z4b9ge6ICOemsGlE
04Yoh9EpECP+ei5yS4E1sbntteiSoQcjotmVcIbPaEG5DIDcd4JKfoCWmsnuZESs
qctIJAQy4YY9HJsVGJ2JG7QashFcEQJabtInFgYeKuxla0ZSXBfOBkwHZT/cSv+k
n/NqPMCyEl4B2LiPBVa36GaTUd6fx0SXnIh0Fm+jw6b6j3EjU0QfMJ/JBAlL+oWZ
fXO/pS5L7W+OWk8Fh//iKA==
When I verify the signature generated on the command line, I get the original hash back:
$ openssl base64 -d -in signature.b64 > signature.reversed
$ openssl rsautl -verify -inkey cert.pem -certin -in signature.reversed > signature.reversed.dgst
$ openssl base64 -in signature.reversed.dgst
cQaWLGHi8D/c1kXPG9i49xzAupeBuypvMvuMQlzA/wo=
When I try to verify the signature generated by my code, I do not get the original hash back:
$ openssl base64 -d -in badsig.b64 > badsig
$ openssl rsautl -verify -inkey cert.pem -certin -in badsig > badsig.dgst
$ openssl base64 -in badsig.dgst
MDEwDQYJYIZIAWUDBAIBBQAEIHEGlixh4vA/3NZFzxvYuPccwLqXgbsqbzL7jEJc
wP8K
What's especially frustrating is that I've since added code to verify the signature after I create it, and that code passes. But it's obviously wrong - the remote service rejects our requests because the hashes don't match, and it doesn't match with what's generated by the openssl command line tool. I've verified that both the command line tool and the library I'm using are the same version (1.0.1).
I've added code to dump the private key to compare against what's in the keyfile, and it matches, so I'm reading the key in correctly.
There appear to be no less than 3 different ways to sign a message using libcrypto routines - EVP_Sign(), EVP_DigestSign(), EVP_PKEY_sign() - and I'm not sure which one is the correct one to use in my case. All three give me bad signatures (meaning, when decrypted against the public key, do not result in the original hash value).
So, again, if anyone can point to anything I'm doing that's obviously wrong, I would appreciate it.
MORE EDIT
Also attempting to verify using dgst
as follows, again using the SignedInfo
block above:
$ openssl dgst -sha256 -binary -sign fx-realtime.fundsxpress.com.pem -out signature signedinfo
$ openssl dgst -sha256 -binary -verify publickey.pem -signature signature signedinfo
Verified OK
Reversing the signature my code generates:
$ echo -n 'XfgP1A08UTwz3sUHIVvvV+fq1n3act6+lVBZ8ieDtgh28k1r1/M0tm9MntvK+Hm4
> Be+LjguX2gxhZ4PvVcoCBCugDIsrhxplDeB4bYeY2PEedQL6+IZFX+kFrz6o3RQa
> W7sXK7czogxWpdLAmKnhDJOk2BmKFihkRMTjo9D4z/qylZI9nnX29HNdg3uV2BYw
> zHh8GvYO8fy1ugqfFW80na+hLBAtBP6fwTTv10DS2L8n+ixQcnxlKW5pyBOXlR/r
> mZEqwU+A996G0573HkGFeFvXzArlRFg/7mkKoyUHyqyDzkf5eC+vTnpEy1CP75Yc
> lvd7ldSrwREisPnyxu47sg=='> computed_signature.b64
$ openssl base64 -e -in computed_signature.b64 > computed_signature
$ openssl dgst -sha256 -binary -verify publickey.pem -signature computed_signature signedinfo
Verification Failure