From caa95ebc918e757c3a25e91b706d4049fbab7b55 Mon Sep 17 00:00:00 2001 From: Marcin Rataj Date: Mon, 12 Sep 2022 23:45:19 +0200 Subject: [PATCH] fix: require V2 signatures This is part of deprecation described in https://github.com/ipfs/js-ipfs/issues/4197 - record creation continues to create both V1 and V2 signatures - record validation no longer accepts V1 signatures Meaning: - modern nodes are 100% V2, they ignore V1 signatures - legacy nodes (go-ipfs < v0.9.0, js-ipfs before Jun 2021) are still able to resolve names created by js-ipns, because V1 is still present --- README.md | 10 +++++----- src/index.ts | 6 +++--- src/validator.ts | 5 ++--- test/index.spec.ts | 24 +++++++++++++++++++++--- 4 files changed, 31 insertions(+), 14 deletions(-) diff --git a/README.md b/README.md index 1203347..d41b56e 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,6 @@ # ipns -[![ipfs.io](https://img.shields.io/badge/project-IPFS-blue.svg?style=flat-square)](http://ipfs.io) -[![IRC](https://img.shields.io/badge/freenode-%23ipfs-blue.svg?style=flat-square)](http://webchat.freenode.net/?channels=%23ipfs) +[![ipfs.tech](https://img.shields.io/badge/project-IPFS-blue.svg?style=flat-square)](https://ipfs.tech) [![Discord](https://img.shields.io/discord/806902334369824788?style=flat-square)](https://discord.gg/ipfs) [![codecov](https://img.shields.io/codecov/c/github/ipfs/js-ipns.svg?style=flat-square)](https://codecov.io/gh/ipfs/js-ipns) [![CI](https://img.shields.io/github/workflow/status/ipfs/js-ipns/test%20&%20maybe%20release/master?style=flat-square)](https://github.com/ipfs/js-ipns/actions/workflows/js-test-and-release.yml) @@ -126,7 +125,7 @@ const validator = ipns.validator Contains an object with `validate (marshalledData, key)` and `select (dataA, dataB)` functions. -The `validate` async function aims to verify if an IPNS record is valid. First the record is unmarshalled, then the public key is obtained and finally the record is validated (signature and validity are verified). +The `validate` async function aims to verify if an IPNS record is valid. First the record is unmarshalled, then the public key is obtained and finally the record is validated (`signatureV2` of CBOR `data` is verified). The `select` function is responsible for deciding which ipns record is the best (newer) between two records. Both records are unmarshalled and their sequence numbers are compared. If the first record provided is the newer, the operation result will be `0`, otherwise the operation result will be `1`. @@ -151,10 +150,11 @@ Returns a `Promise` that resolves to an object with the entry's properties eg: ```js { value: Uint8Array, - signature: Uint8Array, validityType: 0, validity: Uint8Array, - sequence: 2 + sequence: 2, + signatureV2: Uint8Array, + data: Uint8Array } ``` diff --git a/src/index.ts b/src/index.ts index 5d08559..fcb4332 100644 --- a/src/index.ts +++ b/src/index.ts @@ -95,7 +95,7 @@ const _create = async (peerId: PeerId, value: Uint8Array, seq: number | bigint, } const privateKey = await unmarshalPrivateKey(peerId.privateKey) - const signatureV1 = await sign(privateKey, value, validityType, isoValidity) + const signatureV1 = await signLegacyV1(privateKey, value, validityType, isoValidity) const data = createCborData(value, isoValidity, validityType, seq, ttl) const sigData = ipnsEntryDataForV2Sig(data) const signatureV2 = await privateKey.sign(sigData) @@ -144,9 +144,9 @@ export { peerIdToRoutingKey } from './utils.js' export { peerIdFromRoutingKey } from './utils.js' /** - * Sign ipns record data + * Sign ipns record data using the legacy V1 signature scheme */ -const sign = async (privateKey: PrivateKey, value: Uint8Array, validityType: IpnsEntry.ValidityType, validity: Uint8Array) => { +const signLegacyV1 = async (privateKey: PrivateKey, value: Uint8Array, validityType: IpnsEntry.ValidityType, validity: Uint8Array) => { try { const dataForSignature = ipnsEntryDataForV1Sig(value, validityType, validity) diff --git a/src/validator.ts b/src/validator.ts index 70a5a51..c4ef3b5 100644 --- a/src/validator.ts +++ b/src/validator.ts @@ -2,7 +2,7 @@ import errCode from 'err-code' import { toString as uint8ArrayToString } from 'uint8arrays/to-string' import { equals as uint8ArrayEquals } from 'uint8arrays/equals' import { IpnsEntry } from './pb/ipns.js' -import { parseRFC3339, extractPublicKey, ipnsEntryDataForV1Sig, ipnsEntryDataForV2Sig, unmarshal, peerIdFromRoutingKey, parseCborData } from './utils.js' +import { parseRFC3339, extractPublicKey, ipnsEntryDataForV2Sig, unmarshal, peerIdFromRoutingKey, parseCborData } from './utils.js' import * as ERRORS from './errors.js' import type { IPNSEntry } from './index.js' import type { PublicKey } from '@libp2p/interface-keys' @@ -27,8 +27,7 @@ export const validate = async (publicKey: PublicKey, entry: IPNSEntry) => { validateCborDataMatchesPbData(entry) } else { - signature = entry.signature ?? new Uint8Array(0) - dataForSignature = ipnsEntryDataForV1Sig(value, validityType, validity) + throw errCode(new Error('missing data or signatureV2'), ERRORS.ERR_SIGNATURE_VERIFICATION) } // Validate Signature diff --git a/test/index.spec.ts b/test/index.spec.ts index 0cbe097..eafca97 100644 --- a/test/index.spec.ts +++ b/test/index.spec.ts @@ -60,17 +60,35 @@ describe('ipns', function () { await ipnsValidator(peerIdToRoutingKey(peerId), marshal(entry)) }) - it('should validate a v1 message', async () => { + it('should fail to validate a v1 (deprecated legacy) message', async () => { const sequence = 0 const validity = 1000000 const entry = await ipns.create(peerId, cid, sequence, validity) - // extra fields added for v2 sigs + // remove the extra fields added for v2 sigs delete entry.data delete entry.signatureV2 - await ipnsValidator(peerIdToRoutingKey(peerId), marshal(entry)) + // confirm a v1 exists + expect(entry).to.have.property('signature') + + await expect(ipnsValidator(peerIdToRoutingKey(peerId), marshal(entry))).to.eventually.be.rejected().with.property('code', ERRORS.ERR_SIGNATURE_VERIFICATION) + }) + + it('should fail to validate a v2 without v2 signature (ignore v1)', async () => { + const sequence = 0 + const validity = 1000000 + + const entry = await ipns.create(peerId, cid, sequence, validity) + + // remove v2 sig + delete entry.signatureV2 + + // confirm a v1 exists + expect(entry).to.have.property('signature') + + await expect(ipnsValidator(peerIdToRoutingKey(peerId), marshal(entry))).to.eventually.be.rejected().with.property('code', ERRORS.ERR_SIGNATURE_VERIFICATION) }) it('should fail to validate a bad record', async () => {