RSA signing verification between platforms

I need to verify data that is RSA signed by a JavaScript app, but the Xojo Crypto library is older and limited in options. I control the JS code, so what is the best, most secure way to achieve this? (We use the crypto library on the JS side, but are open to using others.)

The solution must work on the three desktop platforms, can use Shell calls, but cannot rely on plugins.

I have not filled in my “verify” version of this code yet, but this works for generating a signature compatible with Xojo’s keys. The input for privateKey in this case is a Xojo private key converted into PEM format:

Protected Function PEMEncodePrivateKey(Key As String) as String Var Base64 As String = EncodeBase64(Crypto.DEREncodePrivateKey(Key), 0) Var Lines() As String = Array("-----BEGIN PRIVATE KEY-----") While Base64.Length > 64 Lines.AddRow(Base64.Left(64)) Base64 = Base64.Middle(64) Wend If Base64.Length > 0 Then Lines.AddRow(Base64) End If Lines.AddRow("-----END PRIVATE KEY-----") Return Lines.Join(Encodings.UTF8.Chr(10)) End Function

[code]async createSignature (privateKey, data) {
try {
if (!(‘TextEncoder’ in window)) {
console.log(‘No TextEncoder’);
return false;
}
let lines = privateKey.trim().split(’
');
if ((lines[0].indexOf(‘BEGIN PRIVATE KEY’) === -1 || lines[lines.length - 1].indexOf(‘END PRIVATE KEY’) === -1) && (lines[0].indexOf(‘BEGIN RSA PRIVATE KEY’) === -1 || lines[lines.length - 1].indexOf(‘END RSA PRIVATE KEY’) === -1)) {
console.log(‘Unable to find private key tags’);
return false;
}

	lines = lines.slice(1, lines.length - 1);
	let bytes = Buffer.from(lines.join(''), 'base64').buffer;
	
	let importedKey = await window.crypto.subtle.importKey('pkcs8', bytes, { name: 'RSASSA-PKCS1-v1_5', hash: 'SHA-1' }, false, [ 'sign' ]);
	let encoder = new TextEncoder();
	let signedData = await window.crypto.subtle.sign('RSASSA-PKCS1-v1_5', importedKey, encoder.encode(data));
	return String.fromCharCode.apply(null, new Uint8Array(signedData));
} catch (err) {
	console.log('Unable to sign: ' + err.message);
	return false;
}

}[/code]

Perhaps this serves as some inspiration?

Without testing, is “window.crypto.subtle” the same as using the crypto lib in a node-js app? (What I know about JS could fill a thimble, frankly.)

I don’t have an exact answer for you. I know I ran into problems following the NodeJS documentation because it is for the server-side code. Some of its functionality is ported to the native client-side crypto routines, but I couldn’t tell you exactly which ones.

I’ll play, thanks.

Does this do the same thing in Xojo, or have I missed something?

Var Base64 As String = EncodeBase64(Crypto.DEREncodePrivateKey(Key), 64)
Var Lines() As String = Array( _
  "-----BEGIN PRIVATE KEY-----", _
  Base64.ReplaceLineEndings( &uA ), _
  "-----END PRIVATE KEY-----" )

Return String.FromArray( Lines, &uA )

I think at the time I wrote that, I was using a “new framework” compatible implementation that did not introduce line breaks. So yes, I believe your version is more sane. But you also don’t need the array at all.

Return "-----BEGIN PRIVATE KEY-----" + &uA + EncodeBase64(Crypto.DEREncodePrivateKey(Key), 64).ReplaceLineEndings(&uA) + &uA + "-----END PRIVATE KEY-----"

I tried this.

const privateKey = `-----BEGIN PRIVATE KEY-----
MIIEoQIBAAKCAQEArHqAMTbEdCTPVb2hjJhaGfr7Ndbiz0+ydtK5E1EWpL5F9oC3
2gvwirFkHucp6ghA9pDI2kAAabYNoS7GFcaVo9F40k3T3fL4vCwRTinWJIsbDnyo
Q2RGaYe9elN0j2giRQtH8lquujH/p6SbfBr8f1sdEsHXzLxEmFGGV+gVRzi5Vhlx
Y5U9jk1ZRpbB8DCGpDKKJB183np1wHORG9RFCEQ4H4QhjCz+1LOW3HFh3FyFkeQS
XOOFhM03MptS+Iv7gk7aeG2guhyDKb8RZxf7BsFw4S4Bn7zhgC7eCY2o0JvqdZ8g
wKNbCvL10ISe9E0ZD38253kaSczVSxxT+VdytQIBEQKCAQAAgd294svfNlHtbbv1
HpDUZ+QgiOoUL6txSmhzQb6PixnORyexlR4Sen+Koc8uniRVDfqRNOTlNH0HQCM3
yB0NSCU/3Zf23VOOGzxJQ+ND30eb5sRCdQA1BCn4E9Jp1WN9MPlvZSksN91dGtZK
5/w5mR1zQVK0jhtOwP4i5NLi2EGRTpA/vVgeZULfF8FO5+HhOsUh82Y6MZa94sxj
HTfBCDFgHMEi5aTF2aXPkPyuioB1D4vAtuEhX2oc5ye9rihiBZmb2Pr0Gv7p3fjP
+8JKMq/tSB/Jmf1ZwvVNe2AfWQqNkdNpNwRCfu045emxpUPbh0+Jen1r18LhukeI
sYvpAoGBANaNan4L/W1CVcFLCvQgIni68dayjRCEzGu3eKk0ohr+fc6V342okLRZ
O/a9C3WTXosmiCa+wB5ZR+JOTI80faLxm9U3bgPVI6b+wiTs0VJho3GzfJ4FndSk
MH2Oh98ON9dsbBMZ0a81b8ZzigYD+QaV/C1VCZReS5h22SGZTLeNAoGBAM3MV16w
Hsftesev/xEEFg7HMuJaTKcP0dXXw3REZB2q5NXjdBGKnXO2Z2hy1lR5C17ytk+r
QGbYzGaBTfvYwLZhQwJE0lCxHk2QLng/6J4ZzFgJ5yvZmpGlEJWTKiAgIg8DhkXp
wAOE8B/LoDkA1fLpzWb7wPKw8mkI3DDUPOnJAoGAGT3QSxB4Kvi+y3I9hiHl8BX+
VX5q8uJyZwaGqn6pqNKlY5kpW/W2q85DSjRbs3q/1CKmmyWAA5IIdPonH+gOx+Aw
c2/u00ZAbf/amu6vNt5PdsnSbPGaGQRB8KdbR2sVoN+UPnuCFJzf+TrE8aYdTBGl
MoJ5mPwI5MKwIhIJBokCgYEAqXsaxnLsLCz7s4HhHRJshKQLyXeKa3Zwkfz7ULDK
60FxKJ0yaMyBqpY3CrjsvglUqIulMo0H3DoRvdPl3nZEWfW+tpMHjb8J5YXL6o77
zX6oSICgQjq7hwBoArVt/FayovPX/VcWmXyJg5iiENODBEgSkQuP1uwS7RZa+wkj
GuECgYAHQ2O1hL8aKxbZR/2OLNNhbbwJHxAlM2eXofavxx59eiA5byUbStV474qT
G7J7dlVNKtv3E22HovdXH0jOXFfqO9IyOWkEVpBQTiPezpkdo5ZzODUrNSAK8ikA
TNp1SmNgc9fQFUBMxA2lbhqWgLzE2lYsDek4prZwYmW4EhAxLw==
-----END PRIVATE KEY-----
`

let lines = privateKey.trim().split('\
')
if (
	(lines[0].indexOf('BEGIN PRIVATE KEY') === -1 ||
		lines[lines.length - 1].indexOf('END PRIVATE KEY') === -1) &&
	(lines[0].indexOf('BEGIN RSA PRIVATE KEY') === -1 ||
		lines[lines.length - 1].indexOf('END RSA PRIVATE KEY') === -1)
) {
	console.log('Unable to find private key tags')
	return false
}

lines = lines.slice(1, lines.length - 1)
let bytes = Buffer.from(lines.join(''), 'base64').buffer

const crypto = require('crypto')
const sign = crypto.createSign('SHA1')
sign.write('data')
sign.end()

const sig = sign.sign(privateKey)
console.log(sig)

(That’s a test private key that won’t be used for anything.)

The error stemming from the “sig = sign( …” line:

internal/crypto/sig.js:105
  const ret = this[kHandle].sign(data, format, type, passphrase, rsaPadding,
                            ^

Error: error:0D0680A8:asn1 encoding routines:asn1_check_tlen:wrong tag
    at Sign.sign (internal/crypto/sig.js:105:29)
    at Object.<anonymous> (/Users/ktekinay/Documents/MacTechnologies/Clients/AMPS/Repos/dragon-js/src/try.js:49:18)
    at Module._compile (internal/modules/cjs/loader.js:1158:30)
    at Object.Module._extensions..js (internal/modules/cjs/loader.js:1178:10)
    at Module.load (internal/modules/cjs/loader.js:1002:32)
    at Function.Module._load (internal/modules/cjs/loader.js:901:14)
    at Function.executeUserEntryPoint [as runMain] (internal/modules/run_main.js:74:12)
    at internal/main/run_main_module.js:18:47 {
  opensslErrorStack: [
    'error:0907B00D:PEM routines:PEM_read_bio_PrivateKey:ASN1 lib',
    'error:0D08303A:asn1 encoding routines:asn1_template_noexp_d2i:nested asn1 error',
    'error:0D07803A:asn1 encoding routines:asn1_item_embed_d2i:nested asn1 error'
  ],
  library: 'asn1 encoding routines',
  function: 'asn1_check_tlen',
  reason: 'wrong tag',
  code: 'ERR_OSSL_ASN1_WRONG_TAG'
}

Still open to suggestions, but this has become academic.

Unfortunately I don’t know. Even my own code I feel like I got lucky with.

OpenSSL doesn’t like that key as is, but likes it with these markers “-----BEGIN RSA PRIVATE KEY-----” “-----END RSA PRIVATE KEY-----”, maybe the JS library has similar issues.

(tested with openssl rsa -check -noout -in keyFile.pem)