Some checks failed
Tests / 🛃 Unit tests (push) Failing after 13s
Tests / 🗄 DB tests (push) Failing after 19s
Tests / 🐍 Lint python scripts (push) Failing after 1s
Tests / ⌨ Golang Lint (push) Failing after 1s
Tests / 📦 Packit config lint (push) Failing after 1s
Tests / 🔍 Check source preparation (push) Failing after 1s
Tests / 🔍 Check for valid snapshot urls (push) Failing after 1s
Tests / 🔍 Check for missing or unused runner repos (push) Failing after 1s
Tests / 🐚 Shellcheck (push) Failing after 1s
Tests / 📦 RPMlint (push) Failing after 1s
Tests / Gitlab CI trigger helper (push) Failing after 1s
Tests / 🎀 kube-linter (push) Failing after 1s
Tests / 🧹 cloud-cleaner-is-enabled (push) Successful in 3s
Tests / 🔍 Check spec file osbuild/images dependencies (push) Failing after 1s
856 lines
24 KiB
Go
856 lines
24 KiB
Go
package security
|
|
|
|
import (
|
|
"crypto"
|
|
"crypto/rand"
|
|
"crypto/rsa"
|
|
"crypto/sha256"
|
|
"crypto/x509"
|
|
"encoding/base64"
|
|
"encoding/json"
|
|
"encoding/pem"
|
|
"fmt"
|
|
"os"
|
|
"os/exec"
|
|
"path/filepath"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/sirupsen/logrus"
|
|
)
|
|
|
|
type SigningVerifier struct {
|
|
logger *logrus.Logger
|
|
config *SigningConfig
|
|
gpg *GPGManager
|
|
cosign *CosignManager
|
|
keyManager *KeyManager
|
|
trustStore *TrustStore
|
|
}
|
|
|
|
type SigningConfig struct {
|
|
GPGHomeDir string `json:"gpg_home_dir"`
|
|
CosignKeyPath string `json:"cosign_key_path"`
|
|
TrustStorePath string `json:"trust_store_path"`
|
|
KeyRingPath string `json:"key_ring_path"`
|
|
SigningKeyID string `json:"signing_key_id"`
|
|
VerifySignatures bool `json:"verify_signatures"`
|
|
Metadata map[string]string `json:"metadata"`
|
|
}
|
|
|
|
type GPGManager struct {
|
|
homeDir string
|
|
keyRing string
|
|
signingKey string
|
|
logger *logrus.Logger
|
|
}
|
|
|
|
type CosignManager struct {
|
|
keyPath string
|
|
password string
|
|
logger *logrus.Logger
|
|
}
|
|
|
|
type KeyManager struct {
|
|
keys map[string]SigningKey
|
|
keyRing string
|
|
logger *logrus.Logger
|
|
}
|
|
|
|
type TrustStore struct {
|
|
trustedKeys map[string]TrustedKey
|
|
caCerts map[string]CACertificate
|
|
crls map[string]CRL
|
|
logger *logrus.Logger
|
|
}
|
|
|
|
type SigningKey struct {
|
|
ID string `json:"id"`
|
|
Type string `json:"type"`
|
|
Algorithm string `json:"algorithm"`
|
|
KeySize int `json:"key_size"`
|
|
Fingerprint string `json:"fingerprint"`
|
|
Created time.Time `json:"created"`
|
|
Expires time.Time `json:"expires"`
|
|
UserID string `json:"user_id"`
|
|
Email string `json:"email"`
|
|
PublicKey string `json:"public_key"`
|
|
PrivateKey string `json:"private_key,omitempty"`
|
|
Metadata map[string]interface{} `json:"metadata"`
|
|
}
|
|
|
|
type TrustedKey struct {
|
|
ID string `json:"id"`
|
|
Fingerprint string `json:"fingerprint"`
|
|
TrustLevel string `json:"trust_level"`
|
|
Added time.Time `json:"added"`
|
|
Expires time.Time `json:"expires"`
|
|
Metadata map[string]interface{} `json:"metadata"`
|
|
}
|
|
|
|
type CACertificate struct {
|
|
ID string `json:"id"`
|
|
Subject string `json:"subject"`
|
|
Issuer string `json:"issuer"`
|
|
ValidFrom time.Time `json:"valid_from"`
|
|
ValidTo time.Time `json:"valid_to"`
|
|
Fingerprint string `json:"fingerprint"`
|
|
PEM string `json:"pem"`
|
|
Metadata map[string]interface{} `json:"metadata"`
|
|
}
|
|
|
|
type CRL struct {
|
|
ID string `json:"id"`
|
|
Issuer string `json:"issuer"`
|
|
ThisUpdate time.Time `json:"this_update"`
|
|
NextUpdate time.Time `json:"next_update"`
|
|
Revoked []RevokedCertificate `json:"revoked"`
|
|
Metadata map[string]interface{} `json:"metadata"`
|
|
}
|
|
|
|
type RevokedCertificate struct {
|
|
SerialNumber string `json:"serial_number"`
|
|
RevocationDate time.Time `json:"revocation_date"`
|
|
Reason string `json:"reason"`
|
|
}
|
|
|
|
type SignatureResult struct {
|
|
ID string `json:"id"`
|
|
Target string `json:"target"`
|
|
TargetType string `json:"target_type"`
|
|
Signer string `json:"signer"`
|
|
Algorithm string `json:"algorithm"`
|
|
Signature string `json:"signature"`
|
|
Timestamp time.Time `json:"timestamp"`
|
|
Valid bool `json:"valid"`
|
|
Verified bool `json:"verified"`
|
|
Metadata map[string]interface{} `json:"metadata"`
|
|
}
|
|
|
|
type VerificationResult struct {
|
|
ID string `json:"id"`
|
|
Target string `json:"target"`
|
|
TargetType string `json:"target_type"`
|
|
Signatures []SignatureResult `json:"signatures"`
|
|
Valid bool `json:"valid"`
|
|
TrustChain []TrustLink `json:"trust_chain"`
|
|
Warnings []string `json:"warnings"`
|
|
Errors []string `json:"errors"`
|
|
Metadata map[string]interface{} `json:"metadata"`
|
|
}
|
|
|
|
type TrustLink struct {
|
|
From string `json:"from"`
|
|
To string `json:"to"`
|
|
Type string `json:"type"`
|
|
Algorithm string `json:"algorithm"`
|
|
Valid bool `json:"valid"`
|
|
Metadata map[string]interface{} `json:"metadata"`
|
|
}
|
|
|
|
func NewSigningVerifier(config *SigningConfig, logger *logrus.Logger) *SigningVerifier {
|
|
verifier := &SigningVerifier{
|
|
logger: logger,
|
|
config: config,
|
|
gpg: NewGPGManager(config.GPGHomeDir, config.KeyRingPath, config.SigningKeyID, logger),
|
|
cosign: NewCosignManager(config.CosignKeyPath, logger),
|
|
keyManager: NewKeyManager(config.KeyRingPath, logger),
|
|
trustStore: NewTrustStore(config.TrustStorePath, logger),
|
|
}
|
|
|
|
return verifier
|
|
}
|
|
|
|
func NewGPGManager(homeDir, keyRing, signingKey string, logger *logrus.Logger) *GPGManager {
|
|
return &GPGManager{
|
|
homeDir: homeDir,
|
|
keyRing: keyRing,
|
|
signingKey: signingKey,
|
|
logger: logger,
|
|
}
|
|
}
|
|
|
|
func NewCosignManager(keyPath string, logger *logrus.Logger) *CosignManager {
|
|
return &CosignManager{
|
|
keyPath: keyPath,
|
|
logger: logger,
|
|
}
|
|
}
|
|
|
|
func NewKeyManager(keyRing string, logger *logrus.Logger) *KeyManager {
|
|
return &KeyManager{
|
|
keys: make(map[string]SigningKey),
|
|
keyRing: keyRing,
|
|
logger: logger,
|
|
}
|
|
}
|
|
|
|
func NewTrustStore(trustStorePath string, logger *logrus.Logger) *TrustStore {
|
|
return &TrustStore{
|
|
trustedKeys: make(map[string]TrustedKey),
|
|
caCerts: make(map[string]CACertificate),
|
|
crls: make(map[string]CRL),
|
|
logger: logger,
|
|
}
|
|
}
|
|
|
|
func (sv *SigningVerifier) SignTarget(target string, targetType string, algorithm string) (*SignatureResult, error) {
|
|
sv.logger.Infof("Signing target: %s (type: %s, algorithm: %s)", target, targetType, algorithm)
|
|
|
|
// Create signature result
|
|
result := &SignatureResult{
|
|
ID: generateSignatureID(),
|
|
Target: target,
|
|
TargetType: targetType,
|
|
Algorithm: algorithm,
|
|
Timestamp: time.Now(),
|
|
Metadata: make(map[string]interface{}),
|
|
}
|
|
|
|
// Sign based on target type
|
|
switch targetType {
|
|
case "package", "deb":
|
|
if err := sv.signPackage(target, result); err != nil {
|
|
return nil, fmt.Errorf("package signing failed: %w", err)
|
|
}
|
|
case "container", "image":
|
|
if err := sv.signContainer(target, result); err != nil {
|
|
return nil, fmt.Errorf("container signing failed: %w", err)
|
|
}
|
|
case "file":
|
|
if err := sv.signFile(target, result); err != nil {
|
|
return nil, fmt.Errorf("file signing failed: %w", err)
|
|
}
|
|
default:
|
|
return nil, fmt.Errorf("unsupported target type: %s", targetType)
|
|
}
|
|
|
|
sv.logger.Infof("Successfully signed target: %s", target)
|
|
return result, nil
|
|
}
|
|
|
|
func (sv *SigningVerifier) signPackage(target string, result *SignatureResult) error {
|
|
// Use dpkg-sig for Debian package signing
|
|
cmd := exec.Command("dpkg-sig", "--sign", "origin", target)
|
|
cmd.Stdout = os.Stdout
|
|
cmd.Stderr = os.Stderr
|
|
|
|
if err := cmd.Run(); err != nil {
|
|
return fmt.Errorf("dpkg-sig failed: %w", err)
|
|
}
|
|
|
|
// Get signature information
|
|
result.Signer = sv.config.SigningKeyID
|
|
result.Valid = true
|
|
|
|
// Extract signature from package
|
|
if err := sv.extractPackageSignature(target, result); err != nil {
|
|
sv.logger.Warnf("Failed to extract package signature: %v", err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (sv *SigningVerifier) signContainer(target string, result *SignatureResult) error {
|
|
// Use cosign for container signing
|
|
cmd := exec.Command("cosign", "sign", "--key", sv.config.CosignKeyPath, target)
|
|
cmd.Stdout = os.Stdout
|
|
cmd.Stderr = os.Stderr
|
|
|
|
if err := cmd.Run(); err != nil {
|
|
return fmt.Errorf("cosign signing failed: %w", err)
|
|
}
|
|
|
|
// Get signature information
|
|
result.Signer = "cosign"
|
|
result.Valid = true
|
|
|
|
// Extract signature from container
|
|
if err := sv.extractContainerSignature(target, result); err != nil {
|
|
sv.logger.Warnf("Failed to extract container signature: %v", err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (sv *SigningVerifier) signFile(target string, result *SignatureResult) error {
|
|
// Use GPG for file signing
|
|
cmd := exec.Command("gpg", "--detach-sign", "--armor", "--local-user", sv.config.SigningKeyID, target)
|
|
cmd.Stdout = os.Stdout
|
|
cmd.Stderr = os.Stderr
|
|
|
|
if err := cmd.Run(); err != nil {
|
|
return fmt.Errorf("GPG signing failed: %w", err)
|
|
}
|
|
|
|
// Get signature information
|
|
result.Signer = sv.config.SigningKeyID
|
|
result.Valid = true
|
|
|
|
// Extract signature from file
|
|
if err := sv.extractFileSignature(target, result); err != nil {
|
|
sv.logger.Warnf("Failed to extract file signature: %v", err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (sv *SigningVerifier) extractPackageSignature(target string, result *SignatureResult) error {
|
|
// Extract signature from Debian package
|
|
cmd := exec.Command("dpkg-sig", "--verify", target)
|
|
output, err := cmd.Output()
|
|
if err != nil {
|
|
return fmt.Errorf("dpkg-sig verify failed: %w", err)
|
|
}
|
|
|
|
// Parse output to extract signature
|
|
lines := strings.Split(string(output), "\n")
|
|
for _, line := range lines {
|
|
if strings.Contains(line, "GOODSIG") {
|
|
parts := strings.Fields(line)
|
|
if len(parts) >= 3 {
|
|
result.Signature = parts[2]
|
|
break
|
|
}
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (sv *SigningVerifier) extractContainerSignature(target string, result *SignatureResult) error {
|
|
// Extract cosign signature
|
|
cmd := exec.Command("cosign", "verify", "--key", sv.config.CosignKeyPath, target)
|
|
output, err := cmd.Output()
|
|
if err != nil {
|
|
return fmt.Errorf("cosign verify failed: %w", err)
|
|
}
|
|
|
|
// Parse output to extract signature
|
|
lines := strings.Split(string(output), "\n")
|
|
for _, line := range lines {
|
|
if strings.Contains(line, "Signature:") {
|
|
parts := strings.Split(line, ":")
|
|
if len(parts) >= 2 {
|
|
result.Signature = strings.TrimSpace(parts[1])
|
|
break
|
|
}
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (sv *SigningVerifier) extractFileSignature(target string, result *SignatureResult) error {
|
|
// Read GPG signature file
|
|
sigFile := target + ".asc"
|
|
if _, err := os.Stat(sigFile); os.IsNotExist(err) {
|
|
return fmt.Errorf("signature file not found: %s", sigFile)
|
|
}
|
|
|
|
sigData, err := os.ReadFile(sigFile)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to read signature file: %w", err)
|
|
}
|
|
|
|
result.Signature = base64.StdEncoding.EncodeToString(sigData)
|
|
return nil
|
|
}
|
|
|
|
func (sv *SigningVerifier) VerifyTarget(target string, targetType string) (*VerificationResult, error) {
|
|
sv.logger.Infof("Verifying target: %s (type: %s)", target, targetType, targetType)
|
|
|
|
// Create verification result
|
|
result := &VerificationResult{
|
|
ID: generateVerificationID(),
|
|
Target: target,
|
|
TargetType: targetType,
|
|
Signatures: []SignatureResult{},
|
|
TrustChain: []TrustLink{},
|
|
Warnings: []string{},
|
|
Errors: []string{},
|
|
Metadata: make(map[string]interface{}),
|
|
}
|
|
|
|
// Verify based on target type
|
|
switch targetType {
|
|
case "package", "deb":
|
|
if err := sv.verifyPackage(target, result); err != nil {
|
|
result.Errors = append(result.Errors, err.Error())
|
|
}
|
|
case "container", "image":
|
|
if err := sv.verifyContainer(target, result); err != nil {
|
|
result.Errors = append(result.Errors, err.Error())
|
|
}
|
|
case "file":
|
|
if err := sv.verifyFile(target, result); err != nil {
|
|
result.Errors = append(result.Errors, err.Error())
|
|
}
|
|
default:
|
|
return nil, fmt.Errorf("unsupported target type: %s", targetType)
|
|
}
|
|
|
|
// Verify trust chain
|
|
if err := sv.verifyTrustChain(result); err != nil {
|
|
result.Warnings = append(result.Warnings, "Trust chain verification failed: "+err.Error())
|
|
}
|
|
|
|
// Determine overall validity
|
|
result.Valid = len(result.Errors) == 0 && len(result.Signatures) > 0
|
|
|
|
sv.logger.Infof("Verification completed for target: %s, valid: %t", target, result.Valid)
|
|
return result, nil
|
|
}
|
|
|
|
func (sv *SigningVerifier) verifyPackage(target string, result *VerificationResult) error {
|
|
// Use dpkg-sig for Debian package verification
|
|
cmd := exec.Command("dpkg-sig", "--verify", target)
|
|
output, err := cmd.Output()
|
|
if err != nil {
|
|
return fmt.Errorf("dpkg-sig verify failed: %w", err)
|
|
}
|
|
|
|
// Parse verification output
|
|
lines := strings.Split(string(output), "\n")
|
|
for _, line := range lines {
|
|
if strings.Contains(line, "GOODSIG") {
|
|
parts := strings.Fields(line)
|
|
if len(parts) >= 3 {
|
|
signature := SignatureResult{
|
|
ID: generateSignatureID(),
|
|
Target: target,
|
|
TargetType: "package",
|
|
Signer: parts[2],
|
|
Algorithm: "RSA",
|
|
Valid: true,
|
|
Verified: true,
|
|
Timestamp: time.Now(),
|
|
Metadata: make(map[string]interface{}),
|
|
}
|
|
result.Signatures = append(result.Signatures, signature)
|
|
}
|
|
} else if strings.Contains(line, "BADSIG") {
|
|
result.Errors = append(result.Errors, "Bad signature detected")
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (sv *SigningVerifier) verifyContainer(target string, result *VerificationResult) error {
|
|
// Use cosign for container verification
|
|
cmd := exec.Command("cosign", "verify", "--key", sv.config.CosignKeyPath, target)
|
|
output, err := cmd.Output()
|
|
if err != nil {
|
|
return fmt.Errorf("cosign verify failed: %w", err)
|
|
}
|
|
|
|
// Parse verification output
|
|
lines := strings.Split(string(output), "\n")
|
|
for _, line := range lines {
|
|
if strings.Contains(line, "Verified") {
|
|
signature := SignatureResult{
|
|
ID: generateSignatureID(),
|
|
Target: target,
|
|
TargetType: "container",
|
|
Signer: "cosign",
|
|
Algorithm: "ECDSA",
|
|
Valid: true,
|
|
Verified: true,
|
|
Timestamp: time.Now(),
|
|
Metadata: make(map[string]interface{}),
|
|
}
|
|
result.Signatures = append(result.Signatures, signature)
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (sv *SigningVerifier) verifyFile(target string, result *VerificationResult) error {
|
|
// Use GPG for file verification
|
|
cmd := exec.Command("gpg", "--verify", target+".asc", target)
|
|
output, err := cmd.Output()
|
|
if err != nil {
|
|
return fmt.Errorf("GPG verify failed: %w", err)
|
|
}
|
|
|
|
// Parse verification output
|
|
lines := strings.Split(string(output), "\n")
|
|
for _, line := range lines {
|
|
if strings.Contains(line, "Good signature") {
|
|
parts := strings.Fields(line)
|
|
if len(parts) >= 3 {
|
|
signature := SignatureResult{
|
|
ID: generateSignatureID(),
|
|
Target: target,
|
|
TargetType: "file",
|
|
Signer: parts[2],
|
|
Algorithm: "RSA",
|
|
Valid: true,
|
|
Verified: true,
|
|
Timestamp: time.Now(),
|
|
Metadata: make(map[string]interface{}),
|
|
}
|
|
result.Signatures = append(result.Signatures, signature)
|
|
}
|
|
} else if strings.Contains(line, "Bad signature") {
|
|
result.Errors = append(result.Errors, "Bad signature detected")
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (sv *SigningVerifier) verifyTrustChain(result *VerificationResult) error {
|
|
for _, signature := range result.Signatures {
|
|
// Verify key trust
|
|
if err := sv.verifyKeyTrust(signature.Signer, result); err != nil {
|
|
sv.logger.Warnf("Key trust verification failed for %s: %v", signature.Signer, err)
|
|
}
|
|
|
|
// Verify certificate chain if applicable
|
|
if err := sv.verifyCertificateChain(signature.Signer, result); err != nil {
|
|
sv.logger.Warnf("Certificate chain verification failed for %s: %v", signature.Signer, err)
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (sv *SigningVerifier) verifyKeyTrust(keyID string, result *VerificationResult) error {
|
|
// Check if key is in trusted key store
|
|
if trustedKey, exists := sv.trustStore.trustedKeys[keyID]; exists {
|
|
trustLink := TrustLink{
|
|
From: keyID,
|
|
To: "trusted_key_store",
|
|
Type: "trusted_key",
|
|
Algorithm: "GPG",
|
|
Valid: true,
|
|
Metadata: make(map[string]interface{}),
|
|
}
|
|
result.TrustChain = append(result.TrustChain, trustLink)
|
|
return nil
|
|
}
|
|
|
|
// Check GPG key trust
|
|
cmd := exec.Command("gpg", "--list-keys", "--with-colons", keyID)
|
|
output, err := cmd.Output()
|
|
if err != nil {
|
|
return fmt.Errorf("failed to list GPG key: %w", err)
|
|
}
|
|
|
|
// Parse trust level
|
|
lines := strings.Split(string(output), "\n")
|
|
for _, line := range lines {
|
|
if strings.HasPrefix(line, "pub:") {
|
|
parts := strings.Split(line, ":")
|
|
if len(parts) >= 10 {
|
|
trustLevel := parts[1]
|
|
if trustLevel == "f" || trustLevel == "u" {
|
|
trustLink := TrustLink{
|
|
From: keyID,
|
|
To: "gpg_trusted",
|
|
Type: "gpg_trust",
|
|
Algorithm: "GPG",
|
|
Valid: true,
|
|
Metadata: make(map[string]interface{}),
|
|
}
|
|
result.TrustChain = append(result.TrustChain, trustLink)
|
|
return nil
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
result.Warnings = append(result.Warnings, fmt.Sprintf("Key %s not in trusted store", keyID))
|
|
return nil
|
|
}
|
|
|
|
func (sv *SigningVerifier) verifyCertificateChain(keyID string, result *VerificationResult) error {
|
|
// Check for X.509 certificates
|
|
cmd := exec.Command("gpg", "--export", keyID)
|
|
output, err := cmd.Output()
|
|
if err != nil {
|
|
return fmt.Errorf("failed to export GPG key: %w", err)
|
|
}
|
|
|
|
// Try to parse as X.509 certificate
|
|
if len(output) > 0 {
|
|
block, _ := pem.Decode(output)
|
|
if block != nil && block.Type == "CERTIFICATE" {
|
|
cert, err := x509.ParseCertificate(block.Bytes)
|
|
if err == nil {
|
|
// Verify certificate chain
|
|
if err := sv.verifyX509Chain(cert, result); err != nil {
|
|
sv.logger.Warnf("X.509 chain verification failed: %v", err)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (sv *SigningVerifier) verifyX509Chain(cert *x509.Certificate, result *VerificationResult) error {
|
|
// Check if certificate is in CA store
|
|
for _, caCert := range sv.trustStore.caCerts {
|
|
if caCert.Fingerprint == sv.calculateFingerprint(cert.Raw) {
|
|
trustLink := TrustLink{
|
|
From: cert.Subject.CommonName,
|
|
To: caCert.Subject.CommonName,
|
|
Type: "x509_ca",
|
|
Algorithm: "RSA",
|
|
Valid: true,
|
|
Metadata: make(map[string]interface{}),
|
|
}
|
|
result.TrustChain = append(result.TrustChain, trustLink)
|
|
return nil
|
|
}
|
|
}
|
|
|
|
// Check system CA store
|
|
roots := x509.NewCertPool()
|
|
if ok := roots.AppendCertsFromPEM([]byte(sv.getSystemCAs())); ok {
|
|
opts := x509.VerifyOptions{
|
|
Roots: roots,
|
|
}
|
|
|
|
if _, err := cert.Verify(opts); err == nil {
|
|
trustLink := TrustLink{
|
|
From: cert.Subject.CommonName,
|
|
To: "system_ca_store",
|
|
Type: "x509_system",
|
|
Algorithm: "RSA",
|
|
Valid: true,
|
|
Metadata: make(map[string]interface{}),
|
|
}
|
|
result.TrustChain = append(result.TrustChain, trustLink)
|
|
return nil
|
|
}
|
|
}
|
|
|
|
result.Warnings = append(result.Warnings, "Certificate not in trusted CA store")
|
|
return nil
|
|
}
|
|
|
|
func (sv *SigningVerifier) calculateFingerprint(data []byte) string {
|
|
hash := sha256.Sum256(data)
|
|
return fmt.Sprintf("%x", hash)
|
|
}
|
|
|
|
func (sv *SigningVerifier) getSystemCAs() string {
|
|
// Common CA certificate locations
|
|
caPaths := []string{
|
|
"/etc/ssl/certs/ca-certificates.crt",
|
|
"/etc/ssl/certs/ca-bundle.crt",
|
|
"/usr/share/ssl/certs/ca-bundle.crt",
|
|
}
|
|
|
|
for _, path := range caPaths {
|
|
if data, err := os.ReadFile(path); err == nil {
|
|
return string(data)
|
|
}
|
|
}
|
|
|
|
return ""
|
|
}
|
|
|
|
func (sv *SigningVerifier) GenerateKeyPair(keyType string, keySize int, userID string, email string) (*SigningKey, error) {
|
|
sv.logger.Infof("Generating %s key pair (size: %d) for %s <%s>", keyType, keySize, userID, email)
|
|
|
|
key := &SigningKey{
|
|
ID: generateKeyID(),
|
|
Type: keyType,
|
|
Algorithm: "RSA",
|
|
KeySize: keySize,
|
|
Created: time.Now(),
|
|
Expires: time.Now().AddDate(2, 0, 0), // 2 years
|
|
UserID: userID,
|
|
Email: email,
|
|
Metadata: make(map[string]interface{}),
|
|
}
|
|
|
|
switch keyType {
|
|
case "gpg":
|
|
if err := sv.generateGPGKey(key); err != nil {
|
|
return nil, fmt.Errorf("GPG key generation failed: %w", err)
|
|
}
|
|
case "cosign":
|
|
if err := sv.generateCosignKey(key); err != nil {
|
|
return nil, fmt.Errorf("Cosign key generation failed: %w", err)
|
|
}
|
|
default:
|
|
return nil, fmt.Errorf("unsupported key type: %s", keyType)
|
|
}
|
|
|
|
// Add to key manager
|
|
sv.keyManager.keys[key.ID] = *key
|
|
|
|
sv.logger.Infof("Successfully generated key: %s", key.ID)
|
|
return key, nil
|
|
}
|
|
|
|
func (sv *SigningVerifier) generateGPGKey(key *SigningKey) error {
|
|
// Generate GPG key using gpg command
|
|
cmd := exec.Command("gpg", "--batch", "--gen-key", "--yes")
|
|
|
|
// Create batch file for key generation
|
|
batchContent := fmt.Sprintf(`Key-Type: RSA
|
|
Key-Length: %d
|
|
Name-Real: %s
|
|
Name-Email: %s
|
|
Expire-Date: 2y
|
|
%commit
|
|
`, key.KeySize, key.UserID, key.Email)
|
|
|
|
batchFile := filepath.Join(sv.config.GPGHomeDir, "batch.txt")
|
|
if err := os.WriteFile(batchFile, []byte(batchContent), 0600); err != nil {
|
|
return fmt.Errorf("failed to write batch file: %w", err)
|
|
}
|
|
defer os.Remove(batchFile)
|
|
|
|
cmd = exec.Command("gpg", "--batch", "--gen-key", batchFile)
|
|
cmd.Dir = sv.config.GPGHomeDir
|
|
|
|
if err := cmd.Run(); err != nil {
|
|
return fmt.Errorf("GPG key generation failed: %w", err)
|
|
}
|
|
|
|
// Export public key
|
|
exportCmd := exec.Command("gpg", "--armor", "--export", key.Email)
|
|
exportCmd.Dir = sv.config.GPGHomeDir
|
|
|
|
publicKey, err := exportCmd.Output()
|
|
if err != nil {
|
|
return fmt.Errorf("failed to export public key: %w", err)
|
|
}
|
|
|
|
key.PublicKey = string(publicKey)
|
|
|
|
// Get fingerprint
|
|
fingerprintCmd := exec.Command("gpg", "--fingerprint", key.Email)
|
|
fingerprintCmd.Dir = sv.config.GPGHomeDir
|
|
|
|
fingerprintOutput, err := fingerprintCmd.Output()
|
|
if err != nil {
|
|
return fmt.Errorf("failed to get fingerprint: %w", err)
|
|
}
|
|
|
|
// Parse fingerprint from output
|
|
lines := strings.Split(string(fingerprintOutput), "\n")
|
|
for _, line := range lines {
|
|
if strings.Contains(line, "Key fingerprint =") {
|
|
parts := strings.Split(line, "=")
|
|
if len(parts) >= 2 {
|
|
key.Fingerprint = strings.TrimSpace(parts[1])
|
|
break
|
|
}
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (sv *SigningVerifier) generateCosignKey(key *SigningKey) error {
|
|
// Generate cosign key pair
|
|
cmd := exec.Command("cosign", "generate-key-pair")
|
|
|
|
if err := cmd.Run(); err != nil {
|
|
return fmt.Errorf("cosign key generation failed: %w", err)
|
|
}
|
|
|
|
// Read generated keys
|
|
cosignKey := sv.config.CosignKeyPath
|
|
if cosignKey == "" {
|
|
cosignKey = "cosign.key"
|
|
}
|
|
|
|
privateKey, err := os.ReadFile(cosignKey)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to read private key: %w", err)
|
|
}
|
|
|
|
publicKey, err := os.ReadFile(cosignKey + ".pub")
|
|
if err != nil {
|
|
return fmt.Errorf("failed to read public key: %w", err)
|
|
}
|
|
|
|
key.PrivateKey = string(privateKey)
|
|
key.PublicKey = string(publicKey)
|
|
key.Algorithm = "ECDSA"
|
|
|
|
// Generate fingerprint
|
|
hash := sha256.Sum256(publicKey)
|
|
key.Fingerprint = fmt.Sprintf("%x", hash)
|
|
|
|
return nil
|
|
}
|
|
|
|
func (sv *SigningVerifier) AddTrustedKey(key *SignedKey) error {
|
|
sv.logger.Infof("Adding trusted key: %s", key.ID)
|
|
|
|
trustedKey := TrustedKey{
|
|
ID: key.ID,
|
|
Fingerprint: key.Fingerprint,
|
|
TrustLevel: "trusted",
|
|
Added: time.Now(),
|
|
Expires: key.Expires,
|
|
Metadata: make(map[string]interface{}),
|
|
}
|
|
|
|
sv.trustStore.trustedKeys[key.ID] = trustedKey
|
|
|
|
// Import into GPG keyring
|
|
if err := sv.importGPGKey(key.PublicKey); err != nil {
|
|
sv.logger.Warnf("Failed to import GPG key: %v", err)
|
|
}
|
|
|
|
sv.logger.Infof("Successfully added trusted key: %s", key.ID)
|
|
return nil
|
|
}
|
|
|
|
func (sv *SigningVerifier) importGPGKey(publicKey string) error {
|
|
// Create temporary file for public key
|
|
tempFile, err := os.CreateTemp("", "gpg-key-*")
|
|
if err != nil {
|
|
return fmt.Errorf("failed to create temp file: %w", err)
|
|
}
|
|
defer os.Remove(tempFile.Name())
|
|
|
|
if _, err := tempFile.WriteString(publicKey); err != nil {
|
|
return fmt.Errorf("failed to write public key: %w", err)
|
|
}
|
|
tempFile.Close()
|
|
|
|
// Import key into GPG
|
|
cmd := exec.Command("gpg", "--import", tempFile.Name())
|
|
cmd.Dir = sv.config.GPGHomeDir
|
|
|
|
if err := cmd.Run(); err != nil {
|
|
return fmt.Errorf("GPG import failed: %w", err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// Helper functions
|
|
func generateSignatureID() string {
|
|
return fmt.Sprintf("sig-%d", time.Now().UnixNano())
|
|
}
|
|
|
|
func generateVerificationID() string {
|
|
return fmt.Sprintf("ver-%d", time.Now().UnixNano())
|
|
}
|
|
|
|
func generateKeyID() string {
|
|
return fmt.Sprintf("key-%d", time.Now().UnixNano())
|
|
}
|
|
|
|
// SignedKey type for trusted key addition
|
|
type SignedKey struct {
|
|
ID string `json:"id"`
|
|
Fingerprint string `json:"fingerprint"`
|
|
Expires time.Time `json:"expires"`
|
|
Metadata map[string]interface{} `json:"metadata"`
|
|
}
|