From 37edb7d0e432714d54a1dab80a9dec93eade462d Mon Sep 17 00:00:00 2001 From: dignifiedquire Date: Thu, 19 May 2016 18:47:48 +0200 Subject: [PATCH] inital commit --- .gitignore | 35 ++++++++++++++ .npmignore | 34 ++++++++++++++ .travis.yml | 34 ++++++++++++++ LICENSE | 21 +++++++++ README.md | 19 ++++++++ circle.yml | 12 +++++ package.json | 52 +++++++++++++++++++++ src/crypto.proto | 13 ++++++ src/index.js | 29 ++++++++++++ src/keys/index.js | 5 ++ src/keys/rsa.js | 114 +++++++++++++++++++++++++++++++++++++++++++++ src/utils.js | 13 ++++++ test/index.spec.js | 20 ++++++++ 13 files changed, 401 insertions(+) create mode 100644 .gitignore create mode 100644 .npmignore create mode 100644 .travis.yml create mode 100644 LICENSE create mode 100644 README.md create mode 100644 circle.yml create mode 100644 package.json create mode 100644 src/crypto.proto create mode 100644 src/index.js create mode 100644 src/keys/index.js create mode 100644 src/keys/rsa.js create mode 100644 src/utils.js create mode 100644 test/index.spec.js 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') + }) + }) + }) +})