Skip to content

Instantly share code, notes, and snippets.

@taf2
Created July 12, 2011 03:59
Show Gist options
  • Save taf2/1077369 to your computer and use it in GitHub Desktop.
Save taf2/1077369 to your computer and use it in GitHub Desktop.
A node.js proxy for decrypting encrypted files on the fly by first requesting authorization and then using the retrieved decryption key to decode a remote file while serving it to the client...
#
# securely stream call audio files to the end user
# we store the audio files on s3, it makes storage less expensive and easier to manage
# the cost is when users fetch these files because we need to decrypt them we'll be streaming them
# through this server to decrypt, given their decryption key.
#
# ruby secure_call_stream.rb -e prod -sv
#
require 'goliath'
require 'em-http'
require 'openssl'
require 'yaml'
CONF = YAML.load_file(File.expand_path(File.join(File.dirname(__FILE__),'config.yml')))[(ENV['RACK_ENV'] || 'development')]
class SecureCallStream < Goliath::API
use Goliath::Rack::Params
use Goliath::Rack::Validation::RequestMethod, %w(GET)
def on_headers(env, headers)
env.logger.info 'proxying new request: ' + headers.inspect
env['client-headers'] = headers
end
def response(env)
#src_url = 'http://ct-stage3.s3.amazonaws.com/test.enc.wav'
puts env.params.inspect
http = EM::HttpRequest.new(CONF['endpoint'] + "/calls/secure_access/#{env.params['cid']}.json?token=#{env.params['token']}" )
# set http headers e.g. Cookie from env['client-headers']
http.get
http.callback do
# parse the response and start the audio download
audio_info = JSON.parse(http.response)
cipher = OpenSSL::Cipher::Cipher.new('aes-256-cbc')
cipher.decrypt
cipher.key = Base64.decode64(audio_info['key'])
cipher.iv = Base64.decode64(audio_info['iv'])
http = EM::HttpRequest.new(audio_info['audio_url']).get
http.stream do |chunk|
decrypted_chunk = cipher.update(chunk)
env.chunked_stream_send(decrypted_chunk)
end
http.callback do
decrypted_data = cipher.final
env.chunked_stream_send(decrypted_chunk) if decrypted_chunk && decrypted_chunk.size > 0
env.chunked_stream_close
end
http.errback { puts "error" }
end
chunked_streaming_response(200,{'Content-Type' => 'audio/wav'})
end
end
/*
*
* Streaming server to decrypt recorded calls on the fly.
*
* To start a user sends an HTTPS encrypted request with their decryption password
*
* We send a init request to the rails application asking it to send us the decryption keys for the specific requested call
*
*/
var http = require('http'),
querystring = require('querystring'),
crypto = require('crypto'),
base64 = require('base64'),
URL = require('url'),
config = require(__dirname + '/config')[(process.env.NODE_ENV || 'development')];
// setup the forward request options
var AccessOptions = {
host: config.host,
port: config.port,
path: function(callid, passtoken) { return '/calls/secure_access' }
}
var app = http.createServer(function (req, res) {
var params = querystring.parse(req.url.split('?')[1]);
var format = params.format == 'wav' ? 'audio/wav' : 'audio/mp3';
// send access request
var opts = {host: AccessOptions.host, port: AccessOptions.port, path: AccessOptions.path(params.cid, params.token)};
http.get(opts, function(response) {
var messageBuffer = new Buffer(128); // smallish for testing
var bufferSize = 0;
response.on('data', function(chunk) {
if ((bufferSize + chunk.length) > messageBuffer.length) {
var biggerBuffer = new Buffer( (bufferSize+chunk.length) * 2 );
messageBuffer.copy(biggerBuffer);
delete messageBuffer;
messageBuffer = biggerBuffer;
}
chunk.copy(messageBuffer, bufferSize);
bufferSize += chunk.length;
});
response.on('end', function() {
//console.log(messageBuffer.toString('utf8', 0, bufferSize));
var message = JSON.parse(messageBuffer.toString('utf8', 0, bufferSize));
var decipher = crypto.createDecipheriv('aes-256-cbc', base64.decode(message.key), base64.decode(message.iv));
var uri = URL.parse(message.audio_url);
var opts = {host: uri.hostname, port: uri.port, path: uri.pathname};
var request = http.get(opts, function(response) {
res.writeHead(200, {'Content-Type': format});
response.on('data', function(chunk) {
res.write(decipher.update(chunk, 'binary', 'binary'), 'binary');
});
response.on('end', function() {
res.end(decipher.final('binary'), 'binary');
});
});
});
});
});
module.exports = exports = app;
if (!module.parent) {
app.listen(1337, "127.0.0.1");
process.on('SIGINT', process.exit.bind(process));
console.log('Server running at http://127.0.0.1:1337/');
}
@taf2
Copy link
Author

taf2 commented Jul 12, 2011

The goliath version is less complete and has a dependency on base64 encoding in away i believe is unnecessary, but haven't explored in more detail...

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment