feat(cmd/scm): add manifest sign and verify commands
Some checks failed
Security Scan / security (push) Failing after 16s
Test / test (push) Successful in 2m18s

Co-Authored-By: Virgil <virgil@lethean.io>
This commit is contained in:
Virgil 2026-04-02 14:24:55 +00:00
parent 48d1eb22b0
commit e73809cf8d
3 changed files with 240 additions and 0 deletions

View file

@ -7,6 +7,8 @@
// - compile: Compile .core/manifest.yaml into core.json
// - index: Build marketplace index from repository directories
// - export: Export a compiled manifest as JSON to stdout
// - sign: Sign .core/manifest.yaml with an ed25519 private key
// - verify: Verify a manifest signature with an ed25519 public key
package scm
import (
@ -39,4 +41,6 @@ func AddScmCommands(root *cli.Command) {
addCompileCommand(scmCmd)
addIndexCommand(scmCmd)
addExportCommand(scmCmd)
addSignCommand(scmCmd)
addVerifyCommand(scmCmd)
}

137
cmd/scm/cmd_sign_verify.go Normal file
View file

@ -0,0 +1,137 @@
// 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
}

View file

@ -0,0 +1,99 @@
// SPDX-License-Identifier: EUPL-1.2
package scm
import (
"crypto/ed25519"
filepath "dappco.re/go/core/scm/internal/ax/filepathx"
os "dappco.re/go/core/scm/internal/ax/osx"
"encoding/hex"
"testing"
"dappco.re/go/core/io"
"dappco.re/go/core/scm/manifest"
"forge.lthn.ai/core/cli/pkg/cli"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestRunSign_Good_WritesSignedManifest_Good(t *testing.T) {
dir := t.TempDir()
coreDir := filepath.Join(dir, ".core")
require.NoError(t, os.MkdirAll(coreDir, 0755))
require.NoError(t, os.WriteFile(filepath.Join(coreDir, "manifest.yaml"), []byte(`
code: signed-cli
name: Signed CLI
version: 1.0.0
`), 0644))
pub, priv, err := ed25519.GenerateKey(nil)
require.NoError(t, err)
err = runSign(dir, hex.EncodeToString(priv))
require.NoError(t, err)
raw, err := io.Local.Read(filepath.Join(dir, ".core", "manifest.yaml"))
require.NoError(t, err)
m, err := manifest.Parse([]byte(raw))
require.NoError(t, err)
assert.Equal(t, "signed-cli", m.Code)
assert.NotEmpty(t, m.Sign)
valid, err := manifest.Verify(m, pub)
require.NoError(t, err)
assert.True(t, valid)
}
func TestRunVerify_Good_ValidSignature_Good(t *testing.T) {
dir := t.TempDir()
coreDir := filepath.Join(dir, ".core")
require.NoError(t, os.MkdirAll(coreDir, 0755))
pub, priv, err := ed25519.GenerateKey(nil)
require.NoError(t, err)
m := &manifest.Manifest{
Code: "verified-cli",
Name: "Verified CLI",
Version: "1.0.0",
}
require.NoError(t, manifest.Sign(m, priv))
data, err := manifest.MarshalYAML(m)
require.NoError(t, err)
require.NoError(t, os.WriteFile(filepath.Join(coreDir, "manifest.yaml"), data, 0644))
err = runVerify(dir, hex.EncodeToString(pub))
require.NoError(t, err)
}
func TestAddScmCommands_Good_SignAndVerifyRegistered_Good(t *testing.T) {
root := &cli.Command{Use: "root"}
AddScmCommands(root)
var scmCmd *cli.Command
for _, cmd := range root.Commands() {
if cmd.Name() == "scm" {
scmCmd = cmd
break
}
}
require.NotNil(t, scmCmd)
var signCmd *cli.Command
var verifyCmd *cli.Command
for _, cmd := range scmCmd.Commands() {
switch cmd.Name() {
case "sign":
signCmd = cmd
case "verify":
verifyCmd = cmd
}
}
require.NotNil(t, signCmd)
require.NotNil(t, verifyCmd)
assert.NotNil(t, signCmd.Flags().Lookup("sign-key"))
assert.NotNil(t, verifyCmd.Flags().Lookup("public-key"))
}