diff --git a/pkg/util/certificates/csr.go b/pkg/util/certificates/csr.go index 86b037df42c..72140720d33 100644 --- a/pkg/util/certificates/csr.go +++ b/pkg/util/certificates/csr.go @@ -17,9 +17,18 @@ limitations under the License. package certificates import ( + "crypto/ecdsa" + "crypto/elliptic" + cryptorand "crypto/rand" + "crypto/rsa" "crypto/x509" + "crypto/x509/pkix" "encoding/pem" "errors" + "fmt" + "io/ioutil" + "net" + "os" "k8s.io/kubernetes/pkg/apis/certificates" ) @@ -38,3 +47,96 @@ func ParseCertificateRequestObject(obj *certificates.CertificateSigningRequest) } return csr, nil } + +// NewCertificateRequest generates a PEM-encoded CSR using the supplied private +// key file, subject, and SANs. If the private key file does not exist, it generates a +// new ECDSA P256 key to use and writes it to the keyFile path. +func NewCertificateRequest(keyFile string, subject *pkix.Name, dnsSANs []string, ipSANs []net.IP) ([]byte, error) { + var privateKey interface{} + + if _, err := os.Stat(keyFile); os.IsNotExist(err) { + privateKey, err = ecdsa.GenerateKey(elliptic.P256(), cryptorand.Reader) + if err != nil { + return nil, err + } + + ecdsaKey := privateKey.(*ecdsa.PrivateKey) + derBytes, err := x509.MarshalECPrivateKey(ecdsaKey) + if err != nil { + return nil, err + } + + pemBlock := &pem.Block{ + Type: "EC PRIVATE KEY", + Bytes: derBytes, + } + + err = ioutil.WriteFile(keyFile, pem.EncodeToMemory(pemBlock), os.FileMode(0600)) + if err != nil { + return nil, err + } + } + + keyBytes, err := ioutil.ReadFile(keyFile) + if err != nil { + return nil, err + } + + var block *pem.Block + var sigType x509.SignatureAlgorithm + + block, _ = pem.Decode(keyBytes) + + switch block.Type { + case "EC PRIVATE KEY": + privateKey, err = x509.ParseECPrivateKey(block.Bytes) + if err != nil { + return nil, err + } + ecdsaKey := privateKey.(*ecdsa.PrivateKey) + switch ecdsaKey.Curve.Params().BitSize { + case 521: + sigType = x509.ECDSAWithSHA512 + case 384: + sigType = x509.ECDSAWithSHA384 + default: + sigType = x509.ECDSAWithSHA256 + } + case "RSA PRIVATE KEY": + privateKey, err = x509.ParsePKCS1PrivateKey(block.Bytes) + if err != nil { + return nil, err + } + rsaKey := privateKey.(*rsa.PrivateKey) + keySize := rsaKey.N.BitLen() + switch { + case keySize >= 4096: + sigType = x509.SHA512WithRSA + case keySize >= 3072: + sigType = x509.SHA384WithRSA + default: + sigType = x509.SHA256WithRSA + } + default: + return nil, fmt.Errorf("unsupported key type: %s", block.Type) + } + + template := &x509.CertificateRequest{ + Subject: *subject, + SignatureAlgorithm: sigType, + DNSNames: dnsSANs, + IPAddresses: ipSANs, + } + + csr, err := x509.CreateCertificateRequest(cryptorand.Reader, template, privateKey) + if err != nil { + return nil, err + } + + pemBlock := &pem.Block{ + Type: "CERTIFICATE REQUEST", + Bytes: csr, + } + + return pem.EncodeToMemory(pemBlock), nil +} diff --git a/pkg/util/certificates/csr_test.go b/pkg/util/certificates/csr_test.go new file mode 100644 index 00000000000..12a99ba5564 --- /dev/null +++ b/pkg/util/certificates/csr_test.go @@ -0,0 +1,21 @@ +package certificates + +import ( + "crypto/x509/pkix" + "net" + "testing" +) + +func TestNewCertificateRequest(t *testing.T) { + keyFile := "testdata/dontUseThisKey.pem" + subject := &pkix.Name{ + CommonName: "kube-worker", + } + dnsSANs := []string{"localhost"} + ipSANs := []net.IP{net.ParseIP("127.0.0.1")} + + _, err := NewCertificateRequest(keyFile, subject, dnsSANs, ipSANs) + if err != nil { + t.Error(err) + } +} diff --git a/pkg/util/certificates/testdata/dontUseThisKey.pem b/pkg/util/certificates/testdata/dontUseThisKey.pem new file mode 100644 index 00000000000..d6432631dca --- /dev/null +++ b/pkg/util/certificates/testdata/dontUseThisKey.pem @@ -0,0 +1,6 @@ +-----BEGIN EC PRIVATE KEY----- +MIGkAgEBBDAPEbSXwyDfWf0+61Oofd7aHkmdX69mrzD2Xb1CHF5syfsoRIhnG0dJ +ozBulPZCDDWgBwYFK4EEACKhZANiAATjlMJAtKhEPqU/i7MsrgKcK/RmXHC6He7W +0p69+9qFXg2raJ9zvvbKxkiu2ELOYRDAz0utcFTBOIgoUJEzBVmsjZQ7dvFa1BKP +Ym7MFAKG3O2espBqXn+audgdHGh5B0I= +-----END EC PRIVATE KEY-----