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.

317 lines
7.5 KiB

  1. // Copyright 2011 Mark Cavage <mcavage@gmail.com> All rights reserved.
  2. var assert = require('assert');
  3. var Buffer = require('safer-buffer').Buffer;
  4. var ASN1 = require('./types');
  5. var errors = require('./errors');
  6. // --- Globals
  7. var newInvalidAsn1Error = errors.newInvalidAsn1Error;
  8. var DEFAULT_OPTS = {
  9. size: 1024,
  10. growthFactor: 8
  11. };
  12. // --- Helpers
  13. function merge(from, to) {
  14. assert.ok(from);
  15. assert.equal(typeof (from), 'object');
  16. assert.ok(to);
  17. assert.equal(typeof (to), 'object');
  18. var keys = Object.getOwnPropertyNames(from);
  19. keys.forEach(function (key) {
  20. if (to[key])
  21. return;
  22. var value = Object.getOwnPropertyDescriptor(from, key);
  23. Object.defineProperty(to, key, value);
  24. });
  25. return to;
  26. }
  27. // --- API
  28. function Writer(options) {
  29. options = merge(DEFAULT_OPTS, options || {});
  30. this._buf = Buffer.alloc(options.size || 1024);
  31. this._size = this._buf.length;
  32. this._offset = 0;
  33. this._options = options;
  34. // A list of offsets in the buffer where we need to insert
  35. // sequence tag/len pairs.
  36. this._seq = [];
  37. }
  38. Object.defineProperty(Writer.prototype, 'buffer', {
  39. get: function () {
  40. if (this._seq.length)
  41. throw newInvalidAsn1Error(this._seq.length + ' unended sequence(s)');
  42. return (this._buf.slice(0, this._offset));
  43. }
  44. });
  45. Writer.prototype.writeByte = function (b) {
  46. if (typeof (b) !== 'number')
  47. throw new TypeError('argument must be a Number');
  48. this._ensure(1);
  49. this._buf[this._offset++] = b;
  50. };
  51. Writer.prototype.writeInt = function (i, tag) {
  52. if (typeof (i) !== 'number')
  53. throw new TypeError('argument must be a Number');
  54. if (typeof (tag) !== 'number')
  55. tag = ASN1.Integer;
  56. var sz = 4;
  57. while ((((i & 0xff800000) === 0) || ((i & 0xff800000) === 0xff800000 >> 0)) &&
  58. (sz > 1)) {
  59. sz--;
  60. i <<= 8;
  61. }
  62. if (sz > 4)
  63. throw newInvalidAsn1Error('BER ints cannot be > 0xffffffff');
  64. this._ensure(2 + sz);
  65. this._buf[this._offset++] = tag;
  66. this._buf[this._offset++] = sz;
  67. while (sz-- > 0) {
  68. this._buf[this._offset++] = ((i & 0xff000000) >>> 24);
  69. i <<= 8;
  70. }
  71. };
  72. Writer.prototype.writeNull = function () {
  73. this.writeByte(ASN1.Null);
  74. this.writeByte(0x00);
  75. };
  76. Writer.prototype.writeEnumeration = function (i, tag) {
  77. if (typeof (i) !== 'number')
  78. throw new TypeError('argument must be a Number');
  79. if (typeof (tag) !== 'number')
  80. tag = ASN1.Enumeration;
  81. return this.writeInt(i, tag);
  82. };
  83. Writer.prototype.writeBoolean = function (b, tag) {
  84. if (typeof (b) !== 'boolean')
  85. throw new TypeError('argument must be a Boolean');
  86. if (typeof (tag) !== 'number')
  87. tag = ASN1.Boolean;
  88. this._ensure(3);
  89. this._buf[this._offset++] = tag;
  90. this._buf[this._offset++] = 0x01;
  91. this._buf[this._offset++] = b ? 0xff : 0x00;
  92. };
  93. Writer.prototype.writeString = function (s, tag) {
  94. if (typeof (s) !== 'string')
  95. throw new TypeError('argument must be a string (was: ' + typeof (s) + ')');
  96. if (typeof (tag) !== 'number')
  97. tag = ASN1.OctetString;
  98. var len = Buffer.byteLength(s);
  99. this.writeByte(tag);
  100. this.writeLength(len);
  101. if (len) {
  102. this._ensure(len);
  103. this._buf.write(s, this._offset);
  104. this._offset += len;
  105. }
  106. };
  107. Writer.prototype.writeBuffer = function (buf, tag) {
  108. if (typeof (tag) !== 'number')
  109. throw new TypeError('tag must be a number');
  110. if (!Buffer.isBuffer(buf))
  111. throw new TypeError('argument must be a buffer');
  112. this.writeByte(tag);
  113. this.writeLength(buf.length);
  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) {
  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);
  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 newInvalidAsn1Error('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 newInvalidAsn1Error('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 = Buffer.alloc(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;