writer.js 7.1 KB

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