Let's use a test key (don't use it, it's just a test, I don't use it either):
-----BEGIN PRIVATE KEY-----
MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgkeS6LTDov1L2oShp6PgM2STESyMD1YTQkxUtxSJc1PahRANCAAR+uKNDd0l2odP4excxvTBY5bQxQBufCYb2TkVgp1NBefyO603LHzOelGVnnIFfamtJvv55AwVxt0nUlS0z450U
-----END PRIVATE KEY-----
I want to sign in Javascript and verify in PHP but I don't get the same result so I tried to get at first same result for signing. Here is my code:
PHP:
$challenge=file_get_contents("/tmp/challenge-XIABFf");
$key=openssl_pkey_get_private(file_get_contents("/tmp/private-key"));
$response="";
openssl_sign($challenge,$response, $key, OPENSSL_ALGO_SHA384);
var_dump(base64_encode($response));
I get : MEYCIQCQt9+reL0WRzi26ft8CEUMRDhnd4vh50F3sYL9BA2x0QIhAJCD7bakD6jXIfYTNpEoGPTR9guusC7CFxbbFcKPvroq
which is 96 characters.
Now in JS :
function arrayBufferToBase64(buffer) {
var binary = '';
var bytes = new Uint8Array(buffer);
for (var i = 0; i < bytes.byteLength; i++) {
binary += String.fromCharCode(bytes[i]);
}
return btoa(binary);
}
function base64ToArrayBuffer(base64) {
var binary_string = atob(base64);
var len = binary_string.length;
var bytes = new Uint8Array(len);
for (var i = 0; i < len; i++) {
bytes[i] = binary_string.charCodeAt(i);
}
return bytes.buffer;
}
key=await window.crypto.subtle.importKey("pkcs8",base64ToArrayBuffer("MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgkeS6LTDov1L2oShp6PgM2STESyMD1YTQkxUtxSJc1PahRANCAAR+uKNDd0l2odP4excxvTBY5bQxQBufCYb2TkVgp1NBefyO603LHzOelGVnnIFfamtJvv55AwVxt0nUlS0z450U"), {name: "ECDSA", namedCurve: "P-256"},true,["sign"]);
message=await window.crypto.subtle.sign({"name": "ECDSA", "hash": "SHA-384"},key,base64ToArrayBuffer("4BRYPzkacla7x5uR0bXtkQXC18odd9yCfcefjIrh")); console.log(arrayBufferToBase64(message));
The 4BRY... is the base64 of the challenge. And the result is : ZDDWSkpkwS8N8LYhHib/Qeb+P8DMRtKIT925AhO8m6bD/K5wAYEYa3Fq89Ik8z6t62a0dtuN8kydxNWttgFcdA==
(88 characters) which is not the same as PHP, why?
After some deep and help from @Dhaval-purohit, I identified why there is a 8 characters difference between my signature.
I found this library (called jsrsassign) on Github and they do import functions to do the trick such as :
They need the hexadecimal form so using this function, you can sign and get a result that OpenSSL can match :
/* Stackoverflow DorianCoding */
async function signchallenge(key, challenge) {
var response = await window.crypto.subtle.sign({
name: "ECDSA",
hash: "SHA-384"
}, key, base64ToArrayBuffer(challenge));
if (!response) return Promise.reject();
return arrayBufferToBase64(hextoArrayBuffer(KJUR.crypto.ECDSA.concatSigToASN1Sig(ArrayBuffertohex(response))));
}
You need to implement arraybuffer <=> base64 and hex <=> base64 functions (there is example in my question).
Then a signature like : 3d282410b1f47bfb0a4aae0caf7bef2a59d62b4ecd41037ff913cd71e922a26843c390e1a042cf6199421e55f12b4983845f5c503fcac1b70e3f8e13ec39a8bb
turns into :
3045022100e28ba263bfa655a5bbe2b84d6ef2cdcb821d941f60e375c17b016030eb10e97e0220219927e83f702626ef00dcd0714c232cb5299730b29082e3ec72e94c0b776da2
and as first byte are often the same (3045, ASN.1 format should start by MEU/QCI... in base64) :
MEQCID0oJBCx9Hv7CkquDK977ypZ1itOzUEDf/kTzXHpIqJoAiBDw5DhoELPYZlCHlXxK0mDhF9cUD/KwbcOP44T7Dmouw==
And now OpenSSL is happy.