/* 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()) } }