segment-decrypt.js 2.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131
  1. 'use strict';
  2. const Url = require('url');
  3. const Crypto = require('crypto');
  4. const Pati = require('pati');
  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. };
  16. internals.KeyFetcher.prototype.fetch = function () {
  17. let key = new Buffer(0);
  18. let headers = {};
  19. if (this.cookie) {
  20. headers.Cookie = this.cookie;
  21. }
  22. const dispatcher = new Pati.EventDispatcher(UriStream(this.uri, { headers: headers, whitelist: internals.allowedProtocols, timeout: internals.fetchTimeout }));
  23. dispatcher.on('data', (chunk) => {
  24. key = Buffer.concat([key, chunk]);
  25. });
  26. dispatcher.on('end', () => {
  27. dispatcher.end(key);
  28. });
  29. return dispatcher.finish();
  30. };
  31. internals.KeyFetcher.prototype.get = function () {
  32. if (!this.key) {
  33. this.key = this.fetch();
  34. }
  35. return this.key;
  36. };
  37. internals.fetchKey = function (keyUri, options) {
  38. if (options.key) {
  39. return options.key;
  40. }
  41. const uri = Url.resolve(options.base, keyUri);
  42. let fetcher = internals.keyCache[uri];
  43. if (!fetcher) {
  44. fetcher = internals.keyCache[uri] = new internals.KeyFetcher(uri, options.cookie);
  45. }
  46. return fetcher.get();
  47. };
  48. internals.getIdentityKey = function (keyAttrs) {
  49. for (let idx = 0; idx < keyAttrs.length; idx++) {
  50. let key = keyAttrs[idx];
  51. let keyformat = key.quotedString('keyformat');
  52. if (!keyformat || keyformat === 'identity') {
  53. return {
  54. method: key.enumeratedString('method'),
  55. uri: key.quotedString('uri'),
  56. iv: key.hexadecimalInteger('iv')
  57. };
  58. }
  59. }
  60. return null;
  61. };
  62. exports.decrypt = async function (stream, keyAttrs, options, next) {
  63. if (!keyAttrs || !options) {
  64. return stream;
  65. }
  66. let key = internals.getIdentityKey(keyAttrs);
  67. if (!key || key.method === 'NONE') {
  68. return stream;
  69. }
  70. if (key.method !== 'AES-128' || !key.uri || !key.iv) {
  71. // TODO: hard error when key is not recognized?
  72. throw new Error('unknown encryption parameters');
  73. }
  74. let keyData;
  75. try {
  76. keyData = await internals.fetchKey(key.uri, options);
  77. }
  78. catch (err) {
  79. throw new Error('key fetch failed: ' + (err.stack || err));
  80. }
  81. let decrypt;
  82. try {
  83. decrypt = Crypto.createDecipheriv('aes-128-cbc', keyData, key.iv);
  84. } catch (ex) {
  85. throw new Error('crypto setup failed: ' + (ex.stack || ex));
  86. }
  87. // forward stream errors
  88. stream.on('error', (err) => {
  89. decrypt.emit('error', err);
  90. });
  91. return stream.pipe(decrypt);
  92. };