diff --git a/ctlog.go b/ctlog.go
index 143eeeec..7c570212 100644
--- a/ctlog.go
+++ b/ctlog.go
@@ -17,14 +17,14 @@ package sctfe
 import (
 	"context"
 	"crypto"
+	"crypto/x509"
+	"encoding/asn1"
 	"errors"
 	"fmt"
 	"net/http"
 	"strings"
 	"time"
 
-	"github.com/google/certificate-transparency-go/asn1"
-	"github.com/google/certificate-transparency-go/x509"
 	"github.com/transparency-dev/static-ct/internal/scti"
 	"github.com/transparency-dev/static-ct/internal/x509util"
 	"github.com/transparency-dev/static-ct/storage"
diff --git a/internal/scti/chain_validation.go b/internal/scti/chain_validation.go
index 9c315da3..a0b168c5 100644
--- a/internal/scti/chain_validation.go
+++ b/internal/scti/chain_validation.go
@@ -16,14 +16,15 @@ package scti
 
 import (
 	"bytes"
+	"crypto/x509"
+	"encoding/asn1"
 	"errors"
 	"fmt"
 	"strconv"
 	"strings"
 	"time"
 
-	"github.com/google/certificate-transparency-go/asn1"
-	"github.com/google/certificate-transparency-go/x509"
+	"github.com/transparency-dev/static-ct/internal/types"
 	"github.com/transparency-dev/static-ct/internal/x509util"
 	"k8s.io/klog/v2"
 )
@@ -127,7 +128,7 @@ func NewChainValidationOpts(trustedRoots *x509util.PEMCertPool, rejectExpired, r
 // by the spec.
 func isPrecertificate(cert *x509.Certificate) (bool, error) {
 	for _, ext := range cert.Extensions {
-		if x509.OIDExtensionCTPoison.Equal(ext.Id) {
+		if types.OIDExtensionCTPoison.Equal(ext.Id) {
 			if !ext.Critical || !bytes.Equal(asn1.NullBytes, ext.Value) {
 				return false, fmt.Errorf("CT poison ext is not critical or invalid: %v", ext)
 			}
@@ -139,6 +140,66 @@ func isPrecertificate(cert *x509.Certificate) (bool, error) {
 	return false, nil
 }
 
+// getLaxVerifiedChain returns a verified certificate chain, allowing for specific
+// errors that are commonly raised with certificates submitted to CT logs.
+//
+// Allowed x509 errors:
+//
+//   - UnhandledCriticalExtension: Precertificates have the poison extension
+//     which the Go library code does not recognize; also the Go library code
+//     does not support the standard PolicyConstraints extension (which is
+//     required to be marked critical, RFC 5280 s4.2.1.11)
+//   - Expired: CT logs should be able to log expired certificates.
+//   - IncompatibleUsage: Pre-issued precertificates have the Certificate
+//     Transparency EKU, which intermediates don't have. Also some leaves have
+//     unknown EKUs that should not be bounced just because the intermediate
+//     does not also have them (cf.  https://github.com/golang/go/issues/24590)
+//     so disable EKU checks inside the x509 library, but we've already done our
+//     own check on the leaf above.
+//   - NoValidChains: Do no enforce policy validation.
+//   - TooManyIntermediates: path length checks get confused by the presence of
+//     an additional pre-issuer intermediate.
+//   - CANotAuthorizedForThisName: allow to log all certificates, even if they
+//     have been isued by a CA trhat is not auhotized to issue certs for a
+//     given domain.
+//
+// TODO(phboneff): this doesn't work because, as it should, cert.Verify()
+// does not return a chain when it raises an error.
+func getLaxVerifiedChain(cert *x509.Certificate, opts x509.VerifyOptions) ([][]*x509.Certificate, error) {
+	chains, err := cert.Verify(opts)
+	switch err.(type) {
+	// TODO(phboneff): check if we could make the x509 library aware of the CT
+	// poison.
+	// TODO(phboneff): re-evaluate whether PolicyConstraints is still an issue.
+	case x509.UnhandledCriticalExtension:
+		return chains, nil
+	case x509.CertificateInvalidError:
+		if e, ok := err.(x509.CertificateInvalidError); ok {
+			switch e.Reason {
+			// TODO(phboneff): if need be, change time to make sure that the cert is
+			// never considered as expired.
+			// TODO(phboneff): see if TooManyIntermediates handling could be improved
+			// upstream.
+			// TODO(phboneff): see if it's necessary to log certs for which
+			// CANotAuthorizedForThisName is raised. If browsers all check this
+			// as well, then there is no need to log these certs.
+			case x509.Expired, x509.TooManyIntermediates, x509.CANotAuthorizedForThisName:
+				return chains, nil
+			// TODO(phboneff): check if we can remove these two exceptions.
+			// NoValidChains was not a thing back when x509 was forked in ctgo.
+			// New CT logs should all filter incoming certs with EKU, and
+			// https://github.com/golang/go/issues/24590 has been updated,
+			// so we should be able to remove IncompatibleUsage as well.
+			case x509.IncompatibleUsage, x509.NoValidChains:
+				return chains, nil
+			default:
+				return chains, err
+			}
+		}
+	}
+	return chains, err
+}
+
 // validateChain takes the certificate chain as it was parsed from a JSON request. Ensures all
 // elements in the chain decode as X.509 certificates. Ensures that there is a valid path from the
 // end entity certificate in the chain to a trusted root cert, possibly using the intermediates
@@ -152,8 +213,8 @@ func validateChain(rawChain [][]byte, validationOpts ChainValidationOpts) ([]*x5
 
 	for i, certBytes := range rawChain {
 		cert, err := x509.ParseCertificate(certBytes)
-		if x509.IsFatal(err) {
-			return nil, err
+		if err != nil {
+			return nil, fmt.Errorf("x509.ParseCertificate(): %v", err)
 		}
 
 		chain = append(chain, cert)
@@ -223,32 +284,16 @@ func validateChain(rawChain [][]byte, validationOpts ChainValidationOpts) ([]*x5
 		}
 	}
 
-	// We can now do the verification.  Use fairly lax options for verification, as
+	// We can now do the verification. Use fairly lax options for verification, as
 	// CT is intended to observe certificates rather than police them.
 	verifyOpts := x509.VerifyOptions{
-		Roots:             validationOpts.trustedRoots.CertPool(),
-		CurrentTime:       now,
-		Intermediates:     intermediatePool.CertPool(),
-		DisableTimeChecks: true,
-		// Precertificates have the poison extension; also the Go library code does not
-		// support the standard PolicyConstraints extension (which is required to be marked
-		// critical, RFC 5280 s4.2.1.11), so never check unhandled critical extensions.
-		DisableCriticalExtensionChecks: true,
-		// Pre-issued precertificates have the Certificate Transparency EKU; also some
-		// leaves have unknown EKUs that should not be bounced just because the intermediate
-		// does not also have them (cf. https://github.com/golang/go/issues/24590) so
-		// disable EKU checks inside the x509 library, but we've already done our own check
-		// on the leaf above.
-		DisableEKUChecks: true,
-		// Path length checks get confused by the presence of an additional
-		// pre-issuer intermediate, so disable them.
-		DisablePathLenChecks:        true,
-		DisableNameConstraintChecks: true,
-		DisableNameChecks:           false,
-		KeyUsages:                   validationOpts.extKeyUsages,
+		Roots:         validationOpts.trustedRoots.CertPool(),
+		CurrentTime:   now,
+		Intermediates: intermediatePool.CertPool(),
+		KeyUsages:     validationOpts.extKeyUsages,
 	}
 
-	verifiedChains, err := cert.Verify(verifyOpts)
+	verifiedChains, err := getLaxVerifiedChain(cert, verifyOpts)
 	if err != nil {
 		return nil, err
 	}
diff --git a/internal/scti/chain_validation_test.go b/internal/scti/chain_validation_test.go
index f1af6577..35cab51a 100644
--- a/internal/scti/chain_validation_test.go
+++ b/internal/scti/chain_validation_test.go
@@ -15,16 +15,18 @@
 package scti
 
 import (
+	"crypto/md5"
+	"crypto/x509"
+	"crypto/x509/pkix"
+	"encoding/asn1"
 	"encoding/base64"
 	"encoding/pem"
 	"strings"
 	"testing"
 	"time"
 
-	"github.com/google/certificate-transparency-go/asn1"
-	"github.com/google/certificate-transparency-go/x509"
-	"github.com/google/certificate-transparency-go/x509/pkix"
 	"github.com/transparency-dev/static-ct/internal/testdata"
+	"github.com/transparency-dev/static-ct/internal/types"
 	"github.com/transparency-dev/static-ct/internal/x509util"
 )
 
@@ -35,13 +37,13 @@ func wipeExtensions(cert *x509.Certificate) *x509.Certificate {
 
 func makePoisonNonCritical(cert *x509.Certificate) *x509.Certificate {
 	// Invalid as a pre-cert because poison extension needs to be marked as critical.
-	cert.Extensions = []pkix.Extension{{Id: x509.OIDExtensionCTPoison, Critical: false, Value: asn1.NullBytes}}
+	cert.Extensions = []pkix.Extension{{Id: types.OIDExtensionCTPoison, Critical: false, Value: asn1.NullBytes}}
 	return cert
 }
 
 func makePoisonNonNull(cert *x509.Certificate) *x509.Certificate {
 	// Invalid as a pre-cert because poison extension is not ASN.1 NULL value.
-	cert.Extensions = []pkix.Extension{{Id: x509.OIDExtensionCTPoison, Critical: false, Value: []byte{0x42, 0x42, 0x42}}}
+	cert.Extensions = []pkix.Extension{{Id: types.OIDExtensionCTPoison, Critical: false, Value: []byte{0x42, 0x42, 0x42}}}
 	return cert
 }
 
@@ -271,7 +273,7 @@ func TestValidateChain(t *testing.T) {
 			if len(gotPath) != test.wantPathLen {
 				t.Errorf("|ValidateChain()|=%d; want %d", len(gotPath), test.wantPathLen)
 				for _, c := range gotPath {
-					t.Logf("Subject: %s Issuer: %s", x509util.NameToString(c.Subject), x509util.NameToString(c.Issuer))
+					t.Logf("Subject: %s Issuer: %s", c.Subject, c.Issuer)
 				}
 			}
 		})
@@ -475,8 +477,8 @@ func pemToCert(t *testing.T, pemData string) *x509.Certificate {
 	}
 
 	cert, err := x509.ParseCertificate(bytes.Bytes)
-	if x509.IsFatal(err) {
-		t.Fatal(err)
+	if err != nil {
+		t.Fatalf("x509.ParseCertificate(): %v", err)
 	}
 
 	return cert
@@ -536,7 +538,7 @@ func TestCMPreIssuedCert(t *testing.T) {
 				t.Fatalf("failed to ValidateChain: %v", err)
 			}
 			for i, c := range chain {
-				t.Logf("chain[%d] = \n%s", i, x509util.CertificateToString(c))
+				t.Logf("chain[%d] = \n%s", i, md5.Sum(c.Raw))
 			}
 		})
 	}
diff --git a/internal/scti/ctlog.go b/internal/scti/ctlog.go
index b7dc7c7c..a54c1c61 100644
--- a/internal/scti/ctlog.go
+++ b/internal/scti/ctlog.go
@@ -4,10 +4,10 @@ import (
 	"context"
 	"crypto"
 	"crypto/ecdsa"
+	"crypto/x509"
 	"errors"
 	"fmt"
 
-	"github.com/google/certificate-transparency-go/x509"
 	"github.com/transparency-dev/static-ct/internal/types"
 	"github.com/transparency-dev/static-ct/modules/dedup"
 	"github.com/transparency-dev/static-ct/storage"
diff --git a/internal/scti/handlers.go b/internal/scti/handlers.go
index 25d58d00..e9b80fdd 100644
--- a/internal/scti/handlers.go
+++ b/internal/scti/handlers.go
@@ -17,6 +17,7 @@ package scti
 import (
 	"context"
 	"crypto/sha256"
+	"crypto/x509"
 	"encoding/base64"
 	"encoding/json"
 	"errors"
@@ -29,10 +30,10 @@ import (
 	"time"
 
 	"github.com/google/certificate-transparency-go/tls"
-	"github.com/google/certificate-transparency-go/x509"
 	"github.com/prometheus/client_golang/prometheus"
 	"github.com/prometheus/client_golang/prometheus/promauto"
 	"github.com/transparency-dev/static-ct/internal/types"
+	"github.com/transparency-dev/static-ct/internal/x509util"
 	"github.com/transparency-dev/static-ct/modules/dedup"
 	tessera "github.com/transparency-dev/trillian-tessera"
 	"github.com/transparency-dev/trillian-tessera/ctonly"
@@ -491,7 +492,7 @@ func entryFromChain(chain []*x509.Certificate, isPrecert bool, timestamp uint64)
 
 	// Next, post-process the DER-encoded TBSCertificate, to remove the CT poison
 	// extension and possibly update the issuer field.
-	defangedTBS, err := x509.BuildPrecertTBS(cert.RawTBSCertificate, preIssuer)
+	defangedTBS, err := x509util.BuildPrecertTBS(cert.RawTBSCertificate, preIssuer)
 	if err != nil {
 		return nil, fmt.Errorf("failed to remove poison extension: %v", err)
 	}
@@ -508,10 +509,9 @@ func entryFromChain(chain []*x509.Certificate, isPrecert bool, timestamp uint64)
 
 // isPreIssuer indicates whether a certificate is a pre-cert issuer with the specific
 // certificate transparency extended key usage.
-// copied form certificate-transparency-go/serialization.go
-func isPreIssuer(issuer *x509.Certificate) bool {
-	for _, eku := range issuer.ExtKeyUsage {
-		if eku == x509.ExtKeyUsageCertificateTransparency {
+func isPreIssuer(cert *x509.Certificate) bool {
+	for _, ext := range cert.Extensions {
+		if types.OIDExtKeyUsageCertificateTransparency.Equal(ext.Id) {
 			return true
 		}
 	}
diff --git a/internal/scti/handlers_test.go b/internal/scti/handlers_test.go
index 68a6c1f9..3d98f61f 100644
--- a/internal/scti/handlers_test.go
+++ b/internal/scti/handlers_test.go
@@ -19,6 +19,7 @@ import (
 	"bytes"
 	"context"
 	"crypto"
+	"crypto/x509"
 	"encoding/hex"
 	"encoding/json"
 	"fmt"
@@ -30,7 +31,6 @@ import (
 	"time"
 
 	"github.com/golang/mock/gomock"
-	"github.com/google/certificate-transparency-go/x509"
 	"github.com/google/go-cmp/cmp"
 	"github.com/google/go-cmp/cmp/cmpopts"
 	"github.com/transparency-dev/static-ct/internal/testdata"
diff --git a/internal/scti/requestlog.go b/internal/scti/requestlog.go
index 1d696115..291e5f9e 100644
--- a/internal/scti/requestlog.go
+++ b/internal/scti/requestlog.go
@@ -16,11 +16,10 @@ package scti
 
 import (
 	"context"
+	"crypto/x509"
 	"encoding/hex"
 	"time"
 
-	"github.com/google/certificate-transparency-go/x509"
-	"github.com/google/certificate-transparency-go/x509util"
 	"k8s.io/klog/v2"
 )
 
@@ -86,8 +85,8 @@ func (dlr *DefaultRequestLog) addDERToChain(_ context.Context, d []byte) {
 // certificate that is part of a submitted chain.
 func (dlr *DefaultRequestLog) addCertToChain(_ context.Context, cert *x509.Certificate) {
 	klog.V(vLevel).Infof("RL: Cert: Sub: %s Iss: %s notBef: %s notAft: %s",
-		x509util.NameToString(cert.Subject),
-		x509util.NameToString(cert.Issuer),
+		cert.Subject,
+		cert.Issuer,
 		cert.NotBefore.Format(time.RFC1123Z),
 		cert.NotAfter.Format(time.RFC1123Z))
 }
diff --git a/internal/scti/signatures.go b/internal/scti/signatures.go
index d9b78948..736c12c5 100644
--- a/internal/scti/signatures.go
+++ b/internal/scti/signatures.go
@@ -18,12 +18,12 @@ import (
 	"crypto"
 	"crypto/rand"
 	"crypto/sha256"
+	"crypto/x509"
 	"encoding/binary"
 	"fmt"
 	"time"
 
 	"github.com/google/certificate-transparency-go/tls"
-	"github.com/google/certificate-transparency-go/x509"
 	tfl "github.com/transparency-dev/formats/log"
 	"github.com/transparency-dev/static-ct/internal/types"
 	"golang.org/x/mod/sumdb/note"
diff --git a/internal/scti/signatures_test.go b/internal/scti/signatures_test.go
index 5b9105bb..f4feb8e8 100644
--- a/internal/scti/signatures_test.go
+++ b/internal/scti/signatures_test.go
@@ -18,13 +18,13 @@ import (
 	"bytes"
 	"crypto"
 	"crypto/sha256"
+	"crypto/x509"
 	"encoding/hex"
 	"encoding/pem"
 	"testing"
 	"time"
 
 	"github.com/google/certificate-transparency-go/tls"
-	"github.com/google/certificate-transparency-go/x509"
 	"github.com/kylelemons/godebug/pretty"
 	"github.com/transparency-dev/static-ct/internal/testdata"
 	"github.com/transparency-dev/static-ct/internal/types"
@@ -245,7 +245,7 @@ func TestSerializeV1STHSignatureKAT(t *testing.T) {
 
 func TestBuildV1MerkleTreeLeafForCert(t *testing.T) {
 	cert, err := x509util.CertificateFromPEM([]byte(testdata.LeafSignedByFakeIntermediateCertPEM))
-	if x509.IsFatal(err) {
+	if err != nil {
 		t.Fatalf("failed to set up test cert: %v", err)
 	}
 
@@ -308,7 +308,7 @@ func TestBuildV1MerkleTreeLeafForCert(t *testing.T) {
 
 func TestSignV1SCTForPrecertificate(t *testing.T) {
 	cert, err := x509util.CertificateFromPEM([]byte(testdata.PrecertPEMValid))
-	if x509.IsFatal(err) {
+	if err != nil {
 		t.Fatalf("failed to set up test precert: %v", err)
 	}
 
@@ -367,7 +367,7 @@ func TestSignV1SCTForPrecertificate(t *testing.T) {
 	if got, want := keyHash[:], leaf.TimestampedEntry.PrecertEntry.IssuerKeyHash[:]; !bytes.Equal(got, want) {
 		t.Fatalf("Issuer key hash bytes mismatch, got %v, expected %v", got, want)
 	}
-	defangedTBS, _ := x509.RemoveCTPoison(cert.RawTBSCertificate)
+	defangedTBS, _ := x509util.RemoveCTPoison(cert.RawTBSCertificate)
 	if got, want := leaf.TimestampedEntry.PrecertEntry.TBSCertificate, defangedTBS; !bytes.Equal(got, want) {
 		t.Fatalf("TBS cert mismatch, got %v, expected %v", got, want)
 	}
diff --git a/internal/types/rfc6962.go b/internal/types/rfc6962.go
index e89a4229..e86d0157 100644
--- a/internal/types/rfc6962.go
+++ b/internal/types/rfc6962.go
@@ -2,12 +2,13 @@ package types
 
 import (
 	"crypto/sha256"
+	"crypto/x509"
+	"encoding/asn1"
 	"encoding/base64"
 	"encoding/json"
 	"fmt"
 
 	"github.com/google/certificate-transparency-go/tls"
-	"github.com/google/certificate-transparency-go/x509"
 )
 
 ///////////////////////////////////////////////////////////////////////////////
@@ -43,6 +44,10 @@ const (
 	TreeNodePrefix = byte(0x01)
 )
 
+// OIDExtensionCTPoison is defined in RFC 6962 s3.1.
+var OIDExtensionCTPoison = asn1.ObjectIdentifier{1, 3, 6, 1, 4, 1, 11129, 2, 4, 3}
+var OIDExtKeyUsageCertificateTransparency = asn1.ObjectIdentifier{1, 3, 6, 1, 4, 1, 11129, 2, 4, 4}
+
 // MerkleLeafType represents the MerkleLeafType enum from section 3.4:
 //
 //	enum { timestamped_entry(0), (255) } MerkleLeafType;
@@ -398,19 +403,6 @@ func (m *MerkleTreeLeaf) X509Certificate() (*x509.Certificate, error) {
 	return x509.ParseCertificate(m.TimestampedEntry.X509Entry.Data)
 }
 
-// Precertificate returns the X.509 Precertificate contained within the MerkleTreeLeaf.
-//
-// The returned precertificate is embedded in an x509.Certificate, but is in the
-// form stored internally in the log rather than the original submitted form
-// (i.e. it does not include the poison extension and any changes to reflect the
-// final certificate's issuer have been made; see x509.BuildPrecertTBS).
-func (m *MerkleTreeLeaf) Precertificate() (*x509.Certificate, error) {
-	if m.TimestampedEntry.EntryType != PrecertLogEntryType {
-		return nil, fmt.Errorf("cannot call Precertificate on a MerkleTreeLeaf that is not a precert entry")
-	}
-	return x509.ParseTBSCertificate(m.TimestampedEntry.PrecertEntry.TBSCertificate)
-}
-
 // APIEndpoint is a string that represents one of the Certificate Transparency
 // Log API endpoints.
 type APIEndpoint string
diff --git a/internal/x509util/ct.go b/internal/x509util/ct.go
new file mode 100644
index 00000000..dcb0f31f
--- /dev/null
+++ b/internal/x509util/ct.go
@@ -0,0 +1,193 @@
+// Copyright 2024 Google LLC. All Rights Reserved.
+//
+// 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 x509util
+
+import (
+	"crypto/x509"
+	"crypto/x509/pkix"
+	"encoding/asn1"
+	"errors"
+	"fmt"
+	"math/big"
+	"time"
+)
+
+var (
+	oidExtensionAuthorityKeyId = asn1.ObjectIdentifier{2, 5, 29, 35}
+	// OIDExtensionCTPoison is defined in RFC 6962 s3.1.
+	oidExtensionCTPoison                        = asn1.ObjectIdentifier{1, 3, 6, 1, 4, 1, 11129, 2, 4, 3}
+	oidExtensionKeyUsageCertificateTransparency = asn1.ObjectIdentifier{1, 3, 6, 1, 4, 1, 11129, 2, 4, 4}
+)
+
+type tbsCertificate struct {
+	Raw                asn1.RawContent
+	Version            int `asn1:"optional,explicit,default:0,tag:0"`
+	SerialNumber       *big.Int
+	SignatureAlgorithm pkix.AlgorithmIdentifier
+	Issuer             asn1.RawValue
+	Validity           validity
+	Subject            asn1.RawValue
+	PublicKey          publicKeyInfo
+	UniqueId           asn1.BitString   `asn1:"optional,tag:1"`
+	SubjectUniqueId    asn1.BitString   `asn1:"optional,tag:2"`
+	Extensions         []pkix.Extension `asn1:"omitempty,optional,explicit,tag:3"`
+}
+
+type validity struct {
+	NotBefore, NotAfter time.Time
+}
+
+type publicKeyInfo struct {
+	Raw       asn1.RawContent
+	Algorithm pkix.AlgorithmIdentifier
+	PublicKey asn1.BitString
+}
+
+// removeExtension takes a DER-encoded TBSCertificate, removes the extension
+// specified by oid (preserving the order of other extensions), and returns the
+// result still as a DER-encoded TBSCertificate.  This function will fail if
+// there is not exactly 1 extension of the type specified by the oid present.
+func removeExtension(tbsData []byte, oid asn1.ObjectIdentifier) ([]byte, error) {
+	var tbs tbsCertificate
+	rest, err := asn1.Unmarshal(tbsData, &tbs)
+	if err != nil {
+		return nil, fmt.Errorf("failed to parse TBSCertificate: %v", err)
+	} else if rLen := len(rest); rLen > 0 {
+		return nil, fmt.Errorf("trailing data (%d bytes) after TBSCertificate", rLen)
+	}
+	extAt := -1
+	for i, ext := range tbs.Extensions {
+		if ext.Id.Equal(oid) {
+			if extAt != -1 {
+				return nil, errors.New("multiple extensions of specified type present")
+			}
+			extAt = i
+		}
+	}
+	if extAt == -1 {
+		return nil, errors.New("no extension of specified type present")
+	}
+	tbs.Extensions = append(tbs.Extensions[:extAt], tbs.Extensions[extAt+1:]...)
+	// Clear out the asn1.RawContent so the re-marshal operation sees the
+	// updated structure (rather than just copying the out-of-date DER data).
+	tbs.Raw = nil
+
+	data, err := asn1.Marshal(tbs)
+	if err != nil {
+		return nil, fmt.Errorf("failed to re-marshal TBSCertificate: %v", err)
+	}
+	return data, nil
+}
+
+// BuildPrecertTBS builds a Certificate Transparency pre-certificate (RFC 6962
+// s3.1) from the given DER-encoded TBSCertificate, returning a DER-encoded
+// TBSCertificate.
+//
+// This function removes the CT poison extension (there must be exactly 1 of
+// these), preserving the order of other extensions.
+//
+// If preIssuer is provided, this should be a special intermediate certificate
+// that was used to sign the precert (indicated by having the special
+// CertificateTransparency extended key usage).  In this case, the issuance
+// information of the pre-cert is updated to reflect the next issuer in the
+// chain, i.e. the issuer of this special intermediate:
+//   - The precert's Issuer is changed to the Issuer of the intermediate
+//   - The precert's AuthorityKeyId is changed to the AuthorityKeyId of the
+//     intermediate.
+func BuildPrecertTBS(tbsData []byte, preIssuer *x509.Certificate) ([]byte, error) {
+	data, err := removeExtension(tbsData, oidExtensionCTPoison)
+	if err != nil {
+		return nil, err
+	}
+
+	var tbs tbsCertificate
+	rest, err := asn1.Unmarshal(data, &tbs)
+	if err != nil {
+		return nil, fmt.Errorf("failed to parse TBSCertificate: %v", err)
+	} else if rLen := len(rest); rLen > 0 {
+		return nil, fmt.Errorf("trailing data (%d bytes) after TBSCertificate", rLen)
+	}
+
+	if preIssuer != nil {
+		// Update the precert's Issuer field.  Use the RawIssuer rather than the
+		// parsed Issuer to avoid any chance of ASN.1 differences (e.g. switching
+		// from UTF8String to PrintableString).
+		tbs.Issuer.FullBytes = preIssuer.RawIssuer
+
+		// Also need to update the cert's AuthorityKeyID extension
+		// to that of the preIssuer.
+		var issuerKeyID []byte
+		for _, ext := range preIssuer.Extensions {
+			if ext.Id.Equal(oidExtensionAuthorityKeyId) {
+				issuerKeyID = ext.Value
+				break
+			}
+		}
+
+		// The x509 package does not parse CT EKU, so look for it in
+		// extensions directly.
+		seenCTEKU := false
+		for _, ext := range preIssuer.Extensions {
+			if ext.Id.Equal(oidExtensionKeyUsageCertificateTransparency) {
+				seenCTEKU = true
+				break
+			}
+		}
+		if !seenCTEKU {
+			return nil, fmt.Errorf("issuer does not have CertificateTransparency extended key usage")
+		}
+
+		keyAt := -1
+		for i, ext := range tbs.Extensions {
+			if ext.Id.Equal(oidExtensionAuthorityKeyId) {
+				keyAt = i
+				break
+			}
+		}
+		if keyAt >= 0 {
+			// PreCert has an auth-key-id; replace it with the value from the preIssuer
+			if issuerKeyID != nil {
+				tbs.Extensions[keyAt].Value = issuerKeyID
+			} else {
+				tbs.Extensions = append(tbs.Extensions[:keyAt], tbs.Extensions[keyAt+1:]...)
+			}
+		} else if issuerKeyID != nil {
+			// PreCert did not have an auth-key-id, but the preIssuer does, so add it at the end.
+			authKeyIDExt := pkix.Extension{
+				Id:       oidExtensionAuthorityKeyId,
+				Critical: false,
+				Value:    issuerKeyID,
+			}
+			tbs.Extensions = append(tbs.Extensions, authKeyIDExt)
+		}
+
+		// Clear out the asn1.RawContent so the re-marshal operation sees the
+		// updated structure (rather than just copying the out-of-date DER data).
+		tbs.Raw = nil
+	}
+
+	data, err = asn1.Marshal(tbs)
+	if err != nil {
+		return nil, fmt.Errorf("failed to re-marshal TBSCertificate: %v", err)
+	}
+	return data, nil
+}
+
+// RemoveCTPoison takes a DER-encoded TBSCertificate and removes the CT poison
+// extension (preserving the order of other extensions), and returns the result
+// still as a DER-encoded TBSCertificate.  This function will fail if there is
+// not exactly 1 CT poison extension present.
+func RemoveCTPoison(tbsData []byte) ([]byte, error) {
+	return BuildPrecertTBS(tbsData, nil)
+}
diff --git a/internal/x509util/ct_test.go b/internal/x509util/ct_test.go
new file mode 100644
index 00000000..fb1867a2
--- /dev/null
+++ b/internal/x509util/ct_test.go
@@ -0,0 +1,345 @@
+// Copyright 2024 Google LLC. All Rights Reserved.
+//
+// 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 x509util
+
+import (
+	"bytes"
+	"crypto/rand"
+	"crypto/rsa"
+	"crypto/x509"
+	"crypto/x509/pkix"
+	"encoding/asn1"
+	"encoding/hex"
+	"encoding/pem"
+	"math/big"
+	"reflect"
+	"strings"
+	"testing"
+	"time"
+)
+
+var pemPrivateKey = testingKey(`
+-----BEGIN RSA TESTING KEY-----
+MIICXAIBAAKBgQCxoeCUW5KJxNPxMp+KmCxKLc1Zv9Ny+4CFqcUXVUYH69L3mQ7v
+IWrJ9GBfcaA7BPQqUlWxWM+OCEQZH1EZNIuqRMNQVuIGCbz5UQ8w6tS0gcgdeGX7
+J7jgCQ4RK3F/PuCM38QBLaHx988qG8NMc6VKErBjctCXFHQt14lerd5KpQIDAQAB
+AoGAYrf6Hbk+mT5AI33k2Jt1kcweodBP7UkExkPxeuQzRVe0KVJw0EkcFhywKpr1
+V5eLMrILWcJnpyHE5slWwtFHBG6a5fLaNtsBBtcAIfqTQ0Vfj5c6SzVaJv0Z5rOd
+7gQF6isy3t3w9IF3We9wXQKzT6q5ypPGdm6fciKQ8RnzREkCQQDZwppKATqQ41/R
+vhSj90fFifrGE6aVKC1hgSpxGQa4oIdsYYHwMzyhBmWW9Xv/R+fPyr8ZwPxp2c12
+33QwOLPLAkEA0NNUb+z4ebVVHyvSwF5jhfJxigim+s49KuzJ1+A2RaSApGyBZiwS
+rWvWkB471POAKUYt5ykIWVZ83zcceQiNTwJBAMJUFQZX5GDqWFc/zwGoKkeR49Yi
+MTXIvf7Wmv6E++eFcnT461FlGAUHRV+bQQXGsItR/opIG7mGogIkVXa3E1MCQARX
+AAA7eoZ9AEHflUeuLn9QJI/r0hyQQLEtrpwv6rDT1GCWaLII5HJ6NUFVf4TTcqxo
+6vdM4QGKTJoO+SaCyP0CQFdpcxSAuzpFcKv0IlJ8XzS/cy+mweCMwyJ1PFEc4FX6
+wg/HcAJWY60xZTJDFN+Qfx8ZQvBEin6c2/h+zZi5IVY=
+-----END RSA TESTING KEY-----
+`)
+
+var testPrivateKey *rsa.PrivateKey
+
+func testingKey(s string) string { return strings.ReplaceAll(s, "TESTING KEY", "PRIVATE KEY") }
+
+func init() {
+	block, _ := pem.Decode([]byte(pemPrivateKey))
+
+	var err error
+	if testPrivateKey, err = x509.ParsePKCS1PrivateKey(block.Bytes); err != nil {
+		panic("Failed to parse private key: " + err.Error())
+	}
+}
+
+func makeCert(t *testing.T, template, issuer *x509.Certificate) *x509.Certificate {
+	t.Helper()
+	certData, err := x509.CreateCertificate(rand.Reader, template, issuer, &testPrivateKey.PublicKey, testPrivateKey)
+	if err != nil {
+		t.Fatalf("failed to create pre-cert: %v", err)
+	}
+	cert, err := x509.ParseCertificate(certData)
+	if err != nil {
+		t.Fatalf("failed to re-parse pre-cert: %v", err)
+	}
+	return cert
+}
+
+func TestBuildPrecertTBS(t *testing.T) {
+	poisonExt := pkix.Extension{Id: oidExtensionCTPoison, Critical: true, Value: asn1.NullBytes}
+	// TODO(phboneff): check Critical and value are ok.
+	ctExt := pkix.Extension{Id: oidExtensionKeyUsageCertificateTransparency}
+	preIssuerKeyID := []byte{0x19, 0x09, 0x19, 0x70}
+	issuerKeyID := []byte{0x07, 0x07, 0x20, 0x07}
+	preCertTemplate := x509.Certificate{
+		Version:         3,
+		SerialNumber:    big.NewInt(123),
+		Issuer:          pkix.Name{CommonName: "precert Issuer"},
+		Subject:         pkix.Name{CommonName: "precert subject"},
+		NotBefore:       time.Now(),
+		NotAfter:        time.Now().Add(3 * time.Hour),
+		ExtraExtensions: []pkix.Extension{poisonExt},
+		AuthorityKeyId:  preIssuerKeyID,
+	}
+	preIssuerTemplate := x509.Certificate{
+		Version:         3,
+		SerialNumber:    big.NewInt(1234),
+		Issuer:          pkix.Name{CommonName: "real Issuer"},
+		Subject:         pkix.Name{CommonName: "precert Issuer"},
+		NotBefore:       time.Now(),
+		NotAfter:        time.Now().Add(3 * time.Hour),
+		ExtraExtensions: []pkix.Extension{ctExt},
+		AuthorityKeyId:  issuerKeyID,
+		SubjectKeyId:    preIssuerKeyID,
+	}
+	actualIssuerTemplate := x509.Certificate{
+		Version:      3,
+		SerialNumber: big.NewInt(12345),
+		Issuer:       pkix.Name{CommonName: "real Issuer"},
+		Subject:      pkix.Name{CommonName: "real Issuer"},
+		NotBefore:    time.Now(),
+		NotAfter:     time.Now().Add(3 * time.Hour),
+		SubjectKeyId: issuerKeyID,
+	}
+	preCertWithAKI := makeCert(t, &preCertTemplate, &preIssuerTemplate)
+	preIssuerWithAKI := makeCert(t, &preIssuerTemplate, &actualIssuerTemplate)
+
+	preIssuerTemplate.AuthorityKeyId = nil
+	actualIssuerTemplate.SubjectKeyId = nil
+	preIssuerWithoutAKI := makeCert(t, &preIssuerTemplate, &actualIssuerTemplate)
+
+	preCertTemplate.AuthorityKeyId = nil
+	preIssuerTemplate.SubjectKeyId = nil
+	preCertWithoutAKI := makeCert(t, &preCertTemplate, &preIssuerTemplate)
+
+	preIssuerTemplate.ExtraExtensions = nil
+	invalidPreIssuer := makeCert(t, &preIssuerTemplate, &actualIssuerTemplate)
+
+	akiPrefix := []byte{0x30, 0x06, 0x80, 0x04} // SEQUENCE { [0] { ... } }
+	var tests = []struct {
+		name      string
+		tbs       *x509.Certificate
+		preIssuer *x509.Certificate
+		wantAKI   []byte
+		wantErr   bool
+	}{
+		{
+			name:    "no-preIssuer-provided",
+			tbs:     preCertWithAKI,
+			wantAKI: append(akiPrefix, preIssuerKeyID...),
+		},
+		{
+			name:      "both-with-AKI",
+			tbs:       preCertWithAKI,
+			preIssuer: preIssuerWithAKI,
+			wantAKI:   append(akiPrefix, issuerKeyID...),
+		},
+		{
+			name:      "invalid-preIssuer",
+			tbs:       preCertWithAKI,
+			preIssuer: invalidPreIssuer,
+			wantErr:   true,
+		},
+		{
+			name:      "both-without-AKI",
+			tbs:       preCertWithoutAKI,
+			preIssuer: preIssuerWithoutAKI,
+		},
+		{
+			name:      "precert-with-preIssuer-without-AKI",
+			tbs:       preCertWithAKI,
+			preIssuer: preIssuerWithoutAKI,
+		},
+		{
+			name:      "precert-without-preIssuer-with-AKI",
+			tbs:       preCertWithoutAKI,
+			preIssuer: preIssuerWithAKI,
+			wantAKI:   append(akiPrefix, issuerKeyID...),
+		},
+	}
+	for _, test := range tests {
+		got, err := BuildPrecertTBS(test.tbs.RawTBSCertificate, test.preIssuer)
+		if err != nil {
+			if !test.wantErr {
+				t.Errorf("BuildPrecertTBS(%s)=nil,%q; want _,nil", test.name, err)
+			}
+			continue
+		}
+		if test.wantErr {
+			t.Errorf("BuildPrecertTBS(%s)=_,nil; want _,non-nil", test.name)
+		}
+
+		var tbs tbsCertificate
+		if rest, err := asn1.Unmarshal(got, &tbs); err != nil {
+			t.Errorf("BuildPrecertTBS(%s) gave unparsable TBS: %v", test.name, err)
+			continue
+		} else if len(rest) > 0 {
+			t.Errorf("BuildPrecertTBS(%s) gave extra data in DER", test.name)
+		}
+		if test.preIssuer != nil {
+			if got, want := tbs.Issuer.FullBytes, test.preIssuer.RawIssuer; !bytes.Equal(got, want) {
+				t.Errorf("BuildPrecertTBS(%s).Issuer=%x, want %x", test.name, got, want)
+			}
+		}
+		var gotAKI []byte
+		for _, ext := range tbs.Extensions {
+			if ext.Id.Equal(oidExtensionAuthorityKeyId) {
+				gotAKI = ext.Value
+				break
+			}
+		}
+		if gotAKI != nil {
+			if test.wantAKI != nil {
+				if !reflect.DeepEqual(gotAKI, test.wantAKI) {
+					t.Errorf("BuildPrecertTBS(%s).Extensions[AKI]=%+v, want %+v", test.name, gotAKI, test.wantAKI)
+				}
+			} else {
+				t.Errorf("BuildPrecertTBS(%s).Extensions[AKI]=%+v, want nil", test.name, gotAKI)
+			}
+		} else if test.wantAKI != nil {
+			t.Errorf("BuildPrecertTBS(%s).Extensions[AKI]=nil, want %+v", test.name, test.wantAKI)
+		}
+	}
+}
+
+const (
+	tbsNoPoison = "30820245a003020102020842822a5b866fbfeb300d06092a864886f70d01010b" +
+		"05003071310b3009060355040613024742310f300d060355040813064c6f6e64" +
+		"6f6e310f300d060355040713064c6f6e646f6e310f300d060355040a1306476f" +
+		"6f676c65310c300a060355040b1303456e673121301f0603550403131846616b" +
+		"654365727469666963617465417574686f72697479301e170d31363037313731" +
+		"31313534305a170d3139303331393131313534305a3066310b30090603550406" +
+		"130255533113301106035504080c0a43616c69666f726e696131163014060355" +
+		"04070c0d4d6f756e7461696e205669657731133011060355040a0c0a476f6f67" +
+		"6c6520496e633115301306035504030c0c2a2e676f6f676c652e636f6d305930" +
+		"1306072a8648ce3d020106082a8648ce3d03010703420004c4093984f5158d12" +
+		"54b2029cf901e26d3547d40dd011616609351dcb121495b23fff35bd228e4dfc" +
+		"38502d22d6981ecaa023afa4967e32d1825f3157fb28ff37a381ce3081cb301d" +
+		"0603551d250416301406082b0601050507030106082b06010505070302306806" +
+		"082b06010505070101045c305a302b06082b06010505073002861f687474703a" +
+		"2f2f706b692e676f6f676c652e636f6d2f47494147322e637274302b06082b06" +
+		"010505073001861f687474703a2f2f636c69656e7473312e676f6f676c652e63" +
+		"6f6d2f6f637370301d0603551d0e04160414dbf46e63eee2dcbebf38604f9831" +
+		"d06444f163d830210603551d20041a3018300c060a2b06010401d67902050130" +
+		"08060667810c010202"
+	tbsPoisonFirst = "3082025aa003020102020842822a5b866fbfeb300d06092a864886f70d01010b" +
+		"05003071310b3009060355040613024742310f300d060355040813064c6f6e64" +
+		"6f6e310f300d060355040713064c6f6e646f6e310f300d060355040a1306476f" +
+		"6f676c65310c300a060355040b1303456e673121301f0603550403131846616b" +
+		"654365727469666963617465417574686f72697479301e170d31363037313731" +
+		"31313534305a170d3139303331393131313534305a3066310b30090603550406" +
+		"130255533113301106035504080c0a43616c69666f726e696131163014060355" +
+		"04070c0d4d6f756e7461696e205669657731133011060355040a0c0a476f6f67" +
+		"6c6520496e633115301306035504030c0c2a2e676f6f676c652e636f6d305930" +
+		"1306072a8648ce3d020106082a8648ce3d03010703420004c4093984f5158d12" +
+		"54b2029cf901e26d3547d40dd011616609351dcb121495b23fff35bd228e4dfc" +
+		"38502d22d6981ecaa023afa4967e32d1825f3157fb28ff37a381e33081e03013" +
+		"060a2b06010401d6790204030101ff04020500301d0603551d25041630140608" +
+		"2b0601050507030106082b06010505070302306806082b06010505070101045c" +
+		"305a302b06082b06010505073002861f687474703a2f2f706b692e676f6f676c" +
+		"652e636f6d2f47494147322e637274302b06082b06010505073001861f687474" +
+		"703a2f2f636c69656e7473312e676f6f676c652e636f6d2f6f637370301d0603" +
+		"551d0e04160414dbf46e63eee2dcbebf38604f9831d06444f163d83021060355" +
+		"1d20041a3018300c060a2b06010401d6790205013008060667810c010202"
+	tbsPoisonLast = "3082025aa003020102020842822a5b866fbfeb300d06092a864886f70d01010b" +
+		"05003071310b3009060355040613024742310f300d060355040813064c6f6e64" +
+		"6f6e310f300d060355040713064c6f6e646f6e310f300d060355040a1306476f" +
+		"6f676c65310c300a060355040b1303456e673121301f0603550403131846616b" +
+		"654365727469666963617465417574686f72697479301e170d31363037313731" +
+		"31313534305a170d3139303331393131313534305a3066310b30090603550406" +
+		"130255533113301106035504080c0a43616c69666f726e696131163014060355" +
+		"04070c0d4d6f756e7461696e205669657731133011060355040a0c0a476f6f67" +
+		"6c6520496e633115301306035504030c0c2a2e676f6f676c652e636f6d305930" +
+		"1306072a8648ce3d020106082a8648ce3d03010703420004c4093984f5158d12" +
+		"54b2029cf901e26d3547d40dd011616609351dcb121495b23fff35bd228e4dfc" +
+		"38502d22d6981ecaa023afa4967e32d1825f3157fb28ff37a381e33081e0301d" +
+		"0603551d250416301406082b0601050507030106082b06010505070302306806" +
+		"082b06010505070101045c305a302b06082b06010505073002861f687474703a" +
+		"2f2f706b692e676f6f676c652e636f6d2f47494147322e637274302b06082b06" +
+		"010505073001861f687474703a2f2f636c69656e7473312e676f6f676c652e63" +
+		"6f6d2f6f637370301d0603551d0e04160414dbf46e63eee2dcbebf38604f9831" +
+		"d06444f163d830210603551d20041a3018300c060a2b06010401d67902050130" +
+		"08060667810c0102023013060a2b06010401d6790204030101ff04020500"
+	tbsPoisonMiddle = "3082025aa003020102020842822a5b866fbfeb300d06092a864886f70d01010b" +
+		"05003071310b3009060355040613024742310f300d060355040813064c6f6e64" +
+		"6f6e310f300d060355040713064c6f6e646f6e310f300d060355040a1306476f" +
+		"6f676c65310c300a060355040b1303456e673121301f0603550403131846616b" +
+		"654365727469666963617465417574686f72697479301e170d31363037313731" +
+		"31313534305a170d3139303331393131313534305a3066310b30090603550406" +
+		"130255533113301106035504080c0a43616c69666f726e696131163014060355" +
+		"04070c0d4d6f756e7461696e205669657731133011060355040a0c0a476f6f67" +
+		"6c6520496e633115301306035504030c0c2a2e676f6f676c652e636f6d305930" +
+		"1306072a8648ce3d020106082a8648ce3d03010703420004c4093984f5158d12" +
+		"54b2029cf901e26d3547d40dd011616609351dcb121495b23fff35bd228e4dfc" +
+		"38502d22d6981ecaa023afa4967e32d1825f3157fb28ff37a381e33081e0301d" +
+		"0603551d250416301406082b0601050507030106082b06010505070302306806" +
+		"082b06010505070101045c305a302b06082b06010505073002861f687474703a" +
+		"2f2f706b692e676f6f676c652e636f6d2f47494147322e637274302b06082b06" +
+		"010505073001861f687474703a2f2f636c69656e7473312e676f6f676c652e63" +
+		"6f6d2f6f6373703013060a2b06010401d6790204030101ff04020500301d0603" +
+		"551d0e04160414dbf46e63eee2dcbebf38604f9831d06444f163d83021060355" +
+		"1d20041a3018300c060a2b06010401d6790205013008060667810c010202"
+	tbsPoisonTwice = "3082026fa003020102020842822a5b866fbfeb300d06092a864886f70d01010b" +
+		"05003071310b3009060355040613024742310f300d060355040813064c6f6e64" +
+		"6f6e310f300d060355040713064c6f6e646f6e310f300d060355040a1306476f" +
+		"6f676c65310c300a060355040b1303456e673121301f0603550403131846616b" +
+		"654365727469666963617465417574686f72697479301e170d31363037313731" +
+		"31313534305a170d3139303331393131313534305a3066310b30090603550406" +
+		"130255533113301106035504080c0a43616c69666f726e696131163014060355" +
+		"04070c0d4d6f756e7461696e205669657731133011060355040a0c0a476f6f67" +
+		"6c6520496e633115301306035504030c0c2a2e676f6f676c652e636f6d305930" +
+		"1306072a8648ce3d020106082a8648ce3d03010703420004c4093984f5158d12" +
+		"54b2029cf901e26d3547d40dd011616609351dcb121495b23fff35bd228e4dfc" +
+		"38502d22d6981ecaa023afa4967e32d1825f3157fb28ff37a381f83081f5301d" +
+		"0603551d250416301406082b0601050507030106082b06010505070302306806" +
+		"082b06010505070101045c305a302b06082b06010505073002861f687474703a" +
+		"2f2f706b692e676f6f676c652e636f6d2f47494147322e637274302b06082b06" +
+		"010505073001861f687474703a2f2f636c69656e7473312e676f6f676c652e63" +
+		"6f6d2f6f6373703013060a2b06010401d6790204030101ff04020500301d0603" +
+		"551d0e04160414dbf46e63eee2dcbebf38604f9831d06444f163d83013060a2b" +
+		"06010401d6790204030101ff0402050030210603551d20041a3018300c060a2b" +
+		"06010401d6790205013008060667810c010202"
+)
+
+func TestRemoveCTPoison(t *testing.T) {
+	var tests = []struct {
+		name   string // for human consumption
+		tbs    string // hex encoded
+		want   string // hex encoded
+		errstr string
+	}{
+		{name: "invalid-der", tbs: "01020304", errstr: "failed to parse"},
+		{name: "trailing-data", tbs: tbsPoisonMiddle + "01020304", errstr: "trailing data"},
+		{name: "no-poison-ext", tbs: tbsNoPoison, errstr: "no extension of specified type present"},
+		{name: "two-poison-exts", tbs: tbsPoisonTwice, errstr: "multiple extensions of specified type present"},
+		{name: "poison-first", tbs: tbsPoisonFirst, want: tbsNoPoison},
+		{name: "poison-last", tbs: tbsPoisonLast, want: tbsNoPoison},
+		{name: "poison-middle", tbs: tbsPoisonMiddle, want: tbsNoPoison},
+	}
+	for _, test := range tests {
+		in, _ := hex.DecodeString(test.tbs)
+		got, err := RemoveCTPoison(in)
+		if test.errstr != "" {
+			if err == nil {
+				t.Errorf("RemoveCTPoison(%s)=%s,nil; want error %q", test.name, hex.EncodeToString(got), test.errstr)
+			} else if !strings.Contains(err.Error(), test.errstr) {
+				t.Errorf("RemoveCTPoison(%s)=nil,%q; want error %q", test.name, err, test.errstr)
+			}
+			continue
+		}
+		want, _ := hex.DecodeString(test.want)
+		if err != nil {
+			t.Errorf("RemoveCTPoison(%s)=nil,%q; want %s,nil", test.name, err, test.want)
+		} else if !bytes.Equal(got, want) {
+			t.Errorf("RemoveCTPoison(%s)=%s,nil; want %s,nil", test.name, hex.EncodeToString(got), test.want)
+		}
+	}
+}
diff --git a/internal/x509util/pem_cert_pool.go b/internal/x509util/pem_cert_pool.go
index e419659f..dcaf3256 100644
--- a/internal/x509util/pem_cert_pool.go
+++ b/internal/x509util/pem_cert_pool.go
@@ -16,12 +16,12 @@ package x509util
 
 import (
 	"crypto/sha256"
+	"crypto/x509"
 	"encoding/pem"
 	"errors"
 	"fmt"
 	"os"
 
-	"github.com/google/certificate-transparency-go/x509"
 	"k8s.io/klog/v2"
 )
 
@@ -79,7 +79,7 @@ func (p *PEMCertPool) AppendCertsFromPEM(pemCerts []byte) (ok bool) {
 		}
 
 		cert, err := x509.ParseCertificate(block.Bytes)
-		if x509.IsFatal(err) {
+		if err != nil {
 			klog.Warningf("error parsing PEM certificate: %v", err)
 			return false
 		}
diff --git a/internal/x509util/pem_cert_pool_test.go b/internal/x509util/pem_cert_pool_test.go
index b630e083..3dbd809d 100644
--- a/internal/x509util/pem_cert_pool_test.go
+++ b/internal/x509util/pem_cert_pool_test.go
@@ -15,10 +15,10 @@
 package x509util_test
 
 import (
+	"crypto/x509"
 	"encoding/pem"
 	"testing"
 
-	"github.com/google/certificate-transparency-go/x509"
 	"github.com/transparency-dev/static-ct/internal/x509util"
 )
 
@@ -102,7 +102,7 @@ func parsePEM(t *testing.T, pemCert string) *x509.Certificate {
 	}
 
 	cert, err := x509.ParseCertificate(block.Bytes)
-	if x509.IsFatal(err) {
+	if err != nil {
 		t.Fatalf("Failed to parse PEM certificate: %v", err)
 	}
 	return cert
diff --git a/internal/x509util/x509util.go b/internal/x509util/x509util.go
index d5f0bf6f..968e3ed6 100644
--- a/internal/x509util/x509util.go
+++ b/internal/x509util/x509util.go
@@ -17,739 +17,11 @@
 package x509util
 
 import (
-	"bytes"
-	"crypto/rsa"
-	"encoding/base64"
-	"encoding/hex"
+	"crypto/x509"
 	"encoding/pem"
 	"errors"
-	"fmt"
-	"net"
-	"strconv"
-
-	ct "github.com/google/certificate-transparency-go"
-	"github.com/google/certificate-transparency-go/asn1"
-	"github.com/google/certificate-transparency-go/gossip/minimal/x509ext"
-	"github.com/google/certificate-transparency-go/tls"
-	"github.com/google/certificate-transparency-go/x509"
-	"github.com/google/certificate-transparency-go/x509/pkix"
 )
 
-// OIDForStandardExtension indicates whether oid identifies a standard extension.
-// Standard extensions are listed in RFC 5280 (and other RFCs).
-func OIDForStandardExtension(oid asn1.ObjectIdentifier) bool {
-	if oid.Equal(x509.OIDExtensionSubjectKeyId) ||
-		oid.Equal(x509.OIDExtensionKeyUsage) ||
-		oid.Equal(x509.OIDExtensionExtendedKeyUsage) ||
-		oid.Equal(x509.OIDExtensionAuthorityKeyId) ||
-		oid.Equal(x509.OIDExtensionBasicConstraints) ||
-		oid.Equal(x509.OIDExtensionSubjectAltName) ||
-		oid.Equal(x509.OIDExtensionCertificatePolicies) ||
-		oid.Equal(x509.OIDExtensionNameConstraints) ||
-		oid.Equal(x509.OIDExtensionCRLDistributionPoints) ||
-		oid.Equal(x509.OIDExtensionIssuerAltName) ||
-		oid.Equal(x509.OIDExtensionSubjectDirectoryAttributes) ||
-		oid.Equal(x509.OIDExtensionInhibitAnyPolicy) ||
-		oid.Equal(x509.OIDExtensionPolicyConstraints) ||
-		oid.Equal(x509.OIDExtensionPolicyMappings) ||
-		oid.Equal(x509.OIDExtensionFreshestCRL) ||
-		oid.Equal(x509.OIDExtensionSubjectInfoAccess) ||
-		oid.Equal(x509.OIDExtensionAuthorityInfoAccess) ||
-		oid.Equal(x509.OIDExtensionIPPrefixList) ||
-		oid.Equal(x509.OIDExtensionASList) ||
-		oid.Equal(x509.OIDExtensionCTPoison) ||
-		oid.Equal(x509.OIDExtensionCTSCT) {
-		return true
-	}
-	return false
-}
-
-// OIDInExtensions checks whether the extension identified by oid is present in extensions
-// and returns how many times it occurs together with an indication of whether any of them
-// are marked critical.
-func OIDInExtensions(oid asn1.ObjectIdentifier, extensions []pkix.Extension) (int, bool) {
-	count := 0
-	critical := false
-	for _, ext := range extensions {
-		if ext.Id.Equal(oid) {
-			count++
-			if ext.Critical {
-				critical = true
-			}
-		}
-	}
-	return count, critical
-}
-
-// String formatting for various X.509/ASN.1 types
-func bitStringToString(b asn1.BitString) string { // nolint:deadcode,unused
-	result := hex.EncodeToString(b.Bytes)
-	bitsLeft := b.BitLength % 8
-	if bitsLeft != 0 {
-		result += " (" + strconv.Itoa(8-bitsLeft) + " unused bits)"
-	}
-	return result
-}
-
-func publicKeyAlgorithmToString(algo x509.PublicKeyAlgorithm) string {
-	// Use OpenSSL-compatible strings for the algorithms.
-	switch algo {
-	case x509.RSA:
-		return "rsaEncryption"
-	case x509.ECDSA:
-		return "id-ecPublicKey"
-	default:
-		return strconv.Itoa(int(algo))
-	}
-}
-
-// appendHexData adds a hex dump of binary data to buf, with line breaks
-// after each set of count bytes, and with each new line prefixed with the
-// given prefix.
-func appendHexData(buf *bytes.Buffer, data []byte, count int, prefix string) {
-	for ii, b := range data {
-		if ii%count == 0 {
-			if ii > 0 {
-				buf.WriteString("\n")
-			}
-			buf.WriteString(prefix)
-		}
-		buf.WriteString(fmt.Sprintf("%02x:", b))
-	}
-}
-
-func publicKeyToString(_ x509.PublicKeyAlgorithm, pub interface{}) string {
-	var buf bytes.Buffer
-	switch pub := pub.(type) {
-	case *rsa.PublicKey:
-		bitlen := pub.N.BitLen()
-		buf.WriteString(fmt.Sprintf("                Public Key: (%d bit)\n", bitlen))
-		buf.WriteString("                Modulus:\n")
-		data := pub.N.Bytes()
-		appendHexData(&buf, data, 15, "                    ")
-		buf.WriteString("\n")
-		buf.WriteString(fmt.Sprintf("                Exponent: %d (0x%x)", pub.E, pub.E))
-	default:
-		buf.WriteString(fmt.Sprintf("%v", pub))
-	}
-	return buf.String()
-}
-
-func commaAppend(buf *bytes.Buffer, s string) {
-	if buf.Len() > 0 {
-		buf.WriteString(", ")
-	}
-	buf.WriteString(s)
-}
-
-func keyUsageToString(k x509.KeyUsage) string {
-	var buf bytes.Buffer
-	if k&x509.KeyUsageDigitalSignature != 0 {
-		commaAppend(&buf, "Digital Signature")
-	}
-	if k&x509.KeyUsageContentCommitment != 0 {
-		commaAppend(&buf, "Content Commitment")
-	}
-	if k&x509.KeyUsageKeyEncipherment != 0 {
-		commaAppend(&buf, "Key Encipherment")
-	}
-	if k&x509.KeyUsageDataEncipherment != 0 {
-		commaAppend(&buf, "Data Encipherment")
-	}
-	if k&x509.KeyUsageKeyAgreement != 0 {
-		commaAppend(&buf, "Key Agreement")
-	}
-	if k&x509.KeyUsageCertSign != 0 {
-		commaAppend(&buf, "Certificate Signing")
-	}
-	if k&x509.KeyUsageCRLSign != 0 {
-		commaAppend(&buf, "CRL Signing")
-	}
-	if k&x509.KeyUsageEncipherOnly != 0 {
-		commaAppend(&buf, "Encipher Only")
-	}
-	if k&x509.KeyUsageDecipherOnly != 0 {
-		commaAppend(&buf, "Decipher Only")
-	}
-	return buf.String()
-}
-
-func extKeyUsageToString(u x509.ExtKeyUsage) string {
-	switch u {
-	case x509.ExtKeyUsageAny:
-		return "Any"
-	case x509.ExtKeyUsageServerAuth:
-		return "TLS Web server authentication"
-	case x509.ExtKeyUsageClientAuth:
-		return "TLS Web client authentication"
-	case x509.ExtKeyUsageCodeSigning:
-		return "Signing of executable code"
-	case x509.ExtKeyUsageEmailProtection:
-		return "Email protection"
-	case x509.ExtKeyUsageIPSECEndSystem:
-		return "IPSEC end system"
-	case x509.ExtKeyUsageIPSECTunnel:
-		return "IPSEC tunnel"
-	case x509.ExtKeyUsageIPSECUser:
-		return "IPSEC user"
-	case x509.ExtKeyUsageTimeStamping:
-		return "Time stamping"
-	case x509.ExtKeyUsageOCSPSigning:
-		return "OCSP signing"
-	case x509.ExtKeyUsageMicrosoftServerGatedCrypto:
-		return "Microsoft server gated cryptography"
-	case x509.ExtKeyUsageNetscapeServerGatedCrypto:
-		return "Netscape server gated cryptography"
-	case x509.ExtKeyUsageCertificateTransparency:
-		return "Certificate transparency"
-	default:
-		return "Unknown"
-	}
-}
-
-func attributeOIDToString(oid asn1.ObjectIdentifier) string { // nolint:deadcode,unused
-	switch {
-	case oid.Equal(pkix.OIDCountry):
-		return "Country"
-	case oid.Equal(pkix.OIDOrganization):
-		return "Organization"
-	case oid.Equal(pkix.OIDOrganizationalUnit):
-		return "OrganizationalUnit"
-	case oid.Equal(pkix.OIDCommonName):
-		return "CommonName"
-	case oid.Equal(pkix.OIDSerialNumber):
-		return "SerialNumber"
-	case oid.Equal(pkix.OIDLocality):
-		return "Locality"
-	case oid.Equal(pkix.OIDProvince):
-		return "Province"
-	case oid.Equal(pkix.OIDStreetAddress):
-		return "StreetAddress"
-	case oid.Equal(pkix.OIDPostalCode):
-		return "PostalCode"
-	case oid.Equal(pkix.OIDPseudonym):
-		return "Pseudonym"
-	case oid.Equal(pkix.OIDTitle):
-		return "Title"
-	case oid.Equal(pkix.OIDDnQualifier):
-		return "DnQualifier"
-	case oid.Equal(pkix.OIDName):
-		return "Name"
-	case oid.Equal(pkix.OIDSurname):
-		return "Surname"
-	case oid.Equal(pkix.OIDGivenName):
-		return "GivenName"
-	case oid.Equal(pkix.OIDInitials):
-		return "Initials"
-	case oid.Equal(pkix.OIDGenerationQualifier):
-		return "GenerationQualifier"
-	default:
-		return oid.String()
-	}
-}
-
-// NameToString creates a string description of a pkix.Name object.
-func NameToString(name pkix.Name) string {
-	var result bytes.Buffer
-	addSingle := func(prefix, item string) {
-		if len(item) == 0 {
-			return
-		}
-		commaAppend(&result, prefix)
-		result.WriteString(item)
-	}
-	addList := func(prefix string, items []string) {
-		for _, item := range items {
-			addSingle(prefix, item)
-		}
-	}
-	addList("C=", name.Country)
-	addList("O=", name.Organization)
-	addList("OU=", name.OrganizationalUnit)
-	addList("L=", name.Locality)
-	addList("ST=", name.Province)
-	addList("streetAddress=", name.StreetAddress)
-	addList("postalCode=", name.PostalCode)
-	addSingle("serialNumber=", name.SerialNumber)
-	addSingle("CN=", name.CommonName)
-	for _, atv := range name.Names {
-		value, ok := atv.Value.(string)
-		if !ok {
-			continue
-		}
-		t := atv.Type
-		// All of the defined attribute OIDs are of the form 2.5.4.N, and OIDAttribute is
-		// the 2.5.4 prefix ('id-at' in RFC 5280).
-		if len(t) == 4 && t[0] == pkix.OIDAttribute[0] && t[1] == pkix.OIDAttribute[1] && t[2] == pkix.OIDAttribute[2] {
-			// OID is 'id-at N', so check the final value to figure out which attribute.
-			switch t[3] {
-			case pkix.OIDCommonName[3], pkix.OIDSerialNumber[3], pkix.OIDCountry[3], pkix.OIDLocality[3], pkix.OIDProvince[3],
-				pkix.OIDStreetAddress[3], pkix.OIDOrganization[3], pkix.OIDOrganizationalUnit[3], pkix.OIDPostalCode[3]:
-				continue // covered by explicit fields
-			case pkix.OIDPseudonym[3]:
-				addSingle("pseudonym=", value)
-				continue
-			case pkix.OIDTitle[3]:
-				addSingle("title=", value)
-				continue
-			case pkix.OIDDnQualifier[3]:
-				addSingle("dnQualifier=", value)
-				continue
-			case pkix.OIDName[3]:
-				addSingle("name=", value)
-				continue
-			case pkix.OIDSurname[3]:
-				addSingle("surname=", value)
-				continue
-			case pkix.OIDGivenName[3]:
-				addSingle("givenName=", value)
-				continue
-			case pkix.OIDInitials[3]:
-				addSingle("initials=", value)
-				continue
-			case pkix.OIDGenerationQualifier[3]:
-				addSingle("generationQualifier=", value)
-				continue
-			}
-		}
-		addSingle(t.String()+"=", value)
-	}
-	return result.String()
-}
-
-// OtherNameToString creates a string description of an x509.OtherName object.
-func OtherNameToString(other x509.OtherName) string {
-	return fmt.Sprintf("%v=%v", other.TypeID, hex.EncodeToString(other.Value.Bytes))
-}
-
-// GeneralNamesToString creates a string description of an x509.GeneralNames object.
-func GeneralNamesToString(gname *x509.GeneralNames) string {
-	var buf bytes.Buffer
-	for _, name := range gname.DNSNames {
-		commaAppend(&buf, "DNS:"+name)
-	}
-	for _, email := range gname.EmailAddresses {
-		commaAppend(&buf, "email:"+email)
-	}
-	for _, name := range gname.DirectoryNames {
-		commaAppend(&buf, "DirName:"+NameToString(name))
-	}
-	for _, uri := range gname.URIs {
-		commaAppend(&buf, "URI:"+uri)
-	}
-	for _, ip := range gname.IPNets {
-		if ip.Mask == nil {
-			commaAppend(&buf, "IP Address:"+ip.IP.String())
-		} else {
-			commaAppend(&buf, "IP Address:"+ip.IP.String()+"/"+ip.Mask.String())
-		}
-	}
-	for _, id := range gname.RegisteredIDs {
-		commaAppend(&buf, "Registered ID:"+id.String())
-	}
-	for _, other := range gname.OtherNames {
-		commaAppend(&buf, "othername:"+OtherNameToString(other))
-	}
-	return buf.String()
-}
-
-// CertificateToString generates a string describing the given certificate.
-// The output roughly resembles that from openssl x509 -text.
-func CertificateToString(cert *x509.Certificate) string {
-	var result bytes.Buffer
-	result.WriteString("Certificate:\n")
-	result.WriteString("    Data:\n")
-	result.WriteString(fmt.Sprintf("        Version: %d (%#x)\n", cert.Version, cert.Version-1))
-	result.WriteString(fmt.Sprintf("        Serial Number: %s (0x%s)\n", cert.SerialNumber.Text(10), cert.SerialNumber.Text(16)))
-	result.WriteString(fmt.Sprintf("    Signature Algorithm: %v\n", cert.SignatureAlgorithm))
-	result.WriteString(fmt.Sprintf("        Issuer: %v\n", NameToString(cert.Issuer)))
-	result.WriteString("        Validity:\n")
-	result.WriteString(fmt.Sprintf("            Not Before: %v\n", cert.NotBefore))
-	result.WriteString(fmt.Sprintf("            Not After : %v\n", cert.NotAfter))
-	result.WriteString(fmt.Sprintf("        Subject: %v\n", NameToString(cert.Subject)))
-	result.WriteString("        Subject Public Key Info:\n")
-	result.WriteString(fmt.Sprintf("            Public Key Algorithm: %v\n", publicKeyAlgorithmToString(cert.PublicKeyAlgorithm)))
-	result.WriteString(fmt.Sprintf("%v\n", publicKeyToString(cert.PublicKeyAlgorithm, cert.PublicKey)))
-
-	if len(cert.Extensions) > 0 {
-		result.WriteString("        X509v3 extensions:\n")
-	}
-	// First display the extensions that are already cracked out
-	showAuthKeyID(&result, cert)
-	showSubjectKeyID(&result, cert)
-	showKeyUsage(&result, cert)
-	showExtendedKeyUsage(&result, cert)
-	showBasicConstraints(&result, cert)
-	showSubjectAltName(&result, cert)
-	showNameConstraints(&result, cert)
-	showCertPolicies(&result, cert)
-	showCRLDPs(&result, cert)
-	showAuthInfoAccess(&result, cert)
-	showSubjectInfoAccess(&result, cert)
-	showRPKIAddressRanges(&result, cert)
-	showRPKIASIdentifiers(&result, cert)
-	showCTPoison(&result, cert)
-	showCTSCT(&result, cert)
-	showCTLogSTHInfo(&result, cert)
-
-	showUnhandledExtensions(&result, cert)
-	showSignature(&result, cert)
-
-	return result.String()
-}
-
-func showCritical(result *bytes.Buffer, critical bool) {
-	if critical {
-		result.WriteString(" critical")
-	}
-	result.WriteString("\n")
-}
-
-func showAuthKeyID(result *bytes.Buffer, cert *x509.Certificate) {
-	count, critical := OIDInExtensions(x509.OIDExtensionAuthorityKeyId, cert.Extensions)
-	if count > 0 {
-		result.WriteString("            X509v3 Authority Key Identifier:")
-		showCritical(result, critical)
-		result.WriteString(fmt.Sprintf("                keyid:%v\n", hex.EncodeToString(cert.AuthorityKeyId)))
-	}
-}
-
-func showSubjectKeyID(result *bytes.Buffer, cert *x509.Certificate) {
-	count, critical := OIDInExtensions(x509.OIDExtensionSubjectKeyId, cert.Extensions)
-	if count > 0 {
-		result.WriteString("            X509v3 Subject Key Identifier:")
-		showCritical(result, critical)
-		result.WriteString(fmt.Sprintf("                keyid:%v\n", hex.EncodeToString(cert.SubjectKeyId)))
-	}
-}
-
-func showKeyUsage(result *bytes.Buffer, cert *x509.Certificate) {
-	count, critical := OIDInExtensions(x509.OIDExtensionKeyUsage, cert.Extensions)
-	if count > 0 {
-		result.WriteString("            X509v3 Key Usage:")
-		showCritical(result, critical)
-		result.WriteString(fmt.Sprintf("                %v\n", keyUsageToString(cert.KeyUsage)))
-	}
-}
-
-func showExtendedKeyUsage(result *bytes.Buffer, cert *x509.Certificate) {
-	count, critical := OIDInExtensions(x509.OIDExtensionExtendedKeyUsage, cert.Extensions)
-	if count > 0 {
-		result.WriteString("            X509v3 Extended Key Usage:")
-		showCritical(result, critical)
-		var usages bytes.Buffer
-		for _, usage := range cert.ExtKeyUsage {
-			commaAppend(&usages, extKeyUsageToString(usage))
-		}
-		for _, oid := range cert.UnknownExtKeyUsage {
-			commaAppend(&usages, oid.String())
-		}
-		result.WriteString(fmt.Sprintf("                %v\n", usages.String()))
-	}
-}
-
-func showBasicConstraints(result *bytes.Buffer, cert *x509.Certificate) {
-	count, critical := OIDInExtensions(x509.OIDExtensionBasicConstraints, cert.Extensions)
-	if count > 0 {
-		result.WriteString("            X509v3 Basic Constraints:")
-		showCritical(result, critical)
-		result.WriteString(fmt.Sprintf("                CA:%t", cert.IsCA))
-		if cert.MaxPathLen > 0 || cert.MaxPathLenZero {
-			result.WriteString(fmt.Sprintf(", pathlen:%d", cert.MaxPathLen))
-		}
-		result.WriteString("\n")
-	}
-}
-
-func showSubjectAltName(result *bytes.Buffer, cert *x509.Certificate) {
-	count, critical := OIDInExtensions(x509.OIDExtensionSubjectAltName, cert.Extensions)
-	if count > 0 {
-		result.WriteString("            X509v3 Subject Alternative Name:")
-		showCritical(result, critical)
-		var buf bytes.Buffer
-		for _, name := range cert.DNSNames {
-			commaAppend(&buf, "DNS:"+name)
-		}
-		for _, email := range cert.EmailAddresses {
-			commaAppend(&buf, "email:"+email)
-		}
-		for _, ip := range cert.IPAddresses {
-			commaAppend(&buf, "IP Address:"+ip.String())
-		}
-
-		result.WriteString(fmt.Sprintf("                %v\n", buf.String()))
-		// TODO(drysdale): include other name forms
-	}
-}
-
-func showNameConstraints(result *bytes.Buffer, cert *x509.Certificate) {
-	count, critical := OIDInExtensions(x509.OIDExtensionNameConstraints, cert.Extensions)
-	if count > 0 {
-		result.WriteString("            X509v3 Name Constraints:")
-		showCritical(result, critical)
-		if len(cert.PermittedDNSDomains) > 0 {
-			result.WriteString("                Permitted:\n")
-			var buf bytes.Buffer
-			for _, name := range cert.PermittedDNSDomains {
-				commaAppend(&buf, "DNS:"+name)
-			}
-			result.WriteString(fmt.Sprintf("                  %v\n", buf.String()))
-		}
-		// TODO(drysdale): include other name forms
-	}
-
-}
-
-func showCertPolicies(result *bytes.Buffer, cert *x509.Certificate) {
-	count, critical := OIDInExtensions(x509.OIDExtensionCertificatePolicies, cert.Extensions)
-	if count > 0 {
-		result.WriteString("            X509v3 Certificate Policies:")
-		showCritical(result, critical)
-		for _, oid := range cert.PolicyIdentifiers {
-			result.WriteString(fmt.Sprintf("                Policy: %v\n", oid.String()))
-			// TODO(drysdale): Display any qualifiers associated with the policy
-		}
-	}
-
-}
-
-func showCRLDPs(result *bytes.Buffer, cert *x509.Certificate) {
-	count, critical := OIDInExtensions(x509.OIDExtensionCRLDistributionPoints, cert.Extensions)
-	if count > 0 {
-		result.WriteString("            X509v3 CRL Distribution Points:")
-		showCritical(result, critical)
-		result.WriteString("                Full Name:\n")
-		var buf bytes.Buffer
-		for _, pt := range cert.CRLDistributionPoints {
-			commaAppend(&buf, "URI:"+pt)
-		}
-		result.WriteString(fmt.Sprintf("                    %v\n", buf.String()))
-		// TODO(drysdale): Display other GeneralNames types, plus issuer/reasons/relative-name
-	}
-
-}
-
-func showAuthInfoAccess(result *bytes.Buffer, cert *x509.Certificate) {
-	count, critical := OIDInExtensions(x509.OIDExtensionAuthorityInfoAccess, cert.Extensions)
-	if count > 0 {
-		result.WriteString("            Authority Information Access:")
-		showCritical(result, critical)
-		var issuerBuf bytes.Buffer
-		for _, issuer := range cert.IssuingCertificateURL {
-			commaAppend(&issuerBuf, "URI:"+issuer)
-		}
-		if issuerBuf.Len() > 0 {
-			result.WriteString(fmt.Sprintf("                CA Issuers - %v\n", issuerBuf.String()))
-		}
-		var ocspBuf bytes.Buffer
-		for _, ocsp := range cert.OCSPServer {
-			commaAppend(&ocspBuf, "URI:"+ocsp)
-		}
-		if ocspBuf.Len() > 0 {
-			result.WriteString(fmt.Sprintf("                OCSP - %v\n", ocspBuf.String()))
-		}
-		// TODO(drysdale): Display other GeneralNames types
-	}
-}
-
-func showSubjectInfoAccess(result *bytes.Buffer, cert *x509.Certificate) {
-	count, critical := OIDInExtensions(x509.OIDExtensionSubjectInfoAccess, cert.Extensions)
-	if count > 0 {
-		result.WriteString("            Subject Information Access:")
-		showCritical(result, critical)
-		var tsBuf bytes.Buffer
-		for _, ts := range cert.SubjectTimestamps {
-			commaAppend(&tsBuf, "URI:"+ts)
-		}
-		if tsBuf.Len() > 0 {
-			result.WriteString(fmt.Sprintf("                AD Time Stamping - %v\n", tsBuf.String()))
-		}
-		var repoBuf bytes.Buffer
-		for _, repo := range cert.SubjectCARepositories {
-			commaAppend(&repoBuf, "URI:"+repo)
-		}
-		if repoBuf.Len() > 0 {
-			result.WriteString(fmt.Sprintf("                CA repository - %v\n", repoBuf.String()))
-		}
-	}
-}
-
-func showAddressRange(prefix x509.IPAddressPrefix, afi uint16) string {
-	switch afi {
-	case x509.IPv4AddressFamilyIndicator, x509.IPv6AddressFamilyIndicator:
-		size := 4
-		if afi == x509.IPv6AddressFamilyIndicator {
-			size = 16
-		}
-		ip := make([]byte, size)
-		copy(ip, prefix.Bytes)
-		addr := net.IPNet{IP: ip, Mask: net.CIDRMask(prefix.BitLength, 8*size)}
-		return addr.String()
-	default:
-		return fmt.Sprintf("%x/%d", prefix.Bytes, prefix.BitLength)
-	}
-
-}
-
-func showRPKIAddressRanges(result *bytes.Buffer, cert *x509.Certificate) {
-	count, critical := OIDInExtensions(x509.OIDExtensionIPPrefixList, cert.Extensions)
-	if count > 0 {
-		result.WriteString("            sbgp-ipAddrBlock:")
-		showCritical(result, critical)
-		for _, blocks := range cert.RPKIAddressRanges {
-			afi := blocks.AFI
-			switch afi {
-			case x509.IPv4AddressFamilyIndicator:
-				result.WriteString("                IPv4")
-			case x509.IPv6AddressFamilyIndicator:
-				result.WriteString("                IPv6")
-			default:
-				result.WriteString(fmt.Sprintf("                %d", afi))
-			}
-			if blocks.SAFI != 0 {
-				result.WriteString(fmt.Sprintf(" SAFI=%d", blocks.SAFI))
-			}
-			result.WriteString(":")
-			if blocks.InheritFromIssuer {
-				result.WriteString(" inherit\n")
-				continue
-			}
-			result.WriteString("\n")
-			for _, prefix := range blocks.AddressPrefixes {
-				result.WriteString(fmt.Sprintf("                  %s\n", showAddressRange(prefix, afi)))
-			}
-			for _, ipRange := range blocks.AddressRanges {
-				result.WriteString(fmt.Sprintf("                  [%s, %s]\n", showAddressRange(ipRange.Min, afi), showAddressRange(ipRange.Max, afi)))
-			}
-		}
-	}
-}
-
-func showASIDs(result *bytes.Buffer, asids *x509.ASIdentifiers, label string) {
-	if asids == nil {
-		return
-	}
-	result.WriteString(fmt.Sprintf("                %s:\n", label))
-	if asids.InheritFromIssuer {
-		result.WriteString("                  inherit\n")
-		return
-	}
-	for _, id := range asids.ASIDs {
-		result.WriteString(fmt.Sprintf("                  %d\n", id))
-	}
-	for _, idRange := range asids.ASIDRanges {
-		result.WriteString(fmt.Sprintf("                  %d-%d\n", idRange.Min, idRange.Max))
-	}
-}
-
-func showRPKIASIdentifiers(result *bytes.Buffer, cert *x509.Certificate) {
-	count, critical := OIDInExtensions(x509.OIDExtensionASList, cert.Extensions)
-	if count > 0 {
-		result.WriteString("            sbgp-autonomousSysNum:")
-		showCritical(result, critical)
-		showASIDs(result, cert.RPKIASNumbers, "Autonomous System Numbers")
-		showASIDs(result, cert.RPKIRoutingDomainIDs, "Routing Domain Identifiers")
-	}
-}
-func showCTPoison(result *bytes.Buffer, cert *x509.Certificate) {
-	count, critical := OIDInExtensions(x509.OIDExtensionCTPoison, cert.Extensions)
-	if count > 0 {
-		result.WriteString("            RFC6962 Pre-Certificate Poison:")
-		showCritical(result, critical)
-		result.WriteString("                .....\n")
-	}
-}
-
-func showCTSCT(result *bytes.Buffer, cert *x509.Certificate) {
-	count, critical := OIDInExtensions(x509.OIDExtensionCTSCT, cert.Extensions)
-	if count > 0 {
-		result.WriteString("            RFC6962 Certificate Transparency SCT:")
-		showCritical(result, critical)
-		for i, sctData := range cert.SCTList.SCTList {
-			result.WriteString(fmt.Sprintf("              SCT [%d]:\n", i))
-			var sct ct.SignedCertificateTimestamp
-			_, err := tls.Unmarshal(sctData.Val, &sct)
-			if err != nil {
-				appendHexData(result, sctData.Val, 16, "                  ")
-				result.WriteString("\n")
-				continue
-			}
-			result.WriteString(fmt.Sprintf("                  Version: %d\n", sct.SCTVersion))
-			result.WriteString(fmt.Sprintf("                  LogID: %s\n", base64.StdEncoding.EncodeToString(sct.LogID.KeyID[:])))
-			result.WriteString(fmt.Sprintf("                  Timestamp: %d\n", sct.Timestamp))
-			result.WriteString(fmt.Sprintf("                  Signature: %s\n", sct.Signature.Algorithm))
-			result.WriteString("                  Signature:\n")
-			appendHexData(result, sct.Signature.Signature, 16, "                    ")
-			result.WriteString("\n")
-		}
-	}
-}
-
-func showCTLogSTHInfo(result *bytes.Buffer, cert *x509.Certificate) {
-	count, critical := OIDInExtensions(x509ext.OIDExtensionCTSTH, cert.Extensions)
-	if count > 0 {
-		result.WriteString("            Certificate Transparency STH:")
-		showCritical(result, critical)
-		sthInfo, err := x509ext.LogSTHInfoFromCert(cert)
-		if err != nil {
-			result.WriteString("              Failed to decode STH:\n")
-			return
-		}
-		result.WriteString(fmt.Sprintf("              LogURL: %s\n", string(sthInfo.LogURL)))
-		result.WriteString(fmt.Sprintf("              Version: %d\n", sthInfo.Version))
-		result.WriteString(fmt.Sprintf("              TreeSize: %d\n", sthInfo.TreeSize))
-		result.WriteString(fmt.Sprintf("              Timestamp: %d\n", sthInfo.Timestamp))
-		result.WriteString("              RootHash:\n")
-		appendHexData(result, sthInfo.SHA256RootHash[:], 16, "                    ")
-		result.WriteString("\n")
-		result.WriteString(fmt.Sprintf("              TreeHeadSignature: %s\n", sthInfo.TreeHeadSignature.Algorithm))
-		appendHexData(result, sthInfo.TreeHeadSignature.Signature, 16, "                    ")
-		result.WriteString("\n")
-	}
-}
-
-func showUnhandledExtensions(result *bytes.Buffer, cert *x509.Certificate) {
-	for _, ext := range cert.Extensions {
-		// Skip extensions that are already cracked out
-		if oidAlreadyPrinted(ext.Id) {
-			continue
-		}
-		result.WriteString(fmt.Sprintf("            %v:", ext.Id))
-		showCritical(result, ext.Critical)
-		appendHexData(result, ext.Value, 16, "                ")
-		result.WriteString("\n")
-	}
-}
-
-func showSignature(result *bytes.Buffer, cert *x509.Certificate) {
-	result.WriteString(fmt.Sprintf("    Signature Algorithm: %v\n", cert.SignatureAlgorithm))
-	appendHexData(result, cert.Signature, 18, "         ")
-	result.WriteString("\n")
-}
-
-// TODO(drysdale): remove this once all standard OIDs are parsed and printed.
-func oidAlreadyPrinted(oid asn1.ObjectIdentifier) bool {
-	if oid.Equal(x509.OIDExtensionSubjectKeyId) ||
-		oid.Equal(x509.OIDExtensionKeyUsage) ||
-		oid.Equal(x509.OIDExtensionExtendedKeyUsage) ||
-		oid.Equal(x509.OIDExtensionAuthorityKeyId) ||
-		oid.Equal(x509.OIDExtensionBasicConstraints) ||
-		oid.Equal(x509.OIDExtensionSubjectAltName) ||
-		oid.Equal(x509.OIDExtensionCertificatePolicies) ||
-		oid.Equal(x509.OIDExtensionNameConstraints) ||
-		oid.Equal(x509.OIDExtensionCRLDistributionPoints) ||
-		oid.Equal(x509.OIDExtensionAuthorityInfoAccess) ||
-		oid.Equal(x509.OIDExtensionSubjectInfoAccess) ||
-		oid.Equal(x509.OIDExtensionIPPrefixList) ||
-		oid.Equal(x509.OIDExtensionASList) ||
-		oid.Equal(x509.OIDExtensionCTPoison) ||
-		oid.Equal(x509.OIDExtensionCTSCT) ||
-		oid.Equal(x509ext.OIDExtensionCTSTH) {
-		return true
-	}
-	return false
-}
-
 // CertificateFromPEM takes a certificate in PEM format and returns the
 // corresponding x509.Certificate object.
 func CertificateFromPEM(pemBytes []byte) (*x509.Certificate, error) {
@@ -765,91 +37,3 @@ func CertificateFromPEM(pemBytes []byte) (*x509.Certificate, error) {
 	}
 	return x509.ParseCertificate(block.Bytes)
 }
-
-// CertificatesFromPEM parses one or more certificates from the given PEM data.
-// The PEM certificates must be concatenated.  This function can be used for
-// parsing PEM-formatted certificate chains, but does not verify that the
-// resulting chain is a valid certificate chain.
-func CertificatesFromPEM(pemBytes []byte) ([]*x509.Certificate, error) {
-	var chain []*x509.Certificate
-	for {
-		var block *pem.Block
-		block, pemBytes = pem.Decode(pemBytes)
-		if block == nil {
-			return chain, nil
-		}
-		if block.Type != "CERTIFICATE" {
-			return nil, fmt.Errorf("PEM block is not a CERTIFICATE")
-		}
-		cert, err := x509.ParseCertificate(block.Bytes)
-		if err != nil {
-			return nil, errors.New("failed to parse certificate")
-		}
-		chain = append(chain, cert)
-	}
-}
-
-// ParseSCTsFromSCTList parses each of the SCTs contained within an SCT list.
-func ParseSCTsFromSCTList(sctList *x509.SignedCertificateTimestampList) ([]*ct.SignedCertificateTimestamp, error) {
-	var scts []*ct.SignedCertificateTimestamp
-	for i, data := range sctList.SCTList {
-		sct, err := ExtractSCT(&data)
-		if err != nil {
-			return nil, fmt.Errorf("error extracting SCT number %d: %s", i, err)
-		}
-		scts = append(scts, sct)
-	}
-	return scts, nil
-}
-
-// ExtractSCT deserializes an SCT from a TLS-encoded SCT.
-func ExtractSCT(sctData *x509.SerializedSCT) (*ct.SignedCertificateTimestamp, error) {
-	if sctData == nil {
-		return nil, errors.New("SCT is nil")
-	}
-	var sct ct.SignedCertificateTimestamp
-	if rest, err := tls.Unmarshal(sctData.Val, &sct); err != nil {
-		return nil, fmt.Errorf("error parsing SCT: %s", err)
-	} else if len(rest) > 0 {
-		return nil, fmt.Errorf("extra data (%d bytes) after serialized SCT", len(rest))
-	}
-	return &sct, nil
-}
-
-// MarshalSCTsIntoSCTList serializes SCTs into SCT list.
-func MarshalSCTsIntoSCTList(scts []*ct.SignedCertificateTimestamp) (*x509.SignedCertificateTimestampList, error) {
-	var sctList x509.SignedCertificateTimestampList
-	sctList.SCTList = []x509.SerializedSCT{}
-	for i, sct := range scts {
-		if sct == nil {
-			return nil, fmt.Errorf("SCT number %d is nil", i)
-		}
-		encd, err := tls.Marshal(*sct)
-		if err != nil {
-			return nil, fmt.Errorf("error serializing SCT number %d: %s", i, err)
-		}
-		sctData := x509.SerializedSCT{Val: encd}
-		sctList.SCTList = append(sctList.SCTList, sctData)
-	}
-	return &sctList, nil
-}
-
-var pemCertificatePrefix = []byte("-----BEGIN CERTIFICATE")
-
-// ParseSCTsFromCertificate parses any SCTs that are embedded in the
-// certificate provided.  The certificate bytes provided can be either DER or
-// PEM, provided the PEM data starts with the PEM block marker (i.e. has no
-// leading text).
-func ParseSCTsFromCertificate(certBytes []byte) ([]*ct.SignedCertificateTimestamp, error) {
-	var cert *x509.Certificate
-	var err error
-	if bytes.HasPrefix(certBytes, pemCertificatePrefix) {
-		cert, err = CertificateFromPEM(certBytes)
-	} else {
-		cert, err = x509.ParseCertificate(certBytes)
-	}
-	if err != nil {
-		return nil, fmt.Errorf("failed to parse certificate: %s", err)
-	}
-	return ParseSCTsFromSCTList(&cert.SCTList)
-}
diff --git a/mockstorage/mock_ct_storage.go b/mockstorage/mock_ct_storage.go
index 14b0ad1d..b336b4a5 100644
--- a/mockstorage/mock_ct_storage.go
+++ b/mockstorage/mock_ct_storage.go
@@ -7,9 +7,9 @@ package mockstorage
 import (
 	context "context"
 	reflect "reflect"
+	x509 "crypto/x509"
 
 	gomock "github.com/golang/mock/gomock"
-	x509 "github.com/google/certificate-transparency-go/x509"
 	dedup "github.com/transparency-dev/static-ct/modules/dedup"
 	tessera "github.com/transparency-dev/trillian-tessera"
 	ctonly "github.com/transparency-dev/trillian-tessera/ctonly"
diff --git a/storage/storage.go b/storage/storage.go
index a36f6fbe..076b4e8d 100644
--- a/storage/storage.go
+++ b/storage/storage.go
@@ -17,11 +17,11 @@ package storage
 import (
 	"context"
 	"crypto/sha256"
+	"crypto/x509"
 	"encoding/hex"
 	"fmt"
 	"sync"
 
-	"github.com/google/certificate-transparency-go/x509"
 	"github.com/transparency-dev/static-ct/modules/dedup"
 	tessera "github.com/transparency-dev/trillian-tessera"
 	"github.com/transparency-dev/trillian-tessera/ctonly"