Browse Source

feat(template): add filterPEM function

Signed-off-by: Moritz Johner <beller.moritz@googlemail.com>
Moritz Johner 4 years ago
parent
commit
74fca707b3

+ 40 - 11
docs/guides-templating.md

@@ -6,7 +6,7 @@ With External Secrets Operator you can transform the data from the external secr
 
 You can use templates to inject your secrets into a configuration file that you mount into your pod:
 
-``` yaml
+```yaml
 {% include 'multiline-template-v2-external-secret.yaml' %}
 ```
 
@@ -18,18 +18,20 @@ You do not have to define your templates inline in an ExternalSecret but you can
 {% include 'template-v2-from-secret.yaml' %}
 ```
 
-
 ### Extract Keys and Certificates from PKCS#12 Archive
+
 You can use pre-defined functions to extract data from your secrets. Here: extract keys and certificates from a PKCS#12 archive and store it as PEM.
 
-``` yaml
+```yaml
 {% include 'pkcs12-template-v2-external-secret.yaml' %}
 ```
 
 ### Extract from JWK
+
 You can extract the public or private key parts of a JWK and use them as [PKCS#8](https://pkg.go.dev/crypto/x509#ParsePKCS8PrivateKey) private key or PEM-encoded [PKIX](https://pkg.go.dev/crypto/x509#MarshalPKIXPublicKey) public key.
 
 A JWK looks similar to this:
+
 ```json
 {
   "kty": "RSA",
@@ -43,10 +45,35 @@ A JWK looks similar to this:
 
 And what you want may be a PEM-encoded public or private key portion of it. Take a look at this example on how to transform it into the desired format:
 
-``` yaml
+```yaml
 {% include 'jwk-template-v2-external-secret.yaml' %}
 ```
 
+### Filter PEM blocks
+
+Consider you have a secret that contains both a certificate and a private key encoded in PEM format and it is your goal to use only the certificate from that secret.
+
+```
+-----BEGIN PRIVATE KEY-----
+MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCvxGZOW4IXvGlh
+ . . .
+m8JCpbJXDfSSVxKHgK1Siw4K6pnTsIA2e/Z+Ha2fvtocERjq7VQMAJFaIZSTKo9Q
+JwwY+vj0yxWjyzHUzZB33tg=
+-----END PRIVATE KEY-----
+-----BEGIN CERTIFICATE-----
+MIIDMDCCAhigAwIBAgIQabPaXuZCQaCg+eQAVptGGDANBgkqhkiG9w0BAQsFADAV
+ . . .
+NtFUGA95RGN9s+pl6XY0YARPHf5O76ErC1OZtDTR5RdyQfcM+94gYZsexsXl0aQO
+9YD3Wg==
+-----END CERTIFICATE-----
+
+```
+
+You can achieve that by using the `filterPEM` function to extract a specific type of PEM block from that secret. If multiple blocks of that type (here: `CERTIFICATE`) exist then all of them are returned in the order they are specified.
+```yaml
+{% include 'pem-filter-template-v2-external-secret.yaml' %}
+```
+
 ## Helper functions
 
 !!! info inline end
@@ -59,13 +86,15 @@ In addition to that you can use over 200+ [sprig functions](http://masterminds.g
 
 <br/>
 
-| Function         | Description                                                                                                                                                                                                                  |
-| ---------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
-| pkcs12key        | Extracts all private keys from a PKCS#12 archive and encodes them in **PKCS#8 PEM** format.                                                                                                                                            |
-| pkcs12keyPass    | Same as `pkcs12key`. Uses the provided password to decrypt the PKCS#12 archive.                                                                                                                                                   |
-| pkcs12cert       | Extracts all certificates from a PKCS#12 archive and orders them if possible. If disjunct or multiple leaf certs are provided they are returned as-is. <br/> Sort order: `leaf / intermediate(s) / root`.                     |
-| pkcs12certPass   | Same as `pkcs12cert`. Uses the provided password to decrypt the PKCS#12 archive.                                                                                                                                                   |
-| jwkPublicKeyPem  | Takes an json-serialized JWK and returns an PEM block of type `PUBLIC KEY` that contains the public key. [See here](https://golang.org/pkg/crypto/x509/#MarshalPKIXPublicKey) for details.                     |
+| Function       | Description                                                                                                                                                                                               |
+| -------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
+| pkcs12key      | Extracts all private keys from a PKCS#12 archive and encodes them in **PKCS#8 PEM** format.                                                                                                               |
+| pkcs12keyPass  | Same as `pkcs12key`. Uses the provided password to decrypt the PKCS#12 archive.                                                                                                                           |
+| pkcs12cert     | Extracts all certificates from a PKCS#12 archive and orders them if possible. If disjunct or multiple leaf certs are provided they are returned as-is. <br/> Sort order: `leaf / intermediate(s) / root`. |
+| pkcs12certPass | Same as `pkcs12cert`. Uses the provided password to decrypt the PKCS#12 archive.                                                                                                                          |
+| filterPEM      | Filters PEM blocks with a specific type from a list of PEM blocks.                                                                                                                                        |
+
+| jwkPublicKeyPem | Takes an json-serialized JWK and returns an PEM block of type `PUBLIC KEY` that contains the public key. [See here](https://golang.org/pkg/crypto/x509/#MarshalPKIXPublicKey) for details. |
 | jwkPrivateKeyPem | Takes an json-serialized JWK as `string` and returns an PEM block of type `PRIVATE KEY` that contains the private key in PKCS #8 format. [See here](https://golang.org/pkg/crypto/x509/#MarshalPKCS8PrivateKey) for details. |
 
 ## Migrating from v1

+ 55 - 0
pkg/template/v2/jwk.go

@@ -0,0 +1,55 @@
+/*
+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 template
+
+import (
+	"crypto/x509"
+
+	"github.com/lestrrat-go/jwx/jwk"
+)
+
+func jwkPublicKeyPem(jwkjson string) (string, error) {
+	k, err := jwk.ParseKey([]byte(jwkjson))
+	if err != nil {
+		return "", err
+	}
+	var rawkey interface{}
+	err = k.Raw(&rawkey)
+	if err != nil {
+		return "", err
+	}
+	mpk, err := x509.MarshalPKIXPublicKey(rawkey)
+	if err != nil {
+		return "", err
+	}
+	return pemEncode(string(mpk), "PUBLIC KEY")
+}
+
+func jwkPrivateKeyPem(jwkjson string) (string, error) {
+	k, err := jwk.ParseKey([]byte(jwkjson))
+	if err != nil {
+		return "", err
+	}
+	var mpk []byte
+	var pk interface{}
+	err = k.Raw(&pk)
+	if err != nil {
+		return "", err
+	}
+	mpk, err = x509.MarshalPKCS8PrivateKey(pk)
+	if err != nil {
+		return "", err
+	}
+	return pemEncode(string(mpk), "PRIVATE KEY")
+}

+ 62 - 0
pkg/template/v2/pem.go

@@ -0,0 +1,62 @@
+/*
+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 template
+
+import (
+	"bytes"
+	"encoding/pem"
+	"errors"
+	"strings"
+)
+
+const (
+	errJunk = "error filtering pem: found junk"
+)
+
+func filterPEM(pemType, input string) (string, error) {
+	data := []byte(input)
+	var blocks []byte
+	var block *pem.Block
+	var rest []byte
+	for {
+		block, rest = pem.Decode(data)
+		data = rest
+
+		if block == nil {
+			break
+		}
+		if !strings.EqualFold(block.Type, pemType) {
+			continue
+		}
+
+		var buf bytes.Buffer
+		err := pem.Encode(&buf, block)
+		if err != nil {
+			return "", err
+		}
+		blocks = append(blocks, buf.Bytes()...)
+	}
+
+	if len(blocks) == 0 && len(rest) != 0 {
+		return "", errors.New(errJunk)
+	}
+
+	return string(blocks), nil
+}
+
+func pemEncode(thing, kind string) (string, error) {
+	buf := bytes.NewBuffer(nil)
+	err := pem.Encode(buf, &pem.Block{Type: kind, Bytes: []byte(thing)})
+	return buf.String(), err
+}

+ 180 - 0
pkg/template/v2/pem_test.go

@@ -0,0 +1,180 @@
+/*
+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 template
+
+import "testing"
+
+const (
+	certData = `-----BEGIN CERTIFICATE-----
+MIIDHTCCAgWgAwIBAgIRAKC4yxy9QGocND+6avTf7BgwDQYJKoZIhvcNAQELBQAw
+EjEQMA4GA1UEChMHQWNtZSBDbzAeFw0yMTAzMjAyMDA4MDhaFw0yMTAzMjAyMDM4
+MDhaMBIxEDAOBgNVBAoTB0FjbWUgQ28wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAw
+ggEKAoIBAQC3o6/JdZEqNbqNRkopHhJtJG5c4qS5d0tQ/kZYpfD/v/izAYum4Nzj
+aG15owr92/11W0pxPUliRLti3y6iScTs+ofm2D7p4UXj/Fnho/2xoWSOoWAodgvW
+Y8jh8A0LQALZiV/9QsrJdXZdS47DYZLsQ3z9yFC/CdXkg1l7AQ3fIVGKdrQBr9kE
+1gEDqnKfRxXI8DEQKXr+CKPUwCAytegmy0SHp53zNAvY+kopHytzmJpXLoEhxq4e
+ugHe52vXHdh/HJ9VjNp0xOH1waAgAGxHlltCW0PVd5AJ0SXROBS/a3V9sZCbCrJa
+YOOonQSEswveSv6PcG9AHvpNPot2Xs6hAgMBAAGjbjBsMA4GA1UdDwEB/wQEAwIC
+pDATBgNVHSUEDDAKBggrBgEFBQcDATAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQW
+BBR00805mrpoonp95RmC3B6oLl+cGTAVBgNVHREEDjAMggpnb29ibGUuY29tMA0G
+CSqGSIb3DQEBCwUAA4IBAQAipc1b6JrEDayPjpz5GM5krcI8dCWVd8re0a9bGjjN
+ioWGlu/eTr5El0ffwCNZ2WLmL9rewfHf/bMvYz3ioFZJ2OTxfazqYXNggQz6cMfa
+lbedDCdt5XLVX2TyerGvFram+9Uyvk3l0uM7rZnwAmdirG4Tv94QRaD3q4xTj/c0
+mv+AggtK0aRFb9o47z/BypLdk5mhbf3Mmr88C8XBzEnfdYyf4JpTlZrYLBmDCu5d
+9RLLsjXxhag8xqMtd1uLUM8XOTGzVWacw8iGY+CTtBKqyA+AE6/bDwZvEwVtsKtC
+QJ85ioEpy00NioqcF0WyMZH80uMsPycfpnl5uF7RkW8u
+-----END CERTIFICATE-----
+`
+
+	otherCert = `-----BEGIN CERTIFICATE-----
+MIIBqjCCAU+gAwIBAgIRAPnGGsBUMbZhmh5QdnYdBmUwCgYIKoZIzj0EAwIwGjEY
+MBYGA1UEAxMPaW50ZXJtZWRpYXRlLWNhMB4XDTIyMDIwOTEwMjUzMVoXDTIyMDIx
+MDEwMjUzMVowDjEMMAoGA1UEAxMDZm9vMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcD
+QgAEqnxdeInykx8JZsLi13rZLekoG2cosQ3F+2InVNy7hCQ7soMqdaJsGQ6LFtov
+ogUFtOOTRWrunblqNWGZsowHbKOBgTB/MA4GA1UdDwEB/wQEAwIHgDAdBgNVHSUE
+FjAUBggrBgEFBQcDAQYIKwYBBQUHAwIwHQYDVR0OBBYEFLtundVbuKd73OWzo6SY
+by0Ajeb2MB8GA1UdIwQYMBaAFCLg80J/bZBbOd+Y8+V94l5xM2zEMA4GA1UdEQQH
+MAWCA2ZvbzAKBggqhkjOPQQDAgNJADBGAiEA4K4SbVNqrEtl7RfwBfJFMnWI+X8D
+zMPMc4Xqzp2qTxcCIQDsySgtiakypZfWakpB49zJph0kLwGK8xhWvGMUw1N1/w==
+-----END CERTIFICATE-----
+`
+
+	keyData = `-----BEGIN PRIVATE KEY-----
+MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQC3o6/JdZEqNbqN
+RkopHhJtJG5c4qS5d0tQ/kZYpfD/v/izAYum4NzjaG15owr92/11W0pxPUliRLti
+3y6iScTs+ofm2D7p4UXj/Fnho/2xoWSOoWAodgvWY8jh8A0LQALZiV/9QsrJdXZd
+S47DYZLsQ3z9yFC/CdXkg1l7AQ3fIVGKdrQBr9kE1gEDqnKfRxXI8DEQKXr+CKPU
+wCAytegmy0SHp53zNAvY+kopHytzmJpXLoEhxq4eugHe52vXHdh/HJ9VjNp0xOH1
+waAgAGxHlltCW0PVd5AJ0SXROBS/a3V9sZCbCrJaYOOonQSEswveSv6PcG9AHvpN
+Pot2Xs6hAgMBAAECggEACTGPrmVNZDCWa1Y2hkJ0J7SoNcw+9O4M/jwMp4l/PD6P
+I98S78LYLCZhPLK17SmjUcnFO1AXKW1JeFS2D/fjfP256guvcqQNjLFoioxcOhVb
+ZGyd1Mi8JPqP5wfOj16gBeYDwTkjz9wqldcfiZaL9XoXetkZecbzR2JwC2FtIVuC
+0njTjMNYpaBKnoLb8OTR0EQz7lYEo2MkQiWryz8wseONnFmdfh18p+p10YgCbuCH
+qesrWfDLLxaxZelNtDhDngg9LoCLmarYy7BgShacmUEgJTZ/x3xFC75thK3ln0OY
++ktTgvVotYYaZi7qAjQiEsTvkTAPg5RMpQLd2UIWsQKBgQDCBp+1vURbwGzmTNUg
+HMipD6WDFdLc9DCacx6+ZqsEPTMWQbCpVZrDKiY0Rjt5F+xOCyMr00J5RDJXRC0G
++L7NcJdywOFutT7vB+cmETg7l/6PHweNYBnE66706eTL/KVYZMi4tEinarPWhHmL
+jasfdLANtpDjdWkRt299TkPRbQKBgQDyS8Rr7KZdv04Csqkf+ASmiJpT5R6Y72kc
+3XYpKETyB2FyPZkuh/zInMut9SkkSI9O/jA3zf956jj6sF1DHvp7T8KkIp5OAQeD
+J9AF65m2MnZfHFUeJ6ZQsggwMWqrD0ycIWP7YWtiBHH+D1wGkjYrssq+bvG/yNpA
+LtqdKq9lhQKBgQCZA2hIhy61vRckuEsLvCdzTGeW7UsR/XGnHEqOlaEhArKbRsrv
+gBdA+qiOaSTV5svw8E+YbE7sG6AnuhhYeyreEYEeeoZOLJmpIG5mUwYp2UBj1nC6
+SaOI7OVZOGu7g09SWokBQQxbG4cgEfFY4Sym7fs5lVTGTP3Dfwppo6NQMQKBgQCo
+J5NDP3Lafwk58BpV+H/pv8YzUUDh7M2rXbtCpxLqUdr8OOnVlEUISWFF8m5CIyVq
+MhjuscWLK9Wtjba7/YTjDaDM3sW05xv6lyfU5ATCoNTr/zLHgcb4HAZ4w+L+otiN
+RtMnxB2NYf5mzuwUF2cG/secUEzwyAlIH/xStSwTLQKBgQCRvqF+rqxnegoOgwVW
+qrWPv06wXD8dW2FlPpY5GXqA0l6erSK3YsQQToRmbem9ibPD7bd5P4gNbWfxwK4C
+Wt+1Rcb8OrDhDJbYz85bXBnPecKp4EN0b9SHO0/dsCqn2w30emc+9T/4m1ZDkpBd
+BixHvI/EJ8YK3ta5WdJWKC6hnA==
+-----END PRIVATE KEY-----
+`
+)
+
+const (
+	filterPrivateKey = "private key"
+	filterCert       = "certificate"
+)
+
+func TestFilterPEM(t *testing.T) {
+	type args struct {
+		input   string
+		pemType string
+	}
+	tests := []struct {
+		name    string
+		args    args
+		want    string
+		wantErr bool
+	}{
+		{
+			name: "extract cert / cert first",
+			args: args{
+				input:   certData + keyData,
+				pemType: filterCert,
+			},
+			want: certData,
+		},
+		{
+			name: "extract cert / key first",
+			args: args{
+				input:   keyData + certData,
+				pemType: filterCert,
+			},
+			want: certData,
+		},
+		{
+			name: "extract multiple certs",
+			args: args{
+				input:   keyData + certData + keyData + otherCert,
+				pemType: filterCert,
+			},
+			want: certData + otherCert,
+		},
+		{
+			name: "extract key",
+			args: args{
+				input:   keyData + certData,
+				pemType: filterPrivateKey,
+			},
+			want: keyData,
+		},
+		{
+			name: "key with junk",
+			args: args{
+				input:   certData + keyData + "some ---junk---",
+				pemType: filterPrivateKey,
+			},
+			want: keyData,
+		},
+		{
+			name: "begin/end with junk",
+			args: args{
+				// pem.Decode trims junk from the beginning of the input
+				// so we are able to decode both cert & key
+				input:   "some junk" + certData + keyData + "some ---junk---",
+				pemType: filterPrivateKey,
+			},
+			want: keyData,
+		},
+		{
+			name: "interleaved junk",
+			args: args{
+				// can parse cert but not key due to junk
+				input:   certData + "some junk" + keyData,
+				pemType: filterPrivateKey,
+			},
+			wantErr: true,
+		},
+		{
+			name: "err when junk",
+			args: args{
+				input:   "---junk---",
+				pemType: filterPrivateKey,
+			},
+			wantErr: true,
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			got, err := filterPEM(tt.args.pemType, tt.args.input)
+			if (err != nil) != tt.wantErr {
+				t.Errorf("filterPEM() error = %v, wantErr %v", err, tt.wantErr)
+				return
+			}
+			if got != tt.want {
+				t.Errorf("filterPEM() = %v, want %v", got, tt.want)
+			}
+		})
+	}
+}

+ 109 - 0
pkg/template/v2/pkcs12.go

@@ -0,0 +1,109 @@
+/*
+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 template
+
+import (
+	"bytes"
+	"crypto/x509"
+	"encoding/pem"
+	"fmt"
+
+	"golang.org/x/crypto/pkcs12"
+)
+
+func pkcs12keyPass(pass, input string) (string, error) {
+	blocks, err := pkcs12.ToPEM([]byte(input), pass)
+	if err != nil {
+		return "", fmt.Errorf(errDecodePKCS12WithPass, err)
+	}
+
+	var pemData []byte
+	for _, block := range blocks {
+		// remove bag attributes like localKeyID, friendlyName
+		block.Headers = nil
+		if block.Type == pemTypeCertificate {
+			continue
+		}
+		key, err := parsePrivateKey(block.Bytes)
+		if err != nil {
+			return "", err
+		}
+		// we use pkcs8 because it supports more key types (ecdsa, ed25519), not just RSA
+		block.Bytes, err = x509.MarshalPKCS8PrivateKey(key)
+		if err != nil {
+			return "", err
+		}
+		// report error if encode fails
+		var buf bytes.Buffer
+		if err := pem.Encode(&buf, block); err != nil {
+			return "", err
+		}
+		pemData = append(pemData, buf.Bytes()...)
+	}
+
+	return string(pemData), nil
+}
+
+func parsePrivateKey(block []byte) (interface{}, error) {
+	if k, err := x509.ParsePKCS1PrivateKey(block); err == nil {
+		return k, nil
+	}
+	if k, err := x509.ParsePKCS8PrivateKey(block); err == nil {
+		return k, nil
+	}
+	if k, err := x509.ParseECPrivateKey(block); err == nil {
+		return k, nil
+	}
+	return nil, fmt.Errorf(errParsePrivKey)
+}
+
+func pkcs12key(input string) (string, error) {
+	return pkcs12keyPass("", input)
+}
+
+func pkcs12certPass(pass, input string) (string, error) {
+	blocks, err := pkcs12.ToPEM([]byte(input), pass)
+	if err != nil {
+		return "", fmt.Errorf(errDecodeCertWithPass, err)
+	}
+
+	var pemData []byte
+	for _, block := range blocks {
+		if block.Type != pemTypeCertificate {
+			continue
+		}
+		// remove bag attributes like localKeyID, friendlyName
+		block.Headers = nil
+		// report error if encode fails
+		var buf bytes.Buffer
+		if err := pem.Encode(&buf, block); err != nil {
+			return "", err
+		}
+		pemData = append(pemData, buf.Bytes()...)
+	}
+
+	// try to order certificate chain. If it fails we return
+	// the unordered raw pem data.
+	// This fails if multiple leaf or disjunct certs are provided.
+	ordered, err := fetchCertChains(pemData)
+	if err != nil {
+		return string(pemData), nil
+	}
+
+	return string(ordered), nil
+}
+
+func pkcs12cert(input string) (string, error) {
+	return pkcs12certPass("", input)
+}

+ 2 - 131
pkg/template/v2/template.go

@@ -15,14 +15,10 @@ package template
 
 import (
 	"bytes"
-	"crypto/x509"
-	"encoding/pem"
 	"fmt"
 	tpl "text/template"
 
 	"github.com/Masterminds/sprig/v3"
-	"github.com/lestrrat-go/jwx/jwk"
-	"golang.org/x/crypto/pkcs12"
 	corev1 "k8s.io/api/core/v1"
 )
 
@@ -32,6 +28,8 @@ var tplFuncs = tpl.FuncMap{
 	"pkcs12cert":     pkcs12cert,
 	"pkcs12certPass": pkcs12certPass,
 
+	"filterPEM": filterPEM,
+
 	"jwkPublicKeyPem":  jwkPublicKeyPem,
 	"jwkPrivateKeyPem": jwkPrivateKeyPem,
 }
@@ -95,130 +93,3 @@ func execute(k, val string, data map[string][]byte) ([]byte, error) {
 	}
 	return buf.Bytes(), nil
 }
-
-func pkcs12keyPass(pass, input string) (string, error) {
-	blocks, err := pkcs12.ToPEM([]byte(input), pass)
-	if err != nil {
-		return "", fmt.Errorf(errDecodePKCS12WithPass, err)
-	}
-
-	var pemData []byte
-	for _, block := range blocks {
-		// remove bag attributes like localKeyID, friendlyName
-		block.Headers = nil
-		if block.Type == pemTypeCertificate {
-			continue
-		}
-		key, err := parsePrivateKey(block.Bytes)
-		if err != nil {
-			return "", err
-		}
-		// we use pkcs8 because it supports more key types (ecdsa, ed25519), not just RSA
-		block.Bytes, err = x509.MarshalPKCS8PrivateKey(key)
-		if err != nil {
-			return "", err
-		}
-		// report error if encode fails
-		var buf bytes.Buffer
-		if err := pem.Encode(&buf, block); err != nil {
-			return "", err
-		}
-		pemData = append(pemData, buf.Bytes()...)
-	}
-
-	return string(pemData), nil
-}
-
-func parsePrivateKey(block []byte) (interface{}, error) {
-	if k, err := x509.ParsePKCS1PrivateKey(block); err == nil {
-		return k, nil
-	}
-	if k, err := x509.ParsePKCS8PrivateKey(block); err == nil {
-		return k, nil
-	}
-	if k, err := x509.ParseECPrivateKey(block); err == nil {
-		return k, nil
-	}
-	return nil, fmt.Errorf(errParsePrivKey)
-}
-
-func pkcs12key(input string) (string, error) {
-	return pkcs12keyPass("", input)
-}
-
-func pkcs12certPass(pass, input string) (string, error) {
-	blocks, err := pkcs12.ToPEM([]byte(input), pass)
-	if err != nil {
-		return "", fmt.Errorf(errDecodeCertWithPass, err)
-	}
-
-	var pemData []byte
-	for _, block := range blocks {
-		if block.Type != pemTypeCertificate {
-			continue
-		}
-		// remove bag attributes like localKeyID, friendlyName
-		block.Headers = nil
-		// report error if encode fails
-		var buf bytes.Buffer
-		if err := pem.Encode(&buf, block); err != nil {
-			return "", err
-		}
-		pemData = append(pemData, buf.Bytes()...)
-	}
-
-	// try to order certificate chain. If it fails we return
-	// the unordered raw pem data.
-	// This fails if multiple leaf or disjunct certs are provided.
-	ordered, err := fetchCertChains(pemData)
-	if err != nil {
-		return string(pemData), nil
-	}
-
-	return string(ordered), nil
-}
-
-func pkcs12cert(input string) (string, error) {
-	return pkcs12certPass("", input)
-}
-
-func jwkPublicKeyPem(jwkjson string) (string, error) {
-	k, err := jwk.ParseKey([]byte(jwkjson))
-	if err != nil {
-		return "", err
-	}
-	var rawkey interface{}
-	err = k.Raw(&rawkey)
-	if err != nil {
-		return "", err
-	}
-	mpk, err := x509.MarshalPKIXPublicKey(rawkey)
-	if err != nil {
-		return "", err
-	}
-	return pemEncode(string(mpk), "PUBLIC KEY")
-}
-
-func jwkPrivateKeyPem(jwkjson string) (string, error) {
-	k, err := jwk.ParseKey([]byte(jwkjson))
-	if err != nil {
-		return "", err
-	}
-	var mpk []byte
-	var pk interface{}
-	err = k.Raw(&pk)
-	if err != nil {
-		return "", err
-	}
-	mpk, err = x509.MarshalPKCS8PrivateKey(pk)
-	if err != nil {
-		return "", err
-	}
-	return pemEncode(string(mpk), "PRIVATE KEY")
-}
-
-func pemEncode(thing, kind string) (string, error) {
-	buf := bytes.NewBuffer(nil)
-	err := pem.Encode(buf, &pem.Block{Type: kind, Bytes: []byte(thing)})
-	return buf.String(), err
-}

+ 12 - 0
pkg/template/v2/template_test.go

@@ -353,6 +353,18 @@ func TestExecute(t *testing.T) {
 				"fn": []byte(jwkPrivECPKCS8),
 			},
 		},
+		{
+			name: "filter pem certificate",
+			tpl: map[string][]byte{
+				"fn": []byte(`{{ .secret | filterPEM "CERTIFICATE" }}`),
+			},
+			data: map[string][]byte{
+				"secret": []byte(jwkPrivRSAPKCS8 + pkcs12Cert),
+			},
+			expetedData: map[string][]byte{
+				"fn": []byte(pkcs12Cert),
+			},
+		},
 	}
 
 	for i := range tbl {