arche / internal/object/signing.go

commit 154431fd
 1package object
 2
 3import (
 4	"crypto/rand"
 5	"fmt"
 6	"os"
 7	"path/filepath"
 8
 9	"golang.org/x/crypto/ssh"
10)
11
12func SignCommitBody(body []byte, keyFile string) (sig []byte, fingerprint string, err error) {
13	if keyFile == "" {
14		keyFile, err = FindDefaultSSHKey()
15		if err != nil {
16			return nil, "", err
17		}
18	}
19	pemData, err := os.ReadFile(keyFile)
20	if err != nil {
21		return nil, "", fmt.Errorf("read ssh key %s: %w", keyFile, err)
22	}
23	signer, err := ssh.ParsePrivateKey(pemData)
24	if err != nil {
25		return nil, "", fmt.Errorf("parse ssh key %s: %w", keyFile, err)
26	}
27	ssig, err := signer.Sign(rand.Reader, body)
28	if err != nil {
29		return nil, "", fmt.Errorf("ssh sign: %w", err)
30	}
31	blob := ssh.Marshal(ssig)
32	fp := ssh.FingerprintSHA256(signer.PublicKey())
33	return blob, fp, nil
34}
35
36func VerifyCommitSig(body, sigBlob []byte, pubKey ssh.PublicKey) error {
37	var sig ssh.Signature
38	if err := ssh.Unmarshal(sigBlob, &sig); err != nil {
39		return fmt.Errorf("unmarshal signature: %w", err)
40	}
41	return pubKey.Verify(body, &sig)
42}
43
44func FindDefaultSSHKey() (string, error) {
45	home, err := os.UserHomeDir()
46	if err != nil {
47		return "", fmt.Errorf("find home dir: %w", err)
48	}
49	for _, name := range []string{"id_ed25519", "id_ecdsa", "id_rsa"} {
50		p := filepath.Join(home, ".ssh", name)
51		if _, err := os.Stat(p); err == nil {
52			return p, nil
53		}
54	}
55	return "", fmt.Errorf("no SSH private key found in ~/.ssh; set sign.key in .arche/config.toml or use arche snap --key")
56}