mirror of
https://github.com/nolen777/eagle0.git
synced 2026-07-28 20:55:44 +00:00
Sign manifest with Ed25519 at build time (#5226)
Add optional Ed25519 signing to the manifest_manager. When a signing key is provided, the manifest is signed and the signature is prepended as a header comment that clients can verify. Changes: - manifest_manager: Accept optional private key file as 3rd argument - manifest_manager: Sign manifest content and prepend signature line - installer_build.yml: Pass MANIFEST_SIGNING_KEY to manifest_manager - unity_build.yml: Pass MANIFEST_SIGNING_KEY to manifest_manager - Add generate_manifest_keys.go script to create key pairs The signature line format is: # signature=<base64-encoded-ed25519-signature> To enable signing: 1. Run: go run scripts/generate_manifest_keys.go 2. Add the private key as GitHub secret MANIFEST_SIGNING_KEY 3. Embed the public key in the installer for verification (PR 4) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -68,15 +68,30 @@ jobs:
|
||||
env:
|
||||
ACCESS_KEY_ID: ${{ secrets.ACCESS_KEY_ID }}
|
||||
SECRET_KEY: ${{ secrets.SECRET_KEY }}
|
||||
MANIFEST_SIGNING_KEY: ${{ secrets.MANIFEST_SIGNING_KEY }}
|
||||
run: |
|
||||
# Create installer manifest content
|
||||
# Create installer manifest content
|
||||
INSTALLER_SHA=$(sha256sum ./installer-output/EagleInstaller.exe | cut -d' ' -f1)
|
||||
echo "installer_version=$INSTALLER_SHA" > /tmp/installer_manifest.txt
|
||||
echo "installer_url=installer/EagleInstaller.exe" >> /tmp/installer_manifest.txt
|
||||
|
||||
|
||||
echo "=== Installer manifest content ==="
|
||||
cat /tmp/installer_manifest.txt
|
||||
echo "=================================="
|
||||
|
||||
# Update the unified manifest
|
||||
bazel run //src/main/go/net/eagle0/build/manifest_manager:manifest_manager -- installer /tmp/installer_manifest.txt
|
||||
|
||||
# Write signing key to temp file (if available)
|
||||
SIGNING_ARGS=""
|
||||
if [ -n "$MANIFEST_SIGNING_KEY" ]; then
|
||||
echo "$MANIFEST_SIGNING_KEY" > /tmp/manifest_signing_key
|
||||
chmod 600 /tmp/manifest_signing_key
|
||||
SIGNING_ARGS="/tmp/manifest_signing_key"
|
||||
echo "Manifest signing key available"
|
||||
else
|
||||
echo "Warning: MANIFEST_SIGNING_KEY not set, manifest will be unsigned"
|
||||
fi
|
||||
|
||||
# Update the unified manifest (with optional signing)
|
||||
bazel run //src/main/go/net/eagle0/build/manifest_manager:manifest_manager -- installer /tmp/installer_manifest.txt $SIGNING_ARGS
|
||||
|
||||
# Cleanup
|
||||
rm -f /tmp/manifest_signing_key
|
||||
@@ -63,7 +63,24 @@ jobs:
|
||||
env:
|
||||
ACCESS_KEY_ID: ${{ secrets.ACCESS_KEY_ID }}
|
||||
SECRET_KEY: ${{ secrets.SECRET_KEY }}
|
||||
run: bazel run //src/main/go/net/eagle0/build/manifest_manager:manifest_manager -- unity3d /tmp/unity_manifest.txt
|
||||
MANIFEST_SIGNING_KEY: ${{ secrets.MANIFEST_SIGNING_KEY }}
|
||||
run: |
|
||||
# Write signing key to temp file (if available)
|
||||
SIGNING_ARGS=""
|
||||
if [ -n "$MANIFEST_SIGNING_KEY" ]; then
|
||||
echo "$MANIFEST_SIGNING_KEY" > /tmp/manifest_signing_key
|
||||
chmod 600 /tmp/manifest_signing_key
|
||||
SIGNING_ARGS="/tmp/manifest_signing_key"
|
||||
echo "Manifest signing key available"
|
||||
else
|
||||
echo "Warning: MANIFEST_SIGNING_KEY not set, manifest will be unsigned"
|
||||
fi
|
||||
|
||||
# Update the unified manifest (with optional signing)
|
||||
bazel run //src/main/go/net/eagle0/build/manifest_manager:manifest_manager -- unity3d /tmp/unity_manifest.txt $SIGNING_ARGS
|
||||
|
||||
# Cleanup
|
||||
rm -f /tmp/manifest_signing_key
|
||||
- name: Archive build log
|
||||
if: success() || failure()
|
||||
uses: actions/upload-artifact@v4
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
// +build ignore
|
||||
|
||||
// Script to generate Ed25519 key pair for manifest signing.
|
||||
// Run with: go run scripts/generate_manifest_keys.go
|
||||
//
|
||||
// This will output:
|
||||
// - Private key (base64): Store as MANIFEST_SIGNING_KEY GitHub secret
|
||||
// - Public key (base64): Embed in EagleInstaller for verification
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"crypto/ed25519"
|
||||
"crypto/rand"
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"log"
|
||||
)
|
||||
|
||||
func main() {
|
||||
// Generate Ed25519 key pair
|
||||
publicKey, privateKey, err := ed25519.GenerateKey(rand.Reader)
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to generate key pair: %v", err)
|
||||
}
|
||||
|
||||
// Encode keys as base64
|
||||
privateKeyB64 := base64.StdEncoding.EncodeToString(privateKey)
|
||||
publicKeyB64 := base64.StdEncoding.EncodeToString(publicKey)
|
||||
|
||||
fmt.Println("=== Ed25519 Key Pair for Manifest Signing ===")
|
||||
fmt.Println()
|
||||
fmt.Println("PRIVATE KEY (store as GitHub secret MANIFEST_SIGNING_KEY):")
|
||||
fmt.Println(privateKeyB64)
|
||||
fmt.Println()
|
||||
fmt.Println("PUBLIC KEY (embed in EagleInstaller.cs for verification):")
|
||||
fmt.Println(publicKeyB64)
|
||||
fmt.Println()
|
||||
fmt.Printf("Private key size: %d bytes\n", len(privateKey))
|
||||
fmt.Printf("Public key size: %d bytes\n", len(publicKey))
|
||||
}
|
||||
@@ -1,12 +1,15 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"crypto/ed25519"
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"github.com/nolen777/eagle0/src/main/go/net/eagle0/util/aws"
|
||||
"log"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/nolen777/eagle0/src/main/go/net/eagle0/util/aws"
|
||||
)
|
||||
|
||||
const bucketName = "eagle0-windows"
|
||||
@@ -64,7 +67,7 @@ func (mm *ManifestManager) parseManifest(content string) map[string]string {
|
||||
return sections
|
||||
}
|
||||
|
||||
func (mm *ManifestManager) buildManifest(sections map[string]string) string {
|
||||
func (mm *ManifestManager) buildManifest(sections map[string]string, privateKey ed25519.PrivateKey) string {
|
||||
var manifest strings.Builder
|
||||
|
||||
manifest.WriteString(fmt.Sprintf("# Generated: %s\n", time.Now().Format(time.RFC3339)))
|
||||
@@ -90,10 +93,21 @@ func (mm *ManifestManager) buildManifest(sections map[string]string) string {
|
||||
}
|
||||
}
|
||||
|
||||
return manifest.String()
|
||||
// The content to sign is everything we've built so far
|
||||
contentToSign := manifest.String()
|
||||
|
||||
// If we have a private key, sign the manifest
|
||||
if privateKey != nil {
|
||||
signature := ed25519.Sign(privateKey, []byte(contentToSign))
|
||||
signatureB64 := base64.StdEncoding.EncodeToString(signature)
|
||||
// Prepend signature line at the very beginning
|
||||
return fmt.Sprintf("# signature=%s\n%s", signatureB64, contentToSign)
|
||||
}
|
||||
|
||||
return contentToSign
|
||||
}
|
||||
|
||||
func (mm *ManifestManager) UpdateSection(sectionName, sectionContent string) error {
|
||||
func (mm *ManifestManager) UpdateSection(sectionName, sectionContent string, privateKey ed25519.PrivateKey) error {
|
||||
log.Printf("Updating manifest section: %s", sectionName)
|
||||
|
||||
// Fetch existing manifest
|
||||
@@ -103,14 +117,14 @@ func (mm *ManifestManager) UpdateSection(sectionName, sectionContent string) err
|
||||
existing = ""
|
||||
}
|
||||
|
||||
// Parse sections
|
||||
// Parse sections (strips out old signature line if present)
|
||||
sections := mm.parseManifest(existing)
|
||||
|
||||
// Update the specified section
|
||||
sections[sectionName] = sectionContent
|
||||
|
||||
// Build new manifest
|
||||
newManifest := mm.buildManifest(sections)
|
||||
// Build new manifest (with signature if key provided)
|
||||
newManifest := mm.buildManifest(sections, privateKey)
|
||||
|
||||
// Upload updated manifest with public ACL
|
||||
err = mm.bucketBasics.UploadBytesPublic(bucketName, manifestPath, []byte(newManifest))
|
||||
@@ -118,13 +132,36 @@ func (mm *ManifestManager) UpdateSection(sectionName, sectionContent string) err
|
||||
return fmt.Errorf("failed to upload manifest: %v", err)
|
||||
}
|
||||
|
||||
log.Printf("Successfully updated manifest section: %s", sectionName)
|
||||
if privateKey != nil {
|
||||
log.Printf("Successfully updated and signed manifest section: %s", sectionName)
|
||||
} else {
|
||||
log.Printf("Successfully updated manifest section: %s (unsigned)", sectionName)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func loadPrivateKey(keyPath string) (ed25519.PrivateKey, error) {
|
||||
keyData, err := os.ReadFile(keyPath)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to read private key file: %v", err)
|
||||
}
|
||||
|
||||
// Key should be base64-encoded 64-byte Ed25519 private key
|
||||
keyBytes, err := base64.StdEncoding.DecodeString(strings.TrimSpace(string(keyData)))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to decode private key: %v", err)
|
||||
}
|
||||
|
||||
if len(keyBytes) != ed25519.PrivateKeySize {
|
||||
return nil, fmt.Errorf("invalid private key size: expected %d bytes, got %d", ed25519.PrivateKeySize, len(keyBytes))
|
||||
}
|
||||
|
||||
return ed25519.PrivateKey(keyBytes), nil
|
||||
}
|
||||
|
||||
func main() {
|
||||
if len(os.Args) != 3 {
|
||||
log.Fatal("Usage: manifest_manager <section_name> <section_content_file>")
|
||||
if len(os.Args) < 3 || len(os.Args) > 4 {
|
||||
log.Fatal("Usage: manifest_manager <section_name> <section_content_file> [private_key_file]")
|
||||
}
|
||||
|
||||
sectionName := os.Args[1]
|
||||
@@ -136,6 +173,16 @@ func main() {
|
||||
log.Fatalf("Failed to read content file: %v", err)
|
||||
}
|
||||
|
||||
// Load private key if provided
|
||||
var privateKey ed25519.PrivateKey
|
||||
if len(os.Args) == 4 {
|
||||
privateKey, err = loadPrivateKey(os.Args[3])
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to load private key: %v", err)
|
||||
}
|
||||
log.Println("Ed25519 signing key loaded")
|
||||
}
|
||||
|
||||
// Create manifest manager
|
||||
mm, err := NewManifestManager()
|
||||
if err != nil {
|
||||
@@ -143,7 +190,7 @@ func main() {
|
||||
}
|
||||
|
||||
// Update the section
|
||||
err = mm.UpdateSection(sectionName, string(content))
|
||||
err = mm.UpdateSection(sectionName, string(content), privateKey)
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to update manifest: %v", err)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user