This is an automated email from the ASF dual-hosted git repository.

reshke pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/cloudberry.git

commit 86b4be4e78c1b66f406c844d06b37f956c8c8139
Author: Jacob Champion <[email protected]>
AuthorDate: Mon Aug 10 06:38:25 2026 -0700

    pgcrypto: Add option to revert to prior decryption behavior
    
    The previous commit raises an ERROR during PGP operations if OpenSSL
    does not support the cipher in use. However, any existing messages
    created with faulty encryption will no longer be accessible via
    pgp_[sym|pub]_decrypt().
    
    To help users out of this situation, add a new ignore-cipher-failure
    option which reverts to the broken behavior during decryption only. A
    faulty encryption wrapper, created by an OpenSSL configuration that does
    not support the cipher, can then be stripped back off by that same
    OpenSSL in order to safely reencrypt it. (Note that when OpenSSL does
    support the cipher, corrupted messages will not be decrypted regardless
    of the ignore-cipher-failure setting; this is unchanged.)
    
    The new tests add a corrupted Blowfish message for both public- and
    symmetric-key decryption, resulting in the following test matrix:
    
    - Blowfish supported, default behavior:      fails to decrypt
    - Blowfish supported, ignore-cipher-failure: fails to decrypt
    - Blowfish unsupported, default behavior:    fails to load cipher
    - Blowfish unsupported, ignore-cipher-failure: strips faulty encryption
    
    The previous commit's change to the pubkey tests is expanded similarly:
    correctly encrypted messages cannot be decrypted by an OpenSSL that does
    not support the cipher, regardless of the option's setting, though the
    failure mode will change.
    
    Suggested-by: Noah Misch <[email protected]>
    Reviewed-by: Daniel Gustafsson <[email protected]>
    Reviewed-by: Noah Misch <[email protected]>
    Security: CVE-2026-14663
    Backpatch-through: 14
---
 contrib/pgcrypto/expected/pgp-decrypt.out          | 26 ++++++++++++++++++
 contrib/pgcrypto/expected/pgp-decrypt_1.out        | 30 ++++++++++++++++++++
 contrib/pgcrypto/expected/pgp-info.out             |  3 +-
 contrib/pgcrypto/expected/pgp-pubkey-decrypt.out   | 30 ++++++++++++++++++++
 contrib/pgcrypto/expected/pgp-pubkey-decrypt_1.out | 30 ++++++++++++++++++++
 contrib/pgcrypto/pgp-cfb.c                         | 21 ++++++++++----
 contrib/pgcrypto/pgp-decrypt.c                     |  9 ++++--
 contrib/pgcrypto/pgp-encrypt.c                     |  6 ++--
 contrib/pgcrypto/pgp-pgsql.c                       |  2 ++
 contrib/pgcrypto/pgp-pubkey.c                      |  9 +++++-
 contrib/pgcrypto/pgp.c                             |  9 ++++++
 contrib/pgcrypto/pgp.h                             |  7 ++++-
 contrib/pgcrypto/sql/pgp-decrypt.sql               | 26 ++++++++++++++++++
 contrib/pgcrypto/sql/pgp-pubkey-decrypt.sql        | 27 ++++++++++++++++++
 doc/src/sgml/pgcrypto.sgml                         | 32 ++++++++++++++++++++++
 15 files changed, 254 insertions(+), 13 deletions(-)

diff --git a/contrib/pgcrypto/expected/pgp-decrypt.out 
b/contrib/pgcrypto/expected/pgp-decrypt.out
index 1db89e8c00a..960bf52c855 100644
--- a/contrib/pgcrypto/expected/pgp-decrypt.out
+++ b/contrib/pgcrypto/expected/pgp-decrypt.out
@@ -438,3 +438,29 @@ UCAAw2JRIISttRHMfDpDuZJpvYo=
 '), 'key', 'debug=1');
 NOTICE:  dbg: parse_compressed_data: bzip2 unsupported
 ERROR:  Unsupported compression algorithm
+-- Check ignore-cipher-failure. This message isn't actually encrypted; it was
+-- created with cipher-algo=bf using an OpenSSL that didn't actually support
+-- Blowfish. After the fix for CVE-2026-14663, we no longer create these broken
+-- ciphertexts, but we allow users to return to the previous behavior during
+-- decryption so that the bad wrapper can be stripped.
+--
+-- Note that if Blowfish is supported by the linked OpenSSL, both decryptions
+-- will fail.
+select pgp_sym_decrypt(dearmor('
+-----BEGIN PGP MESSAGE-----
+
+ww0EBAMC8wIKbtvzJtxi0jABUleCwFJWGCkYKcsNdABqdtXaU2VjcmV0LtMUlnPH3A2QBmZrcucm
+1GPb/s2Bkdg=
+=6aqD
+-----END PGP MESSAGE-----
+'), 'wrong key');
+ERROR:  Wrong key or corrupt data
+select pgp_sym_decrypt(dearmor('
+-----BEGIN PGP MESSAGE-----
+
+ww0EBAMC8wIKbtvzJtxi0jABUleCwFJWGCkYKcsNdABqdtXaU2VjcmV0LtMUlnPH3A2QBmZrcucm
+1GPb/s2Bkdg=
+=6aqD
+-----END PGP MESSAGE-----
+'), 'wrong key', 'ignore-cipher-failure=1');
+ERROR:  Wrong key or corrupt data
diff --git a/contrib/pgcrypto/expected/pgp-decrypt_1.out 
b/contrib/pgcrypto/expected/pgp-decrypt_1.out
index ef2e27b8aa7..f00acdcecf2 100644
--- a/contrib/pgcrypto/expected/pgp-decrypt_1.out
+++ b/contrib/pgcrypto/expected/pgp-decrypt_1.out
@@ -434,3 +434,33 @@ UCAAw2JRIISttRHMfDpDuZJpvYo=
 '), 'key', 'debug=1');
 NOTICE:  dbg: parse_compressed_data: bzip2 unsupported
 ERROR:  Unsupported compression algorithm
+-- Check ignore-cipher-failure. This message isn't actually encrypted; it was
+-- created with cipher-algo=bf using an OpenSSL that didn't actually support
+-- Blowfish. After the fix for CVE-2026-14663, we no longer create these broken
+-- ciphertexts, but we allow users to return to the previous behavior during
+-- decryption so that the bad wrapper can be stripped.
+--
+-- Note that if Blowfish is supported by the linked OpenSSL, both decryptions
+-- will fail.
+select pgp_sym_decrypt(dearmor('
+-----BEGIN PGP MESSAGE-----
+
+ww0EBAMC8wIKbtvzJtxi0jABUleCwFJWGCkYKcsNdABqdtXaU2VjcmV0LtMUlnPH3A2QBmZrcucm
+1GPb/s2Bkdg=
+=6aqD
+-----END PGP MESSAGE-----
+'), 'wrong key');
+ERROR:  encrypt error: Cipher cannot be initialized
+select pgp_sym_decrypt(dearmor('
+-----BEGIN PGP MESSAGE-----
+
+ww0EBAMC8wIKbtvzJtxi0jABUleCwFJWGCkYKcsNdABqdtXaU2VjcmV0LtMUlnPH3A2QBmZrcucm
+1GPb/s2Bkdg=
+=6aqD
+-----END PGP MESSAGE-----
+'), 'wrong key', 'ignore-cipher-failure=1');
+ pgp_sym_decrypt 
+-----------------
+ Secret.
+(1 row)
+
diff --git a/contrib/pgcrypto/expected/pgp-info.out 
b/contrib/pgcrypto/expected/pgp-info.out
index 90648383730..909e7f7851e 100644
--- a/contrib/pgcrypto/expected/pgp-info.out
+++ b/contrib/pgcrypto/expected/pgp-info.out
@@ -75,5 +75,6 @@ from encdata order by id;
  B68504FD128E1FF9
  FD0206C409B74875
  FD0206C409B74875
-(5 rows)
+ D936CF64BB73F466
+(6 rows)
 
diff --git a/contrib/pgcrypto/expected/pgp-pubkey-decrypt.out 
b/contrib/pgcrypto/expected/pgp-pubkey-decrypt.out
index b4b6810a3c5..d3bb5f1b06d 100644
--- a/contrib/pgcrypto/expected/pgp-pubkey-decrypt.out
+++ b/contrib/pgcrypto/expected/pgp-pubkey-decrypt.out
@@ -585,6 +585,20 @@ 
blH2nKZC9d6fi4YzSYMepZpMOFR65M80MCMiDUGnZBB8sEADu2/iVtqDUeG8mAA=
 =PHJ1
 -----END PGP MESSAGE-----
 ');
+-- CVE-2026-14663. This message was created with cipher-algo=bf using an 
OpenSSL
+-- that didn't actually support Blowfish.
+insert into encdata (id, data) values (6, '
+-----BEGIN PGP MESSAGE-----
+
+wcBOA9k2z2S7c/RmEAP8DYbU6AeEo6riMMdnf2G62BM9gC0Z32ODydewy3Ki8AnSzpwBDAHuDMcr
+P6RJDWvBOVOwgxHEwR7ZHMoFRDJEXdo6rQ9dQpDtbasMLyi6Lm1q+PbEefVd9WkU7fvFAFQx8k3t
+lxrlWg/byoNplc7/hFxIFO8bN+FIlLgilAdApNcD/3Mg2/nd7pczovsYoryf9ib04kQ+SVWs3iNE
+StoyEXT+oaT8u1vAxiY7fzPpQX1pnlHBUXn+v1J6LQL5Bwi5CTqOyDSyaFfgU0gQwTReFjS6L4Fs
+Cv+2cFwbJBGIzr1aI4DLbzSelkmVm4hbOVeET4DJVlUVhhIyy6ZfoXiTEG6s0jMB2JdRGIl0EUQR
+RMsQdABqdt5jU2VjcmV0IG1zZ9MUIIP4SPiU2pM/nF/A1hrltMhn/ZI=
+=Mkdj
+-----END PGP MESSAGE-----
+');
 -- successful decrypt
 select pgp_pub_decrypt(dearmor(data), dearmor(seckey))
 from keytbl, encdata where keytbl.id=1 and encdata.id=1;
@@ -600,6 +614,13 @@ from keytbl, encdata where keytbl.id=2 and encdata.id=2;
  Secret msg
 (1 row)
 
+select pgp_pub_decrypt(dearmor(data), dearmor(seckey), '', 
'ignore-cipher-failure=1')
+from keytbl, encdata where keytbl.id=2 and encdata.id=2;
+ pgp_pub_decrypt 
+-----------------
+ Secret msg
+(1 row)
+
 select pgp_pub_decrypt(dearmor(data), dearmor(seckey))
 from keytbl, encdata where keytbl.id=3 and encdata.id=3;
  pgp_pub_decrypt 
@@ -654,3 +675,12 @@ from keytbl, encdata where keytbl.id=5 and encdata.id=1;
 select pgp_pub_decrypt(dearmor(data), dearmor(seckey))
 from keytbl, encdata where keytbl.id=6 and encdata.id=5;
 ERROR:  Wrong key or corrupt data
+-- Check that ignore-cipher-failure can strip faulty encryption if OpenSSL
+-- doesn't support the cipher. (The decryption will correctly fail both times 
if
+-- OpenSSL does support it.)
+select pgp_pub_decrypt(dearmor(data), dearmor(seckey))
+from keytbl, encdata where keytbl.id=1 and encdata.id=6;
+ERROR:  Wrong key or corrupt data
+select pgp_pub_decrypt(dearmor(data), dearmor(seckey), '', 
'ignore-cipher-failure=1')
+from keytbl, encdata where keytbl.id=1 and encdata.id=6;
+ERROR:  Wrong key or corrupt data
diff --git a/contrib/pgcrypto/expected/pgp-pubkey-decrypt_1.out 
b/contrib/pgcrypto/expected/pgp-pubkey-decrypt_1.out
index 7e2e1f98fca..ac9307daf5e 100644
--- a/contrib/pgcrypto/expected/pgp-pubkey-decrypt_1.out
+++ b/contrib/pgcrypto/expected/pgp-pubkey-decrypt_1.out
@@ -585,6 +585,20 @@ 
blH2nKZC9d6fi4YzSYMepZpMOFR65M80MCMiDUGnZBB8sEADu2/iVtqDUeG8mAA=
 =PHJ1
 -----END PGP MESSAGE-----
 ');
+-- CVE-2026-14663. This message was created with cipher-algo=bf using an 
OpenSSL
+-- that didn't actually support Blowfish.
+insert into encdata (id, data) values (6, '
+-----BEGIN PGP MESSAGE-----
+
+wcBOA9k2z2S7c/RmEAP8DYbU6AeEo6riMMdnf2G62BM9gC0Z32ODydewy3Ki8AnSzpwBDAHuDMcr
+P6RJDWvBOVOwgxHEwR7ZHMoFRDJEXdo6rQ9dQpDtbasMLyi6Lm1q+PbEefVd9WkU7fvFAFQx8k3t
+lxrlWg/byoNplc7/hFxIFO8bN+FIlLgilAdApNcD/3Mg2/nd7pczovsYoryf9ib04kQ+SVWs3iNE
+StoyEXT+oaT8u1vAxiY7fzPpQX1pnlHBUXn+v1J6LQL5Bwi5CTqOyDSyaFfgU0gQwTReFjS6L4Fs
+Cv+2cFwbJBGIzr1aI4DLbzSelkmVm4hbOVeET4DJVlUVhhIyy6ZfoXiTEG6s0jMB2JdRGIl0EUQR
+RMsQdABqdt5jU2VjcmV0IG1zZ9MUIIP4SPiU2pM/nF/A1hrltMhn/ZI=
+=Mkdj
+-----END PGP MESSAGE-----
+');
 -- successful decrypt
 select pgp_pub_decrypt(dearmor(data), dearmor(seckey))
 from keytbl, encdata where keytbl.id=1 and encdata.id=1;
@@ -596,6 +610,9 @@ from keytbl, encdata where keytbl.id=1 and encdata.id=1;
 select pgp_pub_decrypt(dearmor(data), dearmor(seckey))
 from keytbl, encdata where keytbl.id=2 and encdata.id=2;
 ERROR:  encrypt error: Cipher cannot be initialized
+select pgp_pub_decrypt(dearmor(data), dearmor(seckey), '', 
'ignore-cipher-failure=1')
+from keytbl, encdata where keytbl.id=2 and encdata.id=2;
+ERROR:  Wrong key or corrupt data
 select pgp_pub_decrypt(dearmor(data), dearmor(seckey))
 from keytbl, encdata where keytbl.id=3 and encdata.id=3;
  pgp_pub_decrypt 
@@ -650,3 +667,16 @@ from keytbl, encdata where keytbl.id=5 and encdata.id=1;
 select pgp_pub_decrypt(dearmor(data), dearmor(seckey))
 from keytbl, encdata where keytbl.id=6 and encdata.id=5;
 ERROR:  Wrong key or corrupt data
+-- Check that ignore-cipher-failure can strip faulty encryption if OpenSSL
+-- doesn't support the cipher. (The decryption will correctly fail both times 
if
+-- OpenSSL does support it.)
+select pgp_pub_decrypt(dearmor(data), dearmor(seckey))
+from keytbl, encdata where keytbl.id=1 and encdata.id=6;
+ERROR:  encrypt error: Cipher cannot be initialized
+select pgp_pub_decrypt(dearmor(data), dearmor(seckey), '', 
'ignore-cipher-failure=1')
+from keytbl, encdata where keytbl.id=1 and encdata.id=6;
+ pgp_pub_decrypt 
+-----------------
+ Secret msg
+(1 row)
+
diff --git a/contrib/pgcrypto/pgp-cfb.c b/contrib/pgcrypto/pgp-cfb.c
index e6ae3a49244..c81534bf1ef 100644
--- a/contrib/pgcrypto/pgp-cfb.c
+++ b/contrib/pgcrypto/pgp-cfb.c
@@ -43,6 +43,7 @@ struct PGP_CFB
        int                     pos;
        int                     block_no;
        int                     resync;
+       int                     ignore_decrypt_cipher_failure;  /* for 
CVE-2026-14663 recovery */
        uint8           fr[PGP_MAX_BLOCK];
        uint8           fre[PGP_MAX_BLOCK];
        uint8           encbuf[PGP_MAX_BLOCK];
@@ -50,7 +51,7 @@ struct PGP_CFB
 
 int
 pgp_cfb_create(PGP_CFB **ctx_p, int algo, const uint8 *key, int key_len,
-                          int resync, uint8 *iv)
+                          int resync, uint8 *iv, int 
ignore_decrypt_cipher_failure)
 {
        int                     res;
        PX_Cipher  *ciph;
@@ -71,6 +72,7 @@ pgp_cfb_create(PGP_CFB **ctx_p, int algo, const uint8 *key, 
int key_len,
        ctx->ciph = ciph;
        ctx->block_size = px_cipher_block_size(ciph);
        ctx->resync = resync;
+       ctx->ignore_decrypt_cipher_failure = ignore_decrypt_cipher_failure;
 
        if (iv)
                memcpy(ctx->fr, iv, ctx->block_size);
@@ -195,7 +197,7 @@ mix_decrypt_resync(PGP_CFB *ctx, const uint8 *data, int 
len, uint8 *dst)
  */
 static int
 cfb_process(PGP_CFB *ctx, const uint8 *data, int len, uint8 *dst,
-                       mix_data_t mix_data)
+                       mix_data_t mix_data, int ignore_cipher_failure)
 {
        int                     n;
        int                     res;
@@ -224,7 +226,14 @@ cfb_process(PGP_CFB *ctx, const uint8 *data, int len, 
uint8 *dst,
                int                     err;
 
                err = px_cipher_encrypt(ctx->ciph, 0, ctx->fr, ctx->block_size, 
ctx->fre, &rlen);
-               if (err)
+
+               /*
+                * XXX Ignoring cipher failures is dangerous, but we allow it 
during
+                * decryption to return to the behavior prior to the fix for
+                * CVE-2026-14663. This lets users recover data from a 
badly-encrypted
+                * message.
+                */
+               if (err && !ignore_cipher_failure)
                        ereport(ERROR,
                                        
(errcode(ERRCODE_EXTERNAL_ROUTINE_INVOCATION_EXCEPTION),
                                         errmsg("encrypt error: %s", 
px_strerror(err))));
@@ -259,7 +268,8 @@ pgp_cfb_encrypt(PGP_CFB *ctx, const uint8 *data, int len, 
uint8 *dst)
 {
        mix_data_t      mix = ctx->resync ? mix_encrypt_resync : 
mix_encrypt_normal;
 
-       return cfb_process(ctx, data, len, dst, mix);
+       return cfb_process(ctx, data, len, dst, mix,
+                                          0 /* never ignore cipher failures 
for encrypt */ );
 }
 
 int
@@ -267,5 +277,6 @@ pgp_cfb_decrypt(PGP_CFB *ctx, const uint8 *data, int len, 
uint8 *dst)
 {
        mix_data_t      mix = ctx->resync ? mix_decrypt_resync : 
mix_decrypt_normal;
 
-       return cfb_process(ctx, data, len, dst, mix);
+       return cfb_process(ctx, data, len, dst, mix,
+                                          ctx->ignore_decrypt_cipher_failure);
 }
diff --git a/contrib/pgcrypto/pgp-decrypt.c b/contrib/pgcrypto/pgp-decrypt.c
index e1ea5b3e58d..887fbc19278 100644
--- a/contrib/pgcrypto/pgp-decrypt.c
+++ b/contrib/pgcrypto/pgp-decrypt.c
@@ -595,7 +595,8 @@ decrypt_key(PGP_Context *ctx, const uint8 *src, int len)
        PGP_CFB    *cfb;
 
        res = pgp_cfb_create(&cfb, ctx->s2k_cipher_algo,
-                                                ctx->s2k.key, 
ctx->s2k.key_len, 0, NULL);
+                                                ctx->s2k.key, 
ctx->s2k.key_len, 0, NULL,
+                                                ctx->ignore_cipher_failure);
        if (res < 0)
                return res;
 
@@ -983,7 +984,8 @@ parse_symenc_data(PGP_Context *ctx, PullFilter *pkt, MBuf 
*dst)
        PullFilter *pf_prefix = NULL;
 
        res = pgp_cfb_create(&cfb, ctx->cipher_algo,
-                                                ctx->sess_key, 
ctx->sess_key_len, 1, NULL);
+                                                ctx->sess_key, 
ctx->sess_key_len, 1, NULL,
+                                                ctx->ignore_cipher_failure);
        if (res < 0)
                goto out;
 
@@ -1026,7 +1028,8 @@ parse_symenc_mdc_data(PGP_Context *ctx, PullFilter *pkt, 
MBuf *dst)
        }
 
        res = pgp_cfb_create(&cfb, ctx->cipher_algo,
-                                                ctx->sess_key, 
ctx->sess_key_len, 0, NULL);
+                                                ctx->sess_key, 
ctx->sess_key_len, 0, NULL,
+                                                ctx->ignore_cipher_failure);
        if (res < 0)
                goto out;
 
diff --git a/contrib/pgcrypto/pgp-encrypt.c b/contrib/pgcrypto/pgp-encrypt.c
index f7467c9b1cb..968e8c92ce4 100644
--- a/contrib/pgcrypto/pgp-encrypt.c
+++ b/contrib/pgcrypto/pgp-encrypt.c
@@ -174,7 +174,8 @@ encrypt_init(PushFilter *next, void *init_arg, void 
**priv_p)
                        return res;
        }
        res = pgp_cfb_create(&ciph, ctx->cipher_algo,
-                                                ctx->sess_key, 
ctx->sess_key_len, resync, NULL);
+                                                ctx->sess_key, 
ctx->sess_key_len, resync, NULL,
+                                                0 /* never ignore cipher 
failures for encrypt */ );
        if (res < 0)
                return res;
 
@@ -505,7 +506,8 @@ symencrypt_sesskey(PGP_Context *ctx, uint8 *dst)
        uint8           algo = ctx->cipher_algo;
 
        res = pgp_cfb_create(&cfb, ctx->s2k_cipher_algo,
-                                                ctx->s2k.key, 
ctx->s2k.key_len, 0, NULL);
+                                                ctx->s2k.key, 
ctx->s2k.key_len, 0, NULL,
+                                                0 /* never ignore cipher 
failures for encrypt */ );
        if (res < 0)
                return res;
 
diff --git a/contrib/pgcrypto/pgp-pgsql.c b/contrib/pgcrypto/pgp-pgsql.c
index 838a7c381fc..b7e4d05e2c5 100644
--- a/contrib/pgcrypto/pgp-pgsql.c
+++ b/contrib/pgcrypto/pgp-pgsql.c
@@ -192,6 +192,8 @@ set_arg(PGP_Context *ctx, char *key, char *val,
                res = pgp_set_convert_crlf(ctx, atoi(val));
        else if (strcmp(key, "unicode-mode") == 0)
                res = pgp_set_unicode_mode(ctx, atoi(val));
+       else if (strcmp(key, "ignore-cipher-failure") == 0)
+               res = pgp_set_ignore_cipher_failure(ctx, atoi(val));
 
        /*
         * The remaining options are for debugging/testing and are therefore not
diff --git a/contrib/pgcrypto/pgp-pubkey.c b/contrib/pgcrypto/pgp-pubkey.c
index 9a6561caf9d..470e0debbb0 100644
--- a/contrib/pgcrypto/pgp-pubkey.c
+++ b/contrib/pgcrypto/pgp-pubkey.c
@@ -382,8 +382,15 @@ process_secret_key(PullFilter *pkt, PGP_PubKey **pk_p,
 
                /*
                 * create decrypt filter
+                *
+                * ignore-cipher-failure doesn't apply here; pgcrypto didn't 
encrypt
+                * the secret key to begin with, and any stored encrypted data 
was
+                * generated using the public key, so users don't have a reason 
to
+                * want to incorrectly decrypt this. We'll ignore failures 
during
+                * decryption with the session key, instead.
                 */
-               res = pgp_cfb_create(&cfb, cipher_algo, s2k.key, s2k.key_len, 
0, iv);
+               res = pgp_cfb_create(&cfb, cipher_algo, s2k.key, s2k.key_len, 
0, iv,
+                                                        0 /* don't ignore 
cipher failures */ );
                if (res < 0)
                        return res;
                res = pullf_create(&pf_decrypt, &pgp_decrypt_filter, cfb, pkt);
diff --git a/contrib/pgcrypto/pgp.c b/contrib/pgcrypto/pgp.c
index 44d9aebc2ed..9c2aaadbf76 100644
--- a/contrib/pgcrypto/pgp.c
+++ b/contrib/pgcrypto/pgp.c
@@ -49,6 +49,7 @@ static int    def_use_sess_key = 0;
 static int     def_text_mode = 0;
 static int     def_unicode_mode = 0;
 static int     def_convert_crlf = 0;
+static int     def_ignore_cipher_failure = 0;
 
 struct digest_info
 {
@@ -229,6 +230,7 @@ pgp_init(PGP_Context **ctx_p)
        ctx->unicode_mode = def_unicode_mode;
        ctx->convert_crlf = def_convert_crlf;
        ctx->text_mode = def_text_mode;
+       ctx->ignore_cipher_failure = def_ignore_cipher_failure;
 
        *ctx_p = ctx;
        return 0;
@@ -374,6 +376,13 @@ pgp_set_unicode_mode(PGP_Context *ctx, int mode)
        return 0;
 }
 
+int
+pgp_set_ignore_cipher_failure(PGP_Context *ctx, int ignore)
+{
+       ctx->ignore_cipher_failure = ignore ? 1 : 0;
+       return 0;
+}
+
 int
 pgp_set_symkey(PGP_Context *ctx, const uint8 *key, int len)
 {
diff --git a/contrib/pgcrypto/pgp.h b/contrib/pgcrypto/pgp.h
index f85bbae7545..7033c97433f 100644
--- a/contrib/pgcrypto/pgp.h
+++ b/contrib/pgcrypto/pgp.h
@@ -153,6 +153,9 @@ struct PGP_Context
        int                     convert_crlf;
        int                     unicode_mode;
 
+       /* DANGEROUS recovery aid for CVE-2026-14663. Applies only to 
decryption. */
+       int                     ignore_cipher_failure;
+
        /*
         * internal variables
         */
@@ -261,6 +264,7 @@ int                 pgp_set_compress_level(PGP_Context 
*ctx, int level);
 int                    pgp_set_text_mode(PGP_Context *ctx, int mode);
 int                    pgp_set_unicode_mode(PGP_Context *ctx, int mode);
 int                    pgp_get_unicode_mode(PGP_Context *ctx);
+int                    pgp_set_ignore_cipher_failure(PGP_Context *ctx, int 
ignore);
 
 int                    pgp_set_symkey(PGP_Context *ctx, const uint8 *key, int 
len);
 int                    pgp_set_pubkey(PGP_Context *ctx, MBuf *keypkt,
@@ -281,7 +285,8 @@ int                 pgp_s2k_process(PGP_S2K *s2k, int 
cipher, const uint8 *key, int key_len);
 
 typedef struct PGP_CFB PGP_CFB;
 int                    pgp_cfb_create(PGP_CFB **ctx_p, int algo,
-                                                  const uint8 *key, int 
key_len, int resync, uint8 *iv);
+                                                  const uint8 *key, int 
key_len, int resync, uint8 *iv,
+                                                  int 
ignore_decrypt_cipher_failure);
 void           pgp_cfb_free(PGP_CFB *ctx);
 int                    pgp_cfb_encrypt(PGP_CFB *ctx, const uint8 *data, int 
len, uint8 *dst);
 int                    pgp_cfb_decrypt(PGP_CFB *ctx, const uint8 *data, int 
len, uint8 *dst);
diff --git a/contrib/pgcrypto/sql/pgp-decrypt.sql 
b/contrib/pgcrypto/sql/pgp-decrypt.sql
index 2fe498f2f02..b27ecacaeb2 100644
--- a/contrib/pgcrypto/sql/pgp-decrypt.sql
+++ b/contrib/pgcrypto/sql/pgp-decrypt.sql
@@ -327,3 +327,29 @@ UCAAw2JRIISttRHMfDpDuZJpvYo=
 =AZ9M
 -----END PGP MESSAGE-----
 '), 'key', 'debug=1');
+
+-- Check ignore-cipher-failure. This message isn't actually encrypted; it was
+-- created with cipher-algo=bf using an OpenSSL that didn't actually support
+-- Blowfish. After the fix for CVE-2026-14663, we no longer create these broken
+-- ciphertexts, but we allow users to return to the previous behavior during
+-- decryption so that the bad wrapper can be stripped.
+--
+-- Note that if Blowfish is supported by the linked OpenSSL, both decryptions
+-- will fail.
+select pgp_sym_decrypt(dearmor('
+-----BEGIN PGP MESSAGE-----
+
+ww0EBAMC8wIKbtvzJtxi0jABUleCwFJWGCkYKcsNdABqdtXaU2VjcmV0LtMUlnPH3A2QBmZrcucm
+1GPb/s2Bkdg=
+=6aqD
+-----END PGP MESSAGE-----
+'), 'wrong key');
+
+select pgp_sym_decrypt(dearmor('
+-----BEGIN PGP MESSAGE-----
+
+ww0EBAMC8wIKbtvzJtxi0jABUleCwFJWGCkYKcsNdABqdtXaU2VjcmV0LtMUlnPH3A2QBmZrcucm
+1GPb/s2Bkdg=
+=6aqD
+-----END PGP MESSAGE-----
+'), 'wrong key', 'ignore-cipher-failure=1');
diff --git a/contrib/pgcrypto/sql/pgp-pubkey-decrypt.sql 
b/contrib/pgcrypto/sql/pgp-pubkey-decrypt.sql
index 3f2bae9e40b..40a11e0b2dc 100644
--- a/contrib/pgcrypto/sql/pgp-pubkey-decrypt.sql
+++ b/contrib/pgcrypto/sql/pgp-pubkey-decrypt.sql
@@ -601,6 +601,21 @@ 
blH2nKZC9d6fi4YzSYMepZpMOFR65M80MCMiDUGnZBB8sEADu2/iVtqDUeG8mAA=
 -----END PGP MESSAGE-----
 ');
 
+-- CVE-2026-14663. This message was created with cipher-algo=bf using an 
OpenSSL
+-- that didn't actually support Blowfish.
+insert into encdata (id, data) values (6, '
+-----BEGIN PGP MESSAGE-----
+
+wcBOA9k2z2S7c/RmEAP8DYbU6AeEo6riMMdnf2G62BM9gC0Z32ODydewy3Ki8AnSzpwBDAHuDMcr
+P6RJDWvBOVOwgxHEwR7ZHMoFRDJEXdo6rQ9dQpDtbasMLyi6Lm1q+PbEefVd9WkU7fvFAFQx8k3t
+lxrlWg/byoNplc7/hFxIFO8bN+FIlLgilAdApNcD/3Mg2/nd7pczovsYoryf9ib04kQ+SVWs3iNE
+StoyEXT+oaT8u1vAxiY7fzPpQX1pnlHBUXn+v1J6LQL5Bwi5CTqOyDSyaFfgU0gQwTReFjS6L4Fs
+Cv+2cFwbJBGIzr1aI4DLbzSelkmVm4hbOVeET4DJVlUVhhIyy6ZfoXiTEG6s0jMB2JdRGIl0EUQR
+RMsQdABqdt5jU2VjcmV0IG1zZ9MUIIP4SPiU2pM/nF/A1hrltMhn/ZI=
+=Mkdj
+-----END PGP MESSAGE-----
+');
+
 -- successful decrypt
 select pgp_pub_decrypt(dearmor(data), dearmor(seckey))
 from keytbl, encdata where keytbl.id=1 and encdata.id=1;
@@ -608,6 +623,9 @@ from keytbl, encdata where keytbl.id=1 and encdata.id=1;
 select pgp_pub_decrypt(dearmor(data), dearmor(seckey))
 from keytbl, encdata where keytbl.id=2 and encdata.id=2;
 
+select pgp_pub_decrypt(dearmor(data), dearmor(seckey), '', 
'ignore-cipher-failure=1')
+from keytbl, encdata where keytbl.id=2 and encdata.id=2;
+
 select pgp_pub_decrypt(dearmor(data), dearmor(seckey))
 from keytbl, encdata where keytbl.id=3 and encdata.id=3;
 
@@ -645,3 +663,12 @@ from keytbl, encdata where keytbl.id=5 and encdata.id=1;
 -- test for a short read from prefix_init
 select pgp_pub_decrypt(dearmor(data), dearmor(seckey))
 from keytbl, encdata where keytbl.id=6 and encdata.id=5;
+
+-- Check that ignore-cipher-failure can strip faulty encryption if OpenSSL
+-- doesn't support the cipher. (The decryption will correctly fail both times 
if
+-- OpenSSL does support it.)
+select pgp_pub_decrypt(dearmor(data), dearmor(seckey))
+from keytbl, encdata where keytbl.id=1 and encdata.id=6;
+
+select pgp_pub_decrypt(dearmor(data), dearmor(seckey), '', 
'ignore-cipher-failure=1')
+from keytbl, encdata where keytbl.id=1 and encdata.id=6;
diff --git a/doc/src/sgml/pgcrypto.sgml b/doc/src/sgml/pgcrypto.sgml
index 6dbfec91f54..c2b24f7817f 100644
--- a/doc/src/sgml/pgcrypto.sgml
+++ b/doc/src/sgml/pgcrypto.sgml
@@ -927,6 +927,38 @@ Applies to: pgp_sym_encrypt
 Values: 0, 1
 Default: 0
 Applies to: pgp_sym_encrypt, pgp_pub_encrypt
+</literallayout>
+  </sect4>
+
+  <sect4 id="pgcrypto-pgp-enc-funcs-opts-ignore-cipher-failure">
+   <title>ignore-cipher-failure</title>
+
+   <para>
+    Dangerous! Instructs pgcrypto to use an incorrect decryption algorithm
+    matching the historical behavior prior to the fix for CVE-2026-14663, by
+    completely ignoring failures from the OpenSSL cipher in use. This is
+    intended only for users who need to recover incorrectly-encrypted messages
+    created when the <literal>cipher-algo</literal> was unavailable under the
+    OpenSSL configuration in use. Such faulty messages do not require the
+    correct decryption key when <literal>ignore-cipher-failure</literal> is
+    enabled, so there is no guarantee that the decrypted plaintext actually
+    originated from a holder of the key.
+   </para>
+   <para>
+    Contrast the case of a message which was correctly encrypted, but the 
cipher
+    that produced it is unavailable under the current 
<productname>OpenSSL</productname>
+    configuration. Recovering such plaintext via <filename>pgcrypto</filename>
+    requires making the actual cipher available to OpenSSL by, for example,
+    enabling the appropriate provider. <literal>ignore-cipher-failure</literal>
+    is not necessary or helpful for that scenario. If <quote>decryption</quote>
+    of a correctly encrypted message with this option happens to pass PGP
+    integrity checks, that result is coincidental and does not make the
+    recovered plaintext trustworthy.
+   </para>
+<literallayout>
+Values: 0, 1
+Default: 0
+Applies to: pgp_sym_decrypt, pgp_pub_decrypt
 </literallayout>
   </sect4>
   </sect3>


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to