You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
98 lines
3.5 KiB
98 lines
3.5 KiB
|
2 months ago
|
package tlscert
|
||
|
|
|
||
|
|
import (
|
||
|
|
"crypto/rand"
|
||
|
|
"crypto/x509"
|
||
|
|
"encoding/pem"
|
||
|
|
"os"
|
||
|
|
"path/filepath"
|
||
|
|
"runtime"
|
||
|
|
"strings"
|
||
|
|
"testing"
|
||
|
|
"time"
|
||
|
|
)
|
||
|
|
|
||
|
|
func tlsTestClock() time.Time { return time.Date(2026, 8, 14, 0, 0, 0, 0, time.UTC) }
|
||
|
|
|
||
|
|
func TestGenerateCSRActivateAndReload(t *testing.T) {
|
||
|
|
root := filepath.Join(t.TempDir(), "library")
|
||
|
|
activeCert := filepath.Join(t.TempDir(), "active.crt")
|
||
|
|
activeKey := filepath.Join(filepath.Dir(activeCert), "active.key")
|
||
|
|
repository, err := Open(Options{Root: root, ActiveCert: activeCert, ActiveKey: activeKey, Clock: tlsTestClock})
|
||
|
|
if err != nil {
|
||
|
|
t.Fatal(err)
|
||
|
|
}
|
||
|
|
created, err := repository.Generate("rack-console", Subject{Country: "KR", Organization: "TERA", CommonName: "console.example.local", Email: "ops@example.local"})
|
||
|
|
if err != nil {
|
||
|
|
t.Fatal(err)
|
||
|
|
}
|
||
|
|
if created.KeyBits != 2048 || created.CommonName != "console.example.local" || !created.PrivateKeyStored {
|
||
|
|
t.Fatalf("generated metadata = %#v", created)
|
||
|
|
}
|
||
|
|
csrPEM, err := repository.CSR(created.CertificateID, Subject{CommonName: "console.example.local", Organization: "TERA"})
|
||
|
|
if err != nil {
|
||
|
|
t.Fatal(err)
|
||
|
|
}
|
||
|
|
block, _ := pem.Decode(csrPEM)
|
||
|
|
if block == nil {
|
||
|
|
t.Fatal("CSR is not PEM encoded")
|
||
|
|
}
|
||
|
|
csr, err := x509.ParseCertificateRequest(block.Bytes)
|
||
|
|
if err != nil || csr.CheckSignature() != nil || csr.Subject.CommonName != "console.example.local" {
|
||
|
|
t.Fatalf("CSR parse/signature = %#v, %v", csr, err)
|
||
|
|
}
|
||
|
|
if _, err := repository.Activate(created.CertificateID); err != nil {
|
||
|
|
t.Fatal(err)
|
||
|
|
}
|
||
|
|
if info, err := os.Stat(activeKey); err != nil || runtime.GOOS != "windows" && info.Mode().Perm() != 0o600 {
|
||
|
|
t.Fatalf("active key mode = %v, %v", info, err)
|
||
|
|
}
|
||
|
|
reloaded, err := Open(Options{Root: root, ActiveCert: activeCert, ActiveKey: activeKey, Clock: tlsTestClock})
|
||
|
|
if err != nil {
|
||
|
|
t.Fatal(err)
|
||
|
|
}
|
||
|
|
if metadata, err := reloaded.Metadata(created.CertificateID); err != nil || metadata.FingerprintSHA256 != created.FingerprintSHA256 {
|
||
|
|
t.Fatalf("reloaded metadata = %#v, %v", metadata, err)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestImportEncryptedPEMAndRejectMismatchedKey(t *testing.T) {
|
||
|
|
source, err := Open(Options{Clock: tlsTestClock})
|
||
|
|
if err != nil {
|
||
|
|
t.Fatal(err)
|
||
|
|
}
|
||
|
|
first, err := generateRecord("first", Subject{CommonName: "first.local"}, "generated", tlsTestClock())
|
||
|
|
if err != nil {
|
||
|
|
t.Fatal(err)
|
||
|
|
}
|
||
|
|
second, err := generateRecord("second", Subject{CommonName: "second.local"}, "generated", tlsTestClock())
|
||
|
|
if err != nil {
|
||
|
|
t.Fatal(err)
|
||
|
|
}
|
||
|
|
keyBlock, _ := pem.Decode(first.keyPEM)
|
||
|
|
encrypted, err := x509.EncryptPEMBlock(rand.Reader, "RSA PRIVATE KEY", keyBlock.Bytes, []byte("secret-passphrase"), x509.PEMCipherAES256)
|
||
|
|
if err != nil {
|
||
|
|
t.Fatal(err)
|
||
|
|
}
|
||
|
|
metadata, err := source.Import(ImportRequest{CertificateID: "encrypted", CertificatePEM: first.certPEM, PrivateKeyPEM: pem.EncodeToMemory(encrypted), Password: "secret-passphrase"})
|
||
|
|
if err != nil || metadata.CertificateID != "encrypted" {
|
||
|
|
t.Fatalf("encrypted import = %#v, %v", metadata, err)
|
||
|
|
}
|
||
|
|
if _, err := source.Import(ImportRequest{CertificateID: "mismatch", CertificatePEM: first.certPEM, PrivateKeyPEM: second.keyPEM}); err == nil || !strings.Contains(err.Error(), "does not match") {
|
||
|
|
t.Fatalf("mismatched import error = %v", err)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestRepositoryBoundsNamesAndFactoryDeletion(t *testing.T) {
|
||
|
|
repository, err := Open(Options{Clock: tlsTestClock})
|
||
|
|
if err != nil {
|
||
|
|
t.Fatal(err)
|
||
|
|
}
|
||
|
|
if _, err := repository.Generate("../escape", Subject{CommonName: "bad.local"}); err == nil {
|
||
|
|
t.Fatal("unsafe certificate id accepted")
|
||
|
|
}
|
||
|
|
if err := repository.Delete(FactoryCertificateID); err == nil {
|
||
|
|
t.Fatal("factory certificate deletion accepted")
|
||
|
|
}
|
||
|
|
}
|