@ldclabs/cose-ts
Version:
Implemented Keys, Algorithms (RFC9053), COSE (RFC9052) and CWT (RFC8392) in TypeScript.
206 lines • 7.34 kB
JavaScript
// (c) 2023-present, LDC Labs. All rights reserved.
// See the file LICENSE for licensing terms.
import { x25519 } from '@noble/curves/ed25519';
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, assertInt } from './map';
import { decodeCBOR } from './utils';
// TODO: more checks
// ECDHKey implements key agreement algorithm ECDH for COSE as defined in RFC9053.
// https://datatracker.ietf.org/doc/html/rfc9053#name-direct-key-agreement.
export class ECDHKey extends Key {
static fromBytes(data) {
return new ECDHKey(decodeCBOR(data));
}
static generate(crv, kid) {
const curve = getCurve(crv);
return ECDHKey.fromSecret(crv, curve.utils.randomPrivateKey(), kid);
}
static fromSecret(crv, secret, kid) {
assertBytes(secret, 'secret');
const key = new ECDHKey();
const curve = getCurve(crv);
if (curve === x25519) {
if (secret.length !== 32) {
throw new Error(`cose-ts: ECDHKey.fromSecret: secret size mismatch, expected 32, got ${secret.length}`);
}
}
else {
if (!curve.utils.isValidPrivateKey(secret)) {
throw new Error(`cose-ts: ECDSAKey.fromSecret: secret is not a valid private key for ECDH curve ${crv}`);
}
}
key.crv = crv;
key.setParam(iana.EC2KeyParameterD, secret);
if (kid != null) {
key.setKid(kid);
}
return key;
}
static fromPublic(crv, pubkey, kid) {
assertBytes(pubkey, 'public key');
const key = new ECDHKey();
const curve = getCurve(crv);
if (curve === x25519) {
if (pubkey.length !== 32) {
throw new Error(`cose-ts: ECDHKey.fromPublic: key size mismatch, expected 32, got ${pubkey.length}`);
}
key.setParam(iana.EC2KeyParameterX, pubkey);
}
else {
const crv = curve;
crv.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, crv.CURVE.Fp.BYTES + 1));
key.setParam(iana.EC2KeyParameterY, pubkey.subarray(crv.CURVE.Fp.BYTES + 1));
break;
default:
}
}
key.crv = crv;
if (kid != null) {
key.setKid(kid);
}
return key;
}
constructor(kv) {
super(kv);
}
get crv() {
return this.getType(iana.EC2KeyParameterCrv, assertInt, 'crv');
}
set crv(crv) {
const curve = getCurve(crv);
this.setParam(iana.EC2KeyParameterCrv, crv);
if (curve === x25519) {
this.kty = iana.KeyTypeOKP;
}
else {
this.kty = iana.KeyTypeEC2;
}
}
ecdh(remotePublic) {
const remote = remotePublic instanceof ECDHKey
? remotePublic
: new ECDHKey(remotePublic.toRaw());
const priv = this.getSecretKey();
const pub = remote.getPublicKey();
const crv = this.crv;
if (crv !== remote.crv) {
throw new Error(`cose-ts: ECDHKey.ecdh: curve mismatch, expected ${crv}, got ${remote.crv}`);
}
let curve = getCurve(crv);
if (curve === x25519) {
return x25519.getSharedSecret(priv, pub);
}
const secret = curve.getSharedSecret(priv, pub, true);
const size = getKeySize(crv);
return secret.byteLength === size ? secret : secret.subarray(1);
}
getSecretKey() {
return this.getBytes(iana.EC2KeyParameterD, 'd');
}
getPublicKey() {
const curve = getCurve(this.crv);
if (this.has(iana.EC2KeyParameterX)) {
const x = this.getBytes(iana.EC2KeyParameterX, 'x');
if (curve === x25519) {
return 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;
}
}
if (curve === x25519) {
return curve.getPublicKey(this.getSecretKey());
}
else {
return curve.getPublicKey(this.getSecretKey(), true);
}
}
public() {
const key = new ECDHKey(this.clone());
if (key.has(iana.EC2KeyParameterD)) {
let curve = getCurve(this.crv);
const pk = key.getPublicKey();
if (curve === x25519) {
key.setParam(iana.EC2KeyParameterX, pk);
}
else {
curve = curve;
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 = [];
}
return key;
}
}
export function getCurve(crv) {
switch (crv) {
case iana.EllipticCurveP_256:
return p256;
case iana.EllipticCurveP_384:
return p384;
case iana.EllipticCurveP_521:
return p521;
case iana.EllipticCurveX25519:
return x25519;
default:
throw new Error(`cose-ts: unsupported ECDH curve ${crv}`);
}
}
export function getKeySize(crv) {
switch (crv) {
case iana.EllipticCurveP_256:
return 32;
case iana.EllipticCurveP_384:
return 48;
case iana.EllipticCurveP_521:
return 66;
case iana.EllipticCurveX25519:
return 32;
default:
throw new Error(`cose-ts: unsupported ECDH curve ${crv}`);
}
}
//# sourceMappingURL=ecdh.js.map