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

Refactor code into a crypto module #85

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
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: 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>=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