UNPKG

@ldclabs/cose-ts

Version:

Implemented Keys, Algorithms (RFC9053), COSE (RFC9052) and CWT (RFC8392) in TypeScript.

206 lines 7.34 kB
// (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