open-vault/builtin/logical/pki/ocsp_test.go
Steven Clark 312f7a1882
Handle multiple matching issuers in OCSP requests (#16848)
* Handle multiple matching issuers in OCSP requests

 - Select the first issuer that matches our request hashes and has
   the OCSP signing usage enabled. This might not match the exact
   issuer id that issued the certificate but the signatures will be
   okay.

* PR feedback
2022-08-24 09:00:40 -04:00

607 lines
24 KiB
Go

package pki
import (
"bytes"
"context"
"crypto"
"crypto/ecdsa"
"crypto/ed25519"
"crypto/rsa"
"crypto/sha256"
"crypto/x509"
"encoding/base64"
"fmt"
"io"
"net/http"
"strconv"
"strings"
"testing"
"github.com/hashicorp/vault/sdk/logical"
"github.com/stretchr/testify/require"
"golang.org/x/crypto/ocsp"
)
// If the ocsp_disabled flag is set to true in the crl configuration make sure we always
// return an Unauthorized error back as we assume an end-user disabling the feature does
// not want us to act as the OCSP authority and the RFC specifies this is the appropriate response.
func TestOcsp_Disabled(t *testing.T) {
t.Parallel()
type testArgs struct {
reqType string
}
var tests []testArgs
for _, reqType := range []string{"get", "post"} {
tests = append(tests, testArgs{
reqType: reqType,
})
}
for _, tt := range tests {
localTT := tt
t.Run(localTT.reqType, func(t *testing.T) {
b, s, testEnv := setupOcspEnv(t, "rsa")
resp, err := CBWrite(b, s, "config/crl", map[string]interface{}{
"ocsp_disable": "true",
})
requireSuccessNilResponse(t, resp, err)
resp, err = sendOcspRequest(t, b, s, localTT.reqType, testEnv.leafCertIssuer1, testEnv.issuer1, crypto.SHA1)
require.NoError(t, err)
requireFieldsSetInResp(t, resp, "http_content_type", "http_status_code", "http_raw_body")
require.Equal(t, 401, resp.Data["http_status_code"])
require.Equal(t, ocspResponseContentType, resp.Data["http_content_type"])
respDer := resp.Data["http_raw_body"].([]byte)
require.Equal(t, ocsp.UnauthorizedErrorResponse, respDer)
})
}
}
// If we can't find the issuer within the request and have no default issuer to sign an Unknown response
// with return an UnauthorizedErrorResponse/according to/the RFC, similar to if we are disabled (lack of authority)
// This behavior differs from CRLs when an issuer is removed from a mount.
func TestOcsp_UnknownIssuerWithNoDefault(t *testing.T) {
t.Parallel()
_, _, testEnv := setupOcspEnv(t, "ec")
// Create another completely empty mount so the created issuer/certificate above is unknown
b, s := createBackendWithStorage(t)
resp, err := sendOcspRequest(t, b, s, "get", testEnv.leafCertIssuer1, testEnv.issuer1, crypto.SHA1)
require.NoError(t, err)
requireFieldsSetInResp(t, resp, "http_content_type", "http_status_code", "http_raw_body")
require.Equal(t, 401, resp.Data["http_status_code"])
require.Equal(t, ocspResponseContentType, resp.Data["http_content_type"])
respDer := resp.Data["http_raw_body"].([]byte)
require.Equal(t, ocsp.UnauthorizedErrorResponse, respDer)
}
// If the issuer in the request does exist, but the request coming in associates the serial with the
// wrong issuer return an Unknown response back to the caller.
func TestOcsp_WrongIssuerInRequest(t *testing.T) {
t.Parallel()
b, s, testEnv := setupOcspEnv(t, "ec")
serial := serialFromCert(testEnv.leafCertIssuer1)
resp, err := CBWrite(b, s, "revoke", map[string]interface{}{
"serial_number": serial,
})
requireSuccessNonNilResponse(t, resp, err, "revoke")
resp, err = sendOcspRequest(t, b, s, "get", testEnv.leafCertIssuer1, testEnv.issuer2, crypto.SHA1)
require.NoError(t, err)
requireFieldsSetInResp(t, resp, "http_content_type", "http_status_code", "http_raw_body")
require.Equal(t, 200, resp.Data["http_status_code"])
require.Equal(t, ocspResponseContentType, resp.Data["http_content_type"])
respDer := resp.Data["http_raw_body"].([]byte)
ocspResp, err := ocsp.ParseResponse(respDer, testEnv.issuer1)
require.NoError(t, err, "parsing ocsp get response")
require.Equal(t, ocsp.Unknown, ocspResp.Status)
}
// Verify that requests we can't properly decode result in the correct response of MalformedRequestError
func TestOcsp_MalformedRequests(t *testing.T) {
t.Parallel()
type testArgs struct {
reqType string
}
var tests []testArgs
for _, reqType := range []string{"get", "post"} {
tests = append(tests, testArgs{
reqType: reqType,
})
}
for _, tt := range tests {
localTT := tt
t.Run(localTT.reqType, func(t *testing.T) {
b, s, _ := setupOcspEnv(t, "rsa")
badReq := []byte("this is a bad request")
var resp *logical.Response
var err error
switch localTT.reqType {
case "get":
resp, err = sendOcspGetRequest(b, s, badReq)
case "post":
resp, err = sendOcspPostRequest(b, s, badReq)
default:
t.Fatalf("bad request type")
}
require.NoError(t, err)
requireFieldsSetInResp(t, resp, "http_content_type", "http_status_code", "http_raw_body")
require.Equal(t, 400, resp.Data["http_status_code"])
require.Equal(t, ocspResponseContentType, resp.Data["http_content_type"])
respDer := resp.Data["http_raw_body"].([]byte)
require.Equal(t, ocsp.MalformedRequestErrorResponse, respDer)
})
}
}
// Validate that we properly handle a revocation entry that contains an issuer ID that no longer exists,
// the best we can do in this use case is to respond back with the default issuer that we don't know
// the issuer that they are requesting (we can't guarantee that the client is actually requesting a serial
// from that issuer)
func TestOcsp_InvalidIssuerIdInRevocationEntry(t *testing.T) {
t.Parallel()
b, s, testEnv := setupOcspEnv(t, "ec")
ctx := context.Background()
// Revoke the entry
serial := serialFromCert(testEnv.leafCertIssuer1)
resp, err := CBWrite(b, s, "revoke", map[string]interface{}{
"serial_number": serial,
})
requireSuccessNonNilResponse(t, resp, err, "revoke")
// Twiddle the entry so that the issuer id is no longer valid.
storagePath := revokedPath + normalizeSerial(serial)
var revInfo revocationInfo
revEntry, err := s.Get(ctx, storagePath)
require.NoError(t, err, "failed looking up storage path: %s", storagePath)
err = revEntry.DecodeJSON(&revInfo)
require.NoError(t, err, "failed decoding storage entry: %v", revEntry)
revInfo.CertificateIssuer = "00000000-0000-0000-0000-000000000000"
revEntry, err = logical.StorageEntryJSON(storagePath, revInfo)
require.NoError(t, err, "failed re-encoding revocation info: %v", revInfo)
err = s.Put(ctx, revEntry)
require.NoError(t, err, "failed writing out new revocation entry: %v", revEntry)
// Send the request
resp, err = sendOcspRequest(t, b, s, "get", testEnv.leafCertIssuer1, testEnv.issuer1, crypto.SHA1)
require.NoError(t, err)
requireFieldsSetInResp(t, resp, "http_content_type", "http_status_code", "http_raw_body")
require.Equal(t, 200, resp.Data["http_status_code"])
require.Equal(t, ocspResponseContentType, resp.Data["http_content_type"])
respDer := resp.Data["http_raw_body"].([]byte)
ocspResp, err := ocsp.ParseResponse(respDer, testEnv.issuer1)
require.NoError(t, err, "parsing ocsp get response")
require.Equal(t, ocsp.Unknown, ocspResp.Status)
}
// Validate that we properly handle an unknown issuer use-case but that the default issuer
// does not have the OCSP usage flag set, we can't do much else other than reply with an
// Unauthorized response.
func TestOcsp_UnknownIssuerIdWithDefaultHavingOcspUsageRemoved(t *testing.T) {
t.Parallel()
b, s, testEnv := setupOcspEnv(t, "ec")
ctx := context.Background()
// Revoke the entry
serial := serialFromCert(testEnv.leafCertIssuer1)
resp, err := CBWrite(b, s, "revoke", map[string]interface{}{
"serial_number": serial,
})
requireSuccessNonNilResponse(t, resp, err, "revoke")
// Twiddle the entry so that the issuer id is no longer valid.
storagePath := revokedPath + normalizeSerial(serial)
var revInfo revocationInfo
revEntry, err := s.Get(ctx, storagePath)
require.NoError(t, err, "failed looking up storage path: %s", storagePath)
err = revEntry.DecodeJSON(&revInfo)
require.NoError(t, err, "failed decoding storage entry: %v", revEntry)
revInfo.CertificateIssuer = "00000000-0000-0000-0000-000000000000"
revEntry, err = logical.StorageEntryJSON(storagePath, revInfo)
require.NoError(t, err, "failed re-encoding revocation info: %v", revInfo)
err = s.Put(ctx, revEntry)
require.NoError(t, err, "failed writing out new revocation entry: %v", revEntry)
// Update our issuers to no longer have the OcspSigning usage
resp, err = CBPatch(b, s, "issuer/"+testEnv.issuerId1.String(), map[string]interface{}{
"usage": "read-only,issuing-certificates,crl-signing",
})
requireSuccessNonNilResponse(t, resp, err, "failed resetting usage flags on issuer1")
resp, err = CBPatch(b, s, "issuer/"+testEnv.issuerId2.String(), map[string]interface{}{
"usage": "read-only,issuing-certificates,crl-signing",
})
requireSuccessNonNilResponse(t, resp, err, "failed resetting usage flags on issuer2")
// Send the request
resp, err = sendOcspRequest(t, b, s, "get", testEnv.leafCertIssuer1, testEnv.issuer1, crypto.SHA1)
require.NoError(t, err)
requireFieldsSetInResp(t, resp, "http_content_type", "http_status_code", "http_raw_body")
require.Equal(t, 401, resp.Data["http_status_code"])
require.Equal(t, ocspResponseContentType, resp.Data["http_content_type"])
respDer := resp.Data["http_raw_body"].([]byte)
require.Equal(t, ocsp.UnauthorizedErrorResponse, respDer)
}
// Verify that if we do have a revoked certificate entry for the request, that matches an
// issuer but that issuer does not have the OcspUsage flag set that we return an Unauthorized
// response back to the caller
func TestOcsp_RevokedCertHasIssuerWithoutOcspUsage(t *testing.T) {
b, s, testEnv := setupOcspEnv(t, "ec")
// Revoke our certificate
resp, err := CBWrite(b, s, "revoke", map[string]interface{}{
"serial_number": serialFromCert(testEnv.leafCertIssuer1),
})
requireSuccessNonNilResponse(t, resp, err, "revoke")
// Update our issuer to no longer have the OcspSigning usage
resp, err = CBPatch(b, s, "issuer/"+testEnv.issuerId1.String(), map[string]interface{}{
"usage": "read-only,issuing-certificates,crl-signing",
})
requireSuccessNonNilResponse(t, resp, err, "failed resetting usage flags on issuer")
requireFieldsSetInResp(t, resp, "usage")
// Do not assume a specific ordering for usage...
usages, err := NewIssuerUsageFromNames(strings.Split(resp.Data["usage"].(string), ","))
require.NoError(t, err, "failed parsing usage return value")
require.True(t, usages.HasUsage(IssuanceUsage))
require.True(t, usages.HasUsage(CRLSigningUsage))
require.False(t, usages.HasUsage(OCSPSigningUsage))
// Request an OCSP request from it, we should get an Unauthorized response back
resp, err = sendOcspRequest(t, b, s, "get", testEnv.leafCertIssuer1, testEnv.issuer1, crypto.SHA1)
requireSuccessNonNilResponse(t, resp, err, "ocsp get request")
requireFieldsSetInResp(t, resp, "http_content_type", "http_status_code", "http_raw_body")
require.Equal(t, 401, resp.Data["http_status_code"])
require.Equal(t, ocspResponseContentType, resp.Data["http_content_type"])
respDer := resp.Data["http_raw_body"].([]byte)
require.Equal(t, ocsp.UnauthorizedErrorResponse, respDer)
}
// Verify if our matching issuer for a revocation entry has no key associated with it that
// we bail with an Unauthorized response.
func TestOcsp_RevokedCertHasIssuerWithoutAKey(t *testing.T) {
b, s, testEnv := setupOcspEnv(t, "ec")
// Revoke our certificate
resp, err := CBWrite(b, s, "revoke", map[string]interface{}{
"serial_number": serialFromCert(testEnv.leafCertIssuer1),
})
requireSuccessNonNilResponse(t, resp, err, "revoke")
// Delete the key associated with our issuer
resp, err = CBRead(b, s, "issuer/"+testEnv.issuerId1.String())
requireSuccessNonNilResponse(t, resp, err, "failed reading issuer")
requireFieldsSetInResp(t, resp, "key_id")
keyId := resp.Data["key_id"].(keyID)
// This is a bit naughty but allow me to delete the key...
sc := b.makeStorageContext(context.Background(), s)
issuer, err := sc.fetchIssuerById(testEnv.issuerId1)
require.NoError(t, err, "failed to get issuer from storage")
issuer.KeyID = ""
err = sc.writeIssuer(issuer)
require.NoError(t, err, "failed to write issuer update")
resp, err = CBDelete(b, s, "key/"+keyId.String())
requireSuccessNonNilResponse(t, resp, err, "failed deleting key")
// Request an OCSP request from it, we should get an Unauthorized response back
resp, err = sendOcspRequest(t, b, s, "get", testEnv.leafCertIssuer1, testEnv.issuer1, crypto.SHA1)
requireSuccessNonNilResponse(t, resp, err, "ocsp get request")
requireFieldsSetInResp(t, resp, "http_content_type", "http_status_code", "http_raw_body")
require.Equal(t, 401, resp.Data["http_status_code"])
require.Equal(t, ocspResponseContentType, resp.Data["http_content_type"])
respDer := resp.Data["http_raw_body"].([]byte)
require.Equal(t, ocsp.UnauthorizedErrorResponse, respDer)
}
// Verify if for some reason an end-user has rotated an existing certificate using the same
// key so our algo matches multiple issuers and one has OCSP usage disabled. We expect that
// even if a prior issuer issued the certificate, the new matching issuer can respond and sign
// the response to the caller on its behalf.
//
// NOTE: This test is a bit at the mercy of iteration order of the issuer ids.
// If it becomes flaky, most likely something is wrong in the code
// and not the test.
func TestOcsp_MultipleMatchingIssuersOneWithoutSigningUsage(t *testing.T) {
b, s, testEnv := setupOcspEnv(t, "ec")
// Create a matching issuer as issuer1 with the same backing key
resp, err := CBWrite(b, s, "root/rotate/existing", map[string]interface{}{
"key_ref": testEnv.keyId1,
"ttl": "40h",
"common_name": "example-ocsp.com",
})
requireSuccessNonNilResponse(t, resp, err, "rotate issuer failed")
requireFieldsSetInResp(t, resp, "issuer_id")
rotatedCert := parseCert(t, resp.Data["certificate"].(string))
// Remove ocsp signing from our issuer
resp, err = CBPatch(b, s, "issuer/"+testEnv.issuerId1.String(), map[string]interface{}{
"usage": "read-only,issuing-certificates,crl-signing",
})
requireSuccessNonNilResponse(t, resp, err, "failed resetting usage flags on issuer")
requireFieldsSetInResp(t, resp, "usage")
// Do not assume a specific ordering for usage...
usages, err := NewIssuerUsageFromNames(strings.Split(resp.Data["usage"].(string), ","))
require.NoError(t, err, "failed parsing usage return value")
require.True(t, usages.HasUsage(IssuanceUsage))
require.True(t, usages.HasUsage(CRLSigningUsage))
require.False(t, usages.HasUsage(OCSPSigningUsage))
// Request an OCSP request from it, we should get a Good response back, from the rotated cert
resp, err = sendOcspRequest(t, b, s, "get", testEnv.leafCertIssuer1, testEnv.issuer1, crypto.SHA1)
requireSuccessNonNilResponse(t, resp, err, "ocsp get request")
requireFieldsSetInResp(t, resp, "http_content_type", "http_status_code", "http_raw_body")
require.Equal(t, 200, resp.Data["http_status_code"])
require.Equal(t, ocspResponseContentType, resp.Data["http_content_type"])
respDer := resp.Data["http_raw_body"].([]byte)
ocspResp, err := ocsp.ParseResponse(respDer, testEnv.issuer1)
require.NoError(t, err, "parsing ocsp get response")
require.Equal(t, ocsp.Good, ocspResp.Status)
require.Equal(t, crypto.SHA1, ocspResp.IssuerHash)
require.Equal(t, 0, ocspResp.RevocationReason)
require.Equal(t, testEnv.leafCertIssuer1.SerialNumber, ocspResp.SerialNumber)
require.Equal(t, rotatedCert, ocspResp.Certificate)
requireOcspSignatureAlgoForKey(t, rotatedCert.PublicKey, ocspResp.SignatureAlgorithm)
requireOcspResponseSignedBy(t, ocspResp, rotatedCert.PublicKey)
}
func TestOcsp_ValidRequests(t *testing.T) {
t.Parallel()
type testArgs struct {
reqType string
caKeyType string
reqHash crypto.Hash
}
var tests []testArgs
for _, reqType := range []string{"get", "post"} {
for _, caKeyType := range []string{"rsa", "ec"} { // "ed25519" is not supported at the moment in x/crypto/ocsp
for _, requestHash := range []crypto.Hash{crypto.SHA1, crypto.SHA256} {
tests = append(tests, testArgs{
reqType: reqType,
caKeyType: caKeyType,
reqHash: requestHash,
})
}
}
}
for _, tt := range tests {
localTT := tt
testName := fmt.Sprintf("%s-%s-%s", localTT.reqType, localTT.caKeyType, localTT.reqHash)
t.Run(testName, func(t *testing.T) {
runOcspRequestTest(t, localTT.reqType, localTT.caKeyType, localTT.reqHash)
})
}
}
func runOcspRequestTest(t *testing.T, requestType string, caKeyType string, requestHash crypto.Hash) {
b, s, testEnv := setupOcspEnv(t, caKeyType)
// Non-revoked cert
resp, err := sendOcspRequest(t, b, s, requestType, testEnv.leafCertIssuer1, testEnv.issuer1, requestHash)
requireSuccessNonNilResponse(t, resp, err, "ocsp get request")
requireFieldsSetInResp(t, resp, "http_content_type", "http_status_code", "http_raw_body")
require.Equal(t, 200, resp.Data["http_status_code"])
require.Equal(t, ocspResponseContentType, resp.Data["http_content_type"])
respDer := resp.Data["http_raw_body"].([]byte)
ocspResp, err := ocsp.ParseResponse(respDer, testEnv.issuer1)
require.NoError(t, err, "parsing ocsp get response")
require.Equal(t, ocsp.Good, ocspResp.Status)
require.Equal(t, requestHash, ocspResp.IssuerHash)
require.Equal(t, testEnv.issuer1, ocspResp.Certificate)
require.Equal(t, 0, ocspResp.RevocationReason)
require.Equal(t, testEnv.leafCertIssuer1.SerialNumber, ocspResp.SerialNumber)
requireOcspSignatureAlgoForKey(t, testEnv.issuer1.PublicKey, ocspResp.SignatureAlgorithm)
requireOcspResponseSignedBy(t, ocspResp, testEnv.issuer1.PublicKey)
// Now revoke it
resp, err = CBWrite(b, s, "revoke", map[string]interface{}{
"serial_number": serialFromCert(testEnv.leafCertIssuer1),
})
requireSuccessNonNilResponse(t, resp, err, "revoke")
resp, err = sendOcspRequest(t, b, s, requestType, testEnv.leafCertIssuer1, testEnv.issuer1, requestHash)
requireSuccessNonNilResponse(t, resp, err, "ocsp get request with revoked")
requireFieldsSetInResp(t, resp, "http_content_type", "http_status_code", "http_raw_body")
require.Equal(t, 200, resp.Data["http_status_code"])
require.Equal(t, ocspResponseContentType, resp.Data["http_content_type"])
respDer = resp.Data["http_raw_body"].([]byte)
ocspResp, err = ocsp.ParseResponse(respDer, testEnv.issuer1)
require.NoError(t, err, "parsing ocsp get response with revoked")
require.Equal(t, ocsp.Revoked, ocspResp.Status)
require.Equal(t, requestHash, ocspResp.IssuerHash)
require.Equal(t, testEnv.issuer1, ocspResp.Certificate)
require.Equal(t, 0, ocspResp.RevocationReason)
require.Equal(t, testEnv.leafCertIssuer1.SerialNumber, ocspResp.SerialNumber)
requireOcspSignatureAlgoForKey(t, testEnv.issuer1.PublicKey, ocspResp.SignatureAlgorithm)
requireOcspResponseSignedBy(t, ocspResp, testEnv.issuer1.PublicKey)
// Request status for our second issuer
resp, err = sendOcspRequest(t, b, s, requestType, testEnv.leafCertIssuer2, testEnv.issuer2, requestHash)
requireSuccessNonNilResponse(t, resp, err, "ocsp get request")
requireFieldsSetInResp(t, resp, "http_content_type", "http_status_code", "http_raw_body")
require.Equal(t, 200, resp.Data["http_status_code"])
require.Equal(t, ocspResponseContentType, resp.Data["http_content_type"])
respDer = resp.Data["http_raw_body"].([]byte)
ocspResp, err = ocsp.ParseResponse(respDer, testEnv.issuer2)
require.NoError(t, err, "parsing ocsp get response")
require.Equal(t, ocsp.Good, ocspResp.Status)
require.Equal(t, requestHash, ocspResp.IssuerHash)
require.Equal(t, testEnv.issuer2, ocspResp.Certificate)
require.Equal(t, 0, ocspResp.RevocationReason)
require.Equal(t, testEnv.leafCertIssuer2.SerialNumber, ocspResp.SerialNumber)
requireOcspSignatureAlgoForKey(t, testEnv.issuer2.PublicKey, ocspResp.SignatureAlgorithm)
requireOcspResponseSignedBy(t, ocspResp, testEnv.issuer2.PublicKey)
}
func requireOcspSignatureAlgoForKey(t *testing.T, key crypto.PublicKey, algorithm x509.SignatureAlgorithm) {
switch key.(type) {
case *rsa.PublicKey:
require.Equal(t, x509.SHA256WithRSA, algorithm)
case *ecdsa.PublicKey:
require.Equal(t, x509.ECDSAWithSHA256, algorithm)
case ed25519.PublicKey:
require.Equal(t, x509.PureEd25519, algorithm)
default:
t.Fatalf("unsupported public key type %T", key)
}
}
type ocspTestEnv struct {
issuer1 *x509.Certificate
issuer2 *x509.Certificate
issuerId1 issuerID
issuerId2 issuerID
leafCertIssuer1 *x509.Certificate
leafCertIssuer2 *x509.Certificate
keyId1 keyID
keyId2 keyID
}
func setupOcspEnv(t *testing.T, keyType string) (*backend, logical.Storage, *ocspTestEnv) {
b, s := createBackendWithStorage(t)
var issuerCerts []*x509.Certificate
var leafCerts []*x509.Certificate
var issuerIds []issuerID
var keyIds []keyID
for i := 0; i < 2; i++ {
resp, err := CBWrite(b, s, "root/generate/internal", map[string]interface{}{
"key_type": keyType,
"ttl": "40h",
"common_name": "example-ocsp.com",
})
requireSuccessNonNilResponse(t, resp, err, "root/generate/internal")
requireFieldsSetInResp(t, resp, "issuer_id", "key_id")
issuerId := resp.Data["issuer_id"].(issuerID)
keyId := resp.Data["key_id"].(keyID)
resp, err = CBWrite(b, s, "roles/test"+strconv.FormatInt(int64(i), 10), map[string]interface{}{
"allow_bare_domains": true,
"allow_subdomains": true,
"allowed_domains": "foobar.com",
"no_store": false,
"generate_lease": false,
"issuer_ref": issuerId,
"key_type": keyType,
})
requireSuccessNilResponse(t, resp, err, "roles/test"+strconv.FormatInt(int64(i), 10))
resp, err = CBWrite(b, s, "issue/test"+strconv.FormatInt(int64(i), 10), map[string]interface{}{
"common_name": "test.foobar.com",
})
requireSuccessNonNilResponse(t, resp, err, "roles/test"+strconv.FormatInt(int64(i), 10))
requireFieldsSetInResp(t, resp, "certificate", "issuing_ca", "serial_number")
leafCert := parseCert(t, resp.Data["certificate"].(string))
issuingCa := parseCert(t, resp.Data["issuing_ca"].(string))
issuerCerts = append(issuerCerts, issuingCa)
leafCerts = append(leafCerts, leafCert)
issuerIds = append(issuerIds, issuerId)
keyIds = append(keyIds, keyId)
}
testEnv := &ocspTestEnv{
issuerId1: issuerIds[0],
issuer1: issuerCerts[0],
leafCertIssuer1: leafCerts[0],
keyId1: keyIds[0],
issuerId2: issuerIds[1],
issuer2: issuerCerts[1],
leafCertIssuer2: leafCerts[1],
keyId2: keyIds[1],
}
return b, s, testEnv
}
func sendOcspRequest(t *testing.T, b *backend, s logical.Storage, getOrPost string, cert, issuer *x509.Certificate, requestHash crypto.Hash) (*logical.Response, error) {
ocspRequest := generateRequest(t, requestHash, cert, issuer)
switch strings.ToLower(getOrPost) {
case "get":
return sendOcspGetRequest(b, s, ocspRequest)
case "post":
return sendOcspPostRequest(b, s, ocspRequest)
default:
t.Fatalf("unsupported value for sendOcspRequest getOrPost arg: %s", getOrPost)
}
return nil, nil
}
func sendOcspGetRequest(b *backend, s logical.Storage, ocspRequest []byte) (*logical.Response, error) {
urlEncoded := base64.StdEncoding.EncodeToString(ocspRequest)
return CBRead(b, s, "ocsp/"+urlEncoded)
}
func sendOcspPostRequest(b *backend, s logical.Storage, ocspRequest []byte) (*logical.Response, error) {
reader := io.NopCloser(bytes.NewReader(ocspRequest))
resp, err := b.HandleRequest(context.Background(), &logical.Request{
Operation: logical.UpdateOperation,
Path: "ocsp",
Storage: s,
MountPoint: "pki/",
HTTPRequest: &http.Request{
Body: reader,
},
})
return resp, err
}
func generateRequest(t *testing.T, requestHash crypto.Hash, cert *x509.Certificate, issuer *x509.Certificate) []byte {
opts := &ocsp.RequestOptions{Hash: requestHash}
ocspRequestDer, err := ocsp.CreateRequest(cert, issuer, opts)
require.NoError(t, err, "Failed generating OCSP request")
return ocspRequestDer
}
func requireOcspResponseSignedBy(t *testing.T, ocspResp *ocsp.Response, key crypto.PublicKey) {
require.Contains(t, []x509.SignatureAlgorithm{x509.SHA256WithRSA, x509.ECDSAWithSHA256}, ocspResp.SignatureAlgorithm)
hasher := sha256.New()
hashAlgo := crypto.SHA256
hasher.Write(ocspResp.TBSResponseData)
hashData := hasher.Sum(nil)
switch key.(type) {
case *rsa.PublicKey:
err := rsa.VerifyPKCS1v15(key.(*rsa.PublicKey), hashAlgo, hashData, ocspResp.Signature)
require.NoError(t, err, "the ocsp response was not signed by the expected public rsa key.")
case *ecdsa.PublicKey:
verify := ecdsa.VerifyASN1(key.(*ecdsa.PublicKey), hashData, ocspResp.Signature)
require.True(t, verify, "the certificate was not signed by the expected public ecdsa key.")
}
}