188 lines · 6.8 KB
import { fromBase64, toBase64 } from "../crypto";

const encoder = new TextEncoder();
const MAGIC = "SSHSIG";
const GIT_NAMESPACE = "git";
const ARMOR = /-----BEGIN SSH SIGNATURE-----([\s\S]*?)-----END SSH SIGNATURE-----/;
const ED25519_KEY_BYTES = 32;
const UNCOMPRESSED_POINT = 4;

class SshReader {
  private position = 0;
  constructor(private readonly bytes: Uint8Array) {}
  raw(length: number): Uint8Array {
    if (this.position + length > this.bytes.byteLength) throw new Error("Truncated SSH data");
    const out = this.bytes.subarray(this.position, this.position + length);
    this.position += length;
    return out;
  }
  uint32(): number {
    const view = new DataView(this.bytes.buffer, this.bytes.byteOffset + this.position, 4);
    this.raw(4);
    return view.getUint32(0);
  }
  string(): Uint8Array {
    return this.raw(this.uint32());
  }
  text(): string {
    return new TextDecoder().decode(this.string());
  }
}

function sshString(value: Uint8Array | string): Uint8Array {
  const bytes = typeof value === "string" ? encoder.encode(value) : value;
  const out = new Uint8Array(4 + bytes.byteLength);
  new DataView(out.buffer).setUint32(0, bytes.byteLength);
  out.set(bytes, 4);
  return out;
}

function concat(parts: Uint8Array[]): Uint8Array<ArrayBuffer> {