@ldclabs/cose-ts
Version:
Implemented Keys, Algorithms (RFC9053), COSE (RFC9052) and CWT (RFC8392) in TypeScript.
178 lines • 6.18 kB
JavaScript
// (c) 2023-present, LDC Labs. All rights reserved.
// See the file LICENSE for licensing terms.
import { p256 } from '@noble/curves/p256';
import { p384 } from '@noble/curves/p384';
import { p521 } from '@noble/curves/p521';
import * as iana from './iana';
import { Key } from './key';
import { assertBytes } from './map';
import { decodeCBOR } from './utils';
// TODO: more checks
// ECDSAKey implements signature algorithm ECDSA for COSE as defined in RFC9053.
// https://datatracker.ietf.org/doc/html/rfc9053#name-ecdsa.
export class ECDSAKey extends Key {
static fromBytes(data) {
return new ECDSAKey(decodeCBOR(data));
}
static generate(alg, kid) {
const curve = getCurve(alg);
return ECDSAKey.fromSecret(curve.utils.randomPrivateKey(), kid);
}
static fromSecret(secret, kid) {
assertBytes(secret, 'secret');
const alg = getAlg(secret.length);
const key = new ECDSAKey();
key.alg = alg;
const curve = getCurve(alg);
if (!curve.utils.isValidPrivateKey(secret)) {
throw new Error(`cose-ts: ECDSAKey.fromSecret: secret is not a valid private key for ECDSA alg ${alg}`);
}
key.setParam(iana.EC2KeyParameterD, secret);
if (kid != null) {
key.setKid(kid);
}
return key;
}
static fromPublic(pubkey, kid) {
assertBytes(pubkey, 'public key');
if (pubkey.length < 33) {
throw new Error(`cose-ts: ECDSAKey.fromPublic: public key size mismatch, expected at least 33, got ${pubkey.length}`);
}
const alg = pubkey[0] == 0x04
? getAlg((pubkey.length - 1) / 2)
: getAlg(pubkey.length - 1);
const key = new ECDSAKey();
key.alg = alg;
const curve = getCurve(alg);
curve.ProjectivePoint.fromHex(pubkey); // validate public key
switch (pubkey[0]) {
case 0x02:
key.setParam(iana.EC2KeyParameterY, false);
key.setParam(iana.EC2KeyParameterX, pubkey.subarray(1));
break;
case 0x03:
key.setParam(iana.EC2KeyParameterY, true);
key.setParam(iana.EC2KeyParameterX, pubkey.subarray(1));
break;
case 0x04:
key.setParam(iana.EC2KeyParameterX, pubkey.subarray(1, curve.CURVE.Fp.BYTES + 1));
key.setParam(iana.EC2KeyParameterY, pubkey.subarray(curve.CURVE.Fp.BYTES + 1));
break;
default:
}
if (kid != null) {
key.setKid(kid);
}
return key;
}
constructor(kv) {
super(kv);
this.kty = iana.KeyTypeEC2;
}
get alg() {
return super.alg;
}
set alg(alg) {
super.alg = alg;
this.setParam(iana.OKPKeyParameterCrv, getCrv(alg));
}
getSecretKey() {
return this.getBytes(iana.EC2KeyParameterD, 'd');
}
getPublicKey() {
const curve = getCurve(this.alg);
if (this.has(iana.EC2KeyParameterX)) {
const x = this.getBytes(iana.EC2KeyParameterX, 'x');
try {
const y = this.getBool(iana.EC2KeyParameterY, 'y');
const pk = new Uint8Array(1 + x.length);
pk[0] = y ? 0x03 : 0x02;
pk.set(x, 1);
return pk;
}
catch (_e) {
const y = this.getBytes(iana.EC2KeyParameterY, 'y');
const pk = new Uint8Array(1 + x.length + y.length);
pk[0] = 0x04;
pk.set(x, 1);
pk.set(y, x.length + 1);
return pk;
}
}
return curve.getPublicKey(this.getSecretKey(), true);
}
public() {
const key = new ECDSAKey(this.clone());
if (key.has(iana.EC2KeyParameterD)) {
const curve = getCurve(this.alg);
const pk = key.getPublicKey();
switch (pk[0]) {
case 0x02:
key.setParam(iana.EC2KeyParameterY, false);
key.setParam(iana.EC2KeyParameterX, pk.subarray(1));
break;
case 0x03:
key.setParam(iana.EC2KeyParameterY, true);
key.setParam(iana.EC2KeyParameterX, pk.subarray(1));
break;
case 0x04:
key.setParam(iana.EC2KeyParameterX, pk.subarray(1, curve.CURVE.Fp.BYTES + 1));
key.setParam(iana.EC2KeyParameterY, pk.subarray(curve.CURVE.Fp.BYTES + 1));
break;
}
key.delete(iana.EC2KeyParameterD);
}
if (key.has(iana.KeyParameterKeyOps)) {
key.ops = [iana.KeyOperationVerify];
}
return key;
}
sign(message) {
const curve = getCurve(this.alg);
const sig = curve.sign(message, this.getSecretKey(), {
prehash: true
});
return sig.toCompactRawBytes();
}
verify(message, signature) {
const curve = getCurve(this.alg);
return curve.verify(signature, message, this.getPublicKey(), {
prehash: true
});
}
}
function getAlg(keySize) {
let alg = iana.AlgorithmES256;
if (keySize === 48) {
alg = iana.AlgorithmES384;
}
else if (keySize >= 65) {
alg = iana.AlgorithmES512;
}
return alg;
}
export function getCrv(alg) {
switch (alg) {
case iana.AlgorithmES256:
return iana.EllipticCurveP_256;
case iana.AlgorithmES384:
return iana.EllipticCurveP_384;
case iana.AlgorithmES512:
return iana.EllipticCurveP_521;
default:
throw new Error(`cose-ts: unsupported ECDSA alg ${alg}`);
}
}
export function getCurve(alg) {
switch (alg) {
case iana.AlgorithmES256:
return p256;
case iana.AlgorithmES384:
return p384;
case iana.AlgorithmES512:
return p521;
default:
throw new Error(`cose-ts: unsupported ECDSA alg ${alg}`);
}
}
//# sourceMappingURL=ecdsa.js.map