Skip to content

Commit 3b1d7ed

Browse files
committed
crypto/x509: create CRLs with Issuer.RawSubject
Per discussion with Roland Shoemaker, this updates x509.CreateRevocationList to mirror the behavior of x509.CreateCertificate, creating an internal struct for the ASN.1 encoding of the CRL. This allows us to switch the Issuer field type to asn1.RawValue, bypassing the round-tripping issues of pkix.Name in most scenarios. Per linked ticket, this resolves issues where a non-Go created certificate can be used to create CRLs which aren't correctly attested to that certificate. In rare cases where the CRL issuer is validated against the certificate's issuer (such as the linked JDK example), this can result in failing to check this CRL for the candidate certificate. Fixes golang#53754 Signed-off-by: Alexander Scheel <[email protected]>
1 parent 244c8b0 commit 3b1d7ed

File tree

2 files changed

+87
-3
lines changed

2 files changed

+87
-3
lines changed

src/crypto/x509/x509.go

+42-3
Original file line numberDiff line numberDiff line change
@@ -2148,6 +2148,30 @@ type RevocationList struct {
21482148
ExtraExtensions []pkix.Extension
21492149
}
21502150

2151+
// These structures reflect the ASN.1 structure of X.509 CRLs better than
2152+
// the existing crypto/x509/pkix variants do. These mirror the existing
2153+
// certificate structs in this file.
2154+
//
2155+
// Notably, we include issuer as an asn1.RawValue, mirroring the behavior of
2156+
// tbsCertificate and allowing raw (unparsed) subjects to be passed cleanly.
2157+
type certificateList struct {
2158+
Raw asn1.RawContent
2159+
TBSCertList tbsCertificateList
2160+
SignatureAlgorithm pkix.AlgorithmIdentifier
2161+
SignatureValue asn1.BitString
2162+
}
2163+
2164+
type tbsCertificateList struct {
2165+
Raw asn1.RawContent
2166+
Version int `asn1:"optional,default:0"`
2167+
Signature pkix.AlgorithmIdentifier
2168+
Issuer asn1.RawValue
2169+
ThisUpdate time.Time
2170+
NextUpdate time.Time `asn1:"optional"`
2171+
RevokedCertificates []pkix.RevokedCertificate `asn1:"optional"`
2172+
Extensions []pkix.Extension `asn1:"tag:0,optional,explicit"`
2173+
}
2174+
21512175
// CreateRevocationList creates a new X.509 v2 Certificate Revocation List,
21522176
// according to RFC 5280, based on template.
21532177
//
@@ -2205,10 +2229,24 @@ func CreateRevocationList(rand io.Reader, template *RevocationList, issuer *Cert
22052229
return nil, err
22062230
}
22072231

2208-
tbsCertList := pkix.TBSCertificateList{
2232+
// Correctly use the issuer's subject sequence if one is specified.
2233+
var issuerSubject []byte = issuer.RawSubject
2234+
if len(issuerSubject) == 0 {
2235+
// Ideally we'd always use the raw unparsed issuer field from the
2236+
// issuer certificate. However, the existing test suite creates
2237+
// on-the-fly certificates lacking parsed subjects, which we have
2238+
// to retain backwards compatibility with.
2239+
marshaledSubject := issuer.Subject.ToRDNSequence()
2240+
issuerSubject, err = asn1.Marshal(marshaledSubject)
2241+
if err != nil {
2242+
return nil, err
2243+
}
2244+
}
2245+
2246+
tbsCertList := tbsCertificateList{
22092247
Version: 1, // v2
22102248
Signature: signatureAlgorithm,
2211-
Issuer: issuer.Subject.ToRDNSequence(),
2249+
Issuer: asn1.RawValue{FullBytes: issuerSubject},
22122250
ThisUpdate: template.ThisUpdate.UTC(),
22132251
NextUpdate: template.NextUpdate.UTC(),
22142252
Extensions: []pkix.Extension{
@@ -2234,6 +2272,7 @@ func CreateRevocationList(rand io.Reader, template *RevocationList, issuer *Cert
22342272
if err != nil {
22352273
return nil, err
22362274
}
2275+
tbsCertList.Raw = tbsCertListContents
22372276

22382277
input := tbsCertListContents
22392278
if hashFunc != 0 {
@@ -2254,7 +2293,7 @@ func CreateRevocationList(rand io.Reader, template *RevocationList, issuer *Cert
22542293
return nil, err
22552294
}
22562295

2257-
return asn1.Marshal(pkix.CertificateList{
2296+
return asn1.Marshal(certificateList{
22582297
TBSCertList: tbsCertList,
22592298
SignatureAlgorithm: signatureAlgorithm,
22602299
SignatureValue: asn1.BitString{Bytes: signature, BitLength: len(signature) * 8},

src/crypto/x509/x509_test.go

+45
Original file line numberDiff line numberDiff line change
@@ -2407,6 +2407,18 @@ func TestCreateRevocationList(t *testing.T) {
24072407
if err != nil {
24082408
t.Fatalf("Failed to generate Ed25519 key: %s", err)
24092409
}
2410+
2411+
// Generation command:
2412+
// openssl req -x509 -newkey rsa -keyout key.pem -out cert.pem -days 365 -nodes -subj '/C=US/ST=California/L=San Francisco/O=Internet Widgets, Inc./OU=WWW/CN=Root/[email protected]' -sha256 -addext basicConstraints=CA:TRUE -addext "keyUsage = digitalSignature, keyEncipherment, dataEncipherment, cRLSign, keyCertSign" -utf8
2413+
utf8CAStr := "MIIEITCCAwmgAwIBAgIUXHXy7NdtDv+ClaHvIvlwCYiI4a4wDQYJKoZIhvcNAQELBQAwgZoxCzAJBgNVBAYTAlVTMRMwEQYDVQQIDApDYWxpZm9ybmlhMRYwFAYDVQQHDA1TYW4gRnJhbmNpc2NvMR8wHQYDVQQKDBZJbnRlcm5ldCBXaWRnZXRzLCBJbmMuMQwwCgYDVQQLDANXV1cxDTALBgNVBAMMBFJvb3QxIDAeBgkqhkiG9w0BCQEWEWFkbWluQGV4YW1wbGUuY29tMB4XDTIyMDcwODE1MzgyMFoXDTIzMDcwODE1MzgyMFowgZoxCzAJBgNVBAYTAlVTMRMwEQYDVQQIDApDYWxpZm9ybmlhMRYwFAYDVQQHDA1TYW4gRnJhbmNpc2NvMR8wHQYDVQQKDBZJbnRlcm5ldCBXaWRnZXRzLCBJbmMuMQwwCgYDVQQLDANXV1cxDTALBgNVBAMMBFJvb3QxIDAeBgkqhkiG9w0BCQEWEWFkbWluQGV4YW1wbGUuY29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAmXvp0WNjsZzySWT7Ce5zewQNKq8ujeZGphJ44Vdrwut/b6TcC4iYENds5+7/3PYwBllp3K5TRpCcafSxdhJsvA7/zWlHHNRcJhJLNt9qsKWP6ukI2Iw6OmFMg6kJQ8f67RXkT8HR3v0UqE+lWrA0g+oRuj4erLtfOtSpnl4nsE/Rs2qxbELFWAf7F5qMqH4dUyveWKrNT8eI6YQN+wBg0MAjoKRvDJnBhuo+IvvXX8Aq1QWUcBGPK3or/Ehxy5f/gEmSUXyEU1Ht/vATt2op+eRaEEpBdGRvO+DrKjlcQV2XMN18A9LAX6hCzH43sGye87dj7RZ9yj+waOYNaM7kFQIDAQABo10wWzAdBgNVHQ4EFgQUtbSlrW4hGL2kNjviM6wcCRwvOEEwHwYDVR0jBBgwFoAUtbSlrW4hGL2kNjviM6wcCRwvOEEwDAYDVR0TBAUwAwEB/zALBgNVHQ8EBAMCAbYwDQYJKoZIhvcNAQELBQADggEBAAko82YNNI2n/45L3ya21vufP6nZihIOIxgcRPUMX+IDJZk16qsFdcLgH3KAP8uiVLn8sULuCj35HpViR4IcAk2d+DqfG11l8kY+e5P7nYsViRfy0AatF59/sYlWf+3RdmPXfL70x4mE9OqlMdDm0kR2obps8rng83VLDNvj3R5sBnQwdw6LKLGzaE+RiCTmkH0+P6vnbOJ33su9+9al1+HvJUg3UM1Xq5Bw7TE8DQTetMV3c2Q35RQaJB9pQ4blJOnW9hfnt8yQzU6TU1bU4mRctTm1o1f8btPqUpi+/blhi5MUJK0/myj1XD00pmyfp8QAFl1EfqmTMIBMLg633A0="
2414+
utf8CABytes, _ := base64.StdEncoding.DecodeString(utf8CAStr)
2415+
utf8CA, _ := ParseCertificate(utf8CABytes)
2416+
2417+
utf8KeyStr := "MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCZe+nRY2OxnPJJZPsJ7nN7BA0qry6N5kamEnjhV2vC639vpNwLiJgQ12zn7v/c9jAGWWncrlNGkJxp9LF2Emy8Dv/NaUcc1FwmEks232qwpY/q6QjYjDo6YUyDqQlDx/rtFeRPwdHe/RSoT6VasDSD6hG6Ph6su1861KmeXiewT9GzarFsQsVYB/sXmoyofh1TK95Yqs1Px4jphA37AGDQwCOgpG8MmcGG6j4i+9dfwCrVBZRwEY8reiv8SHHLl/+ASZJRfIRTUe3+8BO3ain55FoQSkF0ZG874OsqOVxBXZcw3XwD0sBfqELMfjewbJ7zt2PtFn3KP7Bo5g1ozuQVAgMBAAECggEAIscjKiD9PAe2Fs9c2tk/LYazfRKI1/pv072nylfGwToffCq8+ZgP7PEDamKLc4QNScME685MbFbkOlYJyBlQriQv7lmGlY/A+Zd3l410XWaGf9IiAP91Sjk13zd0M/micApf23qtlXt/LMwvSadXnvRw4+SjirxCTdBWRt5K2/ZAN550v7bHFk1EZc3UBF6sOoNsjQWh9Ek79UmQYJBPiZDBHO7O2fh2GSIbUutTma+Tb2i1QUZzg+AG3cseF3p1i3uhNrCh+p+01bJSzGTQsRod2xpD1tpWwR3kIftCOmD1XnhpaBQi7PXjEuNbfucaftnoYj2ShDdmgD5RkkbTAQKBgQC8Ghu5MQ/yIeqXg9IpcSxuWtUEAEfK33/cC/IvuntgbNEnWQm5Lif4D6a9zjkxiCS+9HhrUu5U2EV8NxOyaqmtub3Np1Z5mPuI9oiZ119bjUJd4X+jKOTaePWvOv/rL/pTHYqzXohVMrXy+DaTIq4lOcv3n72SuhuTcKU95rhKtQKBgQDQ4t+HsRZd5fJzoCgRQhlNK3EbXQDv2zXqMW3GfpF7GaDP18I530inRURSJa++rvi7/MCFg/TXVS3QC4HXtbzTYTqhE+VHzSr+/OcsqpLE8b0jKBDv/SBkz811PUJDs3LsX31DT3K0zUpMpNSd/5SYTyJKef9L6mxmwlC1S2Yv4QKBgQC57SiYDdnQIRwrtZ2nXvlm/xttAAX2jqJoU9qIuNA4yHaYaRcGVowlUvsiw9OelQ6VPTpGA0wWy0src5lhkrKzSFRHEe+U89U1VVJCljLoYKFIAJvUH5jOJh/am/vYca0COMIfeAJUDHLyfcwb9XyiyRVGZzvP62tUelSq8gIZvQKBgCAHeaDzzWsudCO4ngwvZ3PGwnwgoaElqrmzRJLYG3SVtGvKOJTpINnNLDGwZ6dEaw1gLyEJ38QY4oJxEULDMiXzVasXQuPkmMAqhUP7D7A1JPw8C4TQ+mOa3XUppHx/CpMl/S4SA5OnmsnvyE5Fv0IveCGVXUkFtAN5rihuXEfhAoGANUkuGU3A0Upk2mzv0JTGP4H95JFG93cqnyPNrYs30M6RkZNgTW27yyr+Nhs4/cMdrg1AYTB0+6ItQWSDmYLs7JEbBE/8L8fdD1irIcygjIHE9nJh96TgZCt61kVGLE8758lOdmoB2rZOpGwi16QIhdQb+IyozYqfX+lQUojL/W0="
2418+
utf8KeyBytes, _ := base64.StdEncoding.DecodeString(utf8KeyStr)
2419+
utf8KeyRaw, _ := ParsePKCS8PrivateKey(utf8KeyBytes)
2420+
utf8Key := utf8KeyRaw.(crypto.Signer)
2421+
24102422
tests := []struct {
24112423
name string
24122424
key crypto.Signer
@@ -2675,6 +2687,16 @@ func TestCreateRevocationList(t *testing.T) {
26752687
NextUpdate: time.Time{}.Add(time.Hour * 48),
26762688
},
26772689
},
2690+
{
2691+
name: "valid CA with utf8 Subject fields including Email, empty list",
2692+
key: utf8Key,
2693+
issuer: utf8CA,
2694+
template: &RevocationList{
2695+
Number: big.NewInt(5),
2696+
ThisUpdate: time.Time{}.Add(time.Hour * 24),
2697+
NextUpdate: time.Time{}.Add(time.Hour * 48),
2698+
},
2699+
},
26782700
}
26792701

26802702
for _, tc := range tests {
@@ -2734,6 +2756,29 @@ func TestCreateRevocationList(t *testing.T) {
27342756
t.Fatalf("Unexpected second extension: got %v, want %v",
27352757
parsedCRL.Extensions[1], crlExt)
27362758
}
2759+
2760+
// With Go 1.19's updated RevocationList, we can now directly compare
2761+
// the RawSubject of the certificate to RawIssuer on the parsed CRL.
2762+
// However, this doesn't work with our hacked issuers above (that
2763+
// aren't parsed from a proper DER bundle but are instead manually
2764+
// constructed). Prefer RawSubject when it is set.
2765+
if len(tc.issuer.RawSubject) > 0 {
2766+
if !bytes.Equal(tc.issuer.RawSubject, parsedCRL.RawIssuer) {
2767+
t.Fatalf("Expected issuer.RawSubject, parsedCRL.RawIssuer to be the same; wanted: %v, got: %v", hex.EncodeToString(tc.issuer.RawSubject), hex.EncodeToString(parsedCRL.RawIssuer))
2768+
}
2769+
} else {
2770+
// When we hack our custom Subject in the test cases above,
2771+
// we don't set the additional fields (such as Names) in the
2772+
// hacked issuer. Round-trip a parsing of pkix.Name so that
2773+
// we add these missing fields for the comparison.
2774+
issuerRDN := tc.issuer.Subject.ToRDNSequence()
2775+
var caIssuer pkix.Name
2776+
caIssuer.FillFromRDNSequence(&issuerRDN)
2777+
if !reflect.DeepEqual(caIssuer, parsedCRL.Issuer) {
2778+
t.Fatalf("Expected issuer.Subject, parsedCRL.Issuer to be the same; wanted: %#v, got: %#v", caIssuer, parsedCRL.Issuer)
2779+
}
2780+
}
2781+
27372782
if len(parsedCRL.Extensions[2:]) == 0 && len(tc.template.ExtraExtensions) == 0 {
27382783
// If we don't have anything to check return early so we don't
27392784
// hit a [] != nil false positive below.

0 commit comments

Comments
 (0)