Skip to content

Commit

Permalink
fix: require V2 signatures
Browse files Browse the repository at this point in the history
This is part of deprecation described in ipfs/js-ipfs#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
  • Loading branch information
lidel committed Sep 12, 2022
1 parent 0f5340a commit caa95eb
Show file tree
Hide file tree
Showing 4 changed files with 31 additions and 14 deletions.
10 changes: 5 additions & 5 deletions README.md
@@ -1,7 +1,6 @@
# ipns <!-- omit in toc -->

[![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)
Expand Down Expand Up @@ -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`.

Expand All @@ -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
}
```

Expand Down
6 changes: 3 additions & 3 deletions src/index.ts
Expand Up @@ -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)
Expand Down Expand Up @@ -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)

Expand Down
5 changes: 2 additions & 3 deletions src/validator.ts
Expand Up @@ -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'
Expand All @@ -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
Expand Down
24 changes: 21 additions & 3 deletions test/index.spec.ts
Expand Up @@ -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 () => {
Expand Down

0 comments on commit caa95eb

Please sign in to comment.