all files / src/ publicSshToPem.ts

22.39% Statements 15/67
0% Branches 0/10
5.88% Functions 1/17
23.44% Lines 15/64
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108                                                                                                                                                                                         
/* eslint no-bitwise: 0 */
 
function wrap(text: string, len: number) {
  const length = len || 72;
  let result = "";
  for (let i = 0; i < text.length; i += length) {
    result += text.slice(i, i + length);
    result += "\n";
  }
  return result;
}
 
function pemPublicKey(key: string) {
  return `---- BEGIN RSA PUBLIC KEY ----\n${wrap(key, 65)}---- END RSA PUBLIC KEY ----`;
}
 
function integerToOctet(n: number) {
  const result = [];
  for (let i = n; i > 0; i >>= 8) {
    result.push(i & 0xff);
  }
  return result.reverse();
}
 
function asnEncodeLen(n: number) {
  let result = [];
  if (n >> 7) {
    result = integerToOctet(n);
    result.unshift(0x80 + result.length);
  } else {
    result.push(n);
  }
  return result;
}
 
// TODO: any
function checkHighestBit(v: any[]) {
  if (v[0] >> 7 === 1) {
    v.unshift(0); // add leading zero if first bit is set
  }
  return v;
}
 
// TODO: any
function asn1Int(int: any) {
  const v = checkHighestBit(int);
  const len = asnEncodeLen(v.length);
  return [0x02].concat(len, v); // int tag is 0x02
}
 
// TODO: any
function asn1Seq(seq: any) {
  const len = asnEncodeLen(seq.length);
  return [0x30].concat(len, seq); // seq tag is 0x30
}
 
// TODO: any
function arrayToPem(a: any[]) {
  return window.btoa(a.map(c => String.fromCharCode(c)).join(""));
}
 
// TODO: any
export function arrayToString(a: any) {
  return String.fromCharCode.apply(null, a);
}
 
function stringToArray(s: string) {
  // TODO: any
  return s.split("").map(c => (c as any).charCodeAt());
}
 
function pemToArray(pem: string) {
  return stringToArray(window.atob(pem));
}
 
function arrayToLen(a: number[]) {
  let result = 0;
  for (let i = 0; i < a.length; i += 1) {
    result = result * 256 + a[i];
  }
  return result;
}
 
function decodePublicKey(s: string) {
  const split = s.split(" ");
  const prefix = split[0];
  if (prefix !== "ssh-rsa") {
    throw new Error(`Unknown prefix: ${prefix}`);
  }
  const buffer = pemToArray(split[1]);
  const nameLen = arrayToLen(buffer.splice(0, 4));
  const type = arrayToString(buffer.splice(0, nameLen));
  if (type !== "ssh-rsa") {
    throw new Error(`Unknown key type: ${type}`);
  }
  const exponentLen = arrayToLen(buffer.splice(0, 4));
  const exponent = buffer.splice(0, exponentLen);
  const keyLen = arrayToLen(buffer.splice(0, 4));
  const key = buffer.splice(0, keyLen);
  return { type, exponent, key, name: split[2] };
}
 
export function publicSshToPem(publicKey: string) {
  const { key, exponent } = decodePublicKey(publicKey);
  const seq = [key, exponent].map(asn1Int).reduce((acc, a) => acc.concat(a));
  return pemPublicKey(arrayToPem(asn1Seq(seq)));
}