did stuff
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
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
This commit is contained in:
parent
d228f6d30f
commit
4eeaa43c39
47 changed files with 21390 additions and 31 deletions
1167
internal/security/compliance_checker.go
Normal file
1167
internal/security/compliance_checker.go
Normal file
File diff suppressed because it is too large
Load diff
1003
internal/security/security_scanner.go
Normal file
1003
internal/security/security_scanner.go
Normal file
File diff suppressed because it is too large
Load diff
856
internal/security/signing_verifier.go
Normal file
856
internal/security/signing_verifier.go
Normal file
|
|
@ -0,0 +1,856 @@
|
|||
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"`
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue