commit 37edb7d0e432714d54a1dab80a9dec93eade462d Author: dignifiedquire Date: Thu May 19 18:47:48 2016 +0200 inital commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..254988d --- /dev/null +++ b/.gitignore @@ -0,0 +1,35 @@ +**/node_modules/ +**/*.log +test/repo-tests* + +# Logs +logs +*.log + +coverage + +# Runtime data +pids +*.pid +*.seed + +# Directory for instrumented libs generated by jscoverage/JSCover +lib-cov + +# Coverage directory used by tools like istanbul +coverage + +# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) +.grunt + +# node-waf configuration +.lock-wscript + +build + +# Dependency directory +# https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git +node_modules + +lib +dist diff --git a/.npmignore b/.npmignore new file mode 100644 index 0000000..59335fd --- /dev/null +++ b/.npmignore @@ -0,0 +1,34 @@ +**/node_modules/ +**/*.log +test/repo-tests* + +# Logs +logs +*.log + +coverage + +# Runtime data +pids +*.pid +*.seed + +# Directory for instrumented libs generated by jscoverage/JSCover +lib-cov + +# Coverage directory used by tools like istanbul +coverage + +# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) +.grunt + +# node-waf configuration +.lock-wscript + +build + +# Dependency directory +# https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git +node_modules + +test diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..68f6dd8 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,34 @@ +sudo: false +language: node_js +node_js: + - 4 + - 5 + - stable + +# Make sure we have new NPM. +before_install: + - npm install -g npm + +script: + - npm run lint + - npm test + - npm run coverage + + +before_script: + - export DISPLAY=:99.0 + - sh -e /etc/init.d/xvfb start + +after_success: + - npm run coverage-publish + +env: + - CXX=g++-4.8 + +addons: + firefox: 'latest' + apt: + sources: + - ubuntu-toolchain-r-test + packages: + - g++-4.8 diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..8294e7e --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2016 Friedel Ziegelmayer + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..3077ed8 --- /dev/null +++ b/README.md @@ -0,0 +1,19 @@ +# JavaScript libp2p Crytpo + +[![](https://img.shields.io/badge/made%20by-Protocol%20Labs-blue.svg?style=flat-square)](http://ipn.io) +[![](https://img.shields.io/badge/project-IPFS-blue.svg?style=flat-square)](http://ipfs.io/) +[![](https://img.shields.io/badge/freenode-%23ipfs-blue.svg?style=flat-square)](http://webchat.freenode.net/?channels=%23ipfs) +[![Coverage Status](https://coveralls.io/repos/github/ipfs/js-libp2p-crypto/badge.svg?branch=master)](https://coveralls.io/github/ipfs/js-libp2p-crypto?branch=master) +[![Travis CI](https://travis-ci.org/ipfs/js-libp2p-crypto.svg?branch=master)](https://travis-ci.org/ipfs/js-libp2p-crypto) +[![Circle CI](https://circleci.com/gh/ipfs/js-libp2p-crypto.svg?style=svg)](https://circleci.com/gh/ipfs/js-libp2p-crypto) +[![Dependency Status](https://david-dm.org/ipfs/js-libp2p-crypto.svg?style=flat-square)](https://david-dm.org/ipfs/js-libp2p-crypto) [![js-standard-style](https://img.shields.io/badge/code%20style-standard-brightgreen.svg?style=flat-square)](https://github.com/feross/standard) + +> Crypto primitives for libp2p in JavaScript + +## Description + +This repo contains the JavaScript implementation of the crypto primitives +needed for libp2p. This is based on this [go implementation](https://github.com/ipfs/go-libp2p-crypto). + + +## API diff --git a/circle.yml b/circle.yml new file mode 100644 index 0000000..434211a --- /dev/null +++ b/circle.yml @@ -0,0 +1,12 @@ +machine: + node: + version: stable + +dependencies: + pre: + - google-chrome --version + - wget -q -O - https://dl-ssl.google.com/linux/linux_signing_key.pub | sudo apt-key add - + - sudo sh -c 'echo "deb [arch=amd64] http://dl.google.com/linux/chrome/deb/ stable main" >> /etc/apt/sources.list.d/google.list' + - sudo apt-get update + - sudo apt-get --only-upgrade install google-chrome-stable + - google-chrome --version diff --git a/package.json b/package.json new file mode 100644 index 0000000..d648ffa --- /dev/null +++ b/package.json @@ -0,0 +1,52 @@ +{ + "name": "js-libp2p-crypto", + "version": "0.1.0", + "description": "Crypto primitives for libp2p", + "main": "lib/index.js", + "jsnext:main": "src/index.js", + "scripts": { + "lint": "aegir-lint", + "build": "aegir-build", + "test": "aegir-test", + "test:node": "aegir-test --env node", + "test:browser": "aegir-test --env browser", + "release": "aegir-release", + "release-minor": "aegir-release --type minor", + "release-major": "aegir-release --type major", + "coverage": "aegir-coverage", + "coverage-publish": "aegir-coverage publish" + }, + "keywords": [ + "IPFS", + "libp2p", + "crypto", + "rsa" + ], + "author": "Friedel Ziegelmayer ", + "license": "MIT", + "devDependencies": { + "aegir": "^3.0.4", + "chai": "^3.5.0", + "pre-commit": "^1.1.3" + }, + "pre-commit": [ + "lint", + "test" + ], + "engines": { + "node": "^4.0.0" + }, + "repository": { + "type": "git", + "url": "https://github.com/ipfs/js-libp2p-crypto.git" + }, + "bugs": { + "url": "https://github.com/ipfs/js-libp2p-crypto/issues" + }, + "homepage": "https://github.com/ipfs/js-libp2p-crypto", + "dependencies": { + "multihashing": "^0.2.1", + "node-forge": "^0.6.39", + "protocol-buffers": "^3.1.6" + } +} diff --git a/src/crypto.proto b/src/crypto.proto new file mode 100644 index 0000000..345a904 --- /dev/null +++ b/src/crypto.proto @@ -0,0 +1,13 @@ +enum KeyType { + RSA = 0; +} + +message PublicKey { + required KeyType Type = 1; + required bytes Data = 2; +} + +message PrivateKey { + required KeyType Type = 1; + required bytes Data = 2; +} \ No newline at end of file diff --git a/src/index.js b/src/index.js new file mode 100644 index 0000000..e447bf5 --- /dev/null +++ b/src/index.js @@ -0,0 +1,29 @@ +'use strict' + +const keyGenerators = require('./keys') + +exports.utils = require('./utils') + +// Generates a keypair of the given type and bitsize +exports.generateKeyPair = (type, bits) => { + let generator = keyGenerators[type.toLowerCase()] + if (!generator) { + throw new Error('invalid or unsupported key type') + } + + return generator(bits) +} + +// Generates an ephemeral public key and returns a function that will compute +// the shared secret key. +// +// Focuses only on ECDH now, but can be made more general in the future. +exports.generateEphemeralKeyPair = (curveName, cb) => { + throw new Error('Not implemented') +} + +// Generates a set of keys for each party by stretching the shared key. +// (myIV, theirIV, myCipherKey, theirCipherKey, myMACKey, theirMACKey) +exports.keyStretcher = (cipherType, hashType, secret) => { + throw new Error('Not implemented') +} diff --git a/src/keys/index.js b/src/keys/index.js new file mode 100644 index 0000000..3e74b19 --- /dev/null +++ b/src/keys/index.js @@ -0,0 +1,5 @@ +'use strict' + +module.exports = { + rsa: require('./rsa') +} diff --git a/src/keys/rsa.js b/src/keys/rsa.js new file mode 100644 index 0000000..9605738 --- /dev/null +++ b/src/keys/rsa.js @@ -0,0 +1,114 @@ +'use strict' + +const forge = require('node-forge') +const protobuf = require('protocol-buffers') +const fs = require('fs') +const path = require('path') + +const utils = require('../utils') + +const pki = forge.pki +const rsa = pki.rsa + +const pbm = protobuf(fs.readFileSync(path.join(__dirname, '../crypto.proto'))) + +class RsaPublicKey { + constructor (k) { + this._key = k + } + + verify (data, sig) { + const md = forge.md.sha256.create() + md.update(data, 'utf8') + + return this._key.verify(md.digest().bytes(), sig) + } + + marshal () { + return forge.asn1.toDer(pki.privateKeyToAsn1(this._key)).bytes() + } + + get bytes () { + return pbm.PublicKey.encode({ + Type: pbm.KeyType.RSA, + Data: this.marhal() + }) + } + + encrypt (bytes) { + return this._key.encrypt(bytes, 'RSAES-PKCS1-V1_5') + } + + equals (key) { + return this.bytes === key.bytes + } + + hash () { + return utils.keyHash(this.bytes) + } +} + +class RsaPrivateKey { + constructor (privKey, pubKey) { + this._privateKey = privKey + this._publicKey = pubKey + } + + genSecret () { + return forge.random.getBytesSync(16) + } + + sign (message) { + const md = forge.md.sha256.create() + md.update(message, 'utf8') + + return this._privateKey.sign(md) + } + + get public () { + return new RsaPublicKey(this._publicKey) + } + + decrypt (bytes) { + return this._privateKey.decrypt(bytes, 'RSAES-PKCS1-V1_5') + } + + marshal () { + return forge.asn1.toDer(pki.privateKeyToAsn1(this._privateKey)).bytes() + } + + get bytes () { + return pbm.PrivateKey.encode({ + Type: pbm.KeyType.RSA, + Data: this.marshal() + }) + } + + equals (key) { + return this.bytes === key.bytes + } + + hash () { + return utils.keyHash(this.bytes) + } +} + +function unmarshalRsaPrivateKey (bytes) { + const key = pki.privateKeyFromAsn1(forge.asn1.fromDer(bytes)) + + return new RsaPrivateKey(key) +} + +function unmarshalRsaPublicKey (bytes) { + const key = pki.publicKeyFromAsn1(forge.asn1.fromDer(bytes)) + + return new RsaPublicKey(key) +} + +module.exports = function generateRSAKey (bits, cb) { + rsa.generateKeyPair({bits}, (err, keypair) => { + if (err) return cb(err) + + cb(null, new RSAKey(keypair.publicKey, keypair.privateKey)) + }) +} diff --git a/src/utils.js b/src/utils.js new file mode 100644 index 0000000..c3da846 --- /dev/null +++ b/src/utils.js @@ -0,0 +1,13 @@ +'use strict' + +const multihashing = require('multihashing') + +// Check the equality of two keys +exports.keyEqual = (k1, k2) => { + return k1.buffer.equals(k2.buffer) +} + +// Hashes a key +exports.keyHash = (key) => { + return multihashing(key.buffer, 'sha2-256') +} diff --git a/test/index.spec.js b/test/index.spec.js new file mode 100644 index 0000000..0dfc2d5 --- /dev/null +++ b/test/index.spec.js @@ -0,0 +1,20 @@ +/* eslint-env mocha */ +'use strict' + +const expect = require('chai').expect + +const crypto = require('../src') + +describe('libp2p-crypto', () => { + describe('generateKeyPair', () => { + describe('RSA', () => { + it('generates a valid key', () => { + const key = crypto.generateKeyPair('RSA', 2048) + + expect(key).to.have.property('publicKey') + expect(key).to.have.property('privateKey') + expect(key).to.have.property('buffer') + }) + }) + }) +})