170 lines · 6.3 KB
import { parseSshSignature, sshFingerprint } from "./sshsig";
import { parsePgpSignature } from "./openpgp";
import type { PgpKey } from "./openpgp";

const decoder = new TextDecoder();
const SHA1_SIGNATURE_HEADER = "gpgsig";
const SHA256_SIGNATURE_HEADER = "gpgsig-sha256";
const SIGNATURE_HEADERS = new Set([SHA1_SIGNATURE_HEADER, SHA256_SIGNATURE_HEADER]);
const EMAIL = /<([^>]*)>/;
const LF = 0x0a;
const SPACE = 0x20;

export type SignedCommit = {
  payload: Uint8Array;
  signature: string | null;
  committerEmail: string | null;
  malformed: boolean;
};

function headerEnd(raw: Uint8Array): number {
  for (let index = 0; index + 1 < raw.byteLength; index++) {
    if (raw[index] === LF && raw[index + 1] === LF) return index;
  }
  return raw.byteLength;
}

/**
 * Splits a raw commit into the signed payload (commit minus its signature header) and the signature text.
 * Works on bytes so the payload is exactly what was signed; ambiguous headers mark the commit malformed.
 */
export function splitSignedCommit(raw: Uint8Array): SignedCommit {
  const end = headerEnd(raw);
  const kept: Uint8Array[] = [];
  const signatures = new Map<string, string[]>();
  const counts = { author: 0, committer: 0, duplicateSignature: false };
  let inSignature: string[] | null = null;
  let committerEmail: string | null = null;
  let start = 0;
  while (start <= end) {
    let stop = raw.indexOf(LF, start);