segment-decrypt.js 2.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117
  1. "use strict";
  2. var Url = require('url');
  3. var Crypto = require('crypto');
  4. var Oncemore = require('oncemore');
  5. var UriStream = require('uristream');
  6. var internals = {
  7. allowedProtocols: ['http', 'https', 'data'],
  8. fetchTimeout: 10 * 1000,
  9. keyCache: {},
  10. };
  11. internals.KeyFetcher = function (uri, cookie) {
  12. this.uri = uri;
  13. this.cookie = cookie;
  14. this.key = null;
  15. this._gets = [];
  16. };
  17. internals.KeyFetcher.prototype.fetch = function (next) {
  18. var key = new Buffer(0);
  19. var headers = {};
  20. if (this.cookie) {
  21. headers.Cookie = this.cookie;
  22. }
  23. Oncemore(UriStream(this.uri, { headers: headers, whitelist: internals.allowedProtocols, timeout: internals.fetchTimeout }))
  24. .on('data', function(chunk) {
  25. key = Buffer.concat([key, chunk]);
  26. })
  27. .once('error', 'end', function(err) {
  28. return next(err, key);
  29. });
  30. };
  31. internals.KeyFetcher.prototype.get = function (next) {
  32. var self = this;
  33. if (this.key && this.key.length) {
  34. return next(null, this.key);
  35. }
  36. var complete = function (err, key) {
  37. if (!err) {
  38. self.key = key;
  39. }
  40. var gets = self._gets;
  41. self._gets = [];
  42. for (var idx = 0; idx < gets.length; idx++) {
  43. var _next = gets[idx];
  44. process.nextTick(function() {
  45. _next(err, key);
  46. });
  47. }
  48. };
  49. if (this._gets.length === 0) {
  50. this.fetch(complete);
  51. }
  52. return this._gets.push(next);
  53. };
  54. internals.fetchKey = function (keyUri, options, next) {
  55. if (options.key) return next(null, options.key);
  56. var uri = Url.resolve(options.base, keyUri);
  57. var fetcher = internals.keyCache[uri];
  58. if (!fetcher) {
  59. fetcher = internals.keyCache[uri] = new internals.KeyFetcher(uri, options.cookie);
  60. }
  61. return fetcher.get(next);
  62. };
  63. exports.decrypt = function (stream, keyAttrs, options, next) {
  64. var method = keyAttrs && keyAttrs.enumeratedString('method');
  65. if (!method || method === 'NONE') {
  66. return next(null, stream);
  67. }
  68. if (method !== 'AES-128' || !keyAttrs.quotedString('uri') || !keyAttrs.hexadecimalInteger('iv')) {
  69. // TODO: hard error when key is not recognized?
  70. return next(new Error('unknown encryption parameters'), stream);
  71. }
  72. return internals.fetchKey(keyAttrs.quotedString('uri'), options, function(err, key) {
  73. if (err) {
  74. return next(new Error('key fetch failed: ' + (err.stack || err)));
  75. }
  76. var iv = keyAttrs.hexadecimalInteger('iv');
  77. try {
  78. var decrypt = Crypto.createDecipheriv('aes-128-cbc', key, iv);
  79. } catch (ex) {
  80. return next(new Error('crypto setup failed: ' (ex.stack || ex)));
  81. }
  82. // forward stream errors
  83. stream.on('error', function(err) {
  84. decrypt.emit('error', err);
  85. });
  86. return next(null, stream.pipe(decrypt));
  87. });
  88. };