Node-Red configuration
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

writer.js 7.1KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311
  1. var assert = require('assert');
  2. var ASN1 = require('./types');
  3. var errors = require('./errors');
  4. ///--- Globals
  5. var InvalidAsn1Error = errors.InvalidAsn1Error;
  6. var DEFAULT_OPTS = {
  7. size: 1024,
  8. growthFactor: 8
  9. };
  10. ///--- Helpers
  11. function merge(from, to) {
  12. assert.ok(from);
  13. assert.equal(typeof(from), 'object');
  14. assert.ok(to);
  15. assert.equal(typeof(to), 'object');
  16. var keys = Object.getOwnPropertyNames(from);
  17. keys.forEach(function(key) {
  18. if (to[key])
  19. return;
  20. var value = Object.getOwnPropertyDescriptor(from, key);
  21. Object.defineProperty(to, key, value);
  22. });
  23. return to;
  24. }
  25. ///--- API
  26. function Writer(options) {
  27. options = merge(DEFAULT_OPTS, options || {});
  28. this._buf = Buffer.alloc(options.size || 1024);
  29. this._size = this._buf.length;
  30. this._offset = 0;
  31. this._options = options;
  32. // A list of offsets in the buffer where we need to insert
  33. // sequence tag/len pairs.
  34. this._seq = [];
  35. }
  36. Object.defineProperty(Writer.prototype, 'buffer', {
  37. get: function () {
  38. if (this._seq.length)
  39. throw new InvalidAsn1Error(this._seq.length + ' unended sequence(s)');
  40. return (this._buf.slice(0, this._offset));
  41. }
  42. });
  43. Writer.prototype.writeByte = function(b) {
  44. if (typeof(b) !== 'number')
  45. throw new TypeError('argument must be a Number');
  46. this._ensure(1);
  47. this._buf[this._offset++] = b;
  48. };
  49. Writer.prototype.writeInt = function(i, tag) {
  50. if (!Number.isInteger(i))
  51. throw new TypeError('argument must be an integer');
  52. if (typeof(tag) !== 'number')
  53. tag = ASN1.Integer;
  54. let bytes = [];
  55. while (i < -0x80 || i >= 0x80) {
  56. bytes.push(i & 0xff);
  57. i = Math.floor(i / 0x100);
  58. }
  59. bytes.push(i & 0xff);
  60. this._ensure(2 + bytes.length);
  61. this._buf[this._offset++] = tag;
  62. this._buf[this._offset++] = bytes.length;
  63. while (bytes.length) {
  64. this._buf[this._offset++] = bytes.pop();
  65. }
  66. };
  67. Writer.prototype.writeNull = function() {
  68. this.writeByte(ASN1.Null);
  69. this.writeByte(0x00);
  70. };
  71. Writer.prototype.writeEnumeration = function(i, tag) {
  72. if (typeof(i) !== 'number')
  73. throw new TypeError('argument must be a Number');
  74. if (typeof(tag) !== 'number')
  75. tag = ASN1.Enumeration;
  76. return this.writeInt(i, tag);
  77. };
  78. Writer.prototype.writeBoolean = function(b, tag) {
  79. if (typeof(b) !== 'boolean')
  80. throw new TypeError('argument must be a Boolean');
  81. if (typeof(tag) !== 'number')
  82. tag = ASN1.Boolean;
  83. this._ensure(3);
  84. this._buf[this._offset++] = tag;
  85. this._buf[this._offset++] = 0x01;
  86. this._buf[this._offset++] = b ? 0xff : 0x00;
  87. };
  88. Writer.prototype.writeString = function(s, tag) {
  89. if (typeof(s) !== 'string')
  90. throw new TypeError('argument must be a string (was: ' + typeof(s) + ')');
  91. if (typeof(tag) !== 'number')
  92. tag = ASN1.OctetString;
  93. var len = Buffer.byteLength(s);
  94. this.writeByte(tag);
  95. this.writeLength(len);
  96. if (len) {
  97. this._ensure(len);
  98. this._buf.write(s, this._offset);
  99. this._offset += len;
  100. }
  101. };
  102. Writer.prototype.writeBuffer = function(buf, tag) {
  103. if (!Buffer.isBuffer(buf))
  104. throw new TypeError('argument must be a buffer');
  105. // If no tag is specified we will assume `buf` already contains tag and length
  106. if (typeof(tag) === 'number') {
  107. this.writeByte(tag);
  108. this.writeLength(buf.length);
  109. }
  110. if ( buf.length > 0 ) {
  111. this._ensure(buf.length);
  112. buf.copy(this._buf, this._offset, 0, buf.length);
  113. this._offset += buf.length;
  114. }
  115. };
  116. Writer.prototype.writeStringArray = function(strings, tag) {
  117. if (! (strings instanceof Array))
  118. throw new TypeError('argument must be an Array[String]');
  119. var self = this;
  120. strings.forEach(function(s) {
  121. self.writeString(s, tag);
  122. });
  123. };
  124. // This is really to solve DER cases, but whatever for now
  125. Writer.prototype.writeOID = function(s, tag) {
  126. if (typeof(s) !== 'string')
  127. throw new TypeError('argument must be a string');
  128. if (typeof(tag) !== 'number')
  129. tag = ASN1.OID;
  130. if (!/^([0-9]+\.){0,}[0-9]+$/.test(s))
  131. throw new Error('argument is not a valid OID string');
  132. function encodeOctet(bytes, octet) {
  133. if (octet < 128) {
  134. bytes.push(octet);
  135. } else if (octet < 16384) {
  136. bytes.push((octet >>> 7) | 0x80);
  137. bytes.push(octet & 0x7F);
  138. } else if (octet < 2097152) {
  139. bytes.push((octet >>> 14) | 0x80);
  140. bytes.push(((octet >>> 7) | 0x80) & 0xFF);
  141. bytes.push(octet & 0x7F);
  142. } else if (octet < 268435456) {
  143. bytes.push((octet >>> 21) | 0x80);
  144. bytes.push(((octet >>> 14) | 0x80) & 0xFF);
  145. bytes.push(((octet >>> 7) | 0x80) & 0xFF);
  146. bytes.push(octet & 0x7F);
  147. } else {
  148. bytes.push(((octet >>> 28) | 0x80) & 0xFF);
  149. bytes.push(((octet >>> 21) | 0x80) & 0xFF);
  150. bytes.push(((octet >>> 14) | 0x80) & 0xFF);
  151. bytes.push(((octet >>> 7) | 0x80) & 0xFF);
  152. bytes.push(octet & 0x7F);
  153. }
  154. }
  155. var tmp = s.split('.');
  156. var bytes = [];
  157. bytes.push(parseInt(tmp[0], 10) * 40 + parseInt(tmp[1], 10));
  158. tmp.slice(2).forEach(function(b) {
  159. encodeOctet(bytes, parseInt(b, 10));
  160. });
  161. var self = this;
  162. this._ensure(2 + bytes.length);
  163. this.writeByte(tag);
  164. this.writeLength(bytes.length);
  165. bytes.forEach(function(b) {
  166. self.writeByte(b);
  167. });
  168. };
  169. Writer.prototype.writeLength = function(len) {
  170. if (typeof(len) !== 'number')
  171. throw new TypeError('argument must be a Number');
  172. this._ensure(4);
  173. if (len <= 0x7f) {
  174. this._buf[this._offset++] = len;
  175. } else if (len <= 0xff) {
  176. this._buf[this._offset++] = 0x81;
  177. this._buf[this._offset++] = len;
  178. } else if (len <= 0xffff) {
  179. this._buf[this._offset++] = 0x82;
  180. this._buf[this._offset++] = len >> 8;
  181. this._buf[this._offset++] = len;
  182. } else if (len <= 0xffffff) {
  183. this._buf[this._offset++] = 0x83;
  184. this._buf[this._offset++] = len >> 16;
  185. this._buf[this._offset++] = len >> 8;
  186. this._buf[this._offset++] = len;
  187. } else {
  188. throw new InvalidAsn1Error('Length too long (> 4 bytes)');
  189. }
  190. };
  191. Writer.prototype.startSequence = function(tag) {
  192. if (typeof(tag) !== 'number')
  193. tag = ASN1.Sequence | ASN1.Constructor;
  194. this.writeByte(tag);
  195. this._seq.push(this._offset);
  196. this._ensure(3);
  197. this._offset += 3;
  198. };
  199. Writer.prototype.endSequence = function() {
  200. var seq = this._seq.pop();
  201. var start = seq + 3;
  202. var len = this._offset - start;
  203. if (len <= 0x7f) {
  204. this._shift(start, len, -2);
  205. this._buf[seq] = len;
  206. } else if (len <= 0xff) {
  207. this._shift(start, len, -1);
  208. this._buf[seq] = 0x81;
  209. this._buf[seq + 1] = len;
  210. } else if (len <= 0xffff) {
  211. this._buf[seq] = 0x82;
  212. this._buf[seq + 1] = len >> 8;
  213. this._buf[seq + 2] = len;
  214. } else if (len <= 0xffffff) {
  215. this._shift(start, len, 1);
  216. this._buf[seq] = 0x83;
  217. this._buf[seq + 1] = len >> 16;
  218. this._buf[seq + 2] = len >> 8;
  219. this._buf[seq + 3] = len;
  220. } else {
  221. throw new InvalidAsn1Error('Sequence too long');
  222. }
  223. };
  224. Writer.prototype._shift = function(start, len, shift) {
  225. assert.ok(start !== undefined);
  226. assert.ok(len !== undefined);
  227. assert.ok(shift);
  228. this._buf.copy(this._buf, start + shift, start, start + len);
  229. this._offset += shift;
  230. };
  231. Writer.prototype._ensure = function(len) {
  232. assert.ok(len);
  233. if (this._size - this._offset < len) {
  234. var sz = this._size * this._options.growthFactor;
  235. if (sz - this._offset < len)
  236. sz += len;
  237. var buf = Buffer.alloc(sz);
  238. this._buf.copy(buf, 0, 0, this._offset);
  239. this._buf = buf;
  240. this._size = sz;
  241. }
  242. };
  243. ///--- Exported API
  244. module.exports = Writer;