Skip to content

Commit

Permalink
Add crypto module
Browse files Browse the repository at this point in the history
  • Loading branch information
Thomas Parisot committed Feb 3, 2019
1 parent ee08c35 commit 5f49392
Show file tree
Hide file tree
Showing 8 changed files with 257 additions and 121 deletions.
58 changes: 48 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,23 +2,23 @@

> crx is a utility to **package Google Chrome extensions** via a *Node API* and the *command line*. It is written **purely in JavaScript** and **does not require OpenSSL**!
Massive hat tip to the [node-rsa project](https://npmjs.com/node-rsa) for the pure JavaScript encryption!

**Compatibility**: this extension is compatible with `node>=0.10`.

Packages are available to use `crx` with:

- *grunt*: [grunt-crx](https://npmjs.com/grunt-crx)
- *gulp*: [gulp-crx-pack](https://npmjs.com/gulp-crx-pack)
- *webpack*: [crx-webpack-plugin](https://npmjs.com/crx-webpack-plugin)

Massive hat tip to the [node-rsa project](https://npmjs.com/node-rsa) for the pure JavaScript encryption!

**Compatibility**: this extension is compatible with `node>=10`.

## Install

```bash
$ npm install crx
```

## Module API
## crx API

Asynchronous functions returns a native ECMAScript Promise.

Expand Down Expand Up @@ -46,12 +46,17 @@ crx.load( path.resolve(__dirname, './myExtension') )
});
```

### ChromeExtension = require("crx")
### crx = new ChromeExtension(attrs)
### `ChromeExtension(options)`

This module exports the `ChromeExtension` constructor directly, which can take an optional attribute object, which is used to extend the instance.

### crx.load(path|files)
```js
var ChromeExtension = require("crx");

crx = new ChromeExtension({ ... });
```

### `crx.load(path|files)`

Prepares the temporary workspace for the Chrome Extension located at `path` — which is expected to directly contain `manifest.json`.

Expand All @@ -69,7 +74,7 @@ crx.load(['/my/extension/manifest.json', '/my/extension/background.json']).then(
});
```

### crx.pack()
### `crx.pack()`

Packs the Chrome Extension and resolves the promise with a Buffer containing the `.crx` file.

Expand All @@ -81,7 +86,7 @@ crx.load('/path/to/extension')
});
```

### crx.generateUpdateXML()
### `crx.generateUpdateXML()`

Returns a Buffer containing the update.xml file used for `autoupdate`, as specified for `update_url` in the manifest. In this case, the instance must have a property called `codebase`.

Expand All @@ -103,6 +108,39 @@ Generates application id (extension id) from given path.

```js
new crx().generateAppId('/path/to/ext') // epgkjnfaepceeghkjflpimappmlalchn

## crypto API

### `generateAppId(publicKey)`

```js
const crypto = require('crx/crypto');
```

### `generateAppIdFromPath(path)`

```js
const crypto = require('crx/crypto');
```

### `generatePrivateKey()`

```js
const crypto = require('crx/crypto');
```

### `generatePublicKey(privateKey[, format])`

```js
const crypto = require('crx/crypto');
```

## CLI API
Expand Down
40 changes: 10 additions & 30 deletions bin/crx.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@

var path = require("path");
var fs = require("fs");
var rsa = require("node-rsa");
var {promisify} = require("util");
var writeFile = promisify(fs.writeFile);
var readFile = promisify(fs.readFile);
var {generatePrivateKey} = require("../crypto");

var program = require("commander");
var ChromeExtension = require("..");
Expand Down Expand Up @@ -44,16 +44,6 @@ program
program.parse(process.argv);


/**
* Generate a new key file
* @param {String} keyPath path of the key file to create
* @returns {Promise}
*/
function generateKeyFile(keyPath) {
return Promise.resolve(new rsa({ b: 2048 }))
.then(key => key.exportKey("pkcs1-private-pem"))
.then(keyVal => writeFile(keyPath, keyVal));
}

function keygen(dir, program) {
dir = dir ? resolve(cwd, dir) : cwd;
Expand All @@ -65,7 +55,7 @@ function keygen(dir, program) {
throw new Error("key.pem already exists in the given location.");
}

generateKeyFile(keyPath);
generatePrivateKey().then(privateKey => writeFile(keyPath, privateKey));
});
}

Expand Down Expand Up @@ -96,26 +86,16 @@ function pack(dir, program) {
}
}

var crx = new ChromeExtension({
rootDirectory: input,
maxBuffer: program.maxBuffer
});

readFile(keyPath)
.then(null, function(err) {
// If the key file doesn't exist, create one
if (err.code === "ENOENT") {
return generateKeyFile(keyPath);
} else {
throw err;
}
})
.then(function(key) {
crx.privateKey = key;
.then(function(privateKey) {
return new ChromeExtension({
rootDirectory: input,
maxBuffer: program.maxBuffer,
privateKey
});
})
.then(function() {
crx
.load()
.then(function(crx) {
crx.load()
.then(() => crx.loadContents())
.then(function(fileBuffer) {
if (program.zipOutput) {
Expand Down
98 changes: 98 additions & 0 deletions crypto.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
"use strict";

var RSA = require("node-rsa");
var crypto = require("crypto");

/**
* Generate an AppId from a public key
*
* @param {String|Buffer} content Public Key content
* @return {String} AppId
*/
function generateAppId (content) {
if (typeof content !== 'string' && !(content instanceof Buffer)) {
throw new Error('Public key is neither set, nor given');
}

return crypto
.createHash("sha256")
.update(content)
.digest()
.toString("hex")
.split("")
.map(x => (parseInt(x, 16) + 0x0a).toString(26))
.join("")
.slice(0, 32);
}

/**
* Generate an AppId from a filesystem path
*
* @param {String} path Unix or Windows path to the folder containing the manifest.json
* @return {String} AppId
*/
function generateAppIdFromPath (path) {
var charCode = path.charCodeAt(0);

// Handling Windows Path
// 65 (A) < charCode < 122 (z)
if (charCode >= 65 && charCode <= 122 && path[1] === ':') {
path = Buffer.from(
path[0].toUpperCase() + path.slice(1),
"utf-16le"
);
}

return generateAppId(path);
}

/**
* [generatePrivateKey description]
* @return {Promise} [description]
*/
function generatePrivateKey () {
return Promise.resolve(new RSA({ b: 2048 }))
.then(key => key.exportKey("pkcs1-private-pem"));
}

/**
* [generatePublicKey description]
* @param {Buffer} privateKey [description]
* @param {String} format [description]
* @return {String|Buffer} [description]
*/
function generatePublicKey (privateKey, format) {
var ALLOWED_FORMATS = ['der', 'pem'];

return new Promise(function(resolve, reject){
if (!privateKey) {
return reject('Impossible to generate a public key: privateKey option has not been defined or is empty.');
}

if (format && ALLOWED_FORMATS.indexOf(format) === -1) {
return reject('Allowed public key formats are "der" (default) or "pem".');
}

var key = new RSA(privateKey);

resolve(key.exportKey('pkcs8-public-' + (format || 'der')));
});
};

/**
* [sign description]
* @param {Any} content [description]
* @param {Buffer|String} privateKey [description]
* @return {Buffer} [description]
*/
function sign (content, privateKey) {
return crypto.createSign("sha1").update(content).sign(privateKey);
}

module.exports = {
generateAppId: generateAppId,
generateAppIdFromPath: generateAppIdFromPath,
generatePublicKey: generatePublicKey,
generatePrivateKey: generatePrivateKey,
sign: sign,
}
Loading

0 comments on commit 5f49392

Please sign in to comment.