debian-forge-composer/internal/security/signing_verifier.go
robojerk 4eeaa43c39
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
did stuff
2025-08-26 10:34:42 -07:00

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