I'm calling openssl from Delphi (10.3.1) using the indy libraries. This works perfectly when using a 32 bit application. However when the application is compiled to 64 bits, I get an access violation on this line: rc := EVP_PKEY_set1_RSA(@key, FRSA);
05-18-2019, 07:02 AM (This post was last modified: 05-18-2019, 07:05 AM by rlebeau.)
(05-16-2019, 12:13 PM)info@a-dato.net Wrote:
Code:
rc := EVP_PKEY_set1_RSA(@key, FRSA); <== Exception occurs here
Check to make sure that the EVP_PKEY_set1_RSA function pointer is not nil after calling LoadOpenSSLLibrary():
Code:
if not IdSSLOpenSSL.LoadOpenSSLLibrary then
raise Exception.Create('LoadOpenSSLLibrary failed');
if not Assigned(EVP_PKEY_set1_RSA) then // <-- here
raise Exception.Create('EVP_PKEY_set1_RSA is not available');
...
EVP_PKEY_set1_RSA() is not marked as a critical function being used by Indy's own purposes, so if it fails to load from the DLLs, Indy will simply ignore it.
if the EVP_PKEY_set1_RSA pointer is not nil, but you are still getting the AV, then something else is going on.
I added a check on the function pointer and it is not nil. I also checked this line in IdSSLOpenSSLHeaders: @EVP_PKEY_set1_RSA := LoadFunctionCLib(fn_EVP_PKEY_set1_RSA,False); and this proves the function is loaded.
(05-21-2019, 10:50 AM)info@a-dato.net Wrote: Anything else we can check?
Do you have the same error if you use EVP_PKEY_new() to allocate the EVP_PKEY dynamically instead of declaring it statically on the stack? Eventually, Indy will be updated to support OpenSSL 1.1.x and you will have to do this anyway, since OpenSSL 1.1.0+ now uses opaque structures that must be allocated dynamically, so you may as well prepare for this sooner rather than later.
Try something more like this:
Code:
function TOAuth1SignatureMethod_RSA_SHA1.Hash_HMAC_SHA1(const AData, AKey: string): string;
const
cPrivateKeyBegin = '-----BEGIN PRIVATE KEY-----';
cPrivateKeyEnd = '-----END PRIVATE KEY-----';
var
buffer: TBytes;
clean_key: string;
mdLength: TIdC_UInt;
mdctx: PEVP_MD_CTX;
KeyBuffer: PBIO;
FRSA: PRSA;
md: PEVP_MD;
key: PEVP_PKEY;
rc: Integer;
iBegin, iEnd: Integer;
begin
if not IdSSLOpenSSL.LoadOpenSSLLibrary then
raise Exception.Create('LoadOpenSSLLibrary failed');
// Load private key (key must include header and footer)
//-----BEGIN PRIVATE KEY-----
// ....
// ....
//-----END PRIVATE KEY-----
iBegin := AKey.IndexOf(cPrivateKeyBegin);
if iBegin < 0 then
raise Exception.Create('Private key error');
iEnd := AKey.IndexOf(cPrivateKeyEnd, iBegin + Length(cPrivateKeyBegin));
if iEnd < 0 then
raise Exception.Create('Private key error');
Inc(iEnd, Length(cPrivateKeyEnd));
Result := TNetEncoding.Base64.EncodeBytesToString(PByte(buffer), mdLength);
Result := Result.Replace(#13#10, '', [rfReplaceAll]);
end;
BTW, why are you implementing an HMAC SHA1 hash manually? Indy does have a TIdHMACSHA1 class available for that (as well as a TIdEncoderMIME class for encoding data to base64, without line breaks). OpenSSL also has its own HMAC functions, too.
Thanks, that did it, after allocating the key through EVP_PKEY_new() the call to EVP_PKEY_set1_RSA succeeded.
Why I choose this path?
I have little knowledge on this subject and when I needed this hash function I could not find any samples that pointed me in the right direction. I tried several Delphi alternatives but they did not give the right results. I did find a working C# example a decided to convert it to Delphi.
this hash value is calculated: 'SFYyolkv4nUPbzKoFOmHuPaDQE4='
The hash value should be:
'jSXIuYXqNb23f0RHybCpYWlzNAfABN1ie2hGPA7Cj0svoXX2sLaTZdTt11nb8DieULXivftOzxKDPkWjGiJJQsxUaPpltsfYOCg/jSeNEo+clfRpIP3K4UGXDjCQNwV+WNCkggA9S87oEylUBPOdShibOAC1oMocAO/B9SOZm/o='
What is causing this difference? Do I need to setup some additional parameters?
That will not work. Your clean_key contains a base64-encoded string, but the TIdHMAC.Key property expects actual bytes instead. You will have to decode the base64 string first.
What does your AData actually look like? If it contains non-ASCII characters, 8Bit is likely to lose them. Typically, UTF8 is the preferred encoding when hashing string data.
(05-22-2019, 06:53 AM)info@a-dato.net Wrote: this hash value is calculated: 'SFYyolkv4nUPbzKoFOmHuPaDQE4='
The hash value should be:
'jSXIuYXqNb23f0RHybCpYWlzNAfABN1ie2hGPA7Cj0svoXX2sLaTZdTt11nb8DieULXivftOzxKDPkWjGiJJQsxUaPpltsfYOCg/jSeNEo+clfRpIP3K4UGXDjCQNwV+WNCkggA9S87oEylUBPOdShibOAC1oMocAO/B9SOZm/o='
That is WAY TOO LONG for a valid HMAC-SHA1 hash, even when encoded in base64. SHA-1 produces a 20-byte hash, and HMAC-SHA1 simply invokes SHA-1 multiple times but the result is still 20 bytes. The "expected" base64 you have shown above decodes to 128 bytes. The "Indy" base64 you have shown above decodes to 20 bytes. Where are you getting this "expected" hash from exactly?
I can't say why the hash calculated using OpenSSL is different. All I know is that it works.
I updated my test application so that I have an OpenSLL and Indy version of the hashing algorithm (see file OAuth1SignatureMethod_RSA_SHA1). The hash being calculated is very different and the Indy hash is not working (Jira returns an HTTP 500).
If you do, please compile this application (I use Delphi 10.3.1), then press the button labeled '#1: Get Request-Token and Auth-Code'. This should open the login window for Jira.
You can also check 'Use indy' and then try again. This will result in a Http 500 exception.
(05-23-2019, 09:19 AM)info@a-dato.net Wrote: I can't say why the hash calculated using OpenSSL is different. All I know is that it works.
Like I said, HMAC-SHA1 by itself cannot produce such a long hash. Are you perhaps actually showing the COMPLETE OAuth signature after EVERYTHING is hashed and concatenated together? That is a very different thing than just hashing individual values by themselves. Start with hashing 1 value and make sure it hashes correctly, then another value, and another as needed until you read the final result. Find a decent OAuth example that shows you what the intermediate hashes should be at each step, not just what the input and final result are.
(05-23-2019, 09:19 AM)info@a-dato.net Wrote: I updated my test application so that I have an OpenSLL and Indy version of the hashing algorithm (see file OAuth1SignatureMethod_RSA_SHA1). The hash being calculated is very different and the Indy hash is not working (Jira returns an HTTP 500).
Well, considering that Indy uses OpenSSL internally by default, the hashes should be identical. Which likely means either you are not actually hashing the same input data, or you are not using the hash algorithms correctly.
If you do, please compile this application (I use Delphi 10.3.1), then press the button labeled '#1: Get Request-Token and Auth-Code'. This should open the login window for Jira.
You can also check 'Use indy' and then try again. This will result in a Http 500 exception.
Internally, TIdHMACSHA1 uses OpenSSL's HMAC functions (specifically, HMAC_CTX_init(), HMAC_Init_ex(EVP_sha1), HMAC_Update(), and HMAC_Final()), rather than the Digest Signing functions that your OpenSSL code is using. If you use the HMAC functions in your code, are you able to get a result that Jira accepts?