Closed leplatrem closed 8 years ago
@ttaubert and @mozmark confirmed that loading ECDSA from spki
is not supported.
We should use the jwk
instead.
That means we should parse the chain with an ASN.1 library, extract the x
and y
coordinates from the result and provide them as a an object:
const stripped = pemChain.split("\n").slice(1, -2).join("");
const binaryKey = base64ToBinary(stripped);
const asn1 = org.pkijs.fromBER(binaryKey.buffer);
console.log(asn1.result);
const coordX = ?
const coordY = ?
const jwk = {
kty: "EC",
crv: "P-384",
x: coordX,
y: coordY
}
const usages = ["verify"]; //"verify" for public key import, "sign" for private key imports
return window.crypto.subtle.importKey("jwk", jwk, {
name: "ECDSA",
namedCurve: "P-384"
},
false, //whether the key is extractable (i.e. can be used in exportKey),
usages
);
I didn't figure out how to parse and extract the x and y values yet. Plus I'm afraid to be lost between the different expected types/encoding (base64, buffer, UintArray, hex...) :]
edit: example in Go : https://play.golang.org/p/v9GKNxAuKj
Ok I could make it work thanks to @engelke articles 1 and 2.
Using the live parsing tool of asn1js I could figure out how to reach field containing the binary key.
Once I had obtained the bytes of the public key field, I had to split them to obtain the two values for x and y. The first byte of ECDSA binary key contains the compression info (x04
for uncompressed), followed by x
and y
.
The number of bytes obtained was 97, which makes sense: 97 == 1 + 48 + 48
, and 48 bytes is 384 bits (ECDSA P-384).
const content = publicKey.bits.bytes.slice(1);
const length = content.length;
if (length * 8 != 384 * 2) {
throw new Error(`Invalid key size (${length * 8} bits)`)
}
publicKey.x = content.slice(0, length/2);
publicKey.y = content.slice(length/2);
But the jwk
expects the x
and y
to be «base64url» encoded:
function binaryToBase64URL(int8Array) {
return window.btoa(String.fromCharCode.apply(null, int8Array))
.replace(/\+/g, '-').replace(/\//g, '_') // URL friendly
.replace(/\=+$/, ''); // No padding.
}
As a summary, I could get the PEM to be loaded with:
function loadKey(pemChain) {
const stripped = pemChain.split("\n").slice(1, -2).join("");
const der = base64ToBinary(stripped);
var certificate = parseX509ECDSACertificate(der); // x509ecdsa.js
const jwk = {
kty: "EC",
crv: "P-384",
x: binaryToBase64URL(certificate.publicKey.x),
y: binaryToBase64URL(certificate.publicKey.y),
ext: true,
}
const usages = ["verify"]; //"verify" for public key import, "sign" for private key imports
return window.crypto.subtle.importKey("jwk", jwk, {
name: "ECDSA",
namedCurve: "P-384"
},
false, //whether the key is extractable (i.e. can be used in exportKey),
usages
);
}
The parseX509ECDSACertificate()
function can be found in this pull-request elsewhere.
The key is now loaded properly. I now have some failing verification, but that's another story ;)
(I'm closing this, feel free to re-open of ping me if you think this is worth adding to the documentation somewhere)
Thanks @jvehent also for your support!
Thanks for your repo, it was really helpful (if not vital) to get started :)
I have a ECDSA P-384 public key as PEM chain that I fetch from a public URL:
And would like to import it :
But it throws with
DataError: "Data provided to an operation does not meet requirements"
and I can't figure out where to start :)I will continue to investigate and keep you posted...