Created
July 12, 2011 03:59
-
-
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...
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# | |
# 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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
* | |
* 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/'); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
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...