Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add public key support #4

Merged
merged 5 commits into from
Aug 9, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
58 changes: 49 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ const ipns = require('ipns')

ipns.create(privateKey, value, sequenceNumber, lifetime, (err, entryData) => {
// your code goes here
});
})
```

#### Validate record
Expand All @@ -51,23 +51,35 @@ const ipns = require('ipns')
ipns.validate(publicKey, ipnsEntry, (err) => {
// your code goes here
// if no error, the record is valid
});
})
```

#### Embed public key to record

> Not available yet
```js
const ipns = require('ipns')

ipns.embedPublicKey(publicKey, ipnsEntry, (err, ipnsEntryWithEmbedPublicKey) => {
// your code goes here
})
```

#### Extract public key from record

> Not available yet
```js
const ipns = require('ipns')

ipns.extractPublicKey(peerId, ipnsEntry, (err, publicKey) => {
// your code goes here
})
```

#### Datastore key

```js
const ipns = require('ipns')

ipns.getLocalKey(peerId);
ipns.getLocalKey(peerId)
```

Returns a key to be used for storing the ipns entry locally, that is:
Expand All @@ -85,7 +97,7 @@ ipns.create(privateKey, value, sequenceNumber, lifetime, (err, entryData) => {
// ...
const marshalledData = ipns.marshal(entryData)
// ...
});
})
```

Returns the entry data serialized.
Expand All @@ -106,7 +118,7 @@ Returns the entry data structure after being serialized.

```js

ipns.create(privateKey, value, sequenceNumber, lifetime, [callback]);
ipns.create(privateKey, value, sequenceNumber, lifetime, [callback])
```

Create an IPNS record for being stored in a protocol buffer.
Expand All @@ -133,7 +145,7 @@ Create an IPNS record for being stored in a protocol buffer.

```js

ipns.validate(publicKey, ipnsEntry, [callback]);
ipns.validate(publicKey, ipnsEntry, [callback])
```

Validate an IPNS record previously stored in a protocol buffer.
Expand All @@ -147,7 +159,7 @@ Validate an IPNS record previously stored in a protocol buffer.
#### Datastore key

```js
ipns.getDatastoreKey(peerId);
ipns.getDatastoreKey(peerId)
```

Get a key for storing the ipns entry in the datastore.
Expand All @@ -174,6 +186,34 @@ Returns the entry data structure after being serialized.

- `storedData` (Buffer): ipns entry record serialized.

#### Embed public key to record

```js
ipns.embedPublicKey(publicKey, ipnsEntry, [callback])
```

Embed a public key in an IPNS entry. If it is possible to extract the public key from the `peer-id`, there is no need to embed.

- `publicKey` (`PubKey` [RSA Instance](https://github.com/libp2p/js-libp2p-crypto/blob/master/src/keys/rsa-class.js)): key to be used for cryptographic operations.
- `ipnsEntry` (Object): ipns entry record (obtained using the create function).
- `callback` (function): operation result.

`callback` must follow `function (err, resultEntry) {}` signature, where `err` is an error if the operation was not successful. This way, if no error, the operation was successful. If the `resultEntry` is also null, the `peer-id` allows to extract the public key from the `peer-id` and there is no need in extracting it.

#### Extract public key from record

```js
ipns.extractPublicKey(peerId, ipnsEntry, [callback])
```

Extract a public key from an IPNS entry.

- `peerId` (`PeerId` [Instance](https://github.com/libp2p/js-peer-id)): peer identifier object.
- `ipnsEntry` (Object): ipns entry record (obtained using the create function).
- `callback` (function): operation result.

`callback` must follow `function (err, publicKey) {}` signature, where `err` is an error if the operation was not successful. This way, if no error, the validation was successful. The public key (`PubKey` [RSA Instance](https://github.com/libp2p/js-libp2p-crypto/blob/master/src/keys/rsa-class.js)): may be used for cryptographic operations.

## Contribute

Feel free to join in. All welcome. Open an [issue](https://github.com/ipfs/js-ipns/issues)!
Expand Down
7 changes: 4 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,10 @@
"debug": "^3.1.0",
"interface-datastore": "^0.4.2",
"left-pad": "^1.3.0",
"libp2p-crypto": "^0.13.0",
"multihashes": "^0.4.14",
"nano-date": "^2.1.0",
"peer-id": "^0.11.0",
"protons": "^1.0.1"
},
"devDependencies": {
Expand All @@ -48,9 +51,7 @@
"chai-string": "^1.4.0",
"dirty-chai": "^2.0.1",
"ipfs": "^0.29.3",
"ipfsd-ctl": "^0.36.0",
"libp2p-crypto": "^0.13.0",
"multihashes": "^0.4.13"
"ipfsd-ctl": "^0.36.0"
},
"contributors": [
"Vasco Santos <[email protected]>"
Expand Down
3 changes: 3 additions & 0 deletions src/errors.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,6 @@ exports.ERR_UNRECOGNIZED_VALIDITY = 'ERR_UNRECOGNIZED_VALIDITY'
exports.ERR_SIGNATURE_CREATION = 'ERR_SIGNATURE_CREATION'
exports.ERR_SIGNATURE_VERIFICATION = 'ERR_SIGNATURE_VERIFICATION'
exports.ERR_UNRECOGNIZED_FORMAT = 'ERR_UNRECOGNIZED_FORMAT'
exports.ERR_PEER_ID_FROM_PUBLIC_KEY = 'ERR_PEER_ID_FROM_PUBLIC_KEY'
exports.ERR_PUBLIC_KEY_FROM_ID = 'ERR_PUBLIC_KEY_FROM_ID'
exports.ERR_UNDEFINED_PARAMETER = 'ERR_UNDEFINED_PARAMETER'
114 changes: 99 additions & 15 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@ const base32Encode = require('base32-encode')
const Big = require('big.js')
const NanoDate = require('nano-date').default
const { Key } = require('interface-datastore')
const crypto = require('libp2p-crypto')
const PeerId = require('peer-id')
const multihash = require('multihashes')

const debug = require('debug')
const log = debug('jsipns')
Expand All @@ -13,6 +16,7 @@ const ipnsEntryProto = require('./pb/ipns.proto')
const { parseRFC3339 } = require('./utils')
const ERRORS = require('./errors')

const ID_MULTIHASH_CODE = multihash.names.id
/**
* Creates a new ipns entry and signs it with the given private key.
* The ipns entry validity should follow the [RFC3339]{@link https://www.ietf.org/rfc/rfc3339.txt} with nanoseconds precision.
Expand Down Expand Up @@ -44,7 +48,7 @@ const create = (privateKey, value, seq, lifetime, callback) => {

const entry = {
value: value,
signature: signature, // TODO confirm format compliance with go-ipfs
signature: signature,
validityType: validityType,
validity: isoValidity,
sequence: seq
Expand All @@ -68,7 +72,7 @@ const validate = (publicKey, entry, callback) => {
const dataForSignature = ipnsEntryDataForSig(value, validityType, validity)

// Validate Signature
publicKey.verify(dataForSignature, entry.signature, (err, result) => {
publicKey.verify(dataForSignature, entry.signature, (err) => {
if (err) {
log.error('record signature verification failed')
return callback(Object.assign(new Error('record signature verification failed'), { code: ERRORS.ERR_SIGNATURE_VERIFICATION }))
Expand Down Expand Up @@ -100,15 +104,56 @@ const validate = (publicKey, entry, callback) => {
}

/**
* Validates the given ipns entry against the given public key.
* Embed the given public key in the given entry. While not strictly required,
* some nodes (eg. DHT servers) may reject IPNS entries that don't embed their
* public keys as they may not be able to validate them efficiently.
* As a consequence of nodes needing to validade a record upon receipt, they need
* the public key associated with it. For olde RSA keys, it is easier if we just
* send this as part of the record itself. For newer ed25519 keys, the public key
* can be embedded in the peerId.
*
* @param {Object} publicKey public key for validating the record.
* @param {Object} publicKey public key to embed.
* @param {Object} entry ipns entry record.
* @param {function(Error)} [callback]
* @return {Void}
*/
const embedPublicKey = (publicKey, entry, callback) => {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Check publicKey, publicKey.bytes and entry exist?

callback(new Error('not implemented yet'))
if (!publicKey || !publicKey.bytes || !entry) {
const error = 'one or more of the provided parameters are not defined'

log.error(error)
return callback(Object.assign(new Error(error), { code: ERRORS.ERR_UNDEFINED_PARAMETER }))
}

// Create a peer id from the public key.
PeerId.createFromPubKey(publicKey.bytes, (err, peerId) => {
if (err) {
log.error(err)
return callback(Object.assign(new Error(err), { code: ERRORS.ERR_PEER_ID_FROM_PUBLIC_KEY }))
}

// Try to extract the public key from the ID. If we can, no need to embed it
let extractedPublicKey
try {
extractedPublicKey = extractPublicKeyFromId(peerId)
} catch (err) {
log.error(err)
return callback(Object.assign(new Error(err), { code: ERRORS.ERR_PUBLIC_KEY_FROM_ID }))
}

if (extractedPublicKey) {
return callback(null, null)
}

// If we failed to extract the public key from the peer ID, embed it in the record.
try {
entry.pubKey = crypto.keys.marshalPublicKey(publicKey)
} catch (err) {
log.error(err)
return callback(err)
}
callback(null, entry)
})
}

/**
Expand All @@ -120,7 +165,24 @@ const embedPublicKey = (publicKey, entry, callback) => {
* @return {Void}
*/
const extractPublicKey = (peerId, entry, callback) => {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Check peerId and entry exist

callback(new Error('not implemented yet'))
if (!entry || !peerId) {
const error = 'one or more of the provided parameters are not defined'

log.error(error)
return callback(Object.assign(new Error(error), { code: ERRORS.ERR_UNDEFINED_PARAMETER }))
}

if (entry.pubKey) {
let pubKey
try {
pubKey = crypto.keys.unmarshalPublicKey(entry.pubKey)
} catch (err) {
log.error(err)
return callback(err)
}
return callback(null, pubKey)
}
callback(null, peerId.pubKey)
}

// rawStdEncoding with RFC4648
Expand All @@ -139,16 +201,16 @@ const getLocalKey = (key) => new Key(`/ipns/${rawStdEncoding(key)}`)
* Get key for sharing the record in the routing mechanism.
* Format: ${base32(/ipns/<HASH>)}, ${base32(/pk/<HASH>)}
*
* @param {Buffer} key peer identifier object.
* @param {Buffer} pid peer identifier represented by the multihash of the public key as Buffer.
* @returns {Object} containing the `nameKey` and the `ipnsKey`.
*/
const getIdKeys = (key) => {
const getIdKeys = (pid) => {
const pkBuffer = Buffer.from('/pk/')
const ipnsBuffer = Buffer.from('/ipns/')

return {
nameKey: rawStdEncoding(Buffer.concat([pkBuffer, key])),
ipnsKey: rawStdEncoding(Buffer.concat([ipnsBuffer, key]))
pkKey: new Key(rawStdEncoding(Buffer.concat([pkBuffer, pid]))),
ipnsKey: new Key(rawStdEncoding(Buffer.concat([ipnsBuffer, pid])))
}
}

Expand All @@ -164,13 +226,35 @@ const sign = (privateKey, value, validityType, validity, callback) => {
})
}

// Create record data for being signed
const ipnsEntryDataForSig = (value, validityType, eol) => {
// Utility for getting the validity type code name of a validity
const getValidityType = (validityType) => {
if (validityType.toString() === '0') {
return 'EOL'
} else {
const error = `unrecognized validity type ${validityType.toString()}`
log.error(error)
throw Object.assign(new Error(error), { code: ERRORS.ERR_UNRECOGNIZED_VALIDITY })
}
}

// Utility for creating the record data for being signed
const ipnsEntryDataForSig = (value, validityType, validity) => {
const valueBuffer = Buffer.from(value)
const validityTypeBuffer = Buffer.from(validityType.toString())
const eolBuffer = Buffer.from(eol)
const validityTypeBuffer = Buffer.from(getValidityType(validityType))
const validityBuffer = Buffer.from(validity)

return Buffer.concat([valueBuffer, validityBuffer, validityTypeBuffer])
}

// Utility for extracting the public key from a peer-id
const extractPublicKeyFromId = (peerId) => {
const decodedId = multihash.decode(peerId.id)

if (decodedId.code !== ID_MULTIHASH_CODE) {
return null
}

return Buffer.concat([valueBuffer, validityTypeBuffer, eolBuffer])
return crypto.keys.unmarshalPublicKey(decodedId.digest)
}

module.exports = {
Expand Down
Loading