Repository: lestrrat-go/jwx
Pull request #647 (2022-03-14 02:34:18)

Rework cert chain

codecov-commenter: # [Codecov](https://codecov.io/gh/lestrrat-go/jwx/pull/647?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=lestrrat-go) Report
> Merging [#647](https://codecov.io/gh/lestrrat-go/jwx/pull/647?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=lestrrat-go) (9b2d824) into [v2](https://codecov.io/gh/lestrrat-go/jwx/commit/c695bf50fa116ac3120c3d9f2c4754879e5b3d52?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=lestrrat-go) (c695bf5) will **decrease** coverage by `0.29%`.
> The diff coverage is `47.42%`.

[![Impacted file tree graph](https://codecov.io/gh/lestrrat-go/jwx/pull/647/graphs/tree.svg?width=650&height=150&src=pr&token=kU2RSDosPt&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=lestrrat-go)](https://codecov.io/gh/lestrrat-go/jwx/pull/647?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=lestrrat-go)

```diff
@@            Coverage Diff             @@
##               v2     #647      +/-   ##
==========================================
- Coverage   72.17%   71.87%   -0.30%     
==========================================
  Files          88       86       -2     
  Lines        9817     9822       +5     
==========================================
- Hits         7085     7060      -25     
- Misses       1926     1962      +36     
+ Partials      806      800       -6     
```


| [Impacted Files](https://codecov.io/gh/lestrrat-go/jwx/pull/647?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=lestrrat-go) | Coverage Δ | |
|---|---|---|
| [jwk/jwk.go](https://codecov.io/gh/lestrrat-go/jwx/pull/647/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=lestrrat-go#diff-andrL2p3ay5nbw==) | `56.15% <8.33%> (-4.81%)` | :arrow_down: |
| [cert/cert.go](https://codecov.io/gh/lestrrat-go/jwx/pull/647/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=lestrrat-go#diff-Y2VydC9jZXJ0Lmdv) | `20.00% <20.00%> (ø)` | |
| [jwe/headers\_gen.go](https://codecov.io/gh/lestrrat-go/jwx/pull/647/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=lestrrat-go#diff-andlL2hlYWRlcnNfZ2VuLmdv) | `67.72% <33.33%> (ø)` | |
| [jws/headers\_gen.go](https://codecov.io/gh/lestrrat-go/jwx/pull/647/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=lestrrat-go#diff-andzL2hlYWRlcnNfZ2VuLmdv) | `66.46% <33.33%> (ø)` | |
| [cert/chain.go](https://codecov.io/gh/lestrrat-go/jwx/pull/647/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=lestrrat-go#diff-Y2VydC9jaGFpbi5nbw==) | `84.61% <84.61%> (ø)` | |
| [jwk/ecdsa\_gen.go](https://codecov.io/gh/lestrrat-go/jwx/pull/647/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=lestrrat-go#diff-andrL2VjZHNhX2dlbi5nbw==) | `72.91% <87.50%> (+0.31%)` | :arrow_up: |
| [jwk/okp\_gen.go](https://codecov.io/gh/lestrrat-go/jwx/pull/647/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=lestrrat-go#diff-andrL29rcF9nZW4uZ28=) | `71.36% <87.50%> (+0.32%)` | :arrow_up: |
| [jwk/rsa\_gen.go](https://codecov.io/gh/lestrrat-go/jwx/pull/647/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=lestrrat-go#diff-andrL3JzYV9nZW4uZ28=) | `72.20% <87.50%> (+0.29%)` | :arrow_up: |
| [jwk/symmetric\_gen.go](https://codecov.io/gh/lestrrat-go/jwx/pull/647/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=lestrrat-go#diff-andrL3N5bW1ldHJpY19nZW4uZ28=) | `78.54% <100.00%> (+0.11%)` | :arrow_up: |
| [internal/base64/base64.go](https://codecov.io/gh/lestrrat-go/jwx/pull/647/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=lestrrat-go#diff-aW50ZXJuYWwvYmFzZTY0L2Jhc2U2NC5nbw==) | `86.66% <0.00%> (-6.67%)` | :arrow_down: |
| ... and [4 more](https://codecov.io/gh/lestrrat-go/jwx/pull/647/diff?src=pr&el=tree-more&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=lestrrat-go) | |

------

[Continue to review full report at Codecov](https://codecov.io/gh/lestrrat-go/jwx/pull/647?src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=lestrrat-go).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=lestrrat-go)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/lestrrat-go/jwx/pull/647?src=pr&el=footer&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=lestrrat-go). Last update [c695bf5...9b2d824](https://codecov.io/gh/lestrrat-go/jwx/pull/647?src=pr&el=lastupdated&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=lestrrat-go). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=lestrrat-go).


Patch:
diff --git a/Changes.v2 b/Changes.v2
index 4089924e..a3883150 100644
--- a/Changes.v2
+++ b/Changes.v2
@@ -42,6 +42,13 @@ Changes
     to avoid being listed in the `go doc` -- however, now that we can
     make them separate modules this is no longer necessary.
 
+  * New package `cert` has been added to handle `x5c` certificate
+    chains, and to work with certificates
+      * cert.Chain to store base64 encoded ASN.1 DER format certificates
+      * cert.EncodeBase64 to encode ASN.1 DER format certificate using base64
+      * cert.Create to create a base64 encoded ASN.1 DER format certificates
+      * cert.Parse to parse base64 encoded ASN.1 DER format certificates
+
   # JWE
 
   * [jwe] `jwe.Compact()`'s signature has changed to 
@@ -94,6 +101,8 @@ Changes
     by using `jwe.WithKeyProvider()`. Because this was the only consumer for
     `jwe.DecryptCtx`, this type has been removed as well.
 
+  * [jwe] `x5c` field type has been changed to `*cert.Chain` instead of `[]string`
+
   # JWK
 
   * [jwk] `jwk.New()` has been renamed to `jwk.FromRaw()`, which hopefully will
@@ -113,8 +122,11 @@ Changes
     https://github.com/lestrrat-go/jwx/blob/v2/docs/99-faq.md#why-is-jwkkeyalgorithm-and-jwakeyalgorithm-so-confusing
     for more details
 
-  * [jwk] jwk.SetFetcher and jwk.SetFetchFunc has been added.
-    They represent something that can fetch a jwk.Set
+  * [jwk] `jwk.SetFetcher` and `jwk.SetFetchFunc` has been added.
+    They represent something that can fetch a `jwk.Set`
+
+  * [jwk] `jwk.CertificateChain` has been removed, use `*cert.Chain`
+  * [jwk] `x5c` field type has been changed to `*cert.Chain` instead of `[]*x509.Certificate`
 
   # JWS
 
@@ -187,6 +199,8 @@ Changes
     the key used for verification. This is useful in cases you provided
     multiple keys and you want to know which one was successful
 
+  * [jws] `x5c` field type has been changed to `*cert.Chain` instead of `[]string`
+
   # JWT
 
   * [jwt] `jwt.Parse` now verifies the signature and validates the token
diff --git a/cert/cert.go b/cert/cert.go
new file mode 100644
index 00000000..1dfdec65
--- /dev/null
+++ b/cert/cert.go
@@ -0,0 +1,48 @@
+package cert
+
+import (
+	"crypto/x509"
+	stdlibb64 "encoding/base64"
+	"fmt"
+	"io"
+
+	"github.com/lestrrat-go/jwx/v2/internal/base64"
+)
+
+// Create is a wrapper around x509.CreateCertificate, but it additionally
+// encodes it in base64 so that it can be easily added to `x5c` fields
+func Create(rand io.Reader, template, parent *x509.Certificate, pub, priv interface{}) ([]byte, error) {
+	der, err := x509.CreateCertificate(rand, template, parent, pub, priv)
+	if err != nil {
+		return nil, fmt.Errorf(`failed to create x509 certificate: %w`, err)
+	}
+	return EncodeBase64(der)
+}
+
+// EncodeBase64 is a utility function to encode ASN.1 DER certificates
+// using base64 encoding. This operation is normally done by `pem.Encode`
+// but since PEM would include the markers (`-----BEGIN`, and the like)
+// while `x5c` fields do not need this, this function can be used to
+// shave off a few lines
+func EncodeBase64(der []byte) ([]byte, error) {
+	enc := stdlibb64.StdEncoding
+	dst := make([]byte, enc.EncodedLen(len(der)))
+	enc.Encode(dst, der)
+	return dst, nil
+}
+
+// Parse is a utility function to decode a base64 encoded
+// ASN.1 DER format certificate, and to parse the byte sequence.
+// The certificate must be in PKIX format, and it must not contain PEM markers
+func Parse(src []byte) (*x509.Certificate, error) {
+	dst, err := base64.Decode(src)
+	if err != nil {
+		return nil, fmt.Errorf(`failed to base64 decode the certificate: %w`, err)
+	}
+
+	cert, err := x509.ParseCertificate(dst)
+	if err != nil {
+		return nil, fmt.Errorf(`failed to parse x509 certificate: %w`, err)
+	}
+	return cert, nil
+}
diff --git a/cert/chain.go b/cert/chain.go
new file mode 100644
index 00000000..9396ce97
--- /dev/null
+++ b/cert/chain.go
@@ -0,0 +1,78 @@
+package cert
+
+import (
+	"bytes"
+	"encoding/json"
+	"fmt"
+)
+
+// Chain represents a certificate chain as used in the `x5c` field of
+// various objects within JOSE.
+//
+// It stores the certificates as a list of base64 encoded []byte
+// sequence. By definition these values must PKIX encoded.
+type Chain struct {
+	certificates [][]byte
+}
+
+func (cc Chain) MarshalJSON() ([]byte, error) {
+	var buf bytes.Buffer
+	buf.WriteByte('[')
+	for i, cert := range cc.certificates {
+		if i > 0 {
+			buf.WriteByte(',')
+		}
+		buf.WriteByte('"')
+		buf.Write(cert)
+		buf.WriteByte('"')
+	}
+	buf.WriteByte(']')
+	return buf.Bytes(), nil
+}
+
+func (cc *Chain) UnmarshalJSON(data []byte) error {
+	var tmp []string
+	if err := json.Unmarshal(data, &tmp); err != nil {
+		return fmt.Errorf(`failed to unmarshal certificate chain: %w`, err)
+	}
+
+	certs := make([][]byte, len(tmp))
+	for i, cert := range tmp {
+		certs[i] = []byte(cert)
+	}
+	cc.certificates = certs
+	return nil
+}
+
+// Get returns the n-th ASN.1 DER + base64 encoded certificate
+// stored. `false` will be returned in the second argument if
+// the corresponding index is out of range.
+func (cc *Chain) Get(index int) ([]byte, bool) {
+	if index < 0 || index > len(cc.certificates) {
+		return nil, false
+	}
+
+	return cc.certificates[index], true
+}
+
+// Len returns the number of certificates stored in this Chain
+func (cc *Chain) Len() int {
+	return len(cc.certificates)
+}
+
+var pemStart = []byte("----- BEGIN CERTIFICATE -----")
+var pemEnd = []byte("----- END CERTIFICATE -----")
+
+func (cc *Chain) AddString(der string) error {
+	return cc.Add([]byte(der))
+}
+
+func (cc *Chain) Add(der []byte) error {
+	// We're going to be nice and remove marker lines if they
+	// give it to us
+	der = bytes.TrimPrefix(der, pemStart)
+	der = bytes.TrimSuffix(der, pemEnd)
+	der = bytes.TrimSpace(der)
+	cc.certificates = append(cc.certificates, der)
+	return nil
+}
diff --git a/jwe/headers_gen.go b/jwe/headers_gen.go
index 35732740..61ce413c 100644
--- a/jwe/headers_gen.go
+++ b/jwe/headers_gen.go
@@ -9,6 +9,7 @@ import (
 	"sort"
 	"sync"
 
+	"github.com/lestrrat-go/jwx/v2/cert"
 	"github.com/lestrrat-go/jwx/v2/internal/base64"
 	"github.com/lestrrat-go/jwx/v2/internal/json"
 	"github.com/lestrrat-go/jwx/v2/internal/pool"
@@ -51,7 +52,7 @@ type Headers interface {
 	JWKSetURL() string
 	KeyID() string
 	Type() string
-	X509CertChain() []string
+	X509CertChain() *cert.Chain
 	X509CertThumbprint() string
 	X509CertThumbprintS256() string
 	X509URL() string
@@ -86,7 +87,7 @@ type stdHeaders struct {
 	jwkSetURL              *string
 	keyID                  *string
 	typ                    *string
-	x509CertChain          []string
+	x509CertChain          *cert.Chain
 	x509CertThumbprint     *string
 	x509CertThumbprintS256 *string
 	x509URL                *string
@@ -194,7 +195,7 @@ func (h *stdHeaders) Type() string {
 	return *(h.typ)
 }
 
-func (h *stdHeaders) X509CertChain() []string {
+func (h *stdHeaders) X509CertChain() *cert.Chain {
 	h.mu.RLock()
 	defer h.mu.RUnlock()
 	return h.x509CertChain
@@ -465,7 +466,7 @@ func (h *stdHeaders) setNoLock(name string, value interface{}) error {
 		}
 		return fmt.Errorf(`invalid value for %s key: %T`, TypeKey, value)
 	case X509CertChainKey:
-		if v, ok := value.([]string); ok {
+		if v, ok := value.(*cert.Chain); ok {
 			h.x509CertChain = v
 			return nil
 		}
@@ -643,11 +644,11 @@ LOOP:
 					return fmt.Errorf(`failed to decode value for key %s: %w`, TypeKey, err)
 				}
 			case X509CertChainKey:
-				var decoded []string
+				var decoded cert.Chain
 				if err := dec.Decode(&decoded); err != nil {
 					return fmt.Errorf(`failed to decode value for key %s: %w`, X509CertChainKey, err)
 				}
-				h.x509CertChain = decoded
+				h.x509CertChain = &decoded
 			case X509CertThumbprintKey:
 				if err := json.AssignNextStringToken(&h.x509CertThumbprint, dec); err != nil {
 					return fmt.Errorf(`failed to decode value for key %s: %w`, X509CertThumbprintKey, err)
diff --git a/jwk/certchain.go b/jwk/certchain.go
deleted file mode 100644
index 43380cdb..00000000
--- a/jwk/certchain.go
+++ /dev/null
@@ -1,85 +0,0 @@
-package jwk
-
-import (
-	"crypto/x509"
-	"fmt"
-
-	"github.com/lestrrat-go/jwx/v2/internal/json"
-
-	"github.com/lestrrat-go/jwx/v2/internal/base64"
-)
-
-func (c CertificateChain) MarshalJSON() ([]byte, error) {
-	certs := c.Get()
-	encoded := make([]string, len(certs))
-	for i := 0; i < len(certs); i++ {
-		encoded[i] = base64.EncodeToStringStd(certs[i].Raw)
-	}
-	return json.Marshal(encoded)
-}
-
-func (c *CertificateChain) UnmarshalJSON(buf []byte) error {
-	var list []string
-	if err := json.Unmarshal(buf, &list); err != nil {
-		return fmt.Errorf(`failed to unmarshal JSON into []string: %w`, err)
-	}
-
-	var tmp CertificateChain
-	if err := tmp.Accept(list); err != nil {
-		return err
-	}
-
-	*c = tmp
-	return nil
-}
-
-func (c CertificateChain) Get() []*x509.Certificate {
-	return c.certs
-}
-
-func (c *CertificateChain) Accept(v interface{}) error {
-	var list []string
-
-	switch x := v.(type) {
-	case string:
-		list = []string{x}
-	case []interface{}:
-		list = make([]string, len(x))
-		for i, e := range x {
-			if es, ok := e.(string); ok {
-				list[i] = es
-				continue
-			}
-			return fmt.Errorf(`invalid list element type: expected string, got %T at element %d`, e, i)
-		}
-	case []string:
-		list = x
-	case CertificateChain:
-		certs := make([]*x509.Certificate, len(x.certs))
-		copy(certs, x.certs)
-		*c = CertificateChain{
-			certs: certs,
-		}
-		return nil
-	default:
-		return fmt.Errorf(`invalid type for CertificateChain: %T`, v)
-	}
-
-	certs := make([]*x509.Certificate, len(list))
-	for i, e := range list {
-		buf, err := base64.DecodeString(e)
-		if err != nil {
-			return fmt.Errorf(`failed to base64 decode list element: %w`, err)
-		}
-		cert, err := x509.ParseCertificate(buf)
-		if err != nil {
-			return fmt.Errorf(`failed to parse certificate: %w`, err)
-		}
-		certs[i] = cert
-	}
-
-	*c = CertificateChain{
-		certs: certs,
-	}
-	return nil
-}
diff --git a/jwk/ecdsa_gen.go b/jwk/ecdsa_gen.go
index af454460..3b83e091 100644
--- a/jwk/ecdsa_gen.go
+++ b/jwk/ecdsa_gen.go
@@ -6,12 +6,12 @@ import (
 	"bytes"
 	"context"
 	"crypto/ecdsa"
-	"crypto/x509"
 	"fmt"
 	"sort"
 	"sync"
 
 	"github.com/lestrrat-go/iter/mapiter"
+	"github.com/lestrrat-go/jwx/v2/cert"
 	"github.com/lestrrat-go/jwx/v2/internal/base64"
 	"github.com/lestrrat-go/jwx/v2/internal/iter"
 	"github.com/lestrrat-go/jwx/v2/internal/json"
@@ -41,10 +41,10 @@ type ecdsaPublicKey struct {
 	keyOps                 *KeyOperationList // https://tools.ietf.org/html/rfc7517#section-4.3
 	keyUsage               *string           // https://tools.ietf.org/html/rfc7517#section-4.2
 	x                      []byte
-	x509CertChain          *CertificateChain // https://tools.ietf.org/html/rfc7515#section-4.1.6
-	x509CertThumbprint     *string           // https://tools.ietf.org/html/rfc7515#section-4.1.7
-	x509CertThumbprintS256 *string           // https://tools.ietf.org/html/rfc7515#section-4.1.8
-	x509URL                *string           // https://tools.ietf.org/html/rfc7515#section-4.1.5
+	x509CertChain          *cert.Chain // https://tools.ietf.org/html/rfc7515#section-4.1.6
+	x509CertThumbprint     *string     // https://tools.ietf.org/html/rfc7515#section-4.1.7
+	x509CertThumbprintS256 *string     // https://tools.ietf.org/html/rfc7515#section-4.1.8
+	x509URL                *string     // https://tools.ietf.org/html/rfc7515#section-4.1.5
 	y                      []byte
 	privateParams          map[string]interface{}
 	mu                     *sync.RWMutex
@@ -104,11 +104,8 @@ func (h *ecdsaPublicKey) X() []byte {
 	return h.x
 }
 
-func (h *ecdsaPublicKey) X509CertChain() []*x509.Certificate {
-	if h.x509CertChain != nil {
-		return h.x509CertChain.Get()
-	}
-	return nil
+func (h *ecdsaPublicKey) X509CertChain() *cert.Chain {
+	return h.x509CertChain
 }
 
 func (h *ecdsaPublicKey) X509CertThumbprint() string {
@@ -161,7 +158,7 @@ func (h *ecdsaPublicKey) makePairs() []*HeaderPair {
 		pairs = append(pairs, &HeaderPair{Key: ECDSAXKey, Value: h.x})
 	}
 	if h.x509CertChain != nil {
-		pairs = append(pairs, &HeaderPair{Key: X509CertChainKey, Value: *(h.x509CertChain)})
+		pairs = append(pairs, &HeaderPair{Key: X509CertChainKey, Value: h.x509CertChain})
 	}
 	if h.x509CertThumbprint != nil {
 		pairs = append(pairs, &HeaderPair{Key: X509CertThumbprintKey, Value: *(h.x509CertThumbprint)})
@@ -225,7 +222,7 @@ func (h *ecdsaPublicKey) Get(name string) (interface{}, bool) {
 		if h.x509CertChain == nil {
 			return nil, false
 		}
-		return h.x509CertChain.Get(), true
+		return h.x509CertChain, true
 	case X509CertThumbprintKey:
 		if h.x509CertThumbprint == nil {
 			return nil, false
@@ -316,12 +313,11 @@ func (h *ecdsaPublicKey) setNoLock(name string, value interface{}) error {
 		}
 		return fmt.Errorf(`invalid value for %s key: %T`, ECDSAXKey, value)
 	case X509CertChainKey:
-		var acceptor CertificateChain
-		if err := acceptor.Accept(value); err != nil {
-			return fmt.Errorf(`invalid value for %s key: %w`, X509CertChainKey, err)
+		if v, ok := value.(*cert.Chain); ok {
+			h.x509CertChain = v
+			return nil
 		}
-		h.x509CertChain = &acceptor
-		return nil
+		return fmt.Errorf(`invalid value for %s key: %T`, X509CertChainKey, value)
 	case X509CertThumbprintKey:
 		if v, ok := value.(string); ok {
 			h.x509CertThumbprint = &v
@@ -473,7 +469,7 @@ LOOP:
 					return fmt.Errorf(`failed to decode value for key %s: %w`, ECDSAXKey, err)
 				}
 			case X509CertChainKey:
-				var decoded CertificateChain
+				var decoded cert.Chain
 				if err := dec.Decode(&decoded); err != nil {
 					return fmt.Errorf(`failed to decode value for key %s: %w`, X509CertChainKey, err)
 				}
@@ -607,10 +603,10 @@ type ecdsaPrivateKey struct {
 	keyOps                 *KeyOperationList // https://tools.ietf.org/html/rfc7517#section-4.3
 	keyUsage               *string           // https://tools.ietf.org/html/rfc7517#section-4.2
 	x                      []byte
-	x509CertChain          *CertificateChain // https://tools.ietf.org/html/rfc7515#section-4.1.6
-	x509CertThumbprint     *string           // https://tools.ietf.org/html/rfc7515#section-4.1.7
-	x509CertThumbprintS256 *string           // https://tools.ietf.org/html/rfc7515#section-4.1.8
-	x509URL                *string           // https://tools.ietf.org/html/rfc7515#section-4.1.5
+	x509CertChain          *cert.Chain // https://tools.ietf.org/html/rfc7515#section-4.1.6
+	x509CertThumbprint     *string     // https://tools.ietf.org/html/rfc7515#section-4.1.7
+	x509CertThumbprintS256 *string     // https://tools.ietf.org/html/rfc7515#section-4.1.8
+	x509URL                *string     // https://tools.ietf.org/html/rfc7515#section-4.1.5
 	y                      []byte
 	privateParams          map[string]interface{}
 	mu                     *sync.RWMutex
@@ -674,11 +670,8 @@ func (h *ecdsaPrivateKey) X() []byte {
 	return h.x
 }
 
-func (h *ecdsaPrivateKey) X509CertChain() []*x509.Certificate {
-	if h.x509CertChain != nil {
-		return h.x509CertChain.Get()
-	}
-	return nil
+func (h *ecdsaPrivateKey) X509CertChain() *cert.Chain {
+	return h.x509CertChain
 }
 
 func (h *ecdsaPrivateKey) X509CertThumbprint() string {
@@ -734,7 +727,7 @@ func (h *ecdsaPrivateKey) makePairs() []*HeaderPair {
 		pairs = append(pairs, &HeaderPair{Key: ECDSAXKey, Value: h.x})
 	}
 	if h.x509CertChain != nil {
-		pairs = append(pairs, &HeaderPair{Key: X509CertChainKey, Value: *(h.x509CertChain)})
+		pairs = append(pairs, &HeaderPair{Key: X509CertChainKey, Value: h.x509CertChain})
 	}
 	if h.x509CertThumbprint != nil {
 		pairs = append(pairs, &HeaderPair{Key: X509CertThumbprintKey, Value: *(h.x509CertThumbprint)})
@@ -803,7 +796,7 @@ func (h *ecdsaPrivateKey) Get(name string) (interface{}, bool) {
 		if h.x509CertChain == nil {
 			return nil, false
 		}
-		return h.x509CertChain.Get(), true
+		return h.x509CertChain, true
 	case X509CertThumbprintKey:
 		if h.x509CertThumbprint == nil {
 			return nil, false
@@ -900,12 +893,11 @@ func (h *ecdsaPrivateKey) setNoLock(name string, value interface{}) error {
 		}
 		return fmt.Errorf(`invalid value for %s key: %T`, ECDSAXKey, value)
 	case X509CertChainKey:
-		var acceptor CertificateChain
-		if err := acceptor.Accept(value); err != nil {
-			return fmt.Errorf(`invalid value for %s key: %w`, X509CertChainKey, err)
+		if v, ok := value.(*cert.Chain); ok {
+			h.x509CertChain = v
+			return nil
 		}
-		h.x509CertChain = &acceptor
-		return nil
+		return fmt.Errorf(`invalid value for %s key: %T`, X509CertChainKey, value)
 	case X509CertThumbprintKey:
 		if v, ok := value.(string); ok {
 			h.x509CertThumbprint = &v
@@ -1064,7 +1056,7 @@ LOOP:
 					return fmt.Errorf(`failed to decode value for key %s: %w`, ECDSAXKey, err)
 				}
 			case X509CertChainKey:
-				var decoded CertificateChain
+				var decoded cert.Chain
 				if err := dec.Decode(&decoded); err != nil {
 					return fmt.Errorf(`failed to decode value for key %s: %w`, X509CertChainKey, err)
 				}
diff --git a/jwk/interface.go b/jwk/interface.go
index 20f135f7..57a573fe 100644
--- a/jwk/interface.go
+++ b/jwk/interface.go
@@ -2,7 +2,6 @@ package jwk
 
 import (
 	"context"
-	"crypto/x509"
 	"net/http"
 	"sync"
 
@@ -24,10 +23,6 @@ const (
 	ForEncryption KeyUsageType = "enc"
 )
 
-type CertificateChain struct {
-	certs []*x509.Certificate
-}
-
 type KeyOperation string
 type KeyOperationList []KeyOperation
 
diff --git a/jwk/interface_gen.go b/jwk/interface_gen.go
index 4dfb7548..aa938218 100644
--- a/jwk/interface_gen.go
+++ b/jwk/interface_gen.go
@@ -5,8 +5,8 @@ package jwk
 import (
 	"context"
 	"crypto"
-	"crypto/x509"
 
+	"github.com/lestrrat-go/jwx/v2/cert"
 	"github.com/lestrrat-go/jwx/v2/jwa"
 )
 
@@ -106,7 +106,7 @@ type Key interface {
 	// X509URL returns `x58` of a JWK
 	X509URL() string
 	// X509CertChain returns `x5c` of a JWK
-	X509CertChain() []*x509.Certificate
+	X509CertChain() *cert.Chain
 	// X509CertThumbprint returns `x5t` of a JWK
 	X509CertThumbprint() string
 	// X509CertThumbprintS256 returns `x5t#S256` of a JWK
diff --git a/jwk/jwk.go b/jwk/jwk.go
index 2d7b6bd3..da582f5e 100644
--- a/jwk/jwk.go
+++ b/jwk/jwk.go
@@ -235,6 +235,131 @@ func PublicRawKeyOf(v interface{}) (interface{}, error) {
 	}
 }
 
+const (
+	pmPrivateKey = `PRIVATE KEY`
+	pmPublicKey  = `PUBLIC KEY`
+)
+
+// EncodeX509 encodes the key into a byte sequence in ASN.1 DER format
+// suitable for to be PEM encoded. The key can be a jwk.Key or a raw key
+// instance, but it must be one of the types supported by `x509` package.
+//
+// This function will try to do the right thing depending on the key type
+// (i.e. switch between `x509.MarshalPKCS1PRivateKey` and `x509.MarshalECPrivateKey`),
+// but for public keys, it will always use `x509.MarshalPKIXPublicKey`.
+// Please manually perform the encoding if you need more fine grained control
+//
+// The first return value is the name that can be used for `(pem.Block).Type`.
+// The second return value is the encoded byte sequence.
+func EncodeX509(v interface{}) (string, []byte, error) {
+	// we can't import jwk, so just use the interface
+	if key, ok := v.(interface{ Raw(interface{}) error }); ok {
+		var raw interface{}
+		if err := key.Raw(&raw); err != nil {
+			return "", nil, fmt.Errorf(`failed to get raw key out of %T: %w`, key, err)
+		}
+
+		v = raw
+	}
+
+	// Try to convert it into a certificate
+	switch v := v.(type) {
+	case *rsa.PrivateKey:
+		return "RSA PRIVATE KEY", x509.MarshalPKCS1PrivateKey(v), nil
+	case *ecdsa.PrivateKey:
+		marshaled, err := x509.MarshalECPrivateKey(v)
+		if err != nil {
+			return "", nil, err
+		}
+		return "ECDSA PRIVATE KEY", marshaled, nil
+	case ed25519.PrivateKey:
+		marshaled, err := x509.MarshalPKCS8PrivateKey(v)
+		if err != nil {
+			return "", nil, err
+		}
+		return pmPrivateKey, marshaled, nil
+	case *rsa.PublicKey, *ecdsa.PublicKey, ed25519.PublicKey:
+		marshaled, err := x509.MarshalPKIXPublicKey(v)
+		if err != nil {
+			return "", nil, err
+		}
+		return pmPublicKey, marshaled, nil
+	default:
+		return "", nil, fmt.Errorf(`unsupported type %T for ASN.1 DER encoding`, v)
+	}
+}
+
+// EncodePEM encodes the key into a PEM encoded ASN.1 DER format.
+// The key can be a jwk.Key or a raw key instance, but it must be one of
+// the types supported by `x509` package.
+//
+// Internally, it uses the same routine as `jwk.EncodeX509()`, and therefore
+// the same caveats apply
+func EncodePEM(v interface{}) ([]byte, error) {
+	typ, marshaled, err := EncodeX509(v)
+	if err != nil {
+		return nil, fmt.Errorf(`failed to encode key in x509: %w`, err)
+	}
+
+	block := &pem.Block{
+		Type:  typ,
+		Bytes: marshaled,
+	}
+	return pem.EncodeToMemory(block), nil
+}
+
+// DecodePEM decodes a key in PEM encoded ASN.1 DER format.
+// and returns a raw key
+func DecodePEM(src []byte) (interface{}, []byte, error) {
+	block, rest := pem.Decode(src)
+	if block == nil {
+		return nil, nil, fmt.Errorf(`failed to decode PEM data`)
+	}
+
+	switch block.Type {
+	// Handle the semi-obvious cases
+	case "RSA PRIVATE KEY":
+		key, err := x509.ParsePKCS1PrivateKey(block.Bytes)
+		if err != nil {
+			return nil, nil, fmt.Errorf(`failed to parse PKCS1 private key: %w`, err)
+		}
+		return key, rest, nil
+	case "RSA PUBLIC KEY":
+		key, err := x509.ParsePKCS1PublicKey(block.Bytes)
+		if err != nil {
+			return nil, nil, fmt.Errorf(`failed to parse PKCS1 public key: %w`, err)
+		}
+		return key, rest, nil
+	case "EC PRIVATE KEY":
+		key, err := x509.ParseECPrivateKey(block.Bytes)
+		if err != nil {
+			return nil, nil, fmt.Errorf(`failed to parse EC private key: %w`, err)
+		}
+		return key, rest, nil
+	case pmPublicKey:
+		// XXX *could* return dsa.PublicKey
+		key, err := x509.ParsePKIXPublicKey(block.Bytes)
+		if err != nil {
+			return nil, nil, fmt.Errorf(`failed to parse PKIX public key: %w`, err)
+		}
+		return key, rest, nil
+	case pmPrivateKey:
+		key, err := x509.ParsePKCS8PrivateKey(block.Bytes)
+		if err != nil {
+			return nil, nil, fmt.Errorf(`failed to parse PKCS8 private key: %w`, err)
+		}
+		return key, rest, nil
+	case "CERTIFICATE":
+		cert, err := x509.ParseCertificate(block.Bytes)
+		if err != nil {
+			return nil, nil, fmt.Errorf(`failed to parse certificate: %w`, err)
+		}
+		return cert.PublicKey, rest, nil
+	default:
+		return nil, nil, fmt.Errorf(`invalid PEM block type %s`, block.Type)
+	}
+}
+
 type SetFetcher interface {
 	Fetch(context.Context, string, ...FetchOption) (Set, error)
 }
@@ -340,59 +465,6 @@ func ParseRawKey(data []byte, rawkey interface{}) error {
 	return nil
 }
 
-// parsePEMEncodedRawKey parses a key in PEM encoded ASN.1 DER format. It tires its
-// best to determine the key type, but when it just can't, it will return
-// an error
-func parsePEMEncodedRawKey(src []byte) (interface{}, []byte, error) {
-	block, rest := pem.Decode(src)
-	if block == nil {
-		return nil, nil, fmt.Errorf(`failed to decode PEM data`)
-	}
-
-	switch block.Type {
-	// Handle the semi-obvious cases
-	case "RSA PRIVATE KEY":
-		key, err := x509.ParsePKCS1PrivateKey(block.Bytes)
-		if err != nil {
-			return nil, nil, fmt.Errorf(`failed to parse PKCS1 private key: %w`, err)
-		}
-		return key, rest, nil
-	case "RSA PUBLIC KEY":
-		key, err := x509.ParsePKCS1PublicKey(block.Bytes)
-		if err != nil {
-			return nil, nil, fmt.Errorf(`failed to parse PKCS1 public key: %w`, err)
-		}
-		return key, rest, nil
-	case "EC PRIVATE KEY":
-		key, err := x509.ParseECPrivateKey(block.Bytes)
-		if err != nil {
-			return nil, nil, fmt.Errorf(`failed to parse EC private key: %w`, err)
-		}
-		return key, rest, nil
-	case "PUBLIC KEY":
-		// XXX *could* return dsa.PublicKey
-		key, err := x509.ParsePKIXPublicKey(block.Bytes)
-		if err != nil {
-			return nil, nil, fmt.Errorf(`failed to parse PKIX public key: %w`, err)
-		}
-		return key, rest, nil
-	case "PRIVATE KEY":
-		key, err := x509.ParsePKCS8PrivateKey(block.Bytes)
-		if err != nil {
-			return nil, nil, fmt.Errorf(`failed to parse PKCS8 private key: %w`, err)
-		}
-		return key, rest, nil
-	case "CERTIFICATE":
-		cert, err := x509.ParseCertificate(block.Bytes)
-		if err != nil {
-			return nil, nil, fmt.Errorf(`failed to parse certificate: %w`, err)
-		}
-		return cert.PublicKey, rest, nil
-	default:
-		return nil, nil, fmt.Errorf(`invalid PEM block type %s`, block.Type)
-	}
-}
-
 type setDecodeCtx struct {
 	json.DecodeCtx
 	ignoreParseError bool
@@ -438,7 +510,7 @@ func ParseKey(data []byte, options ...ParseOption) (Key, error) {
 	}
 
 	if parsePEM {
-		raw, _, err := parsePEMEncodedRawKey(data)
+		raw, _, err := DecodePEM(data)
 		if err != nil {
 			return nil, fmt.Errorf(`failed to parse PEM encoded key: %w`, err)
 		}
@@ -536,7 +608,7 @@ func Parse(src []byte, options ...ParseOption) (Set, error) {
 	if parsePEM {
 		src = bytes.TrimSpace(src)
 		for len(src) > 0 {
-			raw, rest, err := parsePEMEncodedRawKey(src)
+			raw, rest, err := DecodePEM(src)
 			if err != nil {
 				return nil, fmt.Errorf(`failed to parse PEM encoded key: %w`, err)
 			}
@@ -691,7 +763,7 @@ func asnEncode(key Key) (string, []byte, error) {
 		if err != nil {
 			return "", nil, fmt.Errorf(`failed to marshal PKCS8: %w`, err)
 		}
-		return "PRIVATE KEY", buf, nil
+		return pmPrivateKey, buf, nil
 	case RSAPublicKey, ECDSAPublicKey, OKPPublicKey:
 		var rawkey interface{}
 		if err := key.Raw(&rawkey); err != nil {
@@ -701,7 +773,7 @@ func asnEncode(key Key) (string, []byte, error) {
 		if err != nil {
 			return "", nil, fmt.Errorf(`failed to marshal PKIX: %w`, err)
 		}
-		return "PUBLIC KEY", buf, nil
+		return pmPublicKey, buf, nil
 	default:
 		return "", nil, fmt.Errorf(`unsupported key type %T`, key)
 	}
diff --git a/jwk/okp_gen.go b/jwk/okp_gen.go
index 4c05ea7b..8be02970 100644
--- a/jwk/okp_gen.go
+++ b/jwk/okp_gen.go
@@ -5,12 +5,12 @@ package jwk
 import (
 	"bytes"
 	"context"
-	"crypto/x509"
 	"fmt"
 	"sort"
 	"sync"
 
 	"github.com/lestrrat-go/iter/mapiter"
+	"github.com/lestrrat-go/jwx/v2/cert"
 	"github.com/lestrrat-go/jwx/v2/internal/base64"
 	"github.com/lestrrat-go/jwx/v2/internal/iter"
 	"github.com/lestrrat-go/jwx/v2/internal/json"
@@ -38,10 +38,10 @@ type okpPublicKey struct {
 	keyOps                 *KeyOperationList // https://tools.ietf.org/html/rfc7517#section-4.3
 	keyUsage               *string           // https://tools.ietf.org/html/rfc7517#section-4.2
 	x                      []byte
-	x509CertChain          *CertificateChain // https://tools.ietf.org/html/rfc7515#section-4.1.6
-	x509CertThumbprint     *string           // https://tools.ietf.org/html/rfc7515#section-4.1.7
-	x509CertThumbprintS256 *string           // https://tools.ietf.org/html/rfc7515#section-4.1.8
-	x509URL                *string           // https://tools.ietf.org/html/rfc7515#section-4.1.5
+	x509CertChain          *cert.Chain // https://tools.ietf.org/html/rfc7515#section-4.1.6
+	x509CertThumbprint     *string     // https://tools.ietf.org/html/rfc7515#section-4.1.7
+	x509CertThumbprintS256 *string     // https://tools.ietf.org/html/rfc7515#section-4.1.8
+	x509URL                *string     // https://tools.ietf.org/html/rfc7515#section-4.1.5
 	privateParams          map[string]interface{}
 	mu                     *sync.RWMutex
 	dc                     json.DecodeCtx
@@ -100,11 +100,8 @@ func (h *okpPublicKey) X() []byte {
 	return h.x
 }
 
-func (h *okpPublicKey) X509CertChain() []*x509.Certificate {
-	if h.x509CertChain != nil {
-		return h.x509CertChain.Get()
-	}
-	return nil
+func (h *okpPublicKey) X509CertChain() *cert.Chain {
+	return h.x509CertChain
 }
 
 func (h *okpPublicKey) X509CertThumbprint() string {
@@ -153,7 +150,7 @@ func (h *okpPublicKey) makePairs() []*HeaderPair {
 		pairs = append(pairs, &HeaderPair{Key: OKPXKey, Value: h.x})
 	}
 	if h.x509CertChain != nil {
-		pairs = append(pairs, &HeaderPair{Key: X509CertChainKey, Value: *(h.x509CertChain)})
+		pairs = append(pairs, &HeaderPair{Key: X509CertChainKey, Value: h.x509CertChain})
 	}
 	if h.x509CertThumbprint != nil {
 		pairs = append(pairs, &HeaderPair{Key: X509CertThumbprintKey, Value: *(h.x509CertThumbprint)})
@@ -214,7 +211,7 @@ func (h *okpPublicKey) Get(name string) (interface{}, bool) {
 		if h.x509CertChain == nil {
 			return nil, false
 		}
-		return h.x509CertChain.Get(), true
+		return h.x509CertChain, true
 	case X509CertThumbprintKey:
 		if h.x509CertThumbprint == nil {
 			return nil, false
@@ -300,12 +297,11 @@ func (h *okpPublicKey) setNoLock(name string, value interface{}) error {
 		}
 		return fmt.Errorf(`invalid value for %s key: %T`, OKPXKey, value)
 	case X509CertChainKey:
-		var acceptor CertificateChain
-		if err := acceptor.Accept(value); err != nil {
-			return fmt.Errorf(`invalid value for %s key: %w`, X509CertChainKey, err)
+		if v, ok := value.(*cert.Chain); ok {
+			h.x509CertChain = v
+			return nil
 		}
-		h.x509CertChain = &acceptor
-		return nil
+		return fmt.Errorf(`invalid value for %s key: %T`, X509CertChainKey, value)
 	case X509CertThumbprintKey:
 		if v, ok := value.(string); ok {
 			h.x509CertThumbprint = &v
@@ -448,7 +444,7 @@ LOOP:
 					return fmt.Errorf(`failed to decode value for key %s: %w`, OKPXKey, err)
 				}
 			case X509CertChainKey:
-				var decoded CertificateChain
+				var decoded cert.Chain
 				if err := dec.Decode(&decoded); err != nil {
 					return fmt.Errorf(`failed to decode value for key %s: %w`, X509CertChainKey, err)
 				}
@@ -574,10 +570,10 @@ type okpPrivateKey struct {
 	keyOps                 *KeyOperationList // https://tools.ietf.org/html/rfc7517#section-4.3
 	keyUsage               *string           // https://tools.ietf.org/html/rfc7517#section-4.2
 	x                      []byte
-	x509CertChain          *CertificateChain // https://tools.ietf.org/html/rfc7515#section-4.1.6
-	x509CertThumbprint     *string           // https://tools.ietf.org/html/rfc7515#section-4.1.7
-	x509CertThumbprintS256 *string           // https://tools.ietf.org/html/rfc7515#section-4.1.8
-	x509URL                *string           // https://tools.ietf.org/html/rfc7515#section-4.1.5
+	x509CertChain          *cert.Chain // https://tools.ietf.org/html/rfc7515#section-4.1.6
+	x509CertThumbprint     *string     // https://tools.ietf.org/html/rfc7515#section-4.1.7
+	x509CertThumbprintS256 *string     // https://tools.ietf.org/html/rfc7515#section-4.1.8
+	x509URL                *string     // https://tools.ietf.org/html/rfc7515#section-4.1.5
 	privateParams          map[string]interface{}
 	mu                     *sync.RWMutex
 	dc                     json.DecodeCtx
@@ -640,11 +636,8 @@ func (h *okpPrivateKey) X() []byte {
 	return h.x
 }
 
-func (h *okpPrivateKey) X509CertChain() []*x509.Certificate {
-	if h.x509CertChain != nil {
-		return h.x509CertChain.Get()
-	}
-	return nil
+func (h *okpPrivateKey) X509CertChain() *cert.Chain {
+	return h.x509CertChain
 }
 
 func (h *okpPrivateKey) X509CertThumbprint() string {
@@ -696,7 +689,7 @@ func (h *okpPrivateKey) makePairs() []*HeaderPair {
 		pairs = append(pairs, &HeaderPair{Key: OKPXKey, Value: h.x})
 	}
 	if h.x509CertChain != nil {
-		pairs = append(pairs, &HeaderPair{Key: X509CertChainKey, Value: *(h.x509CertChain)})
+		pairs = append(pairs, &HeaderPair{Key: X509CertChainKey, Value: h.x509CertChain})
 	}
 	if h.x509CertThumbprint != nil {
 		pairs = append(pairs, &HeaderPair{Key: X509CertThumbprintKey, Value: *(h.x509CertThumbprint)})
@@ -762,7 +755,7 @@ func (h *okpPrivateKey) Get(name string) (interface{}, bool) {
 		if h.x509CertChain == nil {
 			return nil, false
 		}
-		return h.x509CertChain.Get(), true
+		return h.x509CertChain, true
 	case X509CertThumbprintKey:
 		if h.x509CertThumbprint == nil {
 			return nil, false
@@ -854,12 +847,11 @@ func (h *okpPrivateKey) setNoLock(name string, value interface{}) error {
 		}
 		return fmt.Errorf(`invalid value for %s key: %T`, OKPXKey, value)
 	case X509CertChainKey:
-		var acceptor CertificateChain
-		if err := acceptor.Accept(value); err != nil {
-			return fmt.Errorf(`invalid value for %s key: %w`, X509CertChainKey, err)
+		if v, ok := value.(*cert.Chain); ok {
+			h.x509CertChain = v
+			return nil
 		}
-		h.x509CertChain = &acceptor
-		return nil
+		return fmt.Errorf(`invalid value for %s key: %T`, X509CertChainKey, value)
 	case X509CertThumbprintKey:
 		if v, ok := value.(string); ok {
 			h.x509CertThumbprint = &v
@@ -1009,7 +1001,7 @@ LOOP:
 					return fmt.Errorf(`failed to decode value for key %s: %w`, OKPXKey, err)
 				}
 			case X509CertChainKey:
-				var decoded CertificateChain
+				var decoded cert.Chain
 				if err := dec.Decode(&decoded); err != nil {
 					return fmt.Errorf(`failed to decode value for key %s: %w`, X509CertChainKey, err)
 				}
diff --git a/jwk/rsa_gen.go b/jwk/rsa_gen.go
index a165f543..bbf93e44 100644
--- a/jwk/rsa_gen.go
+++ b/jwk/rsa_gen.go
@@ -6,12 +6,12 @@ import (
 	"bytes"
 	"context"
 	"crypto/rsa"
-	"crypto/x509"
 	"fmt"
 	"sort"
 	"sync"
 
 	"github.com/lestrrat-go/iter/mapiter"
+	"github.com/lestrrat-go/jwx/v2/cert"
 	"github.com/lestrrat-go/jwx/v2/internal/base64"
 	"github.com/lestrrat-go/jwx/v2/internal/iter"
 	"github.com/lestrrat-go/jwx/v2/internal/json"
@@ -44,10 +44,10 @@ type rsaPublicKey struct {
 	keyOps                 *KeyOperationList // https://tools.ietf.org/html/rfc7517#section-4.3
 	keyUsage               *string           // https://tools.ietf.org/html/rfc7517#section-4.2
 	n                      []byte
-	x509CertChain          *CertificateChain // https://tools.ietf.org/html/rfc7515#section-4.1.6
-	x509CertThumbprint     *string           // https://tools.ietf.org/html/rfc7515#section-4.1.7
-	x509CertThumbprintS256 *string           // https://tools.ietf.org/html/rfc7515#section-4.1.8
-	x509URL                *string           // https://tools.ietf.org/html/rfc7515#section-4.1.5
+	x509CertChain          *cert.Chain // https://tools.ietf.org/html/rfc7515#section-4.1.6
+	x509CertThumbprint     *string     // https://tools.ietf.org/html/rfc7515#section-4.1.7
+	x509CertThumbprintS256 *string     // https://tools.ietf.org/html/rfc7515#section-4.1.8
+	x509URL                *string     // https://tools.ietf.org/html/rfc7515#section-4.1.5
 	privateParams          map[string]interface{}
 	mu                     *sync.RWMutex
 	dc                     json.DecodeCtx
@@ -103,11 +103,8 @@ func (h *rsaPublicKey) N() []byte {
 	return h.n
 }
 
-func (h *rsaPublicKey) X509CertChain() []*x509.Certificate {
-	if h.x509CertChain != nil {
-		return h.x509CertChain.Get()
-	}
-	return nil
+func (h *rsaPublicKey) X509CertChain() *cert.Chain {
+	return h.x509CertChain
 }
 
 func (h *rsaPublicKey) X509CertThumbprint() string {
@@ -156,7 +153,7 @@ func (h *rsaPublicKey) makePairs() []*HeaderPair {
 		pairs = append(pairs, &HeaderPair{Key: RSANKey, Value: h.n})
 	}
 	if h.x509CertChain != nil {
-		pairs = append(pairs, &HeaderPair{Key: X509CertChainKey, Value: *(h.x509CertChain)})
+		pairs = append(pairs, &HeaderPair{Key: X509CertChainKey, Value: h.x509CertChain})
 	}
 	if h.x509CertThumbprint != nil {
 		pairs = append(pairs, &HeaderPair{Key: X509CertThumbprintKey, Value: *(h.x509CertThumbprint)})
@@ -217,7 +214,7 @@ func (h *rsaPublicKey) Get(name string) (interface{}, bool) {
 		if h.x509CertChain == nil {
 			return nil, false
 		}
-		return h.x509CertChain.Get(), true
+		return h.x509CertChain, true
 	case X509CertThumbprintKey:
 		if h.x509CertThumbprint == nil {
 			return nil, false
@@ -303,12 +300,11 @@ func (h *rsaPublicKey) setNoLock(name string, value interface{}) error {
 		}
 		return fmt.Errorf(`invalid value for %s key: %T`, RSANKey, value)
 	case X509CertChainKey:
-		var acceptor CertificateChain
-		if err := acceptor.Accept(value); err != nil {
-			return fmt.Errorf(`invalid value for %s key: %w`, X509CertChainKey, err)
+		if v, ok := value.(*cert.Chain); ok {
+			h.x509CertChain = v
+			return nil
 		}
-		h.x509CertChain = &acceptor
-		return nil
+		return fmt.Errorf(`invalid value for %s key: %T`, X509CertChainKey, value)
 	case X509CertThumbprintKey:
 		if v, ok := value.(string); ok {
 			h.x509CertThumbprint = &v
@@ -449,7 +445,7 @@ LOOP:
 					return fmt.Errorf(`failed to decode value for key %s: %w`, RSANKey, err)
 				}
 			case X509CertChainKey:
-				var decoded CertificateChain
+				var decoded cert.Chain
 				if err := dec.Decode(&decoded); err != nil {
 					return fmt.Errorf(`failed to decode value for key %s: %w`, X509CertChainKey, err)
 				}
@@ -585,10 +581,10 @@ type rsaPrivateKey struct {
 	p                      []byte
 	q                      []byte
 	qi                     []byte
-	x509CertChain          *CertificateChain // https://tools.ietf.org/html/rfc7515#section-4.1.6
-	x509CertThumbprint     *string           // https://tools.ietf.org/html/rfc7515#section-4.1.7
-	x509CertThumbprintS256 *string           // https://tools.ietf.org/html/rfc7515#section-4.1.8
-	x509URL                *string           // https://tools.ietf.org/html/rfc7515#section-4.1.5
+	x509CertChain          *cert.Chain // https://tools.ietf.org/html/rfc7515#section-4.1.6
+	x509CertThumbprint     *string     // https://tools.ietf.org/html/rfc7515#section-4.1.7
+	x509CertThumbprintS256 *string     // https://tools.ietf.org/html/rfc7515#section-4.1.8
+	x509URL                *string     // https://tools.ietf.org/html/rfc7515#section-4.1.5
 	privateParams          map[string]interface{}
 	mu                     *sync.RWMutex
 	dc                     json.DecodeCtx
@@ -668,11 +664,8 @@ func (h *rsaPrivateKey) QI() []byte {
 	return h.qi
 }
 
-func (h *rsaPrivateKey) X509CertChain() []*x509.Certificate {
-	if h.x509CertChain != nil {
-		return h.x509CertChain.Get()
-	}
-	return nil
+func (h *rsaPrivateKey) X509CertChain() *cert.Chain {
+	return h.x509CertChain
 }
 
 func (h *rsaPrivateKey) X509CertThumbprint() string {
@@ -739,7 +732,7 @@ func (h *rsaPrivateKey) makePairs() []*HeaderPair {
 		pairs = append(pairs, &HeaderPair{Key: RSAQIKey, Value: h.qi})
 	}
 	if h.x509CertChain != nil {
-		pairs = append(pairs, &HeaderPair{Key: X509CertChainKey, Value: *(h.x509CertChain)})
+		pairs = append(pairs, &HeaderPair{Key: X509CertChainKey, Value: h.x509CertChain})
 	}
 	if h.x509CertThumbprint != nil {
 		pairs = append(pairs, &HeaderPair{Key: X509CertThumbprintKey, Value: *(h.x509CertThumbprint)})
@@ -830,7 +823,7 @@ func (h *rsaPrivateKey) Get(name string) (interface{}, bool) {
 		if h.x509CertChain == nil {
 			return nil, false
 		}
-		return h.x509CertChain.Get(), true
+		return h.x509CertChain, true
 	case X509CertThumbprintKey:
 		if h.x509CertThumbprint == nil {
 			return nil, false
@@ -952,12 +945,11 @@ func (h *rsaPrivateKey) setNoLock(name string, value interface{}) error {
 		}
 		return fmt.Errorf(`invalid value for %s key: %T`, RSAQIKey, value)
 	case X509CertChainKey:
-		var acceptor CertificateChain
-		if err := acceptor.Accept(value); err != nil {
-			return fmt.Errorf(`invalid value for %s key: %w`, X509CertChainKey, err)
+		if v, ok := value.(*cert.Chain); ok {
+			h.x509CertChain = v
+			return nil
 		}
-		h.x509CertChain = &acceptor
-		return nil
+		return fmt.Errorf(`invalid value for %s key: %T`, X509CertChainKey, value)
 	case X509CertThumbprintKey:
 		if v, ok := value.(string); ok {
 			h.x509CertThumbprint = &v
@@ -1140,7 +1132,7 @@ LOOP:
 					return fmt.Errorf(`failed to decode value for key %s: %w`, RSAQIKey, err)
 				}
 			case X509CertChainKey:
-				var decoded CertificateChain
+				var decoded cert.Chain
 				if err := dec.Decode(&decoded); err != nil {
 					return fmt.Errorf(`failed to decode value for key %s: %w`, X509CertChainKey, err)
 				}
diff --git a/jwk/symmetric_gen.go b/jwk/symmetric_gen.go
index 7735acfc..7be6d5f2 100644
--- a/jwk/symmetric_gen.go
+++ b/jwk/symmetric_gen.go
@@ -5,12 +5,12 @@ package jwk
 import (
 	"bytes"
 	"context"
-	"crypto/x509"
 	"fmt"
 	"sort"
 	"sync"
 
 	"github.com/lestrrat-go/iter/mapiter"
+	"github.com/lestrrat-go/jwx/v2/cert"
 	"github.com/lestrrat-go/jwx/v2/internal/base64"
 	"github.com/lestrrat-go/jwx/v2/internal/iter"
 	"github.com/lestrrat-go/jwx/v2/internal/json"
@@ -34,10 +34,10 @@ type symmetricKey struct {
 	keyOps                 *KeyOperationList // https://tools.ietf.org/html/rfc7517#section-4.3
 	keyUsage               *string           // https://tools.ietf.org/html/rfc7517#section-4.2
 	octets                 []byte
-	x509CertChain          *CertificateChain // https://tools.ietf.org/html/rfc7515#section-4.1.6
-	x509CertThumbprint     *string           // https://tools.ietf.org/html/rfc7515#section-4.1.7
-	x509CertThumbprintS256 *string           // https://tools.ietf.org/html/rfc7515#section-4.1.8
-	x509URL                *string           // https://tools.ietf.org/html/rfc7515#section-4.1.5
+	x509CertChain          *cert.Chain // https://tools.ietf.org/html/rfc7515#section-4.1.6
+	x509CertThumbprint     *string     // https://tools.ietf.org/html/rfc7515#section-4.1.7
+	x509CertThumbprintS256 *string     // https://tools.ietf.org/html/rfc7515#section-4.1.8
+	x509URL                *string     // https://tools.ietf.org/html/rfc7515#section-4.1.5
 	privateParams          map[string]interface{}
 	mu                     *sync.RWMutex
 	dc                     json.DecodeCtx
@@ -89,11 +89,8 @@ func (h *symmetricKey) Octets() []byte {
 	return h.octets
 }
 
-func (h *symmetricKey) X509CertChain() []*x509.Certificate {
-	if h.x509CertChain != nil {
-		return h.x509CertChain.Get()
-	}
-	return nil
+func (h *symmetricKey) X509CertChain() *cert.Chain {
+	return h.x509CertChain
 }
 
 func (h *symmetricKey) X509CertThumbprint() string {
@@ -139,7 +136,7 @@ func (h *symmetricKey) makePairs() []*HeaderPair {
 		pairs = append(pairs, &HeaderPair{Key: SymmetricOctetsKey, Value: h.octets})
 	}
 	if h.x509CertChain != nil {
-		pairs = append(pairs, &HeaderPair{Key: X509CertChainKey, Value: *(h.x509CertChain)})
+		pairs = append(pairs, &HeaderPair{Key: X509CertChainKey, Value: h.x509CertChain})
 	}
 	if h.x509CertThumbprint != nil {
 		pairs = append(pairs, &HeaderPair{Key: X509CertThumbprintKey, Value: *(h.x509CertThumbprint)})
@@ -195,7 +192,7 @@ func (h *symmetricKey) Get(name string) (interface{}, bool) {
 		if h.x509CertChain == nil {
 			return nil, false
 		}
-		return h.x509CertChain.Get(), true
+		return h.x509CertChain, true
 	case X509CertThumbprintKey:
 		if h.x509CertThumbprint == nil {
 			return nil, false
@@ -275,12 +272,11 @@ func (h *symmetricKey) setNoLock(name string, value interface{}) error {
 		}
 		return fmt.Errorf(`invalid value for %s key: %T`, SymmetricOctetsKey, value)
 	case X509CertChainKey:
-		var acceptor CertificateChain
-		if err := acceptor.Accept(value); err != nil {
-			return fmt.Errorf(`invalid value for %s key: %w`, X509CertChainKey, err)
+		if v, ok := value.(*cert.Chain); ok {
+			h.x509CertChain = v
+			return nil
 		}
-		h.x509CertChain = &acceptor
-		return nil
+		return fmt.Errorf(`invalid value for %s key: %T`, X509CertChainKey, value)
 	case X509CertThumbprintKey:
 		if v, ok := value.(string); ok {
 			h.x509CertThumbprint = &v
@@ -414,7 +410,7 @@ LOOP:
 					return fmt.Errorf(`failed to decode value for key %s: %w`, SymmetricOctetsKey, err)
 				}
 			case X509CertChainKey:
-				var decoded CertificateChain
+				var decoded cert.Chain
 				if err := dec.Decode(&decoded); err != nil {
 					return fmt.Errorf(`failed to decode value for key %s: %w`, X509CertChainKey, err)
 				}
diff --git a/jws/headers_gen.go b/jws/headers_gen.go
index 844409af..c3436b35 100644
--- a/jws/headers_gen.go
+++ b/jws/headers_gen.go
@@ -9,6 +9,7 @@ import (
 	"sort"
 	"sync"
 
+	"github.com/lestrrat-go/jwx/v2/cert"
 	"github.com/lestrrat-go/jwx/v2/internal/base64"
 	"github.com/lestrrat-go/jwx/v2/internal/json"
 	"github.com/lestrrat-go/jwx/v2/internal/pool"
@@ -41,7 +42,7 @@ type Headers interface {
 	JWKSetURL() string
 	KeyID() string
 	Type() string
-	X509CertChain() []string
+	X509CertChain() *cert.Chain
 	X509CertThumbprint() string
 	X509CertThumbprintS256() string
 	X509URL() string
@@ -68,7 +69,7 @@ type stdHeaders struct {
 	jwkSetURL              *string                 // https://tools.ietf.org/html/rfc7515#section-4.1.2
 	keyID                  *string                 // https://tools.ietf.org/html/rfc7515#section-4.1.4
 	typ                    *string                 // https://tools.ietf.org/html/rfc7515#section-4.1.9
-	x509CertChain          []string                // https://tools.ietf.org/html/rfc7515#section-4.1.6
+	x509CertChain          *cert.Chain             // https://tools.ietf.org/html/rfc7515#section-4.1.6
 	x509CertThumbprint     *string                 // https://tools.ietf.org/html/rfc7515#section-4.1.7
 	x509CertThumbprintS256 *string                 // https://tools.ietf.org/html/rfc7515#section-4.1.8
 	x509URL                *string                 // https://tools.ietf.org/html/rfc7515#section-4.1.5
@@ -141,7 +142,7 @@ func (h *stdHeaders) Type() string {
 	return *(h.typ)
 }
 
-func (h *stdHeaders) X509CertChain() []string {
+func (h *stdHeaders) X509CertChain() *cert.Chain {
 	h.mu.RLock()
 	defer h.mu.RUnlock()
 	return h.x509CertChain
@@ -359,7 +360,7 @@ func (h *stdHeaders) setNoLock(name string, value interface{}) error {
 		}
 		return fmt.Errorf(`invalid value for %s key: %T`, TypeKey, value)
 	case X509CertChainKey:
-		if v, ok := value.([]string); ok {
+		if v, ok := value.(*cert.Chain); ok {
 			h.x509CertChain = v
 			return nil
 		}
@@ -492,11 +493,11 @@ LOOP:
 					return fmt.Errorf(`failed to decode value for key %s: %w`, TypeKey, err)
 				}
 			case X509CertChainKey:
-				var decoded []string
+				var decoded cert.Chain
 				if err := dec.Decode(&decoded); err != nil {
 					return fmt.Errorf(`failed to decode value for key %s: %w`, X509CertChainKey, err)
 				}
-				h.x509CertChain = decoded
+				h.x509CertChain = &decoded
 			case X509CertThumbprintKey:
 				if err := json.AssignNextStringToken(&h.x509CertThumbprint, dec); err != nil {
 					return fmt.Errorf(`failed to decode value for key %s: %w`, X509CertThumbprintKey, err)
diff --git a/tools/cmd/genjwe/main.go b/tools/cmd/genjwe/main.go
index f6f269c7..9f367758 100644
--- a/tools/cmd/genjwe/main.go
+++ b/tools/cmd/genjwe/main.go
@@ -73,6 +73,10 @@ func fieldHasAccept(f codegen.Field) bool {
 	return v
 }
 
+func IsPointer(f codegen.Field) bool {
+	return strings.HasPrefix(f.Type(), `*`)
+}
+
 func PointerElem(f codegen.Field) string {
 	return strings.TrimPrefix(f.Type(), `*`)
 }
@@ -334,7 +338,11 @@ func generateHeaders(obj *codegen.Object) error {
 			o.L("h.%s = decoded", f.Name(false))
 		} else {
 			o.L("case %sKey:", f.Name(true))
-			o.L("var decoded %s", f.Type())
+			if IsPointer(f) {
+				o.L("var decoded %s", PointerElem(f))
+			} else {
+				o.L("var decoded %s", f.Type())
+			}
 			o.L("if err := dec.Decode(&decoded); err != nil {")
 			o.L("return fmt.Errorf(`failed to decode value for key %%s: %%w`, %sKey, err)", f.Name(true))
 			o.L("}")
diff --git a/tools/cmd/genjwe/objects.yml b/tools/cmd/genjwe/objects.yml
index 2b0b7593..6306163c 100644
--- a/tools/cmd/genjwe/objects.yml
+++ b/tools/cmd/genjwe/objects.yml
@@ -45,7 +45,7 @@ fields:
     unexported_name: x509CertChain
     exported_name: X509CertChain
     getter: X509CertChain
-    type: "[]string"
+    type: "*cert.Chain"
     json: x5c
   - name: x509CertThumbprint
     unexported_name: x509CertThumbprint
diff --git a/tools/cmd/genjwk/main.go b/tools/cmd/genjwk/main.go
index a6a66aad..5790be04 100644
--- a/tools/cmd/genjwk/main.go
+++ b/tools/cmd/genjwk/main.go
@@ -326,6 +326,8 @@ func generateObject(o *codegen.Output, kt *KeyType, obj *codegen.Object) error {
 			} else {
 				o.L("return h.%s", f.Name(false))
 			}
+		} else {
+			o.L(`return h.%s`, f.Name(false))
 		}
 		o.L("}") // func (h *stdHeaders) %s() %s
 	}
@@ -569,7 +571,11 @@ func generateObject(o *codegen.Output, kt *KeyType, obj *codegen.Object) error {
 				name = kt.Prefix + f.Name(true)
 			}
 			o.L("case %sKey:", name)
-			o.L("var decoded %s", f.Type())
+			if IsPointer(f) {
+				o.L("var decoded %s", PointerElem(f))
+			} else {
+				o.L("var decoded %s", f.Type())
+			}
 			o.L("if err := dec.Decode(&decoded); err != nil {")
 			o.L("return fmt.Errorf(`failed to decode value for key %%s: %%w`, %sKey, err)", name)
 			o.L("}")
diff --git a/tools/cmd/genjwk/objects.yml b/tools/cmd/genjwk/objects.yml
index 5dfb7405..3bb0265f 100644
--- a/tools/cmd/genjwk/objects.yml
+++ b/tools/cmd/genjwk/objects.yml
@@ -27,12 +27,10 @@ std_fields:
     comment: https://tools.ietf.org/html/rfc7515#section-4.1.5
   - name: x509CertChain
     getter: X509CertChain
-    getter_return_value: "[]*x509.Certificate"
-    type: CertificateChain
+    getter_return_value: "*cert.Chain"
+    type: "*cert.Chain"
     json: x5c
     is_std: true
-    hasAccept: true
-    hasGet: true
     noDeref: true
     comment: https://tools.ietf.org/html/rfc7515#section-4.1.6
   - name: x509CertThumbprint
diff --git a/tools/cmd/genjws/main.go b/tools/cmd/genjws/main.go
index f7b1c9c5..049beb28 100644
--- a/tools/cmd/genjws/main.go
+++ b/tools/cmd/genjws/main.go
@@ -67,6 +67,11 @@ func boolFromField(f codegen.Field, field string) (bool, error) {
 	return b, nil
 }
 
+func fieldNoDeref(f codegen.Field) bool {
+	v, _ := boolFromField(f, "noDeref")
+	return v
+}
+
 func fieldHasAccept(f codegen.Field) bool {
 	v, _ := boolFromField(f, "hasAccept")
 	return v
@@ -110,7 +115,11 @@ func generateHeaders(obj *codegen.Object) error {
 	o.L("json.Unmarshaler")
 	// These are the basic values that most jws have
 	for _, f := range obj.Fields() {
-		o.L("%s() %s", f.GetterMethod(true), PointerElem(f))
+		if fieldNoDeref(f) {
+			o.L("%s() %s", f.GetterMethod(true), f.Type())
+		} else {
+			o.L("%s() %s", f.GetterMethod(true), PointerElem(f))
+		}
 	}
 
 	// These are used to iterate through all keys in a header
@@ -340,6 +349,13 @@ func generateHeaders(obj *codegen.Object) error {
 			o.L("return fmt.Errorf(`failed to decode value for key %%s: %%w`, %sKey, err)", f.Name(true))
 			o.L("}")
 			o.L("h.%s = decoded", f.Name(false))
+		} else if fieldNoDeref(f) {
+			o.L("case %sKey:", f.Name(true))
+			o.L("var decoded %s", PointerElem(f))
+			o.L("if err := dec.Decode(&decoded); err != nil {")
+			o.L("return fmt.Errorf(`failed to decode value for key %%s: %%w`, %sKey, err)", f.Name(true))
+			o.L("}")
+			o.L("h.%s = &decoded", f.Name(false))
 		} else {
 			o.L("case %sKey:", f.Name(true))
 			o.L("var decoded %s", f.Type())
diff --git a/tools/cmd/genjws/objects.yml b/tools/cmd/genjws/objects.yml
index 7f146e84..54c4e507 100644
--- a/tools/cmd/genjws/objects.yml
+++ b/tools/cmd/genjws/objects.yml
@@ -28,7 +28,8 @@ fields:
     unexported_name: x509CertChain
     exported_name: X509CertChain
     getter: X509CertChain
-    type: "[]string"
+    type: "*cert.Chain"
+    noDeref: true
     json: x5c
     comment: https://tools.ietf.org/html/rfc7515#section-4.1.6
   - name: x509CertThumbprint


Test patch:
diff --git a/cert/chain_test.go b/cert/chain_test.go
new file mode 100644
index 00000000..2be34529
--- /dev/null
+++ b/cert/chain_test.go
@@ -0,0 +1,74 @@
+package cert_test
+
+import (
+	"testing"
+
+	"github.com/lestrrat-go/jwx/v2/cert"
+	"github.com/stretchr/testify/assert"
+)
+
+var certBytes = []byte(`MIICdDCCAd2gAwIBAgIUEpq1vvAyaiEKhgEE/UKykUcnXi4wDQYJKoZIhvcNAQEL
+BQAwTDELMAkGA1UEBhMCSlAxDjAMBgNVBAgMBVRva3lvMREwDwYDVQQHDAhSb3Bw
+b25naTEMMAoGA1UECgwDSldYMQwwCgYDVQQDDANKV1gwHhcNMjIwMzEzMTMzOTIy
+WhcNMjMwMzEzMTMzOTIyWjBMMQswCQYDVQQGEwJKUDEOMAwGA1UECAwFVG9reW8x
+ETAPBgNVBAcMCFJvcHBvbmdpMQwwCgYDVQQKDANKV1gxDDAKBgNVBAMMA0pXWDCB
+nzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAwHm1AyeTpFWghI3PRTitSBMmbXqQ
+ccrmK+4RZkp4JRhRXH6dc6O0JvsesoMmONegeU3c/FNU7ZTdaXJHGZCo4IUil0gv
+rJRn52LAvCkodNwKG80+xHvGXix3LEaiTPbBmqGCttx5Q+2WsiBjZPHtQU2kOVs4
+k90F++pImEd7Xl8CAwEAAaNTMFEwHQYDVR0OBBYEFN78aX+uEXMpDrZhtEY2e/vR
+jdgSMB8GA1UdIwQYMBaAFN78aX+uEXMpDrZhtEY2e/vRjdgSMA8GA1UdEwEB/wQF
+MAMBAf8wDQYJKoZIhvcNAQELBQADgYEAsrNkfe2+E9fpFkmIYPkxiOGMo0d6edlV
+Q0fW17ZhS1fuM3eQJr61IJvZ4hEP2KjsOEJzRvptxkpVFiDOZf8DbkUVNpeWxorK
+gPt3f4fzO4SIXu7fG89QkR5TJs6lxyZsr1V/IumL4LSx04LhIvMhHiUbbyVHgN8B
+KpDY+K+bsqw=`)
+
+func TestChain(t *testing.T) {
+	goldenCert, err := cert.Parse(certBytes)
+	if !assert.NoError(t, err, `x509.ParseCertificate should succeed`) {
+		return
+	}
+
+	testcases := []struct {
+		Name string
+		Data []byte
+	}{
+		{
+			Name: `proper base64 in ASN.1 DER`,
+			Data: certBytes,
+		},
+		{
+			Name: `proper base64 in ASN.1 DER, but with markers`,
+			Data: append(append([]byte("----- BEGIN CERTIFICATE -----\n"), certBytes...), []byte("\n----- END CERTIFICATE -----")...),
+		},
+	}
+
+	var chain cert.Chain
+	for _, tc := range testcases {
+		tc := tc
+		t.Run(tc.Name, func(t *testing.T) {
+			if !assert.NoError(t, chain.Add(tc.Data), `chain.Add should succeed`) {
+				return
+			}
+		})
+	}
+
+	if !assert.Equal(t, len(testcases), chain.Len(), `certificates in chain should match`) {
+		return
+	}
+
+	for i := 0; i < chain.Len(); i++ {
+		der, ok := chain.Get(i)
+		if !assert.True(t, ok, `chain.Get(%d) should succeed`, i) {
+			return
+		}
+
+		c, err := cert.Parse(der)
+		if !assert.NoError(t, err, `cert.Parse should match`) {
+			return
+		}
+
+		if !assert.True(t, c.Equal(goldenCert), `certificates should match`) {
+			return
+		}
+	}
+}
diff --git a/jwe/headers_test.go b/jwe/headers_test.go
index b9ffd1d0..ce7a1bf1 100644
--- a/jwe/headers_test.go
+++ b/jwe/headers_test.go
@@ -5,6 +5,7 @@ import (
 	"reflect"
 	"testing"
 
+	"github.com/lestrrat-go/jwx/v2/cert"
 	"github.com/lestrrat-go/jwx/v2/internal/jwxtest"
 	"github.com/lestrrat-go/jwx/v2/jwa"
 	"github.com/lestrrat-go/jwx/v2/jwe"
@@ -15,11 +16,15 @@ import (
 var zeroval reflect.Value
 
 func TestHeaders(t *testing.T) {
-	certs := []string{
+	certSrc := []string{
 		"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",
 	}
+	var certs cert.Chain
+	for _, src := range certSrc {
+		_ = certs.AddString(src)
+	}
 
 	rawKey, err := jwxtest.GenerateEcdsaKey(jwa.P521)
 	if !assert.NoError(t, err, `jwxtest.GenerateEcdsaKey should succeed`) {
@@ -86,7 +91,7 @@ func TestHeaders(t *testing.T) {
 		},
 		{
 			Key:    jwe.X509CertChainKey,
-			Value:  certs,
+			Value:  &certs,
 			Method: "X509CertChain",
 		},
 		{
diff --git a/jwk/jwk_internal_test.go b/jwk/jwk_internal_test.go
index 3f258a28..b3085e0a 100644
--- a/jwk/jwk_internal_test.go
+++ b/jwk/jwk_internal_test.go
@@ -5,17 +5,22 @@ import (
 	"fmt"
 	"testing"
 
+	"github.com/lestrrat-go/jwx/v2/cert"
 	"github.com/lestrrat-go/jwx/v2/internal/json"
 	"github.com/lestrrat-go/jwx/v2/jwa"
 	"github.com/stretchr/testify/assert"
 )
 
 func TestX509CertChain(t *testing.T) {
-	certs := []string{
+	certSrc := []string{
 		"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",
 	}
+	var c cert.Chain
+	for _, src := range certSrc {
+		_ = c.AddString(src)
+	}
 	for _, key := range []Key{
 		newRSAPrivateKey(),
 		newRSAPublicKey(),
@@ -25,53 +30,18 @@ func TestX509CertChain(t *testing.T) {
 	} {
 		key := key
 		t.Run("Set X509CertChainKey", func(t *testing.T) {
-			t.Run("string", func(t *testing.T) {
-				if !assert.NoError(t, key.Set(X509CertChainKey, certs[0]), "Set for x5c should succeed") {
-					return
-				}
-
-				gotcerts, ok := key.Get(X509CertChainKey)
-				if !assert.True(t, ok, "Get for x5c should succeed") {
-					return
-				}
-
-				if !assert.Len(t, gotcerts, 1, `should have 1 cert`) {
-					return
-				}
-			})
-			t.Run("[]string", func(t *testing.T) {
-				if !assert.NoError(t, key.Set(X509CertChainKey, certs), "Set for x5c should succeed") {
-					return
-				}
-
-				gotcerts, ok := key.Get(X509CertChainKey)
-				if !assert.True(t, ok, "Get for x5c should succeed") {
-					return
-				}
-
-				if !assert.Len(t, gotcerts, 3, `should have 3 certs`) {
-					return
-				}
-			})
-			t.Run("[]interface{} with string elements", func(t *testing.T) {
-				tmp := make([]interface{}, len(certs))
-				for i := 0; i < len(certs); i++ {
-					tmp[i] = certs[i]
-				}
-
-				if !assert.NoError(t, key.Set(X509CertChainKey, tmp), "Set for x5c should succeed") {
-					return
-				}
-
-				gotcerts, ok := key.Get(X509CertChainKey)
-				if !assert.True(t, ok, "Get for x5c should succeed") {
-					return
-				}
+			if !assert.NoError(t, key.Set(X509CertChainKey, &c), "Set for x5c should succeed") {
+				return
+			}
 
-				if !assert.Len(t, gotcerts, 3, `should have 3 certs`) {
-					return
-				}
-			})
+			v, ok := key.Get(X509CertChainKey)
+			if !assert.True(t, ok, "Get for x5c should succeed") {
+				return
+			}
+			gotcerts := v.(*cert.Chain)
+			if !assert.Equal(t, gotcerts.Len(), 3, `should have 3 cert`) {
+				return
+			}
 		})
 	}
 }
diff --git a/jwk/jwk_test.go b/jwk/jwk_test.go
index 9ec12d3a..3ebba810 100644
--- a/jwk/jwk_test.go
+++ b/jwk/jwk_test.go
@@ -19,6 +19,7 @@ import (
 	"testing"
 	"time"
 
+	"github.com/lestrrat-go/jwx/v2/cert"
 	"github.com/lestrrat-go/jwx/v2/internal/ecutil"
 	"github.com/lestrrat-go/jwx/v2/internal/jose"
 	"github.com/lestrrat-go/jwx/v2/internal/json"
@@ -32,6 +33,7 @@ import (
 )
 
 var zeroval reflect.Value
+var certChain *cert.Chain
 var certChainSrc = []string{
 	"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",
@@ -47,8 +49,10 @@ type keyDef struct {
 var commonDef map[string]keyDef
 
 func init() {
-	var certChain jwk.CertificateChain
-	certChain.Accept(certChainSrc)
+	certChain = &cert.Chain{}
+	for _, src := range certChainSrc {
+		_ = certChain.AddString(src)
+	}
 
 	commonDef = map[string]keyDef{
 		jwk.AlgorithmKey: {
@@ -79,8 +83,8 @@ func init() {
 		},
 		jwk.X509CertChainKey: {
 			Method:   "X509CertChain",
-			Value:    certChainSrc,
-			Expected: certChain.Get(),
+			Value:    certChain,
+			Expected: certChain,
 		},
 		jwk.X509CertThumbprintKey: {
 			Value:  "x5t blah",
diff --git a/jwk/x5c_test.go b/jwk/x5c_test.go
index ade119ca..d367c47a 100644
--- a/jwk/x5c_test.go
+++ b/jwk/x5c_test.go
@@ -3,9 +3,9 @@ package jwk_test
 import (
 	"testing"
 
+	"github.com/lestrrat-go/jwx/v2/cert"
 	"github.com/lestrrat-go/jwx/v2/internal/json"
 
-	"github.com/lestrrat-go/jwx/v2/jwk"
 	"github.com/stretchr/testify/assert"
 )
 
@@ -17,52 +17,33 @@ func Test_X5CHeader(t *testing.T) {
 	}
 
 	t.Run("Marshal/Unmarshal", func(t *testing.T) {
-		// The input contains padding. We can accept either as input, but only emit
-		// strings encoded with padding
-		certsNopad := make([]string, len(certs))
-		for i, cert := range certs {
-			for len(cert) > 0 && cert[len(cert)-1] == '=' {
-				cert = cert[:len(cert)-1]
-			}
-			certsNopad[i] = cert
-		}
-
 		expected, err := json.Marshal(certs)
 		if !assert.NoError(t, err, `json.Marshal should succeed`) {
 			return
 		}
 
-		inputs := map[string][]string{
-			"with padding":    certs,
-			"without padding": certsNopad,
+		// Take the input, and create a json
+		jsonbuf, err := json.Marshal(certs)
+		if !assert.NoError(t, err, `json.Marshal should succeed (for input)`) {
+			return
 		}
-		for k, input := range inputs {
-			input := input
-			t.Run(k, func(t *testing.T) {
-				// Take the input, and create a json
-				jsonbuf, err := json.Marshal(input)
-				if !assert.NoError(t, err, `json.Marshal should succeed (for input)`) {
-					return
-				}
 
-				var c jwk.CertificateChain
-				if !assert.NoError(t, json.Unmarshal(jsonbuf, &c), `json.Unmarshal should succeed`) {
-					return
-				}
+		var c cert.Chain
+		if !assert.NoError(t, json.Unmarshal(jsonbuf, &c), `json.Unmarshal should succeed`) {
+			return
+		}
 
-				if !assert.Len(t, c.Get(), 3, `should have three certs`) {
-					return
-				}
+		if !assert.Equal(t, c.Len(), 3, `should have three certs`) {
+			return
+		}
 
-				buf, err := json.Marshal(c)
-				if !assert.NoError(t, err, `json.Marshal should succeed`) {
-					return
-				}
+		buf, err := json.Marshal(c)
+		if !assert.NoError(t, err, `json.Marshal should succeed`) {
+			return
+		}
 
-				if !assert.Equal(t, expected, buf, `json output should match`) {
-					return
-				}
-			})
+		if !assert.Equal(t, expected, buf, `json output should match`) {
+			return
 		}
 	})
 }
diff --git a/jws/headers_test.go b/jws/headers_test.go
index bf4a4b4a..6c268ee7 100644
--- a/jws/headers_test.go
+++ b/jws/headers_test.go
@@ -5,6 +5,7 @@ import (
 	"reflect"
 	"testing"
 
+	"github.com/lestrrat-go/jwx/v2/cert"
 	"github.com/lestrrat-go/jwx/v2/jwa"
 	"github.com/lestrrat-go/jwx/v2/jwk"
 	"github.com/lestrrat-go/jwx/v2/jws"
@@ -23,11 +24,11 @@ func TestHeader(t *testing.T) {
 	if !assert.NoError(t, err, `jwk.ParseKey should succeed`) {
 		return
 	}
-	certChain := []string{
-		"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",
-	}
+
+	var chain cert.Chain
+	_ = chain.AddString("MIIE3jCCA8agAwIBAgICAwEwDQYJKoZIhvcNAQEFBQAwYzELMAkGA1UEBhMCVVMxITAfBgNVBAoTGFRoZSBHbyBEYWRkeSBHcm91cCwgSW5jLjExMC8GA1UECxMoR28gRGFkZHkgQ2xhc3MgMiBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAeFw0wNjExMTYwMTU0MzdaFw0yNjExMTYwMTU0MzdaMIHKMQswCQYDVQQGEwJVUzEQMA4GA1UECBMHQXJpem9uYTETMBEGA1UEBxMKU2NvdHRzZGFsZTEaMBgGA1UEChMRR29EYWRkeS5jb20sIEluYy4xMzAxBgNVBAsTKmh0dHA6Ly9jZXJ0aWZpY2F0ZXMuZ29kYWRkeS5jb20vcmVwb3NpdG9yeTEwMC4GA1UEAxMnR28gRGFkZHkgU2VjdXJlIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MREwDwYDVQQFEwgwNzk2OTI4NzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMQt1RWMnCZM7DI161+4WQFapmGBWTtwY6vj3D3HKrjJM9N55DrtPDAjhI6zMBS2sofDPZVUBJ7fmd0LJR4h3mUpfjWoqVTr9vcyOdQmVZWt7/v+WIbXnvQAjYwqDL1CBM6nPwT27oDyqu9SoWlm2r4arV3aLGbqGmu75RpRSgAvSMeYddi5Kcju+GZtCpyz8/x4fKL4o/K1w/O5epHBp+YlLpyo7RJlbmr2EkRTcDCVw5wrWCs9CHRK8r5RsL+H0EwnWGu1NcWdrxcx+AuP7q2BNgWJCJjPOq8lh8BJ6qf9Z/dFjpfMFDniNoW1fho3/Rb2cRGadDAW/hOUoz+EDU8CAwEAAaOCATIwggEuMB0GA1UdDgQWBBT9rGEyk2xF1uLuhV+auud2mWjM5zAfBgNVHSMEGDAWgBTSxLDSkdRMEXGzYcs9of7dqGrU4zASBgNVHRMBAf8ECDAGAQH/AgEAMDMGCCsGAQUFBwEBBCcwJTAjBggrBgEFBQcwAYYXaHR0cDovL29jc3AuZ29kYWRkeS5jb20wRgYDVR0fBD8wPTA7oDmgN4Y1aHR0cDovL2NlcnRpZmljYXRlcy5nb2RhZGR5LmNvbS9yZXBvc2l0b3J5L2dkcm9vdC5jcmwwSwYDVR0gBEQwQjBABgRVHSAAMDgwNgYIKwYBBQUHAgEWKmh0dHA6Ly9jZXJ0aWZpY2F0ZXMuZ29kYWRkeS5jb20vcmVwb3NpdG9yeTAOBgNVHQ8BAf8EBAMCAQYwDQYJKoZIhvcNAQEFBQADggEBANKGwOy9+aG2Z+5mC6IGOgRQjhVyrEp0lVPLN8tESe8HkGsz2ZbwlFalEzAFPIUyIXvJxwqoJKSQ3kbTJSMUA2fCENZvD117esyfxVgqwcSeIaha86ykRvOe5GPLL5CkKSkB2XIsKd83ASe8T+5o0yGPwLPk9Qnt0hCqU7S+8MxZC9Y7lhyVJEnfzuz9p0iRFEUOOjZv2kWzRaJBydTXRE4+uXR21aITVSzGh6O1mawGhId/dQb8vxRMDsxuxN89txJx9OjxUUAiKEngHUuHqDTMBqLdElrRhjZkAzVvb3du6/KFUJheqwNTrZEjYx8WnM25sgVjOuH0aBsXBTWVU+4=")
+	_ = chain.AddString("MIIE+zCCBGSgAwIBAgICAQ0wDQYJKoZIhvcNAQEFBQAwgbsxJDAiBgNVBAcTG1ZhbGlDZXJ0IFZhbGlkYXRpb24gTmV0d29yazEXMBUGA1UEChMOVmFsaUNlcnQsIEluYy4xNTAzBgNVBAsTLFZhbGlDZXJ0IENsYXNzIDIgUG9saWN5IFZhbGlkYXRpb24gQXV0aG9yaXR5MSEwHwYDVQQDExhodHRwOi8vd3d3LnZhbGljZXJ0LmNvbS8xIDAeBgkqhkiG9w0BCQEWEWluZm9AdmFsaWNlcnQuY29tMB4XDTA0MDYyOTE3MDYyMFoXDTI0MDYyOTE3MDYyMFowYzELMAkGA1UEBhMCVVMxITAfBgNVBAoTGFRoZSBHbyBEYWRkeSBHcm91cCwgSW5jLjExMC8GA1UECxMoR28gRGFkZHkgQ2xhc3MgMiBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTCCASAwDQYJKoZIhvcNAQEBBQADggENADCCAQgCggEBAN6d1+pXGEmhW+vXX0iG6r7d/+TvZxz0ZWizV3GgXne77ZtJ6XCAPVYYYwhv2vLM0D9/AlQiVBDYsoHUwHU9S3/Hd8M+eKsaA7Ugay9qK7HFiH7Eux6wwdhFJ2+qN1j3hybX2C32qRe3H3I2TqYXP2WYktsqbl2i/ojgC95/5Y0V4evLOtXiEqITLdiOr18SPaAIBQi2XKVlOARFmR6jYGB0xUGlcmIbYsUfb18aQr4CUWWoriMYavx4A6lNf4DD+qta/KFApMoZFv6yyO9ecw3ud72a9nmYvLEHZ6IVDd2gWMZEewo+YihfukEHU1jPEX44dMX4/7VpkI+EdOqXG68CAQOjggHhMIIB3TAdBgNVHQ4EFgQU0sSw0pHUTBFxs2HLPaH+3ahq1OMwgdIGA1UdIwSByjCBx6GBwaSBvjCBuzEkMCIGA1UEBxMbVmFsaUNlcnQgVmFsaWRhdGlvbiBOZXR3b3JrMRcwFQYDVQQKEw5WYWxpQ2VydCwgSW5jLjE1MDMGA1UECxMsVmFsaUNlcnQgQ2xhc3MgMiBQb2xpY3kgVmFsaWRhdGlvbiBBdXRob3JpdHkxITAfBgNVBAMTGGh0dHA6Ly93d3cudmFsaWNlcnQuY29tLzEgMB4GCSqGSIb3DQEJARYRaW5mb0B2YWxpY2VydC5jb22CAQEwDwYDVR0TAQH/BAUwAwEB/zAzBggrBgEFBQcBAQQnMCUwIwYIKwYBBQUHMAGGF2h0dHA6Ly9vY3NwLmdvZGFkZHkuY29tMEQGA1UdHwQ9MDswOaA3oDWGM2h0dHA6Ly9jZXJ0aWZpY2F0ZXMuZ29kYWRkeS5jb20vcmVwb3NpdG9yeS9yb290LmNybDBLBgNVHSAERDBCMEAGBFUdIAAwODA2BggrBgEFBQcCARYqaHR0cDovL2NlcnRpZmljYXRlcy5nb2RhZGR5LmNvbS9yZXBvc2l0b3J5MA4GA1UdDwEB/wQEAwIBBjANBgkqhkiG9w0BAQUFAAOBgQC1QPmnHfbq/qQaQlpE9xXUhUaJwL6e4+PrxeNYiY+Sn1eocSxI0YGyeR+sBjUZsE4OWBsUs5iB0QQeyAfJg594RAoYC5jcdnplDQ1tgMQLARzLrUc+cb53S8wGd9D0VmsfSxOaFIqII6hR8INMqzW/Rn453HWkrugp++85j09VZw==")
+	_ = chain.AddString("MIIC5zCCAlACAQEwDQYJKoZIhvcNAQEFBQAwgbsxJDAiBgNVBAcTG1ZhbGlDZXJ0IFZhbGlkYXRpb24gTmV0d29yazEXMBUGA1UEChMOVmFsaUNlcnQsIEluYy4xNTAzBgNVBAsTLFZhbGlDZXJ0IENsYXNzIDIgUG9saWN5IFZhbGlkYXRpb24gQXV0aG9yaXR5MSEwHwYDVQQDExhodHRwOi8vd3d3LnZhbGljZXJ0LmNvbS8xIDAeBgkqhkiG9w0BCQEWEWluZm9AdmFsaWNlcnQuY29tMB4XDTk5MDYyNjAwMTk1NFoXDTE5MDYyNjAwMTk1NFowgbsxJDAiBgNVBAcTG1ZhbGlDZXJ0IFZhbGlkYXRpb24gTmV0d29yazEXMBUGA1UEChMOVmFsaUNlcnQsIEluYy4xNTAzBgNVBAsTLFZhbGlDZXJ0IENsYXNzIDIgUG9saWN5IFZhbGlkYXRpb24gQXV0aG9yaXR5MSEwHwYDVQQDExhodHRwOi8vd3d3LnZhbGljZXJ0LmNvbS8xIDAeBgkqhkiG9w0BCQEWEWluZm9AdmFsaWNlcnQuY29tMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDOOnHK5avIWZJV16vYdA757tn2VUdZZUcOBVXc65g2PFxTXdMwzzjsvUGJ7SVCCSRrCl6zfN1SLUzm1NZ9WlmpZdRJEy0kTRxQb7XBhVQ7/nHk01xC+YDgkRoKWzk2Z/M/VXwbP7RfZHM047QSv4dk+NoS/zcnwbNDu+97bi5p9wIDAQABMA0GCSqGSIb3DQEBBQUAA4GBADt/UG9vUJSZSWI4OB9L+KXIPqeCgfYrx+jFzug6EILLGACOTb2oWH+heQC1u+mNr0HZDzTuIYEZoDJJKPTEjlbVUjP9UNV+mWwD5MlM/Mtsq2azSiGM5bUMMj4QssxsodyamEwCW/POuZ6lcg5Ktz885hZo+L7tdEy8W9ViH0Pd")
 
 	data := []struct {
 		Key      string
@@ -72,7 +73,7 @@ func TestHeader(t *testing.T) {
 		},
 		{
 			Key:    jws.X509CertChainKey,
-			Value:  certChain,
+			Value:  &chain,
 			Method: "X509CertChain",
 		},
 		{