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"` }