Skip to content

Commit 7ee4c84

Browse files
committed
ocsp: better validate OCSP response's certificates
We make three changes here: 1. Allow iterating over all given certificates to find the one that signed this OCSP response, as RFC 6960 does not guarantee an order and some CAs send multiple certificates, and 2. Allow the passed issuer to match the certificate that directly signed this response, and 3. Lastly, we document the unsafe behavior of calling these functions with issuer=nil, indicating that it performs no trust verification. Previously, when a CA returned the intermediate CA that signed a leaf cert as an additional cert in the response field (without using a delegated OCSP certificate), Go would err with a bad signature, as it expected the intermediate CA to have signed the wire copy (even though it was the exact same certificate). Also includes a code comment around the "bad signature on embedded certificate" error message, indicating that this isn't strictly the correct preposition choice. See also: https://github.com/crtsh/test_websites_monitor/blob/1bd8226b5f963e91d7889ea432a36e3173be8eec/test_websites_monitor.go#L267 See also: golang/go#59641 Fixes golang/go#59641 Signed-off-by: Alexander Scheel <[email protected]>
1 parent 1faeef9 commit 7ee4c84

File tree

2 files changed

+145
-13
lines changed

2 files changed

+145
-13
lines changed

ocsp/ocsp.go

+52-13
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import (
1616
_ "crypto/sha1"
1717
_ "crypto/sha256"
1818
_ "crypto/sha512"
19+
"crypto/subtle"
1920
"crypto/x509"
2021
"crypto/x509/pkix"
2122
"encoding/asn1"
@@ -458,7 +459,9 @@ func ParseRequest(bytes []byte) (*Request, error) {
458459
// the signature on the embedded certificate.
459460
//
460461
// If the response does not contain an embedded certificate and issuer is not
461-
// nil, then issuer will be used to verify the response signature.
462+
// nil, then issuer will be used to verify the response signature. This is unsafe
463+
// unless the OCSP request and response was transmitted over a secure channel,
464+
// such as HTTPS signed by an external CA.
462465
//
463466
// Invalid responses and parse failures will result in a ParseError.
464467
// Error responses will result in a ResponseError.
@@ -551,31 +554,67 @@ func ParseResponseForCert(bytes []byte, cert, issuer *x509.Certificate) (*Respon
551554
}
552555

553556
if len(basicResp.Certificates) > 0 {
554-
// Responders should only send a single certificate (if they
557+
// Responders SHOULD only send a single certificate (if they
555558
// send any) that connects the responder's certificate to the
556559
// original issuer. We accept responses with multiple
557-
// certificates due to a number responders sending them[1], but
558-
// ignore all but the first.
560+
// certificates due to a number responders sending them [1, 2].
561+
// However, we only retain the one that signed this request:
562+
// notably, RFC 6960 does not guarantee an order, but states
563+
// that any delegated OCSP responder certificate MUST be
564+
// directly issued by the CA that issued this certificate [2],
565+
// so any remaining certificates presumably overlap with the
566+
// existing certs known by the caller.
559567
//
560568
// [1] https://github.com/golang/go/issues/21527
561-
ret.Certificate, err = x509.ParseCertificate(basicResp.Certificates[0].FullBytes)
562-
if err != nil {
563-
return nil, err
569+
// [2] https://github.com/golang/go/issues/59641
570+
var lastErr error
571+
for _, candidateBytes := range basicResp.Certificates {
572+
candidate, err := x509.ParseCertificate(candidateBytes.FullBytes)
573+
if err != nil {
574+
return nil, ParseError("bad embedded certificate: " + err.Error())
575+
}
576+
577+
if err := ret.CheckSignatureFrom(candidate); err != nil {
578+
lastErr = err
579+
continue
580+
}
581+
582+
// We found a certificate which directly issued this OCSP
583+
// request; set it as our certificate object and clear any
584+
// errors.
585+
lastErr = nil
586+
ret.Certificate = candidate
587+
break
564588
}
565589

566-
if err := ret.CheckSignatureFrom(ret.Certificate); err != nil {
590+
if lastErr != nil {
591+
// n.b.: this error message string is checked by callers but is
592+
// incorrectly worded: the signature on the _response_ is bad,
593+
// from this selected certificate, not the signature on this
594+
// embedded certificate from some parent issuer.
567595
return nil, ParseError("bad signature on embedded certificate: " + err.Error())
568596
}
597+
}
569598

570-
if issuer != nil {
599+
// When given a trusted issuer CA, use it to validate that this
600+
// response is valid.
601+
if issuer != nil {
602+
if ret.Certificate == nil {
603+
// We have no other information about this request, so require
604+
// it to be signed by this trusted issuer.
605+
if err := ret.CheckSignatureFrom(issuer); err != nil {
606+
return nil, ParseError("bad OCSP signature: " + err.Error())
607+
}
608+
} else if subtle.ConstantTimeCompare(ret.Certificate.Raw, issuer.Raw) == 0 {
609+
// Since we had a certificate, we had two scenarios: (1) either the
610+
// responder unnecessarily sent us the issuer again (guarded by
611+
// the above if), or (2), we have a different certificate here in
612+
// this branch and thus we need to check the signature from
613+
// issuer->ret.Certificate.
571614
if err := issuer.CheckSignature(ret.Certificate.SignatureAlgorithm, ret.Certificate.RawTBSCertificate, ret.Certificate.Signature); err != nil {
572615
return nil, ParseError("bad OCSP signature: " + err.Error())
573616
}
574617
}
575-
} else if issuer != nil {
576-
if err := ret.CheckSignatureFrom(issuer); err != nil {
577-
return nil, ParseError("bad OCSP signature: " + err.Error())
578-
}
579618
}
580619

581620
for _, ext := range singleResp.SingleExtensions {

ocsp/ocsp_test.go

+93
Original file line numberDiff line numberDiff line change
@@ -439,6 +439,42 @@ func TestOCSPDecodeMultiResponseWithoutMatchingCert(t *testing.T) {
439439
}
440440
}
441441

442+
func TestOCSPResponseWithIntermediate(t *testing.T) {
443+
intBlock, _ := pem.Decode([]byte(ocspResponseWithIssuerIssuerPem))
444+
intCert, err := x509.ParseCertificate(intBlock.Bytes)
445+
if err != nil {
446+
t.Errorf("failed to parse issuer certificate: %v", err)
447+
}
448+
449+
ocspBlock, _ := pem.Decode([]byte(ocspResponseWithIssuerPem))
450+
resp, err := ParseResponse(ocspBlock.Bytes, intCert)
451+
if err != nil {
452+
t.Errorf("expected nil error when parsing OCSP response with embedded issuer equal to passed issuer; got: %v", err)
453+
}
454+
if resp.Certificate == nil || !bytes.Equal(resp.Certificate.Raw, intCert.Raw) {
455+
t.Errorf("expected response to contain embedded certificate pointing to issuer")
456+
}
457+
458+
resp, err = ParseResponse(ocspBlock.Bytes, nil)
459+
if err != nil {
460+
t.Errorf("expected nil error when parsing OCSP response with embedded issuer with nil passed issuer; got: %v", err)
461+
}
462+
if resp.Certificate == nil || !bytes.Equal(resp.Certificate.Raw, intCert.Raw) {
463+
t.Errorf("expected response to contain embedded certificate pointing to issuer")
464+
}
465+
466+
rootBlock, _ := pem.Decode([]byte(GTSRoot))
467+
rootCert, err := x509.ParseCertificate(rootBlock.Bytes)
468+
if err != nil {
469+
t.Errorf("failed to parse root certificate: %v", err)
470+
}
471+
472+
_, err = ParseResponse(ocspBlock.Bytes, rootCert)
473+
if err == nil {
474+
t.Errorf("expected error when parsing OCSP request with embedded issuer and unrelated issuer passed; got nil")
475+
}
476+
}
477+
442478
// This OCSP response was taken from GTS's public OCSP responder.
443479
// To recreate:
444480
// $ openssl s_client -tls1 -showcerts -servername golang.org -connect golang.org:443
@@ -743,4 +779,61 @@ const responderCertHex = "308202e2308201caa003020102020101300d06092a864886f70d01
743779
"66705de17afa19d6e8ae91ddf33179d16ebb6ac2c69cae8373d408ebf8c55308be6c04d9" +
744780
"3a25439a94299a65a709756c7a3e568be049d5c38839"
745781

782+
// The below OCSP response and issuer certificate are from
783+
// https://go.dev/play/p/Nr-VKOD_fxH; the OCSP response contains
784+
// an embedded copy of the below issuer certificate.
785+
const ocspResponseWithIssuerPem = "-----BEGIN OCSP RESPONSE-----\n" +
786+
"MIIF2AoBAKCCBdEwggXNBgkrBgEFBQcwAQEEggW+MIIFujCBv6EvMC0xKzApBgNVBAMTImV4YW1w\n" +
787+
"bGUuY29tIEludGVybWVkaWF0ZSBBdXRob3JpdHkYDzIwMjMwNDE0MTgzNjAwWjB7MHkwTTAJBgUr\n" +
788+
"DgMCGgUABBQUuUZ7MA/tkAtRYo9Qxgrp+5AlEwQUOBTBNthss1lVUQGZW7AJQddI0NkCFHyAKcL4\n" +
789+
"qKzgh1qwqIl2jZ7b1Np7gAAYDzIwMjMwNDE0MTgzNjQ4WqARGA8yMDIzMDQxNTA2MzY0OFqhAjAA\n" +
790+
"MA0GCSqGSIb3DQEBCwUAA4IBAQAwTMgZ/ebI/hXQ0lZfPAD9i4lAIM1cvPUH+m/j4SG9s3EnFOwg\n" +
791+
"/LdJ2WWnsafd9Eu6Lrf2mWwZtOuLp03WC4AjY7ghcQU89h1/R/6xv0cjvPIFmh1QP4/ipuGMNOfn\n" +
792+
"Utw/0o6vFgtViryLz+mWJ+zVntcVF8eMgi/68pCJteyyi2eQZXlHWpmHzbUeVDSaCNHsB6e1gI1w\n" +
793+
"cn0aVRG0HVfO6fHUhQlFfGPR0Zd73iU/oyx1XPxO5okBIlwoepWASflDp+11dS9ehQIJVcs9/5LB\n" +
794+
"8RnkMAven/1WNaWWwcKBRtGN7TFdAde7sWS9RDdLfosAWsWQdF6as2JvMdYRsc8zoIID4DCCA9ww\n" +
795+
"ggPYMIICwKADAgECAhR/tZoxrlMDTnlt++SzzH62XFNczjANBgkqhkiG9w0BAQsFADAWMRQwEgYD\n" +
796+
"VQQDEwtleGFtcGxlLmNvbTAeFw0yMzA0MTQxODMxMzRaFw0yMzA1MTYxODMyMDRaMC0xKzApBgNV\n" +
797+
"BAMTImV4YW1wbGUuY29tIEludGVybWVkaWF0ZSBBdXRob3JpdHkwggEiMA0GCSqGSIb3DQEBAQUA\n" +
798+
"A4IBDwAwggEKAoIBAQCdWc6C4prDkjxrBiguqvXPNFsKY2jWpyR22ex7hbkAy+XDwjAQu2d3jnUZ\n" +
799+
"VKSHlbvAbJlwOhZ9jiSAwotsDspoUzNnOhR7XFBR/HTcWAPXkeX16fJHmR67G7bJ35kb2r5P6vFx\n" +
800+
"5R7mqkQqEfk9hSrcj3KVCL/wotyY++tLlIAD7XFr+Sbjqbtkq9wE1wPwVSc5nQexbjlD27T82CBi\n" +
801+
"44D5BeQD+5N/YoWhz+MyPgKcx5UUk3Efkp2l4tEnYh9tJVcJr1o2wqwJRCnlPpY8PUuh8gZmCKIv\n" +
802+
"w+riEEFGvopD8n29dROxjuDHs2kqyw8pp4UkkYwxjajwxyiyNI93p9X5AgMBAAGjggEFMIIBATAO\n" +
803+
"BgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUOBTBNthss1lVUQGZW7AJ\n" +
804+
"QddI0NkwHwYDVR0jBBgwFoAUyMh8vow7VI0BZZg/7cJldteWVpEwagYIKwYBBQUHAQEEXjBcMC0G\n" +
805+
"CCsGAQUFBzABhiFodHRwOi8vbG9jYWxob3N0OjgzMDAvdjEvcGtpL29jc3AwKwYIKwYBBQUHMAKG\n" +
806+
"H2h0dHA6Ly9sb2NhbGhvc3Q6ODMwMC92MS9wa2kvY2EwMgYDVR0fBCswKTAnoCWgI4YhaHR0cDov\n" +
807+
"L2xvY2FsaG9zdDo4MzAwL3YxL3BraS9jcmxzMA0GCSqGSIb3DQEBCwUAA4IBAQBsF1JxQvASHFl0\n" +
808+
"zPJMRscAHKRXjgOrkm1N9J4DMphC3lWZ6RRHQvLZB7rfUAx9/eVF+UCKcvx4eldZJzxp5fe5K5OR\n" +
809+
"1Vuh65unnlq1rkNy6WqGhL8aMuUZ5NGFAKRTONEX5tKGixAy+JM4xX0iIU3OZhlfGx4J+xMyLWy/\n" +
810+
"xRiHALqaCJsSGM8DafNawN6e73ZCQfTng7bCeiwyCV4YgvSFBXQ/zs0nZ9PFn5orDHqZOPS1s9vT\n" +
811+
"hVGqWYJUgARvWfj3jXhAGAry9IujUmPZWeDttX8j7u4h4O2/UJ6fYCIDCGbQsA4Bwm+tCqgLZ8JQ\n" +
812+
"vBfNItA0VDnFokkM4cWpWcfc\n" +
813+
"-----END OCSP RESPONSE-----"
814+
815+
const ocspResponseWithIssuerIssuerPem = "-----BEGIN CERTIFICATE-----\n" +
816+
"MIID2DCCAsCgAwIBAgIUf7WaMa5TA055bfvks8x+tlxTXM4wDQYJKoZIhvcNAQEL\n" +
817+
"BQAwFjEUMBIGA1UEAxMLZXhhbXBsZS5jb20wHhcNMjMwNDE0MTgzMTM0WhcNMjMw\n" +
818+
"NTE2MTgzMjA0WjAtMSswKQYDVQQDEyJleGFtcGxlLmNvbSBJbnRlcm1lZGlhdGUg\n" +
819+
"QXV0aG9yaXR5MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAnVnOguKa\n" +
820+
"w5I8awYoLqr1zzRbCmNo1qckdtnse4W5AMvlw8IwELtnd451GVSkh5W7wGyZcDoW\n" +
821+
"fY4kgMKLbA7KaFMzZzoUe1xQUfx03FgD15Hl9enyR5keuxu2yd+ZG9q+T+rxceUe\n" +
822+
"5qpEKhH5PYUq3I9ylQi/8KLcmPvrS5SAA+1xa/km46m7ZKvcBNcD8FUnOZ0HsW45\n" +
823+
"Q9u0/NggYuOA+QXkA/uTf2KFoc/jMj4CnMeVFJNxH5KdpeLRJ2IfbSVXCa9aNsKs\n" +
824+
"CUQp5T6WPD1LofIGZgiiL8Pq4hBBRr6KQ/J9vXUTsY7gx7NpKssPKaeFJJGMMY2o\n" +
825+
"8McosjSPd6fV+QIDAQABo4IBBTCCAQEwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB\n" +
826+
"/wQFMAMBAf8wHQYDVR0OBBYEFDgUwTbYbLNZVVEBmVuwCUHXSNDZMB8GA1UdIwQY\n" +
827+
"MBaAFMjIfL6MO1SNAWWYP+3CZXbXllaRMGoGCCsGAQUFBwEBBF4wXDAtBggrBgEF\n" +
828+
"BQcwAYYhaHR0cDovL2xvY2FsaG9zdDo4MzAwL3YxL3BraS9vY3NwMCsGCCsGAQUF\n" +
829+
"BzAChh9odHRwOi8vbG9jYWxob3N0OjgzMDAvdjEvcGtpL2NhMDIGA1UdHwQrMCkw\n" +
830+
"J6AloCOGIWh0dHA6Ly9sb2NhbGhvc3Q6ODMwMC92MS9wa2kvY3JsczANBgkqhkiG\n" +
831+
"9w0BAQsFAAOCAQEAbBdScULwEhxZdMzyTEbHABykV44Dq5JtTfSeAzKYQt5VmekU\n" +
832+
"R0Ly2Qe631AMff3lRflAinL8eHpXWSc8aeX3uSuTkdVboeubp55ata5DculqhoS/\n" +
833+
"GjLlGeTRhQCkUzjRF+bShosQMviTOMV9IiFNzmYZXxseCfsTMi1sv8UYhwC6mgib\n" +
834+
"EhjPA2nzWsDenu92QkH054O2wnosMgleGIL0hQV0P87NJ2fTxZ+aKwx6mTj0tbPb\n" +
835+
"04VRqlmCVIAEb1n49414QBgK8vSLo1Jj2Vng7bV/I+7uIeDtv1Cen2AiAwhm0LAO\n" +
836+
"AcJvrQqoC2fCULwXzSLQNFQ5xaJJDOHFqVnH3A==\n" +
837+
"-----END CERTIFICATE-----"
838+
746839
const errorResponseHex = "30030a0101"

0 commit comments

Comments
 (0)