kops/upup/pkg/fi/ca_test.go

401 lines
15 KiB
Go

/*
Copyright 2021 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 fi_test
import (
"math/big"
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"k8s.io/kops/pkg/pki"
"k8s.io/kops/upup/pkg/fi"
)
const (
certData = "-----BEGIN CERTIFICATE-----\nMIIC+DCCAeCgAwIBAgIMFnbTyckBfEgW0qf0MA0GCSqGSIb3DQEBCwUAMBgxFjAU\nBgNVBAMTDWNuPWt1YmVybmV0ZXMwHhcNMjEwNDE2MDI0NjE5WhcNMzEwNDE2MDI0\nNjE5WjAYMRYwFAYDVQQDEw1jbj1rdWJlcm5ldGVzMIIBIjANBgkqhkiG9w0BAQEF\nAAOCAQ8AMIIBCgKCAQEA4JwpEprZ5n8RIEt6jT2lAh+UDgRgx/4px21gjgywQivY\nHVxHAZexVb/E9pBa9Q2G9B1Q7TCO7YsUVRQy4JMDZVt+McFnWVwexnqBYFNcVjkE\nmDgAgvCYGE0P9d/RwRL4KuLHo+u6fv7P0jXMN+CpOxyLhYZZNa0ZOZDHsSiJSQSj\n9WGFGHrbCf0KVDpKieR1uBqHrRO+mLR5zkX2L58m74kjK4dsBhmjeq/7OAoTmiG2\nQgJ/P2IjyhiA2mRqY+hl55lwEUV/0yHYEkJC8LdGkwwZz2eF77aSPGmi/A2CSKgM\nwDTx9m+P7jcpWreYw6NG9BueGoDIve/tgFKwvVFF6QIDAQABo0IwQDAOBgNVHQ8B\nAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUVOJ/Z21wnbF5Kjsi\nJFdZL/rKHJ4wDQYJKoZIhvcNAQELBQADggEBABWm3zNEiAO+HxfT+WYUhS/ot1Rf\nwzQUPmFR+1GGdXxOam4dMQ0X9uJsE5PtNw7kg9ja+jkUuharSXraueu6IbcRCXQ8\nycnjmbhcPdvH8dhQAB+2Y1xyf/olWan7NU98fwwWk3ubX1aUaoBbXJW9IqkmRG6K\nZVTnz0yGdQfqZyZbl48WfIQ3W7qsAnsSYSrhUWNNOHgy6NQRxpgzGohYFAUprFqc\n8Dm02rzpNwe+ZDOInm05UUOblsdeHYrRetfvhnYJl/CEPGAdJGnOjLMjr7V85y4a\n41XKFsjMo2ztvNdLmYiw0dfar4WSdK/AKFzUXEPRwjCe5xMtsMOIkyJtvxw=\n-----END CERTIFICATE-----\n"
tooBigSerialCertData = "-----BEGIN CERTIFICATE-----\nMIIC2DCCAcCgAwIBAgIRALJXAkVj964tq67wMSI8oJQwDQYJKoZIhvcNAQELBQAw\nFTETMBEGA1UEAxMKa3ViZXJuZXRlczAeFw0xNzEyMjcyMzUyNDBaFw0yNzEyMjcy\nMzUyNDBaMBUxEzARBgNVBAMTCmt1YmVybmV0ZXMwggEiMA0GCSqGSIb3DQEBAQUA\nA4IBDwAwggEKAoIBAQDgnCkSmtnmfxEgS3qNPaUCH5QOBGDH/inHbWCODLBCK9gd\nXEcBl7FVv8T2kFr1DYb0HVDtMI7tixRVFDLgkwNlW34xwWdZXB7GeoFgU1xWOQSY\nOACC8JgYTQ/139HBEvgq4sej67p+/s/SNcw34Kk7HIuFhlk1rRk5kMexKIlJBKP1\nYYUYetsJ/QpUOkqJ5HW4GoetE76YtHnORfYvnybviSMrh2wGGaN6r/s4ChOaIbZC\nAn8/YiPKGIDaZGpj6GXnmXARRX/TIdgSQkLwt0aTDBnPZ4XvtpI8aaL8DYJIqAzA\nNPH2b4/uNylat5jDo0b0G54agMi97+2AUrC9UUXpAgMBAAGjIzAhMA4GA1UdDwEB\n/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQBVGR2r\nhzXzRMU5wriPQAJScszNORvoBpXfZoZ09FIupudFxBVU3d4hV9StKnQgPSGA5XQO\nHE97+BxJDuA/rB5oBUsMBjc7y1cde/T6hmi3rLoEYBSnSudCOXJE4G9/0f8byAJe\nrN8+No1r2VgZvZh6p74TEkXv/l3HBPWM7IdUV0HO9JDhSgOVF1fyQKJxRuLJR8jt\nO6mPH2UX0vMwVa4jvwtkddqk2OAdYQvH9rbDjjbzaiW0KnmdueRo92KHAN7BsDZy\nVpXHpqo1Kzg7D3fpaXCf5si7lqqrdJVXH4JC72zxsPehqgi8eIuqOBkiDWmRxAxh\n8yGeRx9AbknHh4Ia\n-----END CERTIFICATE-----\n"
privatekeyData = "-----BEGIN RSA PRIVATE KEY-----\nMIIEpAIBAAKCAQEA4JwpEprZ5n8RIEt6jT2lAh+UDgRgx/4px21gjgywQivYHVxH\nAZexVb/E9pBa9Q2G9B1Q7TCO7YsUVRQy4JMDZVt+McFnWVwexnqBYFNcVjkEmDgA\ngvCYGE0P9d/RwRL4KuLHo+u6fv7P0jXMN+CpOxyLhYZZNa0ZOZDHsSiJSQSj9WGF\nGHrbCf0KVDpKieR1uBqHrRO+mLR5zkX2L58m74kjK4dsBhmjeq/7OAoTmiG2QgJ/\nP2IjyhiA2mRqY+hl55lwEUV/0yHYEkJC8LdGkwwZz2eF77aSPGmi/A2CSKgMwDTx\n9m+P7jcpWreYw6NG9BueGoDIve/tgFKwvVFF6QIDAQABAoIBAA0ktjaTfyrAxsTI\nBezb7Zr5NBW55dvuII299cd6MJo+rI/TRYhvUv48kY8IFXp/hyUjzgeDLunxmIf9\n/Zgsoic9Ol44/g45mMduhcGYPzAAeCdcJ5OB9rR9VfDCXyjYLlN8H8iU0734tTqM\n0V13tQ9zdSqkGPZOIcq/kR/pylbOZaQMe97BTlsAnOMSMKDgnftY4122Lq3GYy+t\nvpr+bKVaQZwvkLoSU3rECCaKaghgwCyX7jft9aEkhdJv+KlwbsGY6WErvxOaLWHd\ncuMQjGapY1Fa/4UD00mvrA260NyKfzrp6+P46RrVMwEYRJMIQ8YBAk6N6Hh7dc0G\n8Z6i1m0CgYEA9HeCJR0TSwbIQ1bDXUrzpftHuidG5BnSBtax/ND9qIPhR/FBW5nj\n22nwLc48KkyirlfIULd0ae4qVXJn7wfYcuX/cJMLDmSVtlM5Dzmi/91xRiFgIzx1\nAsbBzaFjISP2HpSgL+e9FtSXaaqeZVrflitVhYKUpI/AKV31qGHf04sCgYEA6zTV\n99Sb49Wdlns5IgsfnXl6ToRttB18lfEKcVfjAM4frnkk06JpFAZeR+9GGKUXZHqs\nz2qcplw4d/moCC6p3rYPBMLXsrGNEUFZqBlgz72QA6BBq3X0Cg1Bc2ZbK5VIzwkg\nST2SSux6ccROfgULmN5ZiLOtdUKNEZpFF3i3qtsCgYADT/s7dYFlatobz3kmMnXK\nsfTu2MllHdRys0YGHu7Q8biDuQkhrJwhxPW0KS83g4JQym+0aEfzh36bWcl+u6R7\nKhKj+9oSf9pndgk345gJz35RbPJYh+EuAHNvzdgCAvK6x1jETWeKf6btj5pF1U1i\nQ4QNIw/QiwIXjWZeubTGsQKBgQCbduLu2rLnlyyAaJZM8DlHZyH2gAXbBZpxqU8T\nt9mtkJDUS/KRiEoYGFV9CqS0aXrayVMsDfXY6B/S/UuZjO5u7LtklDzqOf1aKG3Q\ndGXPKibknqqJYH+bnUNjuYYNerETV57lijMGHuSYCf8vwLn3oxBfERRX61M/DU8Z\nworz/QKBgQDCTJI2+jdXg26XuYUmM4XXfnocfzAXhXBULt1nENcogNf1fcptAVtu\nBAiz4/HipQKqoWVUYmxfgbbLRKKLK0s0lOWKbYdVjhEm/m2ZU8wtXTagNwkIGoyq\nY/C1Lox4f1ROJnCjc/hfcOjcxX5M8A8peecHWlVtUPKTJgxQ7oMKcw==\n-----END RSA PRIVATE KEY-----\n"
afterCertData = "-----BEGIN CERTIFICATE-----\nMIIBbjCCARigAwIBAgIMFnbWaYo6t3AwKQtWMA0GCSqGSIb3DQEBCwUAMBgxFjAU\nBgNVBAMTDWNuPWt1YmVybmV0ZXMwHhcNMjEwNDE2MDMzNDI0WhcNMzEwNDE2MDMz\nNDI0WjAYMRYwFAYDVQQDEw1jbj1rdWJlcm5ldGVzMFwwDQYJKoZIhvcNAQEBBQAD\nSwAwSAJBANLVh1dSDxJ5EcCd36av7++6+sDKqEm2GAzKIwOlfvPsm+pT+pClr51s\nd1m7V16nhWE6lhWjtsiMF8Q32+P5XZkCAwEAAaNCMEAwDgYDVR0PAQH/BAQDAgEG\nMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFIaNS7TlHC6K0r8yWYM1wExengDq\nMA0GCSqGSIb3DQEBCwUAA0EAoxha8yD6JLJcog/EOMdc5BpVPupQ/0FyO38Mb3l9\n0N7uZle0Tz1FQuadRtouySj37iq9nIxEeTh03Q52hNcl3A==\n-----END CERTIFICATE-----\n"
afterPrivateKeyData = "-----BEGIN RSA PRIVATE KEY-----\nMIIBOQIBAAJBANLVh1dSDxJ5EcCd36av7++6+sDKqEm2GAzKIwOlfvPsm+pT+pCl\nr51sd1m7V16nhWE6lhWjtsiMF8Q32+P5XZkCAwEAAQJASaGZKr3V1bXCpWp9eVFo\nkmjSuhIMw/F8ZLsTj2p08+rEFIBEpTwmimO9TJEWfPB/yFb3dJpxdLKn7oLcGjSM\noQIhAOJGCrXPRZJsizw3iR3J9KaYIx07EMkqOYZf4gjWULcjAiEA7og7B0+jcxt4\nAcYj38QLZt++cUww+F2VRIQom7LUghMCICGDtFRnheuBLkJWC3YhEp6WTCUpOXxC\nR9DyZL1gWQY3AiABvHIZioXZB6Em+ic2sLmYhRZgwro0hJHajs+w2mtbiwIgN5or\n6t9M/Gb5+3SPwfbsT0ySzuVLobmPYv+Sul+MQn8=\n-----END RSA PRIVATE KEY-----\n"
)
func TestAddItem(t *testing.T) {
cert, _ := pki.ParsePEMCertificate([]byte(certData))
tooBigSerialCert, _ := pki.ParsePEMCertificate([]byte(tooBigSerialCertData))
privateKey, _ := pki.ParsePEMPrivateKey([]byte(privatekeyData))
afterCert, _ := pki.ParsePEMCertificate([]byte(afterCertData))
afterPrivateKey, _ := pki.ParsePEMPrivateKey([]byte(afterPrivateKeyData))
type expectedItems []struct {
validateId func(t *testing.T, id string)
cert *pki.Certificate
privateKey *pki.PrivateKey
}
type expected struct {
items expectedItems
primary *pki.PrivateKey
error string
}
tests := []struct {
name string
keyset fi.Keyset
cert *pki.Certificate
privateKey *pki.PrivateKey
expectedPrimary expected
expectedSecondary expected
}{
{
name: "first",
keyset: fi.Keyset{
Items: map[string]*fi.KeysetItem{},
},
cert: cert,
privateKey: privateKey,
expectedPrimary: expected{
items: expectedItems{
{
cert: cert,
privateKey: privateKey,
},
},
primary: privateKey,
},
expectedSecondary: expected{
error: "cannot add secondary item when no existing primary item",
},
},
{
name: "firstBigSerial",
keyset: fi.Keyset{
Items: map[string]*fi.KeysetItem{},
},
cert: tooBigSerialCert,
privateKey: privateKey,
expectedPrimary: expected{
items: expectedItems{
{
validateId: assertSerialNotInFuture,
cert: tooBigSerialCert,
privateKey: privateKey,
},
},
primary: privateKey,
},
expectedSecondary: expected{
error: "cannot add secondary item when no existing primary item",
},
},
{
name: "after",
keyset: fi.Keyset{
Items: map[string]*fi.KeysetItem{
"6952323604391556590983096308": {
Id: "6952323604391556590983096308",
Certificate: cert,
PrivateKey: privateKey,
},
},
Primary: &fi.KeysetItem{
Id: "6952323604391556590983096308",
Certificate: cert,
PrivateKey: privateKey,
},
},
cert: afterCert,
privateKey: afterPrivateKey,
expectedPrimary: expected{
items: expectedItems{
{
cert: cert,
privateKey: privateKey,
},
{
cert: afterCert,
privateKey: afterPrivateKey,
},
},
primary: afterPrivateKey,
},
expectedSecondary: expected{
items: expectedItems{
{
cert: cert,
privateKey: privateKey,
},
{
cert: afterCert,
privateKey: afterPrivateKey,
},
},
primary: privateKey,
},
},
{
name: "bigSerialAfter",
keyset: fi.Keyset{
Items: map[string]*fi.KeysetItem{
"6952335996080054816494652246": {
Id: "6952335996080054816494652246",
Certificate: afterCert,
PrivateKey: afterPrivateKey,
},
},
Primary: &fi.KeysetItem{
Id: "6952335996080054816494652246",
Certificate: afterCert,
PrivateKey: afterPrivateKey,
},
},
cert: tooBigSerialCert,
privateKey: privateKey,
expectedPrimary: expected{
items: expectedItems{
{
cert: afterCert,
privateKey: afterPrivateKey,
},
{
validateId: assertSerialNotInFuture,
cert: tooBigSerialCert,
privateKey: privateKey,
},
},
primary: privateKey,
},
expectedSecondary: expected{
items: expectedItems{
{
cert: afterCert,
privateKey: afterPrivateKey,
},
{
validateId: assertSerialNotInFuture,
cert: tooBigSerialCert,
privateKey: privateKey,
},
},
primary: afterPrivateKey,
},
},
{
name: "before",
keyset: fi.Keyset{
Items: map[string]*fi.KeysetItem{
"6952335996080054816494652246": {
Id: "6952335996080054816494652246",
Certificate: afterCert,
PrivateKey: afterPrivateKey,
},
},
Primary: &fi.KeysetItem{
Id: "6952335996080054816494652246",
Certificate: afterCert,
PrivateKey: afterPrivateKey,
},
},
cert: cert,
privateKey: privateKey,
expectedPrimary: expected{
items: expectedItems{
{
cert: afterCert,
privateKey: afterPrivateKey,
},
{
validateId: func(t *testing.T, id string) {
assertSerialNewerThan(t, id, afterCert)
},
cert: cert,
privateKey: privateKey,
},
},
primary: privateKey,
},
expectedSecondary: expected{
items: expectedItems{
{
cert: afterCert,
privateKey: afterPrivateKey,
},
{
cert: cert,
privateKey: privateKey,
},
},
primary: afterPrivateKey,
},
},
{
name: "first certonly",
keyset: fi.Keyset{
Items: map[string]*fi.KeysetItem{},
},
cert: cert,
expectedPrimary: expected{
error: "private key not provided for primary item",
},
expectedSecondary: expected{
error: "cannot add secondary item when no existing primary item",
},
},
{
name: "after certonly",
keyset: fi.Keyset{
Items: map[string]*fi.KeysetItem{
"6952323604391556590983096308": {
Id: "6952323604391556590983096308",
Certificate: cert,
PrivateKey: privateKey,
},
},
Primary: &fi.KeysetItem{
Id: "6952323604391556590983096308",
Certificate: cert,
PrivateKey: privateKey,
},
},
cert: afterCert,
expectedPrimary: expected{
error: "private key not provided for primary item",
},
expectedSecondary: expected{
items: expectedItems{
{
cert: cert,
privateKey: privateKey,
},
{
validateId: func(t *testing.T, id string) {
assertSerialOlderThan(t, id, cert)
},
cert: afterCert,
},
},
primary: privateKey,
},
},
{
name: "before certonly",
keyset: fi.Keyset{
Items: map[string]*fi.KeysetItem{
"6952335996080054816494652246": {
Id: "6952335996080054816494652246",
Certificate: afterCert,
PrivateKey: afterPrivateKey,
},
},
Primary: &fi.KeysetItem{
Id: "6952335996080054816494652246",
Certificate: afterCert,
PrivateKey: afterPrivateKey,
},
},
cert: cert,
expectedPrimary: expected{
error: "private key not provided for primary item",
},
expectedSecondary: expected{
items: expectedItems{
{
cert: afterCert,
privateKey: afterPrivateKey,
},
{
cert: cert,
},
},
primary: afterPrivateKey,
},
},
}
for _, tc := range tests {
runTestcase := func(t *testing.T, primary bool, tcExpected expected) {
keyset := tc.keyset
keyset.Items = make(map[string]*fi.KeysetItem, len(tc.keyset.Items))
for k, v := range tc.keyset.Items {
keyset.Items[k] = v
}
_, err := keyset.AddItem(tc.cert, tc.privateKey, primary)
if tcExpected.error != "" {
assert.EqualError(t, err, tcExpected.error)
return
}
assert.NoError(t, err)
assert.Equal(t, tcExpected.primary, keyset.Primary.PrivateKey, "primary key")
expected:
for _, expected := range tcExpected.items {
for id, item := range keyset.Items {
if (expected.cert != nil && expected.cert == item.Certificate) ||
expected.privateKey != nil && expected.privateKey == item.PrivateKey {
assert.Same(t, expected.cert, item.Certificate, "item %s", id)
assert.Same(t, expected.privateKey, item.PrivateKey, "item %s", id)
if expected.validateId == nil {
assert.Equal(t, expected.cert.Certificate.SerialNumber.String(), id, "id")
} else {
expected.validateId(t, id)
}
delete(keyset.Items, id)
continue expected
}
}
if expected.cert != nil {
t.Errorf("did not find expected item %q", expected.cert.Certificate.SerialNumber)
} else {
t.Errorf("did not find expected key %q", expected.privateKey)
}
}
for id := range keyset.Items {
t.Errorf("unexpected item %q", id)
}
}
t.Run(tc.name+" primary", func(t *testing.T) {
runTestcase(t, true, tc.expectedPrimary)
})
t.Run(tc.name+" secondary", func(t *testing.T) {
runTestcase(t, false, tc.expectedSecondary)
})
}
}
func assertSerialNotInFuture(t *testing.T, id string) {
version, ok := big.NewInt(0).SetString(id, 10)
require.True(t, ok, "parses as integer")
if version.Cmp(pki.BuildPKISerial(time.Now().UnixNano())) > 0 {
t.Errorf("id %q larger than serial for current time", id)
}
}
func assertSerialNewerThan(t *testing.T, id string, cert *pki.Certificate) {
version, ok := big.NewInt(0).SetString(id, 10)
require.True(t, ok, "parses as integer")
if version.Cmp(cert.Certificate.SerialNumber) <= 0 {
t.Errorf("id %q not larger than %q", id, cert.Certificate.SerialNumber.String())
}
}
func assertSerialOlderThan(t *testing.T, id string, cert *pki.Certificate) {
version, ok := big.NewInt(0).SetString(id, 10)
require.True(t, ok, "parses as integer")
if version.Cmp(cert.Certificate.SerialNumber) >= 0 {
t.Errorf("id %q not smaller than %q", id, cert.Certificate.SerialNumber.String())
}
}