Skip to content

Commit

Permalink
Add support for PKCS8 private keys in CA certs
Browse files Browse the repository at this point in the history
  • Loading branch information
maelk committed Jun 22, 2020
1 parent 3a4b104 commit a871651
Show file tree
Hide file tree
Showing 5 changed files with 153 additions and 6 deletions.
3 changes: 2 additions & 1 deletion controlplane/kubeadm/internal/workload_cluster.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ package internal

import (
"context"
"crypto"
"crypto/rand"
"crypto/rsa"
"crypto/tls"
Expand Down Expand Up @@ -311,7 +312,7 @@ func generateClientCert(caCertEncoded, caKeyEncoded []byte) (tls.Certificate, er
return tls.X509KeyPair(certs.EncodeCertPEM(x509Cert), certs.EncodePrivateKeyPEM(privKey))
}

func newClientCert(caCert *x509.Certificate, key *rsa.PrivateKey, caKey *rsa.PrivateKey) (*x509.Certificate, error) {
func newClientCert(caCert *x509.Certificate, key *rsa.PrivateKey, caKey crypto.Signer) (*x509.Certificate, error) {
cfg := certs.Config{
CommonName: "cluster-api.x-k8s.io",
}
Expand Down
32 changes: 30 additions & 2 deletions util/certs/certs.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,14 @@ limitations under the License.
package certs

import (
"crypto"
"crypto/rand"
"crypto/rsa"
"crypto/x509"
"encoding/pem"

"github.com/pkg/errors"
kerrors "k8s.io/apimachinery/pkg/util/errors"
)

// NewPrivateKey creates an RSA private key
Expand Down Expand Up @@ -76,11 +78,37 @@ func DecodeCertPEM(encoded []byte) (*x509.Certificate, error) {

// DecodePrivateKeyPEM attempts to return a decoded key or nil
// if the encoded input does not contain a private key.
func DecodePrivateKeyPEM(encoded []byte) (*rsa.PrivateKey, error) {
func DecodePrivateKeyPEM(encoded []byte) (crypto.Signer, error) {
block, _ := pem.Decode(encoded)
if block == nil {
return nil, nil
}

return x509.ParsePKCS1PrivateKey(block.Bytes)
errs := []error{}
pkcs1Key, pkcs1Err := x509.ParsePKCS1PrivateKey(block.Bytes)
if pkcs1Err == nil {
return crypto.Signer(pkcs1Key), nil
}
errs = append(errs, pkcs1Err)

// ParsePKCS1PrivateKey will fail with errors.New for many reasons
// including if the format is wrong, so we can retry with PKCS8 or EC
// https://golang.org/src/crypto/x509/pkcs1.go#L58
pkcs8Key, pkcs8Err := x509.ParsePKCS8PrivateKey(block.Bytes)
if pkcs8Err == nil {
pkcs8Signer, ok := pkcs8Key.(crypto.Signer)
if !ok {
return nil, errors.New("x509: certificate private key does not implement crypto.Signer")
}
return pkcs8Signer, nil
}
errs = append(errs, pkcs8Err)

ecKey, ecErr := x509.ParseECPrivateKey(block.Bytes)
if ecErr == nil {
return crypto.Signer(ecKey), nil
}
errs = append(errs, ecErr)

return nil, kerrors.NewAggregate(errs)
}
116 changes: 116 additions & 0 deletions util/certs/certs_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
/*
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 certs

import (
"testing"

. "github.com/onsi/gomega"
)

type decodeTest struct {
key []byte
expectError bool
}

func TestDecodePrivateKeyPEM(t *testing.T) {
g := NewWithT(t)

cases := []decodeTest{
// PKCS1 private key
{
key: []byte(`
-----BEGIN RSA PRIVATE KEY-----
MIICXAIBAAKBgQCgcTrC6rTj6KV5GeUyEODguAY+RMxX0ZzskOZBUFuUn1ADj7qK
vdfF9WHetcvvnnZ+XuCWrHcoRRIiO5Ikpnz0H54J9Zdy5UAIqkGCOIEdhAVDvLBe
oJ7G2x11Lyz/us7EekqNeguZ9xJ+efjWsuPwYxo8iWluR3jcIA3NK5QCLQIDAQAB
AoGBAIr1xwkvM4D57OfYb9RPHhZEDNQ9ziZ5nEqgrW0AZnFxEmIjSFQGXS5Ne3jj
SEC/pK2LC0Y1FfdA65XOtqMbt7hx3QqjBYIu01AyQGYnrSsiSPdLf4RZviEmZ19n
kuZKKI6TjLXG9LfZO9/x3bYJeHa+rgZoSYK/JEUznIn768/BAkEAzKtZhwLH3zcI
mFyOYjIk2pFauz5tt/9pdXOFHRFS3KKsIrbI2NZd5C5dVp5mnRZ27H4g9HZGurxy
3zWfcrRQ1QJBAMiuUH5iIcWdoRJsgUgCmCYsaynzZgLecEF7VOlRWHiJ60bwNZTG
p0TkEewdmPogbCmaAEtovsBFuQ4JCIxVV/kCQFFn+iUUOxGSny2S6uMt1LDGzdLa
IuPjiDu6JgEIye+OGG96SmrM4O2Ib4GrYV8r90Nba5owjTNrDzmu52vFQr0CQDE9
3JB2YdUMraZIq5xQzqanRZBgogpYLHFU4uvxQuUo6mtYq70a1ZZo5CDszkmpxQCc
QjA+vneNZDAWdVuB4XkCQHjO1CcHKWlihm/xmXDVQKK4oWrNrs6MddLwJ6vAZBAw
I8eun6k9HNyEieJTVaB9AVnykoZ78UbCQaipm9W7i4Q=
-----END RSA PRIVATE KEY-----
`),
},
// PKCS8 private key
{
key: []byte(`
-----BEGIN PRIVATE KEY-----
MIICdgIBADANBgkqhkiG9w0BAQEFAASCAmAwggJcAgEAAoGBAKBxOsLqtOPopXkZ
5TIQ4OC4Bj5EzFfRnOyQ5kFQW5SfUAOPuoq918X1Yd61y++edn5e4JasdyhFEiI7
kiSmfPQfngn1l3LlQAiqQYI4gR2EBUO8sF6gnsbbHXUvLP+6zsR6So16C5n3En55
+Nay4/BjGjyJaW5HeNwgDc0rlAItAgMBAAECgYEAivXHCS8zgPns59hv1E8eFkQM
1D3OJnmcSqCtbQBmcXESYiNIVAZdLk17eONIQL+krYsLRjUV90Drlc62oxu3uHHd
CqMFgi7TUDJAZietKyJI90t/hFm+ISZnX2eS5koojpOMtcb0t9k73/Hdtgl4dr6u
BmhJgr8kRTOcifvrz8ECQQDMq1mHAsffNwiYXI5iMiTakVq7Pm23/2l1c4UdEVLc
oqwitsjY1l3kLl1WnmadFnbsfiD0dka6vHLfNZ9ytFDVAkEAyK5QfmIhxZ2hEmyB
SAKYJixrKfNmAt5wQXtU6VFYeInrRvA1lManROQR7B2Y+iBsKZoAS2i+wEW5DgkI
jFVX+QJAUWf6JRQ7EZKfLZLq4y3UsMbN0toi4+OIO7omAQjJ744Yb3pKaszg7Yhv
gathXyv3Q1trmjCNM2sPOa7na8VCvQJAMT3ckHZh1QytpkirnFDOpqdFkGCiClgs
cVTi6/FC5Sjqa1irvRrVlmjkIOzOSanFAJxCMD6+d41kMBZ1W4HheQJAeM7UJwcp
aWKGb/GZcNVAorihas2uzox10vAnq8BkEDAjx66fqT0c3ISJ4lNVoH0BWfKShnvx
RsJBqKmb1buLhA==
-----END PRIVATE KEY-----
`),
},
// EC private key
{
key: []byte(`
-----BEGIN EC PRIVATE KEY-----
MHcCAQEEIOsVFUX30MNP7e+MFRTbdknxaC3q3S8fYvmXtrM9tPJJoAoGCCqGSM49
AwEHoUQDQgAERhsfjOmIFAKxuniysAVbR2GJefo03OombXMr1SuuPyTtlcEbWh4b
X9ZN2FCDgn06wSq/cZvLOl2tGPRt5wSMug==
-----END EC PRIVATE KEY-----
`),
},
// Bad format private key
{
key: []byte(`
-----BEGIN RSA PRIVATE KEY-----
sxcvMIICXAIBAAKBgQCgcTrC6rTj6KV5GeUyEODguAY+RMxX0ZzskOZBUFuUn1ADj7qK
vdfF9WHetcvvnnZ+XuCWrHcoRRIiO5Ikpnz0H54J9Zdy5UAIqkGCOIEdhAVDvLBe
oJ7G2x11Lyz/us7EekqNeguZ9xJ+efjWsuPwYxo8iWluR3jcIA3NK5QCLQIDAQAB
AoGBAIr1xwkvM4D57OfYb9RPHhZEDNQ9ziZ5nEqgrW0AZnFxEmIjSFQGXS5Ne3jj
SEC/pK2LC0Y1FfdA65XOtqMbt7hx3QqjBYIu01AyQGYnrSsiSPdLf4RZviEmZ19n
kuZKKI6TjLXG9LfZO9/x3bYJeHa+rgZoSYK/JEUznIn768/BAkEAzKtZhwLH3zcI
mFyOYjIk2pFauz5tt/9pdXOFHRFS3KKsIrbI2NZd5C5dVp5mnRZ27H4g9HZGurxy
3zWfcrRQ1QJBAMiuUH5iIcWdoRJsgUgCmCYsaynzZgLecEF7VOlRWHiJ60bwNZTG
p0TkEewdmPogbCmaAEtovsBFuQ4JCIxVV/kCQFFn+iUUOxGSny2S6uMt1LDGzdLa
IuPjiDu6JgEIye+OGG96SmrM4O2Ib4GrYV8r90Nba5owjTNrDzmu52vFQr0CQDE9
3JB2YdUMraZIq5xQzqanRZBgogpYLHFU4uvxQuUo6mtYq70a1ZZo5CDszkmpxQCc
QjA+vneNZDAWdVuB4XkCQHjO1CcHKWlihm/xmXDVQKK4oWrNrs6MddLwJ6vAZBAw
I8eun6k9HNyEieJTVaB9AVnykoZ78UbCQaipm9W7i4Q=
-----END RSA PRIVATE KEY-----
`),
expectError: true,
},
}

for _, tc := range cases {
_, err := DecodePrivateKeyPEM(tc.key)
if tc.expectError {
g.Expect(err).NotTo(BeNil())
} else {
g.Expect(err).To(BeNil())
}
}
}
3 changes: 2 additions & 1 deletion util/certs/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ limitations under the License.
package certs

import (
"crypto"
"crypto/rand"
"crypto/rsa"
"crypto/x509"
Expand Down Expand Up @@ -48,7 +49,7 @@ type Config struct {
}

// NewSignedCert creates a signed certificate using the given CA certificate and key.
func (cfg *Config) NewSignedCert(key *rsa.PrivateKey, caCert *x509.Certificate, caKey *rsa.PrivateKey) (*x509.Certificate, error) {
func (cfg *Config) NewSignedCert(key *rsa.PrivateKey, caCert *x509.Certificate, caKey crypto.Signer) (*x509.Certificate, error) {
serial, err := rand.Int(rand.Reader, new(big.Int).SetInt64(math.MaxInt64))
if err != nil {
return nil, errors.Wrap(err, "failed to generate random integer for signed cerficate")
Expand Down
5 changes: 3 additions & 2 deletions util/kubeconfig/kubeconfig.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ package kubeconfig

import (
"context"
"crypto/rsa"
"crypto"
"crypto/x509"
"fmt"
"time"
Expand Down Expand Up @@ -50,7 +50,8 @@ func FromSecret(ctx context.Context, c client.Reader, cluster client.ObjectKey)
}

// New creates a new Kubeconfig using the cluster name and specified endpoint.
func New(clusterName, endpoint string, caCert *x509.Certificate, caKey *rsa.PrivateKey) (*api.Config, error) {
func New(clusterName, endpoint string, caCert *x509.Certificate, caKey crypto.Signer) (*api.Config, error) {

cfg := &certs.Config{
CommonName: "kubernetes-admin",
Organization: []string{"system:masters"},
Expand Down

0 comments on commit a871651

Please sign in to comment.