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.

166 lines
4.1 KiB

  1. // Copyright 2015 Joyent, Inc.
  2. module.exports = {
  3. read: read.bind(undefined, false, undefined),
  4. readType: read.bind(undefined, false),
  5. write: write,
  6. /* semi-private api, used by sshpk-agent */
  7. readPartial: read.bind(undefined, true),
  8. /* shared with ssh format */
  9. readInternal: read,
  10. keyTypeToAlg: keyTypeToAlg,
  11. algToKeyType: algToKeyType
  12. };
  13. var assert = require('assert-plus');
  14. var Buffer = require('safer-buffer').Buffer;
  15. var algs = require('../algs');
  16. var utils = require('../utils');
  17. var Key = require('../key');
  18. var PrivateKey = require('../private-key');
  19. var SSHBuffer = require('../ssh-buffer');
  20. function algToKeyType(alg) {
  21. assert.string(alg);
  22. if (alg === 'ssh-dss')
  23. return ('dsa');
  24. else if (alg === 'ssh-rsa')
  25. return ('rsa');
  26. else if (alg === 'ssh-ed25519')
  27. return ('ed25519');
  28. else if (alg === 'ssh-curve25519')
  29. return ('curve25519');
  30. else if (alg.match(/^ecdsa-sha2-/))
  31. return ('ecdsa');
  32. else
  33. throw (new Error('Unknown algorithm ' + alg));
  34. }
  35. function keyTypeToAlg(key) {
  36. assert.object(key);
  37. if (key.type === 'dsa')
  38. return ('ssh-dss');
  39. else if (key.type === 'rsa')
  40. return ('ssh-rsa');
  41. else if (key.type === 'ed25519')
  42. return ('ssh-ed25519');
  43. else if (key.type === 'curve25519')
  44. return ('ssh-curve25519');
  45. else if (key.type === 'ecdsa')
  46. return ('ecdsa-sha2-' + key.part.curve.data.toString());
  47. else
  48. throw (new Error('Unknown key type ' + key.type));
  49. }
  50. function read(partial, type, buf, options) {
  51. if (typeof (buf) === 'string')
  52. buf = Buffer.from(buf);
  53. assert.buffer(buf, 'buf');
  54. var key = {};
  55. var parts = key.parts = [];
  56. var sshbuf = new SSHBuffer({buffer: buf});
  57. var alg = sshbuf.readString();
  58. assert.ok(!sshbuf.atEnd(), 'key must have at least one part');
  59. key.type = algToKeyType(alg);
  60. var partCount = algs.info[key.type].parts.length;
  61. if (type && type === 'private')
  62. partCount = algs.privInfo[key.type].parts.length;
  63. while (!sshbuf.atEnd() && parts.length < partCount)
  64. parts.push(sshbuf.readPart());
  65. while (!partial && !sshbuf.atEnd())
  66. parts.push(sshbuf.readPart());
  67. assert.ok(parts.length >= 1,
  68. 'key must have at least one part');
  69. assert.ok(partial || sshbuf.atEnd(),
  70. 'leftover bytes at end of key');
  71. var Constructor = Key;
  72. var algInfo = algs.info[key.type];
  73. if (type === 'private' || algInfo.parts.length !== parts.length) {
  74. algInfo = algs.privInfo[key.type];
  75. Constructor = PrivateKey;
  76. }
  77. assert.strictEqual(algInfo.parts.length, parts.length);
  78. if (key.type === 'ecdsa') {
  79. var res = /^ecdsa-sha2-(.+)$/.exec(alg);
  80. assert.ok(res !== null);
  81. assert.strictEqual(res[1], parts[0].data.toString());
  82. }
  83. var normalized = true;
  84. for (var i = 0; i < algInfo.parts.length; ++i) {
  85. var p = parts[i];
  86. p.name = algInfo.parts[i];
  87. /*
  88. * OpenSSH stores ed25519 "private" keys as seed + public key
  89. * concat'd together (k followed by A). We want to keep them
  90. * separate for other formats that don't do this.
  91. */
  92. if (key.type === 'ed25519' && p.name === 'k')
  93. p.data = p.data.slice(0, 32);
  94. if (p.name !== 'curve' && algInfo.normalize !== false) {
  95. var nd;
  96. if (key.type === 'ed25519') {
  97. nd = utils.zeroPadToLength(p.data, 32);
  98. } else {
  99. nd = utils.mpNormalize(p.data);
  100. }
  101. if (nd.toString('binary') !==
  102. p.data.toString('binary')) {
  103. p.data = nd;
  104. normalized = false;
  105. }
  106. }
  107. }
  108. if (normalized)
  109. key._rfc4253Cache = sshbuf.toBuffer();
  110. if (partial && typeof (partial) === 'object') {
  111. partial.remainder = sshbuf.remainder();
  112. partial.consumed = sshbuf._offset;
  113. }
  114. return (new Constructor(key));
  115. }
  116. function write(key, options) {
  117. assert.object(key);
  118. var alg = keyTypeToAlg(key);
  119. var i;
  120. var algInfo = algs.info[key.type];
  121. if (PrivateKey.isPrivateKey(key))
  122. algInfo = algs.privInfo[key.type];
  123. var parts = algInfo.parts;
  124. var buf = new SSHBuffer({});
  125. buf.writeString(alg);
  126. for (i = 0; i < parts.length; ++i) {
  127. var data = key.part[parts[i]].data;
  128. if (algInfo.normalize !== false) {
  129. if (key.type === 'ed25519')
  130. data = utils.zeroPadToLength(data, 32);
  131. else
  132. data = utils.mpNormalize(data);
  133. }
  134. if (key.type === 'ed25519' && parts[i] === 'k')
  135. data = Buffer.concat([data, key.part.A.data]);
  136. buf.writeBuffer(data);
  137. }
  138. return (buf.toBuffer());
  139. }