[release-branch.go1.22] encoding/gob: make x509.Certificate marshalable again

The OID type is not exported data like most of the other x509 structs.
Using it in x509.Certificate made Certificate not gob-compatible anymore,
which breaks real-world code. As a temporary fix, make gob ignore
that field, making it work as well as it did in Go 1.21.

For Go 1.23, we anticipate adding a proper fix and removing the gob
workaround. See #65633 and #66249 for more details.

For #66249.
For #65633.
Fixes #66273.

Change-Id: Idd1431d15063b3009e15d0565cd3120b9fa13f61
Reviewed-on: https://go-review.googlesource.com/c/go/+/571095
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Reviewed-by: Rob Pike <r@golang.org>
Reviewed-by: Roland Shoemaker <roland@golang.org>
Reviewed-on: https://go-review.googlesource.com/c/go/+/571715
Reviewed-by: David Chase <drchase@google.com>
diff --git a/src/crypto/x509/x509.go b/src/crypto/x509/x509.go
index f33283b..15b3b9e 100644
--- a/src/crypto/x509/x509.go
+++ b/src/crypto/x509/x509.go
@@ -780,6 +780,7 @@
 	PolicyIdentifiers []asn1.ObjectIdentifier
 
 	// Policies contains all policy identifiers included in the certificate.
+	// In Go 1.22, encoding/gob cannot handle and ignores this field.
 	Policies []OID
 }
 
diff --git a/src/crypto/x509/x509_test.go b/src/crypto/x509/x509_test.go
index ead0453..548b8d9 100644
--- a/src/crypto/x509/x509_test.go
+++ b/src/crypto/x509/x509_test.go
@@ -19,6 +19,7 @@
 	"crypto/x509/pkix"
 	"encoding/asn1"
 	"encoding/base64"
+	"encoding/gob"
 	"encoding/hex"
 	"encoding/pem"
 	"fmt"
@@ -3999,3 +4000,13 @@
 		t.Errorf("cert.Policies = %v, want: %v", cert.Policies, expectPolicies)
 	}
 }
+
+func TestGob(t *testing.T) {
+	// Test that gob does not reject Certificate.
+	// See go.dev/issue/65633.
+	cert := new(Certificate)
+	err := gob.NewEncoder(io.Discard).Encode(cert)
+	if err != nil {
+		t.Fatal(err)
+	}
+}
diff --git a/src/encoding/gob/encode.go b/src/encoding/gob/encode.go
index 5f4d253..c83071c 100644
--- a/src/encoding/gob/encode.go
+++ b/src/encoding/gob/encode.go
@@ -601,7 +601,7 @@
 	if ut.externalEnc == 0 && srt.Kind() == reflect.Struct {
 		for fieldNum, wireFieldNum := 0, 0; fieldNum < srt.NumField(); fieldNum++ {
 			f := srt.Field(fieldNum)
-			if !isSent(&f) {
+			if !isSent(srt, &f) {
 				continue
 			}
 			op, indir := encOpFor(f.Type, seen, building)
diff --git a/src/encoding/gob/type.go b/src/encoding/gob/type.go
index 30d8ca6..3b1dde4 100644
--- a/src/encoding/gob/type.go
+++ b/src/encoding/gob/type.go
@@ -538,7 +538,7 @@
 		idToTypeSlice[st.id()] = st
 		for i := 0; i < t.NumField(); i++ {
 			f := t.Field(i)
-			if !isSent(&f) {
+			if !isSent(t, &f) {
 				continue
 			}
 			typ := userType(f.Type).base
@@ -576,7 +576,7 @@
 // isSent reports whether this struct field is to be transmitted.
 // It will be transmitted only if it is exported and not a chan or func field
 // or pointer to chan or func.
-func isSent(field *reflect.StructField) bool {
+func isSent(struct_ reflect.Type, field *reflect.StructField) bool {
 	if !isExported(field.Name) {
 		return false
 	}
@@ -589,6 +589,17 @@
 	if typ.Kind() == reflect.Chan || typ.Kind() == reflect.Func {
 		return false
 	}
+
+	// Special case for Go 1.22: the x509.Certificate.Policies
+	// field is unencodable but also unused by default.
+	// Ignore it, so that x509.Certificate continues to be encodeable.
+	// Go 1.23 will add the right methods so that gob can
+	// handle the Policies field, and then we can remove this check.
+	// See go.dev/issue/65633.
+	if field.Name == "Policies" && struct_.PkgPath() == "crypto/x509" && struct_.Name() == "Certificate" {
+		return false
+	}
+
 	return true
 }