Skip to content

Commit

Permalink
TritonDataCenter#296 add client encryption support
Browse files Browse the repository at this point in the history
  • Loading branch information
geek committed Jan 12, 2017
1 parent 23cee39 commit e7300d4
Show file tree
Hide file tree
Showing 8 changed files with 804 additions and 6 deletions.
72 changes: 72 additions & 0 deletions lib/cipher_stream.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
// Copyright (c) 2017 Joyent, Inc. All rights reserved.

var util = require('util');
var stream = require('stream');


// Takes cipher + hmac digest stream of data and untangles the two
function CipherStream(hmacType, contentLength, options) {
this._offset = calcOffset(hmacType, contentLength);
this._digest = new Buffer('');
this._bytesRead = 0;
this._contentLength = contentLength;

stream.Transform.call(this, options);
}
util.inherits(CipherStream, stream.Transform);


// Pass the chunks through until you have reached the offset for the hmac
// After the offset is reached, store the chunks in the _digest variable
CipherStream.prototype._transform =
function _transform(chunk, encoding, callback) {

var chunkSize = Buffer.byteLength(chunk);

// Check if we have reached the offset
if ((chunkSize + this._bytesRead) <= this._offset) {
this._bytesRead += chunkSize;
callback(null, chunk);
return;
}

// Get number of bytes to read from the chunk into the cipher stream
var bytesForCipher = this._offset - this._bytesRead;
this._bytesRead += chunkSize;

if (bytesForCipher > 0) {
var cipher = chunk.slice(0, bytesForCipher);
var hmac = chunk.slice(this._offset);
this._digest = Buffer.concat([this._digest, hmac]);

callback(null, cipher);
return;
}

this._digest = Buffer.concat([this._digest, chunk]);

// Mark the stream as processed
if (this._bytesRead === this._contentLength) {
this.push(null);
}

callback();
};


CipherStream.prototype.digest = function digest() {
return (this._digest);
};


module.exports = CipherStream;


function calcOffset(hmacType, contentLength) {
hmacType = hmacType.toLowerCase();
if (hmacType === 'sha256') {
return (contentLength - 44);
}

return (contentLength);
}
42 changes: 38 additions & 4 deletions lib/client.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ var Watershed = require('watershed').Watershed;
var LOMStream = require('lomstream').LOMStream;

var auth = require('smartdc-auth');
var cse = require('./cse');
var jobshare = require('./jobshare');
var Queue = require('./queue');
var trackmarker = require('./trackmarker');
Expand Down Expand Up @@ -171,7 +172,10 @@ function createOptions(opts, userOpts) {
headers: normalizeHeaders(userOpts.headers),
id: id,
path: opts.path.replace(/\/$/, ''),
query: clone(userOpts.query || {})
query: clone(userOpts.query || {}),
cse_keyId: opts.cse_keyId || userOpts.cse_keyId,
cse_key: opts.cse_key || userOpts.cse_key,
cse_cipher: opts.cse_cipher || userOpts.cse_cipher
};

if (userOpts.role)
Expand Down Expand Up @@ -505,6 +509,7 @@ function MantaClient(options) {
assert.optionalString(options.user, 'options.user');
assert.optionalString(options.subuser, 'options.subuser');
assert.optionalArrayOfString(options.role, 'options.role');
assert.optionalFunc(options.cse_getKey, 'options.getKey');
if (options.sign === null)
delete (options.sign);
assert.optionalFunc(options.sign, 'options.sign');
Expand All @@ -522,6 +527,7 @@ function MantaClient(options) {

this.user = options.user;
this.subuser = options.subuser;
this.cse_getKey = options.cse_getKey;

if (options.role) {
options.headers = options.headers || {};
Expand Down Expand Up @@ -813,6 +819,9 @@ MantaClient.prototype.get = function get(p, opts, cb) {
return;
}

var cse_getKey = (typeof (opts.cse_getKey) === 'function') ?
opts.cse_getKey : self.cse_getKey;

if (res.statusCode === 304) {
log.debug('get: 304, returning null stream');
// this should never happen, but just in case it does, ensure
Expand All @@ -827,7 +836,17 @@ MantaClient.prototype.get = function get(p, opts, cb) {

res.pause();

cb(null, stream, res);
var encTypes = res.headers['m-encrypt-type'] ?
res.headers['m-encrypt-type'].split('/') : [];

// Not encrypted, return original file stream
if (encTypes[0] !== 'client' ||
!cse.isSupportedVersion(encTypes[1])) {
cb(null, stream, res);
} else {
cse.decrypt({cse_getKey: cse_getKey}, stream, res, cb);
}


if (length === false &&
res.headers['content-length'] &&
Expand Down Expand Up @@ -1808,6 +1827,8 @@ MantaClient.prototype.mkdirp = function mkdirp(dir, opts, cb) {
* - cb: callback of the form f(err)
*/
MantaClient.prototype.put = function put(p, input, opts, cb) {
var self = this;

assert.string(p, 'path');
assert.stream(input, 'input');
if (typeof (opts) === 'function') {
Expand Down Expand Up @@ -1843,17 +1864,30 @@ MantaClient.prototype.put = function put(p, input, opts, cb) {
parseInt(opts.copies, 10);
}

if (options.headers['content-length'] === undefined)
if (options.headers['content-length'] === undefined) {
options.headers['transfer-encoding'] = 'chunked';
}

options._original_path = p; // needed for mkdirp case

log.debug(options, 'put: entered');
if (options.cse_keyId !== null && options.cse_keyId !== undefined) {
cse.encrypt(options, input, function (err, encrypted) {
if (err) {
cb(err);
return;
}

doPut(self, log, options, encrypted, cb, opts.mkdirs);
});
return;
}

doPut(this, log, options, input, cb, opts.mkdirs);
};


function doPut(self, log, options, input, cb, allowretry) {
log.debug(options, 'put: entered');
self.signRequest({
headers: options.headers
}, function onSignRequest(err) {
Expand Down
3 changes: 2 additions & 1 deletion lib/create_client.js
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,8 @@ function cloneOptions(options) {
user: options.user,
subuser: options.subuser,
role: options.role,
url: options.url
url: options.url,
cse_getKey: options.cse_getKey
});
}

Expand Down
Loading

0 comments on commit e7300d4

Please sign in to comment.