kops/pkg/pki/issue_test.go

246 lines
7.9 KiB
Go

/*
Copyright 2020 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package pki
import (
"context"
"crypto/rand"
"crypto/rsa"
"crypto/x509"
"crypto/x509/pkix"
"math/big"
"net"
"os"
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
type mockKeystore struct {
t *testing.T
signer string
cert *Certificate
key *PrivateKey
invoked bool
}
// FindPrimaryKeypair implements Keystore
func (m *mockKeystore) FindPrimaryKeypair(ctx context.Context, name string) (*Certificate, *PrivateKey, error) {
assert.False(m.t, m.invoked, "invoked already")
m.invoked = true
assert.Equal(m.t, m.signer, name, "name argument")
return m.cert, m.key, nil
}
func TestIssueCert(t *testing.T) {
origSize := os.Getenv("KOPS_RSA_PRIVATE_KEY_SIZE")
os.Unsetenv("KOPS_RSA_PRIVATE_KEY_SIZE")
defer func() {
os.Setenv("KOPS_RSA_PRIVATE_KEY_SIZE", origSize)
}()
// Generate a new RSA key pair using rsa.GenerateKey
caKey, err := rsa.GenerateKey(rand.Reader, 2048)
require.NoError(t, err)
// Create pki.PrivateKey wrapper for CA key
caPrivateKey := &PrivateKey{Key: caKey}
// Create the CA
caTemplate := &x509.Certificate{
SerialNumber: big.NewInt(1),
Subject: pkix.Name{CommonName: "Test CA"},
NotBefore: time.Now(),
NotAfter: time.Now().Add(10 * 365 * 24 * time.Hour),
KeyUsage: x509.KeyUsageCertSign | x509.KeyUsageCRLSign,
BasicConstraintsValid: true,
IsCA: true,
}
caCertDER, err := x509.CreateCertificate(rand.Reader, caTemplate, caTemplate, &caKey.PublicKey, caKey)
require.NoError(t, err)
caCert, err := x509.ParseCertificate(caCertDER)
require.NoError(t, err)
caCertificate := &Certificate{Certificate: caCert}
for _, tc := range []struct {
name string
req IssueCertRequest
expectedKeyUsage x509.KeyUsage
expectedExtKeyUsage []x509.ExtKeyUsage
expectedSubject pkix.Name
expectedDNSNames []string
expectedIPAddresses []net.IP
}{
{
name: "ca",
req: IssueCertRequest{
Type: "ca",
Subject: pkix.Name{
CommonName: "Test CA",
},
},
expectedKeyUsage: x509.KeyUsageCRLSign | x509.KeyUsageCertSign,
expectedSubject: pkix.Name{CommonName: "Test CA"},
},
{
name: "client",
req: IssueCertRequest{
Type: "client",
Subject: pkix.Name{
CommonName: "Test client",
Organization: []string{"system:masters"},
},
Serial: BuildPKISerial(123456),
},
expectedKeyUsage: x509.KeyUsageDigitalSignature,
expectedExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth},
expectedSubject: pkix.Name{CommonName: "Test client", Organization: []string{"system:masters"}},
},
{
name: "clientOneYear",
req: IssueCertRequest{
Type: "client",
Subject: pkix.Name{
CommonName: "Test client",
},
Validity: time.Hour * 24 * 365,
},
expectedKeyUsage: x509.KeyUsageDigitalSignature,
expectedExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth},
expectedSubject: pkix.Name{CommonName: "Test client"},
},
{
req: IssueCertRequest{
Type: "clientServer",
Subject: pkix.Name{
CommonName: "Test client/server",
},
AlternateNames: []string{"*.internal.test.cluster.local", "localhost", "127.0.0.1"},
PrivateKey: caPrivateKey,
},
expectedKeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageKeyEncipherment,
expectedExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth, x509.ExtKeyUsageServerAuth},
expectedSubject: pkix.Name{CommonName: "Test client/server"},
expectedDNSNames: []string{"*.internal.test.cluster.local", "localhost"},
expectedIPAddresses: []net.IP{net.ParseIP("127.0.0.1").To4()},
},
{
name: "server",
req: IssueCertRequest{
Type: "server",
Subject: pkix.Name{
CommonName: "Test server",
},
AlternateNames: []string{"*.internal.test.cluster.local", "localhost", "127.0.0.1"},
PrivateKey: caPrivateKey,
},
expectedKeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageKeyEncipherment,
expectedExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
expectedSubject: pkix.Name{CommonName: "Test server"},
expectedDNSNames: []string{"*.internal.test.cluster.local", "localhost"},
expectedIPAddresses: []net.IP{net.ParseIP("127.0.0.1").To4()},
},
} {
t.Run(tc.name, func(t *testing.T) {
ctx := context.TODO()
var minExpectedValidity int64
if tc.req.Validity == 0 {
minExpectedValidity = time.Now().Add(time.Hour * 10 * 365 * 24).Unix()
} else {
minExpectedValidity = time.Now().Add(tc.req.Validity).Unix()
}
var keystore Keystore
if tc.req.Type != "ca" {
tc.req.Signer = tc.name + "-signer"
keystore = &mockKeystore{
t: t,
signer: tc.req.Signer,
cert: caCertificate,
key: caPrivateKey,
}
}
certificate, key, caCert, err := IssueCert(ctx, &tc.req, keystore)
require.NoError(t, err)
cert := certificate.Certificate
if tc.req.Signer == "" {
assert.Equal(t, cert.Issuer, cert.Subject, "self-signed")
assert.NoError(t, cert.CheckSignatureFrom(cert), "check signature")
assert.Equal(t, certificate, caCert, "returned CA cert")
} else {
assert.Equal(t, cert.Issuer, caCertificate.Certificate.Subject, "cert signer")
assert.NoError(t, cert.CheckSignatureFrom(caCertificate.Certificate), "check signature")
assert.Equal(t, caCertificate, caCert, "returned CA cert")
}
// type
assert.Equal(t, certificate.IsCA, cert.IsCA, "IsCA matches")
assert.Equal(t, tc.req.Type == "ca", cert.IsCA, "IsCA")
assert.True(t, cert.BasicConstraintsValid, "BasicConstraintsValid")
assert.Equal(t, tc.expectedKeyUsage, cert.KeyUsage, "KeyUsage")
assert.ElementsMatch(t, tc.expectedExtKeyUsage, cert.ExtKeyUsage)
assert.Nil(t, cert.ExtraExtensions, "ExtraExtensions")
// subject
assert.Equal(t, certificate.Subject, cert.Subject, "Subject matches")
actualName := cert.Subject
actualName.Names = nil
assert.Equal(t, tc.expectedSubject, actualName, "Subject")
// alternateNames
assert.Equal(t, tc.expectedDNSNames, cert.DNSNames, "DNSNames")
assert.Equal(t, tc.expectedIPAddresses, cert.IPAddresses, "IPAddresses")
assert.Empty(t, cert.EmailAddresses, "EmailAddresses")
// privateKey
rsaPrivateKey, ok := key.Key.(*rsa.PrivateKey)
require.True(t, ok, "private key is RSA")
if tc.req.PrivateKey == nil {
assert.Equal(t, 2048, rsaPrivateKey.N.BitLen(), "Private key length")
} else {
assert.Equal(t, tc.req.PrivateKey, key, "Private key")
}
assert.Equal(t, &rsaPrivateKey.PublicKey, cert.PublicKey, "certificate public key matches private key")
assert.Equal(t, certificate.PublicKey, cert.PublicKey, "PublicKey")
// serial
if tc.req.Serial != nil {
assert.Equal(t, cert.SerialNumber, tc.req.Serial, "SerialNumber")
} else {
assert.Greater(t, cert.SerialNumber.BitLen(), 110, "SerialNumber bit length")
}
// validity
var maxExpectedValidity int64
if tc.req.Validity == 0 {
maxExpectedValidity = time.Now().Add(time.Hour * 10 * 365 * 24).Unix()
} else {
maxExpectedValidity = time.Now().Add(tc.req.Validity).Unix()
}
assert.Less(t, cert.NotBefore.Unix(), time.Now().Add(time.Hour*-47).Unix(), "NotBefore")
assert.GreaterOrEqual(t, cert.NotAfter.Unix(), minExpectedValidity, "NotAfter")
assert.LessOrEqual(t, cert.NotAfter.Unix(), maxExpectedValidity, "NotAfter")
})
}
}