segment-decrypt.js 3.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145
  1. 'use strict';
  2. const Url = require('url');
  3. const Crypto = require('crypto');
  4. const Oncemore = require('oncemore');
  5. const UriStream = require('uristream');
  6. const 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. let key = new Buffer(0);
  19. let 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', (chunk) => {
  25. key = Buffer.concat([key, chunk]);
  26. })
  27. .once('error', 'end', (err) => {
  28. return next(err, key);
  29. });
  30. };
  31. internals.KeyFetcher.prototype.get = function (next) {
  32. if (this.key && this.key.length) {
  33. return next(null, this.key);
  34. }
  35. const complete = (err, key) => {
  36. if (!err) {
  37. this.key = key;
  38. }
  39. let gets = this._gets;
  40. this._gets = [];
  41. for (let idx = 0; idx < gets.length; idx++) {
  42. process.nextTick(gets[idx], err, key);
  43. }
  44. };
  45. if (this._gets.length === 0) {
  46. this.fetch(complete);
  47. }
  48. return this._gets.push(next);
  49. };
  50. internals.fetchKey = function (keyUri, options, next) {
  51. if (options.key) return next(null, options.key);
  52. let uri = Url.resolve(options.base, keyUri);
  53. let fetcher = internals.keyCache[uri];
  54. if (!fetcher) {
  55. fetcher = internals.keyCache[uri] = new internals.KeyFetcher(uri, options.cookie);
  56. }
  57. return fetcher.get(next);
  58. };
  59. internals.getIdentityKey = function (keyAttrs) {
  60. for (let idx = 0; idx < keyAttrs.length; idx++) {
  61. let key = keyAttrs[idx];
  62. let keyformat = key.quotedString('keyformat');
  63. if (!keyformat || keyformat === 'identity') {
  64. return {
  65. method: key.enumeratedString('method'),
  66. uri: key.quotedString('uri'),
  67. iv: key.hexadecimalInteger('iv')
  68. };
  69. }
  70. }
  71. return null;
  72. };
  73. exports.decrypt = function (stream, keyAttrs, options, next) {
  74. if (!keyAttrs || !options) {
  75. return next(null, stream, false);
  76. }
  77. let key = internals.getIdentityKey(keyAttrs);
  78. if (!key || key.method === 'NONE') {
  79. return next(null, stream, false);
  80. }
  81. if (key.method !== 'AES-128' || !key.uri || !key.iv) {
  82. // TODO: hard error when key is not recognized?
  83. return next(new Error('unknown encryption parameters'), stream);
  84. }
  85. return internals.fetchKey(key.uri, options, (err, keyData) => {
  86. if (err) {
  87. return next(new Error('key fetch failed: ' + (err.stack || err)));
  88. }
  89. let decrypt;
  90. try {
  91. decrypt = Crypto.createDecipheriv('aes-128-cbc', keyData, key.iv);
  92. } catch (ex) {
  93. return next(new Error('crypto setup failed: ' + (ex.stack || ex)));
  94. }
  95. // forward stream errors
  96. stream.on('error', (err) => {
  97. decrypt.emit('error', err);
  98. });
  99. return next(null, stream.pipe(decrypt), true);
  100. });
  101. };