From 009d8514fd9487e86d124eacfb5167de5af476ec Mon Sep 17 00:00:00 2001 From: Philippe Boneff Date: Tue, 11 Feb 2025 17:28:37 +0000 Subject: [PATCH 01/14] use local BuildPrecertTBS instead of ctgo --- internal/scti/handlers.go | 3 ++- internal/scti/signatures_test.go | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/internal/scti/handlers.go b/internal/scti/handlers.go index 25d58d00..63b97c03 100644 --- a/internal/scti/handlers.go +++ b/internal/scti/handlers.go @@ -33,6 +33,7 @@ import ( "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) } diff --git a/internal/scti/signatures_test.go b/internal/scti/signatures_test.go index 5b9105bb..7e37d6d1 100644 --- a/internal/scti/signatures_test.go +++ b/internal/scti/signatures_test.go @@ -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) } From 7a7dd7eaaac1563452c0d7b1cc7548de6baaaa6f Mon Sep 17 00:00:00 2001 From: Philippe Boneff Date: Tue, 4 Feb 2025 16:53:00 +0000 Subject: [PATCH 02/14] sed ctgo/x509 - crypto/x509 # Conflicts: # internal/scti/storage.go # Conflicts: # internal/scti/ctlog.go # Conflicts: # internal/scti/chain_validation.go # internal/scti/handlers_test.go # internal/scti/signatures_test.go # Conflicts: # ctlog.go --- ctlog.go | 3 ++- internal/scti/chain_validation.go | 5 +++++ internal/scti/chain_validation_test.go | 3 ++- internal/scti/ctlog.go | 2 +- internal/scti/handlers.go | 2 +- internal/scti/handlers_test.go | 3 ++- internal/scti/requestlog.go | 2 +- internal/scti/signatures.go | 2 +- internal/scti/signatures_test.go | 3 ++- mockstorage/mock_ct_storage.go | 2 +- storage/storage.go | 2 +- 11 files changed, 19 insertions(+), 10 deletions(-) diff --git a/ctlog.go b/ctlog.go index 143eeeec..04bc48be 100644 --- a/ctlog.go +++ b/ctlog.go @@ -23,8 +23,9 @@ import ( "strings" "time" + "crypto/x509" + "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..49a7db9c 100644 --- a/internal/scti/chain_validation.go +++ b/internal/scti/chain_validation.go @@ -23,8 +23,13 @@ import ( "time" "github.com/google/certificate-transparency-go/asn1" +<<<<<<< HEAD "github.com/google/certificate-transparency-go/x509" "github.com/transparency-dev/static-ct/internal/x509util" +======= + "crypto/x509" + "github.com/google/certificate-transparency-go/x509util" +>>>>>>> eaf33bf (sed ctgo/x509 - crypto/x509) "k8s.io/klog/v2" ) diff --git a/internal/scti/chain_validation_test.go b/internal/scti/chain_validation_test.go index f1af6577..9c401663 100644 --- a/internal/scti/chain_validation_test.go +++ b/internal/scti/chain_validation_test.go @@ -21,8 +21,9 @@ import ( "testing" "time" + "crypto/x509" + "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/x509util" 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 63b97c03..4b36c048 100644 --- a/internal/scti/handlers.go +++ b/internal/scti/handlers.go @@ -29,7 +29,7 @@ import ( "time" "github.com/google/certificate-transparency-go/tls" - "github.com/google/certificate-transparency-go/x509" + "crypto/x509" "github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus/promauto" "github.com/transparency-dev/static-ct/internal/types" diff --git a/internal/scti/handlers_test.go b/internal/scti/handlers_test.go index 68a6c1f9..a8fed82e 100644 --- a/internal/scti/handlers_test.go +++ b/internal/scti/handlers_test.go @@ -29,8 +29,9 @@ import ( "testing" "time" + "crypto/x509" + "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..814aa1a5 100644 --- a/internal/scti/requestlog.go +++ b/internal/scti/requestlog.go @@ -19,7 +19,7 @@ import ( "encoding/hex" "time" - "github.com/google/certificate-transparency-go/x509" + "crypto/x509" "github.com/google/certificate-transparency-go/x509util" "k8s.io/klog/v2" ) diff --git a/internal/scti/signatures.go b/internal/scti/signatures.go index d9b78948..80a8e53d 100644 --- a/internal/scti/signatures.go +++ b/internal/scti/signatures.go @@ -23,7 +23,7 @@ import ( "time" "github.com/google/certificate-transparency-go/tls" - "github.com/google/certificate-transparency-go/x509" + "crypto/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 7e37d6d1..e1cb0b91 100644 --- a/internal/scti/signatures_test.go +++ b/internal/scti/signatures_test.go @@ -23,8 +23,9 @@ import ( "testing" "time" + "crypto/x509" + "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" diff --git a/mockstorage/mock_ct_storage.go b/mockstorage/mock_ct_storage.go index 14b0ad1d..adbbc0ac 100644 --- a/mockstorage/mock_ct_storage.go +++ b/mockstorage/mock_ct_storage.go @@ -9,7 +9,7 @@ import ( reflect "reflect" gomock "github.com/golang/mock/gomock" - x509 "github.com/google/certificate-transparency-go/x509" + x509 "crypto/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" From 295cee0b8ca2fdf5af0316fd5922a3cbdaaa9069 Mon Sep 17 00:00:00 2001 From: Philippe Boneff Date: Tue, 4 Feb 2025 17:39:57 +0000 Subject: [PATCH 03/14] reshuffle imports # Conflicts: # internal/scti/signatures_test.go # Conflicts: # internal/scti/storage.go # Conflicts: # ctlog.go # Conflicts: # internal/scti/chain_validation.go # internal/scti/chain_validation_test.go # internal/scti/handlers_test.go # internal/scti/signatures_test.go # Conflicts: # ctlog.go --- ctlog.go | 3 +-- internal/scti/chain_validation.go | 7 +------ internal/scti/chain_validation_test.go | 3 +-- internal/scti/handlers.go | 2 +- internal/scti/handlers_test.go | 3 +-- internal/scti/requestlog.go | 2 +- internal/scti/signatures.go | 2 +- internal/scti/signatures_test.go | 3 +-- mockstorage/mock_ct_storage.go | 2 +- 9 files changed, 9 insertions(+), 18 deletions(-) diff --git a/ctlog.go b/ctlog.go index 04bc48be..b4f81c22 100644 --- a/ctlog.go +++ b/ctlog.go @@ -17,14 +17,13 @@ package sctfe import ( "context" "crypto" + "crypto/x509" "errors" "fmt" "net/http" "strings" "time" - "crypto/x509" - "github.com/google/certificate-transparency-go/asn1" "github.com/transparency-dev/static-ct/internal/scti" "github.com/transparency-dev/static-ct/internal/x509util" diff --git a/internal/scti/chain_validation.go b/internal/scti/chain_validation.go index 49a7db9c..a4757c9d 100644 --- a/internal/scti/chain_validation.go +++ b/internal/scti/chain_validation.go @@ -16,6 +16,7 @@ package scti import ( "bytes" + "crypto/x509" "errors" "fmt" "strconv" @@ -23,13 +24,7 @@ import ( "time" "github.com/google/certificate-transparency-go/asn1" -<<<<<<< HEAD - "github.com/google/certificate-transparency-go/x509" "github.com/transparency-dev/static-ct/internal/x509util" -======= - "crypto/x509" - "github.com/google/certificate-transparency-go/x509util" ->>>>>>> eaf33bf (sed ctgo/x509 - crypto/x509) "k8s.io/klog/v2" ) diff --git a/internal/scti/chain_validation_test.go b/internal/scti/chain_validation_test.go index 9c401663..9f6acf48 100644 --- a/internal/scti/chain_validation_test.go +++ b/internal/scti/chain_validation_test.go @@ -15,14 +15,13 @@ package scti import ( + "crypto/x509" "encoding/base64" "encoding/pem" "strings" "testing" "time" - "crypto/x509" - "github.com/google/certificate-transparency-go/asn1" "github.com/google/certificate-transparency-go/x509/pkix" "github.com/transparency-dev/static-ct/internal/testdata" diff --git a/internal/scti/handlers.go b/internal/scti/handlers.go index 4b36c048..4f2cb22a 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,7 +30,6 @@ import ( "time" "github.com/google/certificate-transparency-go/tls" - "crypto/x509" "github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus/promauto" "github.com/transparency-dev/static-ct/internal/types" diff --git a/internal/scti/handlers_test.go b/internal/scti/handlers_test.go index a8fed82e..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" @@ -29,8 +30,6 @@ import ( "testing" "time" - "crypto/x509" - "github.com/golang/mock/gomock" "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" diff --git a/internal/scti/requestlog.go b/internal/scti/requestlog.go index 814aa1a5..a44dc7f2 100644 --- a/internal/scti/requestlog.go +++ b/internal/scti/requestlog.go @@ -16,10 +16,10 @@ package scti import ( "context" + "crypto/x509" "encoding/hex" "time" - "crypto/x509" "github.com/google/certificate-transparency-go/x509util" "k8s.io/klog/v2" ) diff --git a/internal/scti/signatures.go b/internal/scti/signatures.go index 80a8e53d..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" - "crypto/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 e1cb0b91..be633ef0 100644 --- a/internal/scti/signatures_test.go +++ b/internal/scti/signatures_test.go @@ -18,13 +18,12 @@ import ( "bytes" "crypto" "crypto/sha256" + "crypto/x509" "encoding/hex" "encoding/pem" "testing" "time" - "crypto/x509" - "github.com/google/certificate-transparency-go/tls" "github.com/kylelemons/godebug/pretty" "github.com/transparency-dev/static-ct/internal/testdata" diff --git a/mockstorage/mock_ct_storage.go b/mockstorage/mock_ct_storage.go index adbbc0ac..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 "crypto/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" From 05ac1f8590006df56e35a459d906dd37e2de748e Mon Sep 17 00:00:00 2001 From: Philippe Boneff Date: Tue, 4 Feb 2025 16:56:46 +0000 Subject: [PATCH 04/14] fix requestLog: implements stringer # Conflicts: # internal/scti/requestlog.go --- internal/scti/requestlog.go | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/internal/scti/requestlog.go b/internal/scti/requestlog.go index a44dc7f2..291e5f9e 100644 --- a/internal/scti/requestlog.go +++ b/internal/scti/requestlog.go @@ -20,7 +20,6 @@ import ( "encoding/hex" "time" - "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)) } From 53c0ff441bbc9302afba8a1d4ff7a3a38066e3ad Mon Sep 17 00:00:00 2001 From: Philippe Boneff Date: Tue, 4 Feb 2025 17:32:10 +0000 Subject: [PATCH 05/14] move pem_cert_pool to x509 package --- internal/x509util/pem_cert_pool.go | 2 +- internal/x509util/pem_cert_pool_test.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/internal/x509util/pem_cert_pool.go b/internal/x509util/pem_cert_pool.go index e419659f..3c629847 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" ) diff --git a/internal/x509util/pem_cert_pool_test.go b/internal/x509util/pem_cert_pool_test.go index b630e083..880adbaa 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" ) From 095025d25d53064b5211487f45c31db897f1aecd Mon Sep 17 00:00:00 2001 From: Philippe Boneff Date: Tue, 4 Feb 2025 17:44:49 +0000 Subject: [PATCH 06/14] fix a bunch of things # Conflicts: # ctlog.go # Conflicts: # ctlog.go --- ctlog.go | 2 +- internal/scti/chain_validation.go | 5 +++-- internal/scti/chain_validation_test.go | 11 ++++++----- internal/types/rfc6962.go | 6 +++++- 4 files changed, 15 insertions(+), 9 deletions(-) diff --git a/ctlog.go b/ctlog.go index b4f81c22..7c570212 100644 --- a/ctlog.go +++ b/ctlog.go @@ -18,13 +18,13 @@ import ( "context" "crypto" "crypto/x509" + "encoding/asn1" "errors" "fmt" "net/http" "strings" "time" - "github.com/google/certificate-transparency-go/asn1" "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 a4757c9d..cc15396a 100644 --- a/internal/scti/chain_validation.go +++ b/internal/scti/chain_validation.go @@ -17,13 +17,14 @@ package scti import ( "bytes" "crypto/x509" + "encoding/asn1" "errors" "fmt" "strconv" "strings" "time" - "github.com/google/certificate-transparency-go/asn1" + "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) } diff --git a/internal/scti/chain_validation_test.go b/internal/scti/chain_validation_test.go index 9f6acf48..ea1fad42 100644 --- a/internal/scti/chain_validation_test.go +++ b/internal/scti/chain_validation_test.go @@ -16,15 +16,16 @@ package scti import ( "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/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 +36,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 +272,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) } } }) diff --git a/internal/types/rfc6962.go b/internal/types/rfc6962.go index e89a4229..754c41d3 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,9 @@ 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} + // MerkleLeafType represents the MerkleLeafType enum from section 3.4: // // enum { timestamped_entry(0), (255) } MerkleLeafType; From 74a8413849ed5d10a5a969c9bd921bc3764ea2fe Mon Sep 17 00:00:00 2001 From: Philippe Boneff Date: Fri, 7 Feb 2025 14:02:46 +0000 Subject: [PATCH 07/14] IN PROGRESS: migrate x509util to standard librairies --- internal/x509util/x509util.go | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/internal/x509util/x509util.go b/internal/x509util/x509util.go index d5f0bf6f..8377e1d0 100644 --- a/internal/x509util/x509util.go +++ b/internal/x509util/x509util.go @@ -19,6 +19,10 @@ package x509util import ( "bytes" "crypto/rsa" + "crypto/tls" + "crypto/x509" + "crypto/x509/pkix" + "encoding/asn1" "encoding/base64" "encoding/hex" "encoding/pem" @@ -27,12 +31,8 @@ import ( "net" "strconv" - ct "github.com/google/certificate-transparency-go" - "github.com/google/certificate-transparency-go/asn1" + types "github.com/google/certificate-transparency-go" "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. @@ -667,7 +667,7 @@ func showCTSCT(result *bytes.Buffer, cert *x509.Certificate) { showCritical(result, critical) for i, sctData := range cert.SCTList.SCTList { result.WriteString(fmt.Sprintf(" SCT [%d]:\n", i)) - var sct ct.SignedCertificateTimestamp + var sct types.SignedCertificateTimestamp _, err := tls.Unmarshal(sctData.Val, &sct) if err != nil { appendHexData(result, sctData.Val, 16, " ") @@ -790,8 +790,8 @@ func CertificatesFromPEM(pemBytes []byte) ([]*x509.Certificate, error) { } // ParseSCTsFromSCTList parses each of the SCTs contained within an SCT list. -func ParseSCTsFromSCTList(sctList *x509.SignedCertificateTimestampList) ([]*ct.SignedCertificateTimestamp, error) { - var scts []*ct.SignedCertificateTimestamp +func ParseSCTsFromSCTList(sctList *x509.SignedCertificateTimestampList) ([]*types.SignedCertificateTimestamp, error) { + var scts []*types.SignedCertificateTimestamp for i, data := range sctList.SCTList { sct, err := ExtractSCT(&data) if err != nil { @@ -803,11 +803,11 @@ func ParseSCTsFromSCTList(sctList *x509.SignedCertificateTimestampList) ([]*ct.S } // ExtractSCT deserializes an SCT from a TLS-encoded SCT. -func ExtractSCT(sctData *x509.SerializedSCT) (*ct.SignedCertificateTimestamp, error) { +func ExtractSCT(sctData *x509.SerializedSCT) (*types.SignedCertificateTimestamp, error) { if sctData == nil { return nil, errors.New("SCT is nil") } - var sct ct.SignedCertificateTimestamp + var sct types.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 { @@ -817,7 +817,7 @@ func ExtractSCT(sctData *x509.SerializedSCT) (*ct.SignedCertificateTimestamp, er } // MarshalSCTsIntoSCTList serializes SCTs into SCT list. -func MarshalSCTsIntoSCTList(scts []*ct.SignedCertificateTimestamp) (*x509.SignedCertificateTimestampList, error) { +func MarshalSCTsIntoSCTList(scts []*types.SignedCertificateTimestamp) (*x509.SignedCertificateTimestampList, error) { var sctList x509.SignedCertificateTimestampList sctList.SCTList = []x509.SerializedSCT{} for i, sct := range scts { @@ -840,7 +840,7 @@ var pemCertificatePrefix = []byte("-----BEGIN CERTIFICATE") // 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) { +func ParseSCTsFromCertificate(certBytes []byte) ([]*types.SignedCertificateTimestamp, error) { var cert *x509.Certificate var err error if bytes.HasPrefix(certBytes, pemCertificatePrefix) { From 1563dec6268eb80592e46f6d6b5f88b513702af4 Mon Sep 17 00:00:00 2001 From: Philippe Boneff Date: Fri, 7 Feb 2025 15:34:12 +0000 Subject: [PATCH 08/14] drop dep on c-t-go/x509 in x509util and delete print funcs # Conflicts: # internal/x509util/x509util.go --- internal/scti/chain_validation_test.go | 3 +- internal/x509util/pem_cert_pool.go | 2 +- internal/x509util/pem_cert_pool_test.go | 2 +- internal/x509util/x509util.go | 816 ------------------------ 4 files changed, 4 insertions(+), 819 deletions(-) diff --git a/internal/scti/chain_validation_test.go b/internal/scti/chain_validation_test.go index ea1fad42..f6079898 100644 --- a/internal/scti/chain_validation_test.go +++ b/internal/scti/chain_validation_test.go @@ -15,6 +15,7 @@ package scti import ( + "crypto/md5" "crypto/x509" "crypto/x509/pkix" "encoding/asn1" @@ -537,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/x509util/pem_cert_pool.go b/internal/x509util/pem_cert_pool.go index 3c629847..dcaf3256 100644 --- a/internal/x509util/pem_cert_pool.go +++ b/internal/x509util/pem_cert_pool.go @@ -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 880adbaa..3dbd809d 100644 --- a/internal/x509util/pem_cert_pool_test.go +++ b/internal/x509util/pem_cert_pool_test.go @@ -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 8377e1d0..968e3ed6 100644 --- a/internal/x509util/x509util.go +++ b/internal/x509util/x509util.go @@ -17,739 +17,11 @@ package x509util import ( - "bytes" - "crypto/rsa" - "crypto/tls" "crypto/x509" - "crypto/x509/pkix" - "encoding/asn1" - "encoding/base64" - "encoding/hex" "encoding/pem" "errors" - "fmt" - "net" - "strconv" - - types "github.com/google/certificate-transparency-go" - "github.com/google/certificate-transparency-go/gossip/minimal/x509ext" ) -// 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 types.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) ([]*types.SignedCertificateTimestamp, error) { - var scts []*types.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) (*types.SignedCertificateTimestamp, error) { - if sctData == nil { - return nil, errors.New("SCT is nil") - } - var sct types.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 []*types.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) ([]*types.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) -} From 0abd0f44606be778229fe6bb81ed0c5c37729564 Mon Sep 17 00:00:00 2001 From: Philippe Boneff Date: Fri, 7 Feb 2025 15:39:41 +0000 Subject: [PATCH 09/14] remove last IsFatal --- internal/scti/chain_validation.go | 4 ++-- internal/scti/chain_validation_test.go | 4 ++-- internal/scti/signatures_test.go | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/internal/scti/chain_validation.go b/internal/scti/chain_validation.go index cc15396a..d46365e5 100644 --- a/internal/scti/chain_validation.go +++ b/internal/scti/chain_validation.go @@ -153,8 +153,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") } chain = append(chain, cert) diff --git a/internal/scti/chain_validation_test.go b/internal/scti/chain_validation_test.go index f6079898..986dc903 100644 --- a/internal/scti/chain_validation_test.go +++ b/internal/scti/chain_validation_test.go @@ -477,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") } return cert diff --git a/internal/scti/signatures_test.go b/internal/scti/signatures_test.go index be633ef0..f4feb8e8 100644 --- a/internal/scti/signatures_test.go +++ b/internal/scti/signatures_test.go @@ -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) } From 0338cddf505981ad30ffcefe794718ffbd7b59c0 Mon Sep 17 00:00:00 2001 From: Philippe Boneff Date: Fri, 7 Feb 2025 17:24:30 +0000 Subject: [PATCH 10/14] inline isPreIssuer check --- internal/scti/handlers.go | 7 +++---- internal/types/rfc6962.go | 1 + 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/internal/scti/handlers.go b/internal/scti/handlers.go index 4f2cb22a..e9b80fdd 100644 --- a/internal/scti/handlers.go +++ b/internal/scti/handlers.go @@ -509,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/types/rfc6962.go b/internal/types/rfc6962.go index 754c41d3..3572b3b1 100644 --- a/internal/types/rfc6962.go +++ b/internal/types/rfc6962.go @@ -46,6 +46,7 @@ const ( // 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: // From 31b17f61a29be76815bf91d2e6b77bbbed73b2e3 Mon Sep 17 00:00:00 2001 From: Philippe Boneff Date: Fri, 7 Feb 2025 18:09:55 +0000 Subject: [PATCH 11/14] remove unused PreCertificate method --- internal/types/rfc6962.go | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/internal/types/rfc6962.go b/internal/types/rfc6962.go index 3572b3b1..e86d0157 100644 --- a/internal/types/rfc6962.go +++ b/internal/types/rfc6962.go @@ -403,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 From 98c3aa2c193abb47b5bd3e0f91208ae3be40653c Mon Sep 17 00:00:00 2001 From: Philippe Boneff Date: Tue, 11 Feb 2025 18:17:27 +0000 Subject: [PATCH 12/14] migrate x509/ct.go away from c-t-go # Conflicts: # internal/x509util/ct.go # internal/x509util/ct_test.go --- internal/x509util/ct.go | 193 ++++++++++++++++++++ internal/x509util/ct_test.go | 345 +++++++++++++++++++++++++++++++++++ 2 files changed, 538 insertions(+) create mode 100644 internal/x509util/ct.go create mode 100644 internal/x509util/ct_test.go 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) + } + } +} From 8cf9a5b20e789abd58dfe85a0faa7a431f034a2a Mon Sep 17 00:00:00 2001 From: Philippe Boneff Date: Wed, 12 Feb 2025 15:24:26 +0000 Subject: [PATCH 13/14] allow specific verify errors to go through --- internal/scti/chain_validation.go | 76 ++++++++++++++++++-------- internal/scti/chain_validation_test.go | 2 +- 2 files changed, 54 insertions(+), 24 deletions(-) diff --git a/internal/scti/chain_validation.go b/internal/scti/chain_validation.go index d46365e5..a0660832 100644 --- a/internal/scti/chain_validation.go +++ b/internal/scti/chain_validation.go @@ -140,6 +140,52 @@ 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. +func getLaxVerifiedChain(cert *x509.Certificate, opts x509.VerifyOptions) ([][]*x509.Certificate, error) { + chains, err := cert.Verify(opts) + switch err.(type) { + case x509.UnhandledCriticalExtension: + return chains, nil + case x509.CertificateInvalidError: + if e, ok := err.(x509.CertificateInvalidError); ok { + switch e.Reason { + 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 @@ -154,7 +200,7 @@ func validateChain(rawChain [][]byte, validationOpts ChainValidationOpts) ([]*x5 for i, certBytes := range rawChain { cert, err := x509.ParseCertificate(certBytes) if err != nil { - return nil, fmt.Errorf("x509.ParseCertificate(): %v") + return nil, fmt.Errorf("x509.ParseCertificate(): %v", err) } chain = append(chain, cert) @@ -224,32 +270,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 986dc903..35cab51a 100644 --- a/internal/scti/chain_validation_test.go +++ b/internal/scti/chain_validation_test.go @@ -478,7 +478,7 @@ func pemToCert(t *testing.T, pemData string) *x509.Certificate { cert, err := x509.ParseCertificate(bytes.Bytes) if err != nil { - t.Fatalf("x509.ParseCertificate(): %v") + t.Fatalf("x509.ParseCertificate(): %v", err) } return cert From e8733da297fa5ce689de2516794256639e59f977 Mon Sep 17 00:00:00 2001 From: Philippe Boneff Date: Thu, 13 Feb 2025 15:40:10 +0000 Subject: [PATCH 14/14] add comments --- internal/scti/chain_validation.go | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/internal/scti/chain_validation.go b/internal/scti/chain_validation.go index a0660832..a0b168c5 100644 --- a/internal/scti/chain_validation.go +++ b/internal/scti/chain_validation.go @@ -144,6 +144,7 @@ func isPrecertificate(cert *x509.Certificate) (bool, error) { // 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 @@ -161,14 +162,27 @@ func isPrecertificate(cert *x509.Certificate) (bool, error) { // - 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.