Skip to content

Commit

Permalink
Add RSA support to TLS libraries
Browse files Browse the repository at this point in the history
Fixes #3131

Wrapped private keys into either `PrivateKeyEC` or `PrivateKeyRSA` to
provide different certificate matching logic and marshaling depending on
the block type.

You can test having an RSA cert for the proxy injector by applying this
patch:

```diff
$ diff -u chart/templates/proxy_injector-rbac.yaml ~/tmp/proxy_injector-rbac.yaml
--- chart/templates/proxy_injector-rbac.yaml    2019-07-24 14:34:43.570616936 -0500
+++ /home/alpeb/tmp/proxy_injector-rbac.yaml    2019-07-24 13:41:03.150285099 -0500
@@ -1,4 +1,5 @@
 {{with .Values -}}
+{{- $ca := genCA "linkerd-proxy-injector.linkerd.svc" 365 -}}
 ---
 ###
 ### Proxy Injector RBAC
@@ -60,8 +61,8 @@
     {{ .CreatedByAnnotation }}: {{ .CliVersion }}
 type: Opaque
 data:
-  crt.pem: {{ b64enc .ProxyInjector.CrtPEM }}
-  key.pem: {{ b64enc .ProxyInjector.KeyPEM }}
+  crt.pem: {{ b64enc $ca.Cert }}
+  key.pem: {{ b64enc $ca.Key }}
 ---
 apiVersion: admissionregistration.k8s.io/v1beta1
 kind: MutatingWebhookConfiguration
@@ -81,7 +82,7 @@
       name: linkerd-proxy-injector
       namespace: {{ .Namespace }}
       path: "/"
-    caBundle: {{ b64enc .ProxyInjector.CrtPEM }}
+    caBundle: {{ b64enc $ca.Cert }}
   failurePolicy: {{ .WebhookFailurePolicy }}
   rules:
   - operations: [ "CREATE" ]
```

This will replace the logic to generate the cert with a call to Helm's
`genCA`, which uses RSA.

Signed-off-by: Alejandro Pedraza <alejandro@buoyant.io>
  • Loading branch information
alpeb committed Jul 24, 2019
1 parent 889a4a0 commit 3c8b794
Show file tree
Hide file tree
Showing 2 changed files with 59 additions and 17 deletions.
22 changes: 17 additions & 5 deletions pkg/tls/codec.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,16 +48,28 @@ func encode(buf *bytes.Buffer, blk *pem.Block) {

// === DECODE ===

// DecodePEMKey parses a PEM-encoded ECDSA private key from the named path.
func DecodePEMKey(txt string) (*ecdsa.PrivateKey, error) {
// DecodePEMKey parses a PEM-encoded private key from the named path.
func DecodePEMKey(txt string) (GenericPrivateKey, error) {
block, _ := pem.Decode([]byte(txt))
if block == nil {
return nil, errors.New("Not PEM-encoded")
}
if block.Type != "EC PRIVATE KEY" {
return nil, fmt.Errorf("Expected 'EC PRIVATE KEY'; found: '%s'", block.Type)
switch block.Type {
case "EC PRIVATE KEY":
k, err := x509.ParseECPrivateKey(block.Bytes)
if err != nil {
return nil, err
}
return PrivateKeyEC{k}, nil
case "RSA PRIVATE KEY":
k, err := x509.ParsePKCS1PrivateKey(block.Bytes)
if err != nil {
return nil, err
}
return PrivateKeyRSA{k}, nil
default:
return nil, fmt.Errorf("Unsupported block type: '%s'", block.Type)
}
return x509.ParseECPrivateKey(block.Bytes)
}

// DecodePEMCertificates parses a string containing PEM-encoded certificates.
Expand Down
54 changes: 42 additions & 12 deletions pkg/tls/cred.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"bytes"
"crypto/ecdsa"
"crypto/rand"
"crypto/rsa"
"crypto/x509"
"encoding/pem"
"errors"
Expand All @@ -12,9 +13,25 @@ import (
)

type (
// PrivateKeyEC wraps an EC private key
PrivateKeyEC struct {
*ecdsa.PrivateKey
}

// PrivateKeyRSA wraps an RSA private key
PrivateKeyRSA struct {
*rsa.PrivateKey
}

// GenericPrivateKey represents either an EC or an RSA private key
GenericPrivateKey interface {
matchesCertificate(*x509.Certificate) bool
marshal() ([]byte, error)
}

// Cred is a container for a certificate, trust chain, and private key.
Cred struct {
PrivateKey *ecdsa.PrivateKey
PrivateKey GenericPrivateKey
Crt
}

Expand All @@ -28,9 +45,28 @@ type (
}
)

func (k PrivateKeyEC) matchesCertificate(c *x509.Certificate) bool {
pub, ok := c.PublicKey.(*ecdsa.PublicKey)
return ok && pub.X.Cmp(k.X) == 0 && pub.Y.Cmp(k.Y) == 0
}

func (k PrivateKeyEC) marshal() ([]byte, error) {
return x509.MarshalECPrivateKey(k.PrivateKey)
}

func (k PrivateKeyRSA) matchesCertificate(c *x509.Certificate) bool {
pub, ok := c.PublicKey.(*rsa.PublicKey)
return ok && pub.N.Cmp(k.N) == 0 && pub.E == k.E
}

func (k PrivateKeyRSA) marshal() ([]byte, error) {
return x509.MarshalPKCS1PrivateKey(k.PrivateKey), nil
}

// validCredOrPanic creates a Cred, panicking if the key does not match the certificate.
func validCredOrPanic(k *ecdsa.PrivateKey, crt Crt) Cred {
if !certificateMatchesKey(crt.Certificate, k) {
func validCredOrPanic(ecKey *ecdsa.PrivateKey, crt Crt) Cred {
k := PrivateKeyEC{ecKey}
if !k.matchesCertificate(crt.Certificate) {
panic("Cert's public key does not match private key")
}
return Cred{Crt: crt, PrivateKey: k}
Expand Down Expand Up @@ -89,9 +125,9 @@ func (crt *Crt) EncodeCertificatePEM() string {

// EncodePrivateKeyPEM emits the private key as PEM-encoded text.
func (cred *Cred) EncodePrivateKeyPEM() string {
b, err := x509.MarshalECPrivateKey(cred.PrivateKey)
b, err := cred.PrivateKey.marshal()
if err != nil {
panic(fmt.Sprintf("Invalid elliptic curve: %s", err))
panic(fmt.Sprintf("Invalid private key: %s", err))
}

return string(pem.EncodeToMemory(&pem.Block{Type: "EC PRIVATE KEY", Bytes: b}))
Expand All @@ -102,12 +138,6 @@ func (cred *Cred) EncodePrivateKeyP8() ([]byte, error) {
return x509.MarshalPKCS8PrivateKey(cred.PrivateKey)
}

// certificateMatchesKey returns whether the key and certificate match.
func certificateMatchesKey(c *x509.Certificate, k *ecdsa.PrivateKey) bool {
pub, ok := c.PublicKey.(*ecdsa.PublicKey)
return ok && pub.X.Cmp(k.X) == 0 && pub.Y.Cmp(k.Y) == 0
}

// SignCrt uses this Cred to sign a new certificate.
//
// This may fail if the Cred contains an end-entity certificate.
Expand Down Expand Up @@ -154,7 +184,7 @@ func ReadPEMCreds(keyPath, crtPath string) (*Cred, error) {
if err != nil {
return nil, err
}
if !certificateMatchesKey(crt.Certificate, key) {
if !key.matchesCertificate(crt.Certificate) {
return nil, errors.New("tls: Public and private key do not match")
}
return &Cred{PrivateKey: key, Crt: *crt}, nil
Expand Down

0 comments on commit 3c8b794

Please sign in to comment.