Why would a stream representing an encrypted-then-decrypted file behave differently to a stream representing the original file? - node.js

I create a HTTP server that streams a video file.
http.createServer((req, res) => {
const file = 'example.mp4';
const size = fs.statSync(file).size;
res.writeHead(200, { 'Content-Length': size, 'Content-Type': 'video/mp4' });
fs.createReadStream(file).pipe(res);
}).listen(1911, '127.0.0.1');
I connect to it in my browser or video player to verify that it works. It does.
I encrypt a file:
fs.createReadStream('example.mp4')
.pipe(crypto.createCipher('aes-256-ctr', 'x'))
.pipe(fs.createWriteStream('encrypted_file'));
I decrypt it and play it back to verify that it works. It does.
Yet combining that decryption and streaming in the manner below fails silently.
const decrypt = crypto.createDecipher('aes-256-ctr', 'x');
http.createServer((req, res) => {
const file = 'encrypted_file';
const size = fs.statSync(file).size;
res.writeHead(200, { 'Content-Length': size, 'Content-Type': 'video/mp4' });
fs.createReadStream(file).pipe(decrypt).pipe(res);
}).listen(1911, '127.0.0.1');
The original and the encrypted file are the same size in bytes, and the original and the encrypted-then-decrypted file both have the same SHA-256 hash. Given that, I'd expect fs.createReadStream(original) and fs.createReadStream(encrypted).pipe(decrypt) to behave identically -- yet they don't. No video data is sent to the user, but no error is displayed to them either, and the error event never fires on the http.Server instance.
What am I missing?

Your deciphering code look ok and works in my variant correctly for short file http://techslides.com/demos/sample-videos/small.mp4 in my test (code based on https://gist.github.com/paolorossi/1993068 which is based on Video streaming with HTML 5 via node.js):
var http = require('http'),
fs = require('fs'),
util = require('util'),
crypto = require('crypto');
http.createServer(function (req, res) {
var path = 'encrypted';
var stat = fs.statSync(path);
var total = stat.size;
const decrypt = crypto.createDecipher('aes-256-ctr', 'x');
console.log('ALL: ' + total);
res.writeHead(200, { 'Content-Length': total, 'Content-Type': 'video/mp4' });
fs.createReadStream(path).pipe(decrypt).pipe(res);
}).listen(1912, '127.0.0.1');
console.log('Server running at http://127.0.0.1:1912/');
Your file may be bigger, so it can be useful to print to console request from the client. In my tests both unencrypted and encrypted servers got two requests and sent 383631 bytes.
The differences between my and your variant are: this is my first node.js server (and one of the first js programs) and it is not first for you. And I don't declare decrypt as global const but as local const. By debug prints I see two requests from the browser; and second one tries to modify global const decrypt in your variant with the error:
Server running at http://127.0.0.1:1912/
ALL: 383631
ALL: 383631
events.js:141
throw er; // Unhandled 'error' event
^
Error: write after end
at writeAfterEnd (_stream_writable.js:159:12)
at Decipher.Writable.write (_stream_writable.js:204:5)
at ReadStream.ondata (_stream_readable.js:528:20)
at emitOne (events.js:77:13)
at ReadStream.emit (events.js:169:7)
at readableAddChunk (_stream_readable.js:146:16)
at ReadStream.Readable.push (_stream_readable.js:110:10)
at onread (fs.js:1743:12)
at FSReqWrap.wrapper [as oncomplete] (fs.js:576:17)
So move decrypt inside server code. Your error is reusing of Decipher object which cant be reused (https://nodejs.org/api/crypto.html#crypto_class_decipher "Once the decipher.final() method has been called, the Decipher object can no longer be used to decrypt data."); and on second call decipher may try to decode file with wrong (non-zero) counter value.

Related

Websocket 'Sec-WebSocket-Accept' header mismatch between ReactJS client and Node.js server

I'm writing an application using WebSockets with a React client on port 8080 (run using Webpack devServer) and Node server and sockets on port 5000. However, the initial handshake always fails with an error: WebSocket connection to 'ws://localhost:5000/' failed: Error during WebSocket handshake: Incorrect 'Sec-WebSocket-Accept' header value
To make sure, I check the request and response of the React app using Chrome devtools, and see the following:
While on my Node server, I logged the sec-websocket-accept header accept key, as well as the headers for my response, and got the following:
It looks like that indeed, the keys don't match. In fact, they don't seem to be the same keys at all. Is there something in between the React client and Node server (like the Webpack devserver that I'm using for React) that's changing them?
My React code:
componentDidMount(){
this.socket = new WebSocket('ws://localhost:5000', ['json']);
this.socket.onerror = err => {
console.log(err)
}
this.socket.onmessage = e => {
let res = JSON.parse(e.data);
console.log(e, res);
let copyArr = [...this.state.message]
copyArr.push(res);
this.setState({
message: copyArr
});
}
}
My Node.js code:
const server = http.createServer();
server.on('upgrade', (req, socket) => {
if(req.headers['upgrade'] !== "websocket"){
socket.end('HTTP/1.1 400 Bad Request');
return;
}
const acceptKey = req.headers['sec-websocket-key'];
const acceptHash = generateValue(acceptKey);
console.log('accepkey', acceptKey, 'hash', acceptHash);
const resHeaders = [ 'HTTP/1.1 101 Web Socket Protocol Handshake', 'Upgrade: WebSocket', 'Connection: Upgrade', `Sec-WebSocket-Accept: ${acceptHash}` ];
console.log(resHeaders);
let protocols = req.headers['sec-websocket-protocol'];
protocols = !protocols ? [] : protocols.split(',').map(name => name.trim());
if(protocols.includes('json')){
console.log('json here');
resHeaders.push(`Sec-WebSocket-Protocol: json`);
}
socket.write(resHeaders.join('\r\n') + '\r\n\r\n');
})
function generateValue(key){
return crypto
.createHash('sha1')
.update(key + '258EAFA5-E914–47DA-95CA-C5AB0DC85B11', 'binary')
.digest('base64');
}
The correct Accept hash for a key of 'S1cb73xifMvqiIpMjvBabg==' is 'R35dUOuC/ldiVp1ZTchRsiHUnvo='.
Your generateValue() function calculates an incorrect hash because it has an incorrect character in the GUID string '258EAFA5-E914–47DA-95CA-C5AB0DC85B11'. If you look very carefully you'll see that the second dash, in '...14–47...', is different from the other dashes. It should be a plain ASCII dash or hyphen with character code 45, but in fact it is a Unicode en-dash with character code 8211. That different character code throws off the calculation.
Fixing that character will make your WebSocket client much happier.
For anyone wondering, the culprits causing the issues, in my case, were the extra new lines and returns I added after writing my headers in my Node.js server. Taking them out and doing:
socket.write(resHeaders.join('\r\n'));
instead of:
socket.write(resHeaders.join('\r\n') + '\r\n\r\n');
solved the handshake mismatch for me.

nodejs url parse returns an extra undefined object

I'm writing a node js script to pull variables from the url line.
var http = require('http');//http package
var url = require('url');// url package
var server = http.createServer(function(req, res){// creates server
res.writeHead(200, {'Content-Type': 'text/html'});
var q = url.parse(req.url, true).query//pulls apart the url
var temp = q.temp;
res.write(temp);
console.log(q.temp);
res.end();
}).listen(7474)
Whenever it's tested, the script returns an extra variable of some kind. If I feed it http://localhost:7474/?temp=29 I get:
29
undefined
inside of my console. For some of my other functions in this script it causes the whole system to crash. And failure as
The first argument must be one of type string or Buffer. Received type undefined
Why is that? And how do I remedy the situation?
You should use res.write() again followed by res.end(). The latter isn't supposed to return data.
Though it can supposedly return a string value in the first argument and then define the encoding format of the string as the second argument, but I've never tried that...
"If data is specified, it is equivalent to calling response.write(data, encoding) followed by response.end(callback)."
https://nodejs.org/api/http.html#http_response_end_data_encoding_callback
var http = require('http');//http package
var url = require('url');// url package
var list = ['0', '0', '1', '0'];
var server = http.createServer(function(req, res){// creates server
res.writeHead(200, {'Content-Type': 'text/html'});
var q = url.parse(req.url, true).query//pulls apart the url
var temp = q.temp;
res.write(temp);
console.log(q.temp);
res.write(list[2] ); //testing artifact displayes them on a browser
res.end();
}).listen(7474)
In you case there is really no reason to have both calls to .write() so I would just combine them...
res.write(JSON.stringify([ temp, list[2] ]));
Or
res.write(JSON.stringify({ temp, listItem:list[2] }));
EDIT:
After running this code myself you have a different issue here, which is giving you those confusing errors. Try this for now ...
var http = require('http');//http package
var url = require('url');// url package
var list = ['0', '0', '1', '0'];
var server = http.createServer(function(req, res){// creates server
if (req.url === '/favicon.ico') {
res.writeHead(204);
res.end();
} else {
res.writeHead(200, {'Content-Type': 'text/html'});
var q = url.parse(req.url, true).query//pulls apart the url
var temp = q.temp;
res.write(temp);
res.write(list[2]); //testing artifact displayes them on a browser
res.end();
}
}).listen(7474)
server.on('listening', function() {
console.log('listening on port 7474')
});
The browser was making a 'default' request to favicon.ico after the page initial page loaded to do just that-load the favicon for each web page. That request will crash your program because it doesn't include the query parameter `?temp=, which you aren't currently checking for.
The solution I provided is not workable solution for a production application, but this is clearly a little demo project, so it should do the job for your use case.
If you want to develop a larger, stable, production application you are going to have to reorganize things quite a bit. This could be a starting point if you don't want to use a framework...
https://adityasridhar.com/posts/how-to-use-nodejs-without-frameworks-and-external-libraries
I use the Express framework for all my node apps, which makes some of the default configuration faster and there are thousands of tutorials online to get you started...
https://medium.com/#purposenigeria/build-a-restful-api-with-node-js-and-express-js-d7e59c7a3dfb
I just Googled these tutorials as an example, but you should do your own research to find the most suitable.

How do I intercept outgoing tcp messages in node?

How can I write a simple stream which intercepts messages?
For example, say I want to log (or eventually transform) a message being sent over the wire by a user's socket.write(...) call.
Following is a minimal program which attempts to do this:
const net = require('net');
const stream = require('stream');
const socket = new net.Socket();
const transformer = new stream.Transform({
transform(chunk,e,cb){
console.log("OUT:"+chunk.toString());
cb();
}});
//const client = socket.pipe(transformer); // <= prints "OUT:" on client, but nothing on server
const client = transformer.pipe(socket); // <= prints nothing on client, but "hello world" on server
socket.on('data', (data)=>{ console.log("IN:"+data.toString()); });
socket.connect(1234, 'localhost', ()=>{ client.write("hello world"); });
When I do socket.pipe(transformer), the client prints "OUT:" (like I want), but doesn't actually send anything to the server. When I swap the pipe locations, transformer.pipe(socket), nothing gets printed to the client but the message gets sent to the server.
Although not listed here, I also tried to use the Writable stream, which does print the message on the client, but it is never sent to the server (if I do a this.push(...) inside the Writable stream, it still doesn't seem to send to the server)
What am I missing here?
EDIT: Reformatted the code for clarity and updated the text
It looks like I needed to change the following line
socket.connect(1234, 'localhost', ()=>{ client.write("hello world"); });
to this
socket.connect(1234, 'localhost', ()=>{ transformer.write("hello world"); });
This is based on #Mr.Phoenix's comment. I expected .pipe() to return a new stream which I could use. I believe that is how Java's netty framework does it and I kept expecting node streams to work the same way.
You're not writing any data out of the stream.
You need to either this.push(chunk) or change the call to cb to cb(null, chunk).
See the docs about implementing transform streams for more info.

Node.js - Render HTML as image or PDF. Works locally, falling over on the server

I'm dabbling with Node and trying to get a basic web server setup that'll accept HTML and return either a PDF or an image depending on the route used.
The below works when running it on my local machine. I've popped it onto two different servers, one using Apache and the other using Nginx. In both of those it's failing to return an image or a PDF. The PDF route returns a 502 and the image route returns an empty image.
It's possible I'm going about something the wrong way, but I'm somewhat at a loss right now as to what I'm missing. Any pointers would be greatly appreciated.
var url = require('url');
var http = require('http');
var wkhtmltox = require('wkhtmltox');
var converter = new wkhtmltox();
// Locations of the binaries can be specified, but this is
// only needed if the programs are located outside your PATH
// converter.wkhtmltopdf = '/opt/local/bin/wkhtmltopdf';
// converter.wkhtmltoimage = '/opt/local/bin/wkhtmltoimage';
http.createServer(function (req, res) {
console.log(url.parse(req.url, true).query);
if (req.url == "/pdf") {
res.writeHead(200, {'Content-Type': 'application/pdf'});
converter.pdf(req, url.parse(req.url, true).query).pipe(res);
} else if (req.url == "/image") {
res.writeHead(200, {'Content-Type': 'image/png'});
converter.image(req, { format: "png" , quality: 75 }).pipe(res);
} else {
res.end('Control is an illusion.');
}
}).listen(1337, '127.0.0.1');
console.log('Server running at http://127.0.0.1:1337/');
This is the error logged on the server for the PDF route. No error is logged for the image route.
Error: This socket has been ended by the other party
at Socket.writeAfterFIN [as write] (net.js:285:12)
at performWork (/var/www/app_name/node_modules/wkhtmltox/lib/wkhtmltox.js:98:22)
at wkhtmltox.pdf (/var/www/app_name/node_modules/wkhtmltox/lib/wkhtmltox.js:113:16)
at Server.<anonymous> (/var/www/app_name/index.js:16:14)
at emitTwo (events.js:106:13)
at Server.emit (events.js:191:7)
at HTTPParser.parserOnIncoming [as onIncoming] (_http_server.js:543:12)
at HTTPParser.parserOnHeadersComplete (_http_common.js:105:23)
I was testing using curl.
curl -d #file_to_render.html -s "http://localhost:1337/pdf" -o test.pdf
curl -d #file_to_render.html -s "http://localhost:1337/image" -o test.png
Please try this command I have resolved it by this command
sudo apt-get install libfontconfig
Try add onError event to pipe
converter.image(req, { format: "png" , quality: 75 }).pipe(res).on('error', function(e){ console.log(e); });

crypto.decipher causes stream not to close in node js

I'm trying to decrypt a file and send it in a response to a client. It works fine for just downloading the file, like this:
input.pipe(res);
but when I add the decipher into the pipe, like this:
input.pipe(decipher).pipe(res);
It causes the file download to stay open in the browser. Do I need to close the decipher stream or something?
Here's the full method:
router.get('/', function(req, res, next) {
var filePath = 'C:\\Users\\Anthony\\test';
var stat = fs.statSync(filePath);
var key = '1234asdf';
var decipher = crypto.createDecipher('aes-256-cbc', key)
res.setHeader('Content-Length', stat.size);
res.setHeader('Content-disposition', 'attachment; filename=test.mp4');
var input = fs.createReadStream(filePath);
input.pipe(decipher).pipe(res);
});
Most likely what is happening is that you're giving the browser the encoded file length and not the decrypted file length, which may be different. You could try omitting the Content-Length header entirely and see if that works (this will cause chunked encoding to be used instead).

Resources