segment-decrypt.js 3.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135
  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. process.nextTick(gets[idx], err, key);
  44. }
  45. };
  46. if (this._gets.length === 0) {
  47. this.fetch(complete);
  48. }
  49. return this._gets.push(next);
  50. };
  51. internals.fetchKey = function (keyUri, options, next) {
  52. if (options.key) return next(null, options.key);
  53. var uri = Url.resolve(options.base, keyUri);
  54. var fetcher = internals.keyCache[uri];
  55. if (!fetcher) {
  56. fetcher = internals.keyCache[uri] = new internals.KeyFetcher(uri, options.cookie);
  57. }
  58. return fetcher.get(next);
  59. };
  60. internals.getIdentityKey = function (keyAttrs) {
  61. for (let idx = 0; idx < keyAttrs.length; idx++) {
  62. let key = keyAttrs[idx];
  63. let keyformat = key.quotedString('keyformat');
  64. if (!keyformat || keyformat === 'identity') {
  65. return {
  66. method: key.enumeratedString('method'),
  67. uri: key.quotedString('uri'),
  68. iv: key.hexadecimalInteger('iv')
  69. };
  70. }
  71. }
  72. return null;
  73. };
  74. exports.decrypt = function (stream, keyAttrs, options, next) {
  75. if (!keyAttrs || !options) {
  76. return next(null, stream, false);
  77. }
  78. let key = internals.getIdentityKey(keyAttrs);
  79. if (!key || key.method === 'NONE') {
  80. return next(null, stream, false);
  81. }
  82. if (key.method !== 'AES-128' || !key.uri || !key.iv) {
  83. // TODO: hard error when key is not recognized?
  84. return next(new Error('unknown encryption parameters'), stream);
  85. }
  86. return internals.fetchKey(key.uri, options, (err, keyData) => {
  87. if (err) {
  88. return next(new Error('key fetch failed: ' + (err.stack || err)));
  89. }
  90. let decrypt;
  91. try {
  92. decrypt = Crypto.createDecipheriv('aes-128-cbc', keyData, key.iv);
  93. } catch (ex) {
  94. return next(new Error('crypto setup failed: ' + (ex.stack || ex)));
  95. }
  96. // forward stream errors
  97. stream.on('error', (err) => {
  98. decrypt.emit('error', err);
  99. });
  100. return next(null, stream.pipe(decrypt), true);
  101. });
  102. };