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}