go-scm/cmd/scm/cmd_sign_verify.go
Virgil e73809cf8d
Some checks failed
Security Scan / security (push) Failing after 16s
Test / test (push) Successful in 2m18s
feat(cmd/scm): add manifest sign and verify commands
Co-Authored-By: Virgil <virgil@lethean.io>
2026-04-02 14:24:55 +00:00

137 lines
3.3 KiB
Go

// SPDX-License-Identifier: EUPL-1.2
package scm
import (
"crypto/ed25519"
"encoding/hex"
"dappco.re/go/core/io"
"dappco.re/go/core/scm/manifest"
"forge.lthn.ai/core/cli/pkg/cli"
)
func addSignCommand(parent *cli.Command) {
var (
dir string
signKey string
)
cmd := &cli.Command{
Use: "sign",
Short: "Sign manifest.yaml with a private key",
Long: "Read .core/manifest.yaml, attach an ed25519 signature, and write the signed manifest back to disk.",
RunE: func(cmd *cli.Command, args []string) error {
return runSign(dir, signKey)
},
}
cmd.Flags().StringVarP(&dir, "dir", "d", ".", "Project root directory")
cmd.Flags().StringVar(&signKey, "sign-key", "", "Hex-encoded ed25519 private key")
parent.AddCommand(cmd)
}
func runSign(dir, signKeyHex string) error {
if signKeyHex == "" {
return cli.Err("sign key is required")
}
medium, err := io.NewSandboxed(dir)
if err != nil {
return cli.WrapVerb(err, "open", dir)
}
m, err := manifest.Load(medium, ".")
if err != nil {
return cli.WrapVerb(err, "load", "manifest")
}
keyBytes, err := hex.DecodeString(signKeyHex)
if err != nil {
return cli.WrapVerb(err, "decode", "sign key")
}
if len(keyBytes) != ed25519.PrivateKeySize {
return cli.Err("sign key must be %d bytes when decoded", ed25519.PrivateKeySize)
}
if err := manifest.Sign(m, ed25519.PrivateKey(keyBytes)); err != nil {
return err
}
data, err := manifest.MarshalYAML(m)
if err != nil {
return cli.WrapVerb(err, "marshal", "manifest")
}
if err := medium.Write(".core/manifest.yaml", string(data)); err != nil {
return cli.WrapVerb(err, "write", ".core/manifest.yaml")
}
cli.Blank()
cli.Print(" %s %s\n", successStyle.Render("signed"), valueStyle.Render(m.Code))
cli.Print(" %s %s\n", dimStyle.Render("output:"), valueStyle.Render(".core/manifest.yaml"))
cli.Blank()
return nil
}
func addVerifyCommand(parent *cli.Command) {
var (
dir string
publicKey string
)
cmd := &cli.Command{
Use: "verify",
Short: "Verify manifest signature with a public key",
Long: "Read .core/manifest.yaml and verify its ed25519 signature against a public key.",
RunE: func(cmd *cli.Command, args []string) error {
return runVerify(dir, publicKey)
},
}
cmd.Flags().StringVarP(&dir, "dir", "d", ".", "Project root directory")
cmd.Flags().StringVar(&publicKey, "public-key", "", "Hex-encoded ed25519 public key")
parent.AddCommand(cmd)
}
func runVerify(dir, publicKeyHex string) error {
if publicKeyHex == "" {
return cli.Err("public key is required")
}
medium, err := io.NewSandboxed(dir)
if err != nil {
return cli.WrapVerb(err, "open", dir)
}
m, err := manifest.Load(medium, ".")
if err != nil {
return cli.WrapVerb(err, "load", "manifest")
}
keyBytes, err := hex.DecodeString(publicKeyHex)
if err != nil {
return cli.WrapVerb(err, "decode", "public key")
}
if len(keyBytes) != ed25519.PublicKeySize {
return cli.Err("public key must be %d bytes when decoded", ed25519.PublicKeySize)
}
valid, err := manifest.Verify(m, ed25519.PublicKey(keyBytes))
if err != nil {
return cli.WrapVerb(err, "verify", "manifest")
}
if !valid {
return cli.Err("signature verification failed for %s", m.Code)
}
cli.Blank()
cli.Success("Signature verified")
cli.Print(" %s %s\n", dimStyle.Render("code:"), valueStyle.Render(m.Code))
cli.Blank()
return nil
}