On Wed, Jul 17, 2024 at 3:42 PM David Zhang <idraw...@gmail.com> wrote:
> Totally agree. Either Implementing OCSP requests over HTTP, then parsing
> the response and then saving the results to a file, or using an OpenSSL
> client with a cron job to periodically update the file should work.
> Using a cron job would likely have less impact on PostgreSQL.

Yeah, my preference would be to farm this job out to OpenSSL entirely.
Implementing OCSP-over-HTTP ourselves seems unlikely to buy us much,
for all the work it would give us.

> Totally agree, then we should limit OCSP stapling check for the
> leaf/PostgreSQL server certificate only in v1.

Sounds good. Maybe a future version can implement a check of the full
chain, but I imagine we'll have to follow someone else's lead.

> > A question from ignorance: how does the client decide that the
> > signature on the OCSP response is actually valid for the specific
> > chain in use?
>
> If I understand correctly, the certificate used by the OCSP responder to
> sign the OCSP response must be valid for the specific chain in use, or
> the admins allow to load a new chain to validate the certificate used to
> sign the OCSP response. I think it would be better to make this part to
> be more open.

Okay. That part needs more design work IMO, and solid testing.

> > If it's okay with you, I'd like to volunteer to refactor out the
> > duplicated recipes in sslfiles.mk. I have some particular ideas in
> > mind and don't want to make you play fetch-a-rock. (No pressure to use
> > what I come up with, if you don't like it.)
> That would be great, thanks a lot in advance!

No problem! I've attached two patches that can be optionally applied
on top of yours:
- 0001 simplifies the response generation into a single recipe.
- 0002 is a bigger change that uses `openssl ca` to generate index
files, as opposed to constructing them manually ourselves.

(The makefile will get even smaller without multi-stapling support,
but I didn't want to combine that with the refactor.)

For 0002, I'm wiping the new CA index for each recipe and rebuilding
it from scratch, then copying it into place (this relies on the
.NOTPARALLEL setting up top for correctness). I think there's probably
an even simpler approach, which would be to generate a single index
that can produce all of our desired responses. I can give that a try
once multi-stapling support is pulled out.

> Thanks a lot for reviewing and providing all your feedback!

You're very welcome, thanks for working on this feature!

--Jacob
From 1f4f030233a895586aa7e114c92f4aa213250752 Mon Sep 17 00:00:00 2001
From: Jacob Champion <jacob.champ...@enterprisedb.com>
Date: Mon, 15 Jul 2024 06:40:07 -0700
Subject: [PATCH 2/2] WIP: move to OpenSSL-constructed index files

---
 src/test/ssl/conf/ocsp.config | 19 +++++++++
 src/test/ssl/sslfiles.mk      | 75 ++++++++++++++---------------------
 2 files changed, 48 insertions(+), 46 deletions(-)
 create mode 100644 src/test/ssl/conf/ocsp.config

diff --git a/src/test/ssl/conf/ocsp.config b/src/test/ssl/conf/ocsp.config
new file mode 100644
index 0000000000..831c2f681c
--- /dev/null
+++ b/src/test/ssl/conf/ocsp.config
@@ -0,0 +1,19 @@
+[ ca ]
+default_ca = ocsp
+
+# A shell of a CA, mostly duplicating the server CA, which is used only during
+# the OCSP index generation recipes.
+[ ocsp ]
+dir = ./ssl/
+
+# The database (or "index") is the main thing we want.
+database = ./ssl/ocsp-certindex
+
+# Everything else should all be unused, so we specify whatever's most
+# convenient. In particular there's no need to have a unique cert/key pair for
+# this.
+certificate = ./ssl/server_ca.crt
+private_key = ./ssl/server_ca.key
+serial = ./ssl/ocsp_ca.srl
+default_md = sha256
+policy = policy_match
diff --git a/src/test/ssl/sslfiles.mk b/src/test/ssl/sslfiles.mk
index 9ba88f0be9..bfefa6a33e 100644
--- a/src/test/ssl/sslfiles.mk
+++ b/src/test/ssl/sslfiles.mk
@@ -196,6 +196,7 @@ CLIENT_CERTS := $(CLIENTS:%=ssl/%.crt)
 root_ca_state_files := ssl/root_ca-certindex ssl/root_ca-certindex.attr 
ssl/root_ca.srl
 server_ca_state_files := ssl/server_ca-certindex ssl/server_ca-certindex.attr 
ssl/server_ca.srl
 client_ca_state_files := ssl/client_ca-certindex ssl/client_ca-certindex.attr 
ssl/client_ca.srl
+ocsp_ca_state_files := ssl/ocsp-certindex ssl/ocsp-certindex.attr 
ssl/ocsp_ca.srl
 
 # These are the workhorse recipes. `openssl ca` can't be safely run from
 # parallel processes, so we must mark the entire Makefile .NOTPARALLEL.
@@ -224,6 +225,7 @@ ssl/%.csr: ssl/%.key conf/%.config
 #
 
 .INTERMEDIATE: $(root_ca_state_files) $(server_ca_state_files) 
$(client_ca_state_files)
+.INTERMEDIATE: $(ocsp_ca_state_files)
 
 # OpenSSL requires a directory to put all generated certificates in. We don't
 # use this for anything, but we need a location.
@@ -245,68 +247,49 @@ ssl/%.srl:
 # OCSP
 #
 .INTERMEDIATE: $(OCSPS:%=ssl/%.idx)
+
 # given status 'V' without 'revocation date' to generate an ocsp response with 
status 'good' for 10000 days
-ssl/server-ocsp-good.idx: ssl/server-cn-only.crt
-       expiration_date=$$($(OPENSSL) x509 -in $< -noout -dates | grep 
"notAfter" | cut -d "=" -f 2 | xargs -I {} date --date="{}" --utc 
+'%Y%m%d%H%M%S'Z); \
-       serial_number=$$($(OPENSSL) x509 -in $< -noout -serial | cut -d "=" -f 
2); \
-       cert_subject=$$($(OPENSSL) x509 -in $< -noout -subject | sed 
's/subject=OU = /\/OU=/' | sed 's/, CN = /\/CN=/'); \
-       echo "V\t$$expiration_date\t\t$$serial_number\tunknown\t$$cert_subject" 
> $@
+ssl/server-ocsp-good.idx: conf/ocsp.config ssl/server-cn-only.crt | 
$(ocsp_ca_state_files)
+       : > ssl/ocsp-certindex
+       openssl ca -config conf/ocsp.config -valid ssl/server-cn-only.crt
+       cp ssl/ocsp-certindex $@
 
 # given status 'R' and 'revocation date' to generate an ocsp response with 
status 'revoked' for 10000 days
-ssl/server-ocsp-revoked.idx: ssl/server-cn-only.crt
-       expiration_date=$$($(OPENSSL) x509 -in $< -noout -dates | grep 
"notAfter" | cut -d "=" -f 2 | xargs -I {} date --date="{}" --utc 
+'%Y%m%d%H%M%S'Z); \
-       serial_number=$$($(OPENSSL) x509 -in $< -noout -serial | cut -d "=" -f 
2); \
-       revocation_date=$$(date --utc +'%y%m%d%H%M%SZ'); \
-       cert_subject=$$($(OPENSSL) x509 -in $< -noout -subject | sed 
's/subject=OU = /\/OU=/' | sed 's/, CN = /\/CN=/'); \
-       echo 
"R\t$$expiration_date\t$$revocation_date\t$$serial_number\tunknown\t$$cert_subject"
 > $@
+ssl/server-ocsp-revoked.idx: conf/ocsp.config ssl/server-cn-only.crt | 
$(ocsp_ca_state_files)
+       : > ssl/ocsp-certindex
+       openssl ca -config conf/ocsp.config -revoke ssl/server-cn-only.crt
+       cp ssl/ocsp-certindex $@
 
 # generate an ocsp response with status 'unknown' using a none-existing 
certificate serial number 1970010100000000
-ssl/server-ocsp-unknown.idx: ssl/server-cn-only.crt
-       expiration_date=$$($(OPENSSL) x509 -in $< -noout -dates | grep 
"notAfter" | cut -d "=" -f 2 | xargs -I {} date --date="{}" --utc 
+'%Y%m%d%H%M%S'Z); \
-       serial_number="1970010100000000"; \
-       cert_subject=$$($(OPENSSL) x509 -in $< -noout -subject | sed 
's/subject=OU = /\/OU=/' | sed 's/, CN = /\/CN=/'); \
-       echo "V\t$$expiration_date\t\t$$serial_number\tunknown\t$$cert_subject" 
> $@
+ssl/server-ocsp-unknown.idx:
+       touch $@
 
 # generate an ocsp response with status 'good' but nextUpdate 'expired' in 
only 1 minute
-ssl/server-ocsp-expired.idx: ssl/server-cn-only.crt
-       expiration_date=$$($(OPENSSL) x509 -in $< -noout -dates | grep 
"notAfter" | cut -d "=" -f 2 | xargs -I {} date --date="{}" --utc 
+'%Y%m%d%H%M%S'Z); \
-       serial_number=$$($(OPENSSL) x509 -in $< -noout -serial | cut -d "=" -f 
2); \
-       cert_subject=$$($(OPENSSL) x509 -in $< -noout -subject | sed 
's/subject=OU = /\/OU=/' | sed 's/, CN = /\/CN=/'); \
-       echo "V\t$$expiration_date\t\t$$serial_number\tunknown\t$$cert_subject" 
> $@
+ssl/server-ocsp-expired.idx: ssl/server-ocsp-good.idx
+       cp $< $@
 
 # server-cn-only.crt (good), ocsp response for server_ca.crt in 
(good|revoked|unknown|expired)
 # good, good
-ssl/server-ca-ocsp-good.idx: ssl/server_ca.crt ssl/server-ocsp-good.idx
-       expiration_date=$$($(OPENSSL) x509 -in $< -noout -dates | grep 
"notAfter" | cut -d "=" -f 2 | xargs -I {} date --date="{}" --utc 
+'%Y%m%d%H%M%S'Z); \
-       serial_number=$$($(OPENSSL) x509 -in $< -noout -serial | cut -d "=" -f 
2); \
-       cert_subject=$$($(OPENSSL) x509 -in $< -noout -subject | sed 
's/subject=OU = /\/OU=/' | sed 's/, CN = /\/CN=/'); \
-       cat ssl/server-ocsp-good.idx > $@; \
-       echo "V\t$$expiration_date\t\t$$serial_number\tunknown\t$$cert_subject" 
>> $@
+ssl/server-ca-ocsp-good.idx: conf/ocsp.config ssl/server-cn-only.crt 
ssl/server_ca.crt | $(ocsp_ca_state_files)
+       : > ssl/ocsp-certindex
+       openssl ca -config conf/ocsp.config -valid ssl/server-cn-only.crt
+       openssl ca -config conf/ocsp.config -valid ssl/server_ca.crt
+       cp ssl/ocsp-certindex $@
 
 # good, revoked
-ssl/server-ca-ocsp-revoked.idx: ssl/server_ca.crt ssl/server-ocsp-good.idx
-       expiration_date=$$($(OPENSSL) x509 -in $< -noout -dates | grep 
"notAfter" | cut -d "=" -f 2 | xargs -I {} date --date="{}" --utc 
+'%Y%m%d%H%M%S'Z); \
-       serial_number=$$($(OPENSSL) x509 -in $< -noout -serial | cut -d "=" -f 
2); \
-       revocation_date=$$(date --utc +'%y%m%d%H%M%SZ'); \
-       cert_subject=$$($(OPENSSL) x509 -in $< -noout -subject | sed 
's/subject=OU = /\/OU=/' | sed 's/, CN = /\/CN=/'); \
-       cat ssl/server-ocsp-good.idx > $@; \
-       echo 
"R\t$$expiration_date\t$$revocation_date\t$$serial_number\tunknown\t$$cert_subject"
 >> $@
+ssl/server-ca-ocsp-revoked.idx: conf/ocsp.config ssl/server-cn-only.crt 
ssl/server_ca.crt | $(ocsp_ca_state_files)
+       : > ssl/ocsp-certindex
+       openssl ca -config conf/ocsp.config -valid ssl/server-cn-only.crt
+       openssl ca -config conf/ocsp.config -revoke ssl/server_ca.crt
+       cp ssl/ocsp-certindex $@
 
 # good, unknown
-ssl/server-ca-ocsp-unknown.idx: ssl/server_ca.crt ssl/server-ocsp-good.idx
-       expiration_date=$$($(OPENSSL) x509 -in $< -noout -dates | grep 
"notAfter" | cut -d "=" -f 2 | xargs -I {} date --date="{}" --utc 
+'%Y%m%d%H%M%S'Z); \
-       serial_number="1970010100000001"; \
-       cert_subject=$$($(OPENSSL) x509 -in $< -noout -subject | sed 
's/subject=OU = /\/OU=/' | sed 's/, CN = /\/CN=/'); \
-       cat ssl/server-ocsp-good.idx > $@; \
-       echo "V\t$$expiration_date\t\t$$serial_number\tunknown\t$$cert_subject" 
>> $@
+ssl/server-ca-ocsp-unknown.idx: ssl/server-ocsp-good.idx
+       cp $< $@
 
 # good, expired
-ssl/server-ca-ocsp-expired.idx: ssl/server_ca.crt ssl/server-ocsp-good.idx
-       expiration_date=$$($(OPENSSL) x509 -in $< -noout -dates | grep 
"notAfter" | cut -d "=" -f 2 | xargs -I {} date --date="{}" --utc 
+'%Y%m%d%H%M%S'Z); \
-       serial_number=$$($(OPENSSL) x509 -in $< -noout -serial | cut -d "=" -f 
2); \
-       cert_subject=$$($(OPENSSL) x509 -in $< -noout -subject | sed 
's/subject=OU = /\/OU=/' | sed 's/, CN = /\/CN=/'); \
-       cat ssl/server-ocsp-good.idx > $@; \
-       echo "V\t$$expiration_date\t\t$$serial_number\tunknown\t$$cert_subject" 
>> $@
+ssl/server-ca-ocsp-expired.idx: ssl/server-ca-ocsp-good.idx
+       cp $< $@
 
 # All of the responses have the server cert in the chain.
 OCSPCHAIN = -issuer ssl/server_ca.crt -cert ssl/server-cn-only.crt
-- 
2.34.1

From 7e7776ec8b3d09f2ad00c55897e2437d39b0f398 Mon Sep 17 00:00:00 2001
From: Jacob Champion <jacob.champ...@enterprisedb.com>
Date: Fri, 12 Jul 2024 11:40:18 -0700
Subject: [PATCH 1/2] WIP: simplify .res generation

---
 src/test/ssl/sslfiles.mk | 49 ++++++++++++++--------------------------
 1 file changed, 17 insertions(+), 32 deletions(-)

diff --git a/src/test/ssl/sslfiles.mk b/src/test/ssl/sslfiles.mk
index fd92970698..9ba88f0be9 100644
--- a/src/test/ssl/sslfiles.mk
+++ b/src/test/ssl/sslfiles.mk
@@ -308,38 +308,23 @@ ssl/server-ca-ocsp-expired.idx: ssl/server_ca.crt 
ssl/server-ocsp-good.idx
        cat ssl/server-ocsp-good.idx > $@; \
        echo "V\t$$expiration_date\t\t$$serial_number\tunknown\t$$cert_subject" 
>> $@
 
-$(OCSPRES):
-# server-cn-only: 'good'
-ssl/server-ocsp-good.res: ssl/server-ocsp-good.idx ssl/server-cn-only.crt 
ssl/ocsp_ca.crt ssl/root+server_ca.crt
-       $(OPENSSL) ocsp -index $< -rsigner ssl/ocsp_ca.crt -rkey 
ssl/ocsp_ca.key -CA ssl/root+server_ca.crt -issuer ssl/server_ca.crt -ndays 
10000 -cert ssl/server-cn-only.crt -respout $@
-
-# server-cn-only: 'revoked'
-ssl/server-ocsp-revoked.res: ssl/server-ocsp-revoked.idx 
ssl/server-cn-only.crt ssl/ocsp_ca.crt ssl/root+server_ca.crt
-       $(OPENSSL) ocsp -index $< -rsigner ssl/ocsp_ca.crt -rkey 
ssl/ocsp_ca.key -CA ssl/root+server_ca.crt -issuer ssl/server_ca.crt -ndays 
10000 -cert ssl/server-cn-only.crt -respout $@
-
-# server-cn-only: 'unknown'
-ssl/server-ocsp-unknown.res: ssl/server-ocsp-unknown.idx 
ssl/server-cn-only.crt ssl/ocsp_ca.crt ssl/root+server_ca.crt
-       $(OPENSSL) ocsp -index $< -rsigner ssl/ocsp_ca.crt -rkey 
ssl/ocsp_ca.key -CA ssl/root+server_ca.crt -issuer ssl/server_ca.crt -ndays 
10000 -cert ssl/server-cn-only.crt -respout $@
-
-# server-cn-only: 'expired'
-ssl/server-ocsp-expired.res: ssl/server-ocsp-expired.idx 
ssl/server-cn-only.crt ssl/ocsp_ca.crt ssl/root+server_ca.crt
-       $(OPENSSL) ocsp -index $< -rsigner ssl/ocsp_ca.crt -rkey 
ssl/ocsp_ca.key -CA ssl/root+server_ca.crt -issuer ssl/server_ca.crt -nmin 1 
-cert ssl/server-cn-only.crt -respout $@
-
-# server-cn-only, server_ca: 'good, good'
-ssl/server-ca-ocsp-good.res: ssl/server-ca-ocsp-good.idx 
ssl/server-cn-only.crt ssl/ocsp_ca.crt ssl/root+server_ca.crt ssl/server_ca.crt
-       $(OPENSSL) ocsp -index $< -rsigner ssl/ocsp_ca.crt -rkey 
ssl/ocsp_ca.key -CA ssl/root+server_ca.crt -ndays 10000 -issuer 
ssl/server_ca.crt -cert ssl/server-cn-only.crt -issuer ssl/root_ca.crt -cert 
ssl/server_ca.crt -respout $@
-
-# server-cn-only, server_ca: 'good, revoked'
-ssl/server-ca-ocsp-revoked.res: ssl/server-ca-ocsp-revoked.idx 
ssl/server-cn-only.crt ssl/ocsp_ca.crt ssl/root+server_ca.crt ssl/server_ca.crt
-       $(OPENSSL) ocsp -index $< -rsigner ssl/ocsp_ca.crt -rkey 
ssl/ocsp_ca.key -CA ssl/root+server_ca.crt -ndays 10000 -issuer 
ssl/server_ca.crt -cert ssl/server-cn-only.crt -issuer ssl/root_ca.crt -cert 
ssl/server_ca.crt -respout $@
-
-# server-cn-only, server_ca: 'good, unknown'
-ssl/server-ca-ocsp-unknown.res: ssl/server-ca-ocsp-unknown.idx 
ssl/server-cn-only.crt ssl/ocsp_ca.crt ssl/root+server_ca.crt ssl/server_ca.crt
-       $(OPENSSL) ocsp -index $< -rsigner ssl/ocsp_ca.crt -rkey 
ssl/ocsp_ca.key -CA ssl/root+server_ca.crt -ndays 10000 -issuer 
ssl/server_ca.crt -cert ssl/server-cn-only.crt -issuer ssl/root_ca.crt -cert 
ssl/server_ca.crt -respout $@
-
-# server-cn-only, server_ca: 'good, expired'
-ssl/server-ca-ocsp-expired.res: ssl/server-ca-ocsp-expired.idx 
ssl/server-cn-only.crt ssl/ocsp_ca.crt ssl/root+server_ca.crt ssl/server_ca.crt
-       $(OPENSSL) ocsp -index $< -rsigner ssl/ocsp_ca.crt -rkey 
ssl/ocsp_ca.key -CA ssl/root+server_ca.crt -nmin 1 -issuer ssl/server_ca.crt 
-cert ssl/server-cn-only.crt -issuer ssl/root_ca.crt -cert ssl/server_ca.crt 
-respout $@
+# All of the responses have the server cert in the chain.
+OCSPCHAIN = -issuer ssl/server_ca.crt -cert ssl/server-cn-only.crt
+$(OCSPRES): ssl/server_ca.crt ssl/server-cn-only.crt
+
+# Additionally, the server CA is part of the server-ca-* responses.
+ssl/server-ca-%.res: OCSPCHAIN += -issuer ssl/root_ca.crt -cert 
ssl/server_ca.crt
+ssl/server-ca-%.res: ssl/root_ca.crt
+
+# Most responses should "never" expire, except the ones being explicitly tested
+# for expiration.
+OCSPEXP = -ndays 10000
+ssl/%-ocsp-expired.res: OCSPEXP = -nmin 1
+
+$(OCSPRES): ssl/%.res: ssl/%.idx ssl/ocsp_ca.crt ssl/ocsp_ca.key 
ssl/root+server_ca.crt
+       $(OPENSSL) ocsp -index $< -respout $@ -rsigner ssl/ocsp_ca.crt \
+               -rkey ssl/ocsp_ca.key -CA ssl/root+server_ca.crt \
+               $(OCSPEXP) $(OCSPCHAIN)
 
 #
 # CRLs
-- 
2.34.1

Reply via email to