diff --git a/pkg/pki/x509/testutils/cert_test_utils.go b/pkg/pki/x509/testutils/cert_test_utils.go
new file mode 100644
index 0000000000000000000000000000000000000000..827505c21bace41019c316d5218b15a65498e8a4
--- /dev/null
+++ b/pkg/pki/x509/testutils/cert_test_utils.go
@@ -0,0 +1,146 @@
+// Copyright 2022 The Sigstore Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package testutils
+
+import (
+	"crypto"
+	"crypto/ecdsa"
+	"crypto/elliptic"
+	"crypto/rand"
+	"crypto/x509"
+	"crypto/x509/pkix"
+	"encoding/asn1"
+	"math/big"
+	"time"
+)
+
+/*
+To use:
+
+rootCert, rootKey, _ := GenerateRootCa()
+subCert, subKey, _ := GenerateSubordinateCa(rootCert, rootKey)
+leafCert, _, _ := GenerateLeafCert("subject", "oidc-issuer", subCert, subKey)
+
+roots := x509.NewCertPool()
+subs := x509.NewCertPool()
+roots.AddCert(rootCert)
+subs.AddCert(subCert)
+opts := x509.VerifyOptions{
+	Roots:         roots,
+	Intermediates: subs,
+	KeyUsages: []x509.ExtKeyUsage{
+		x509.ExtKeyUsageCodeSigning,
+	},
+}
+_, err := leafCert.Verify(opts)
+*/
+
+func createCertificate(template *x509.Certificate, parent *x509.Certificate, pub interface{}, priv crypto.Signer) (*x509.Certificate, error) {
+	certBytes, err := x509.CreateCertificate(rand.Reader, template, parent, pub, priv)
+	if err != nil {
+		return nil, err
+	}
+
+	cert, err := x509.ParseCertificate(certBytes)
+	if err != nil {
+		return nil, err
+	}
+	return cert, nil
+}
+
+func GenerateRootCa() (*x509.Certificate, *ecdsa.PrivateKey, error) {
+	rootTemplate := &x509.Certificate{
+		SerialNumber: big.NewInt(1),
+		Subject: pkix.Name{
+			CommonName:   "sigstore",
+			Organization: []string{"sigstore.dev"},
+		},
+		NotBefore:             time.Now().Add(-5 * time.Minute),
+		NotAfter:              time.Now().Add(5 * time.Hour),
+		KeyUsage:              x509.KeyUsageCertSign | x509.KeyUsageCRLSign,
+		BasicConstraintsValid: true,
+		IsCA:                  true,
+	}
+
+	priv, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	cert, err := createCertificate(rootTemplate, rootTemplate, &priv.PublicKey, priv)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	return cert, priv, nil
+}
+
+func GenerateSubordinateCa(rootTemplate *x509.Certificate, rootPriv crypto.Signer) (*x509.Certificate, *ecdsa.PrivateKey, error) {
+	subTemplate := &x509.Certificate{
+		SerialNumber: big.NewInt(1),
+		Subject: pkix.Name{
+			CommonName:   "sigstore-sub",
+			Organization: []string{"sigstore.dev"},
+		},
+		NotBefore:             time.Now().Add(-2 * time.Minute),
+		NotAfter:              time.Now().Add(2 * time.Hour),
+		KeyUsage:              x509.KeyUsageCertSign | x509.KeyUsageCRLSign,
+		ExtKeyUsage:           []x509.ExtKeyUsage{x509.ExtKeyUsageCodeSigning},
+		BasicConstraintsValid: true,
+		IsCA:                  true,
+	}
+
+	priv, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	cert, err := createCertificate(subTemplate, rootTemplate, &priv.PublicKey, rootPriv)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	return cert, priv, nil
+}
+
+func GenerateLeafCert(subject string, oidcIssuer string, parentTemplate *x509.Certificate, parentPriv crypto.Signer) (*x509.Certificate, *ecdsa.PrivateKey, error) {
+	certTemplate := &x509.Certificate{
+		SerialNumber:   big.NewInt(1),
+		EmailAddresses: []string{subject},
+		NotBefore:      time.Now().Add(-1 * time.Minute),
+		NotAfter:       time.Now().Add(time.Hour),
+		KeyUsage:       x509.KeyUsageDigitalSignature,
+		ExtKeyUsage:    []x509.ExtKeyUsage{x509.ExtKeyUsageCodeSigning},
+		IsCA:           false,
+		ExtraExtensions: []pkix.Extension{{
+			// OID for OIDC Issuer extension
+			Id:       asn1.ObjectIdentifier{1, 3, 6, 1, 4, 1, 57264, 1, 1},
+			Critical: false,
+			Value:    []byte(oidcIssuer),
+		}},
+	}
+
+	priv, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	cert, err := createCertificate(certTemplate, parentTemplate, &priv.PublicKey, parentPriv)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	return cert, priv, nil
+}
diff --git a/pkg/pki/x509/x509.go b/pkg/pki/x509/x509.go
index f12c0814f225600cc072e330b90f3528924a03c8..a92d495d0ac79380926f5a6e6c8afcbb10a7d25e 100644
--- a/pkg/pki/x509/x509.go
+++ b/pkg/pki/x509/x509.go
@@ -69,7 +69,17 @@ func (s Signature) Verify(r io.Reader, k interface{}, opts ...sigsig.VerifyOptio
 
 	p := key.key
 	if p == nil {
-		p = key.cert.c.PublicKey
+		switch {
+		case key.cert != nil:
+			p = key.cert.c.PublicKey
+		case len(key.certs) > 0:
+			if err := verifyCertChain(key.certs); err != nil {
+				return err
+			}
+			p = key.certs[0].PublicKey
+		default:
+			return errors.New("no public key found")
+		}
 	}
 
 	verifier, err := sigsig.LoadVerifier(p, crypto.SHA256)
@@ -81,8 +91,9 @@ func (s Signature) Verify(r io.Reader, k interface{}, opts ...sigsig.VerifyOptio
 
 // PublicKey Public Key that follows the x509 standard
 type PublicKey struct {
-	key  interface{}
-	cert *cert
+	key   interface{}
+	cert  *cert
+	certs []*x509.Certificate
 }
 
 type cert struct {
@@ -97,11 +108,20 @@ func NewPublicKey(r io.Reader) (*PublicKey, error) {
 		return nil, err
 	}
 
-	block, _ := pem.Decode(rawPub)
+	block, rest := pem.Decode(rawPub)
 	if block == nil {
 		return nil, errors.New("invalid public key: failure decoding PEM")
 	}
 
+	// Handle certificate chain, concatenated PEM-encoded certificates
+	if len(rest) > 0 {
+		certs, err := cryptoutils.UnmarshalCertificatesFromPEM(rawPub)
+		if err != nil {
+			return nil, err
+		}
+		return &PublicKey{certs: certs}, nil
+	}
+
 	switch block.Type {
 	case string(cryptoutils.PublicKeyPEMType):
 		key, err := x509.ParsePKIXPublicKey(block.Bytes)
@@ -131,6 +151,8 @@ func (k PublicKey) CanonicalValue() (encoded []byte, err error) {
 		encoded, err = cryptoutils.MarshalPublicKeyToPEM(k.key)
 	case k.cert != nil:
 		encoded, err = cryptoutils.MarshalCertificateToPEM(k.cert.c)
+	case k.certs != nil:
+		encoded, err = cryptoutils.MarshalCertificatesToPEM(k.certs)
 	default:
 		err = fmt.Errorf("x509 public key has not been initialized")
 	}
@@ -142,14 +164,23 @@ func (k PublicKey) CryptoPubKey() crypto.PublicKey {
 	if k.cert != nil {
 		return k.cert.c.PublicKey
 	}
+	if len(k.certs) > 0 {
+		return k.certs[0].PublicKey
+	}
 	return k.key
 }
 
 // EmailAddresses implements the pki.PublicKey interface
 func (k PublicKey) EmailAddresses() []string {
 	var names []string
+	var cert *x509.Certificate
 	if k.cert != nil {
-		for _, name := range k.cert.c.EmailAddresses {
+		cert = k.cert.c
+	} else if len(k.certs) > 0 {
+		cert = k.certs[0]
+	}
+	if cert != nil {
+		for _, name := range cert.EmailAddresses {
 			validate := validator.New()
 			errs := validate.Var(name, "required,email")
 			if errs == nil {
@@ -160,6 +191,31 @@ func (k PublicKey) EmailAddresses() []string {
 	return names
 }
 
+func verifyCertChain(certChain []*x509.Certificate) error {
+	if len(certChain) == 0 {
+		return errors.New("no certificate chain provided")
+	}
+	// No certificate chain to verify
+	if len(certChain) == 1 {
+		return nil
+	}
+	rootPool := x509.NewCertPool()
+	rootPool.AddCert(certChain[len(certChain)-1])
+	subPool := x509.NewCertPool()
+	for _, c := range certChain[1 : len(certChain)-1] {
+		subPool.AddCert(c)
+	}
+	if _, err := certChain[0].Verify(x509.VerifyOptions{
+		Roots:         rootPool,
+		Intermediates: subPool,
+		// Allow any key usage
+		KeyUsages: []x509.ExtKeyUsage{x509.ExtKeyUsageAny},
+	}); err != nil {
+		return err
+	}
+	return nil
+}
+
 func CertChainToPEM(certChain []*x509.Certificate) ([]byte, error) {
 	var pemBytes bytes.Buffer
 	for _, cert := range certChain {
diff --git a/pkg/pki/x509/x509_test.go b/pkg/pki/x509/x509_test.go
index c64dcb547ac0dca04df5a2b8a23e948fe372c646..bb1f9ffd315a818ae599afd7074cece9f3effa53 100644
--- a/pkg/pki/x509/x509_test.go
+++ b/pkg/pki/x509/x509_test.go
@@ -19,10 +19,13 @@ import (
 	"bytes"
 	"context"
 	"crypto"
+	"crypto/ecdsa"
 	"crypto/x509"
+	"reflect"
 	"strings"
 	"testing"
 
+	"github.com/sigstore/rekor/pkg/pki/x509/testutils"
 	"github.com/sigstore/rekor/pkg/signer"
 	"github.com/sigstore/sigstore/pkg/cryptoutils"
 	"github.com/sigstore/sigstore/pkg/signature"
@@ -242,3 +245,80 @@ func TestCertChain_Verify(t *testing.T) {
 		}
 	}
 }
+
+func TestPublicKeyWithCertChain(t *testing.T) {
+	rootCert, rootKey, _ := testutils.GenerateRootCa()
+	subCert, subKey, _ := testutils.GenerateSubordinateCa(rootCert, rootKey)
+	leafCert, leafKey, _ := testutils.GenerateLeafCert("subject@example.com", "oidc-issuer", subCert, subKey)
+
+	pemCertChain, err := cryptoutils.MarshalCertificatesToPEM([]*x509.Certificate{leafCert, subCert, rootCert})
+	if err != nil {
+		t.Fatalf("unexpected error marshalling cert chain: %v", err)
+	}
+
+	pub, err := NewPublicKey(bytes.NewReader(pemCertChain))
+	if err != nil {
+		t.Fatalf("unexpected error generating public key: %v", err)
+	}
+	if pub.certs == nil || !pub.certs[0].Equal(leafCert) || !pub.certs[1].Equal(subCert) || !pub.certs[2].Equal(rootCert) {
+		t.Fatal("expected certificate chain to match provided certificate chain")
+	}
+
+	if !pub.CryptoPubKey().(*ecdsa.PublicKey).Equal(leafKey.Public()) {
+		t.Fatal("expected public keys to match")
+	}
+
+	if !reflect.DeepEqual(pub.EmailAddresses(), leafCert.EmailAddresses) {
+		t.Fatalf("expected matching email addresses, expected %v, got %v", leafCert.EmailAddresses, pub.EmailAddresses())
+	}
+
+	canonicalValue, err := pub.CanonicalValue()
+	if err != nil {
+		t.Fatalf("unexpected error fetching canonical value: %v", err)
+	}
+	if !reflect.DeepEqual(canonicalValue, pemCertChain) {
+		t.Fatalf("expected canonical value %v, got %v", pemCertChain, canonicalValue)
+	}
+
+	// Generate signature to verify
+	data := []byte("test")
+	signer, err := signature.LoadSigner(leafKey, crypto.SHA256)
+	if err != nil {
+		t.Fatal(err)
+	}
+	sigBytes, err := signer.SignMessage(bytes.NewReader(data))
+	if err != nil {
+		t.Fatal(err)
+	}
+	s, err := NewSignature(bytes.NewReader(sigBytes))
+	if err != nil {
+		t.Fatalf("unexpected error generating signature: %v", err)
+	}
+	err = s.Verify(bytes.NewReader(data), pub)
+	if err != nil {
+		t.Fatalf("unexpected error verifying signature, %v", err)
+	}
+
+	// Verify error with invalid chain
+	pemCertChain, _ = cryptoutils.MarshalCertificatesToPEM([]*x509.Certificate{leafCert, rootCert})
+	pub, _ = NewPublicKey(bytes.NewReader(pemCertChain))
+	signer, _ = signature.LoadSigner(leafKey, crypto.SHA256)
+	sigBytes, _ = signer.SignMessage(bytes.NewReader(data))
+	s, _ = NewSignature(bytes.NewReader(sigBytes))
+	err = s.Verify(bytes.NewReader(data), pub)
+	if err == nil || !strings.Contains(err.Error(), "x509: certificate signed by unknown authority") {
+		t.Fatalf("expected error verifying signature, got %v", err)
+	}
+
+	// Verify works with chain without intermediate
+	leafCert, leafKey, _ = testutils.GenerateLeafCert("subject@example.com", "oidc-issuer", rootCert, rootKey)
+	pemCertChain, _ = cryptoutils.MarshalCertificatesToPEM([]*x509.Certificate{leafCert, rootCert})
+	pub, _ = NewPublicKey(bytes.NewReader(pemCertChain))
+	signer, _ = signature.LoadSigner(leafKey, crypto.SHA256)
+	sigBytes, _ = signer.SignMessage(bytes.NewReader(data))
+	s, _ = NewSignature(bytes.NewReader(sigBytes))
+	err = s.Verify(bytes.NewReader(data), pub)
+	if err != nil {
+		t.Fatalf("unexpected error verifying signature, %v", err)
+	}
+}