diff --git a/internal/identity/identity.go b/internal/identity/identity.go index e961d49..1c7a83f 100644 --- a/internal/identity/identity.go +++ b/internal/identity/identity.go @@ -29,33 +29,17 @@ type Identity struct { // Generate creates a new self-signed X.509 certificate and ECDSA private key. func Generate(shortName string) (*Identity, error) { - priv, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) + priv, err := newPrivateKey() if err != nil { - return nil, fmt.Errorf("failed to generate private key: %w", err) + return nil, err } - notBefore := time.Now() - notAfter := notBefore.Add(365 * 24 * time.Hour) - - serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128) - serialNumber, err := rand.Int(rand.Reader, serialNumberLimit) + template, err := newCertificateTemplate(shortName) if err != nil { - return nil, fmt.Errorf("failed to generate serial number: %w", err) + return nil, err } - template := x509.Certificate{ - SerialNumber: serialNumber, - Subject: pkix.Name{ - CommonName: shortName, - }, - NotBefore: notBefore, - NotAfter: notAfter, - KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature, - ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth, x509.ExtKeyUsageClientAuth}, - BasicConstraintsValid: true, - } - - derBytes, err := x509.CreateCertificate(rand.Reader, &template, &template, &priv.PublicKey, priv) + derBytes, err := x509.CreateCertificate(rand.Reader, template, template, &priv.PublicKey, priv) if err != nil { return nil, fmt.Errorf("failed to create certificate: %w", err) } @@ -167,6 +151,37 @@ func MarshalCertificate(cert *x509.Certificate) []byte { return pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: cert.Raw}) } +func newPrivateKey() (*ecdsa.PrivateKey, error) { + priv, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) + if err != nil { + return nil, fmt.Errorf("failed to generate private key: %w", err) + } + return priv, nil +} + +func newCertificateTemplate(shortName string) (*x509.Certificate, error) { + notBefore := time.Now() + notAfter := notBefore.Add(365 * 24 * time.Hour) + + serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128) + serialNumber, err := rand.Int(rand.Reader, serialNumberLimit) + if err != nil { + return nil, fmt.Errorf("failed to generate serial number: %w", err) + } + + return &x509.Certificate{ + SerialNumber: serialNumber, + Subject: pkix.Name{ + CommonName: shortName, + }, + NotBefore: notBefore, + NotAfter: notAfter, + KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature, + ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth, x509.ExtKeyUsageClientAuth}, + BasicConstraintsValid: true, + }, nil +} + // UnmarshalCertificate imports a certificate from PEM format. func UnmarshalCertificate(data []byte) (*x509.Certificate, error) { block, _ := pem.Decode(data) diff --git a/internal/identity/identity_test.go b/internal/identity/identity_test.go new file mode 100644 index 0000000..21dee9a --- /dev/null +++ b/internal/identity/identity_test.go @@ -0,0 +1,29 @@ +// SPDX-License-Identifier: Apache-2.0 + +package identity + +import ( + "testing" +) + +func TestGenerate(t *testing.T) { + shortName := "test-agent" + ident, err := Generate(shortName) + if err != nil { + t.Fatalf("Generate() failed: %v", err) + } + + if ident == nil { + t.Fatal("Generate() returned nil identity") + } + + if ident.Certificate == nil { + t.Error("Generate() returned identity with nil certificate") + } else if ident.Certificate.Subject.CommonName != shortName { + t.Errorf("Generate() certificate common name = %q, want %q", ident.Certificate.Subject.CommonName, shortName) + } + + if ident.PrivateKey == nil { + t.Error("Generate() returned identity with nil private key") + } +}