Location via proxy:   [ UP ]  
[Report a bug]   [Manage cookies]                
Skip to content

Commit 7b758b7

Browse files
committed
pgcrypto: Report errant decryption as "Wrong key or corrupt data".
This has been the predominant outcome. When the output of decrypting with a wrong key coincidentally resembled an OpenPGP packet header, pgcrypto could instead report "Corrupt data", "Not text data" or "Unsupported compression algorithm". The distinct "Corrupt data" message added no value. The latter two error messages misled when the decrypted payload also exhibited fundamental integrity problems. Worse, error message variance in other systems has enabled cryptologic attacks; see RFC 4880 section "14. Security Considerations". Whether these pgcrypto behaviors are likewise exploitable is unknown. In passing, document that pgcrypto does not resist side-channel attacks. Back-patch to 9.0 (all supported versions). Security: CVE-2015-3167
1 parent c669915 commit 7b758b7

File tree

9 files changed

+162
-27
lines changed

9 files changed

+162
-27
lines changed

contrib/pgcrypto/expected/pgp-decrypt.out

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -372,3 +372,54 @@ select pgp_sym_decrypt(pgp_sym_encrypt(repeat('x',65530),'1'),'1') = repeat('x',
372372
(1 row)
373373

374374
-- expected: true
375+
-- Negative tests
376+
-- Decryption with a certain incorrect key yields an apparent Literal Data
377+
-- packet reporting its content to be binary data. Ciphertext source:
378+
-- iterative pgp_sym_encrypt('secret', 'key') until the random prefix gave
379+
-- rise to that property.
380+
select pgp_sym_decrypt(dearmor('
381+
-----BEGIN PGP MESSAGE-----
382+
383+
ww0EBwMCxf8PTrQBmJdl0jcB6y2joE7GSLKRv7trbNsF5Z8ou5NISLUg31llVH/S0B2wl4bvzZjV
384+
VsxxqLSPzNLAeIspJk5G
385+
=mSd/
386+
-----END PGP MESSAGE-----
387+
'), 'wrong-key', 'debug=1');
388+
NOTICE: dbg: prefix_init: corrupt prefix
389+
NOTICE: dbg: parse_literal_data: data type=b
390+
NOTICE: dbg: mdcbuf_finish: bad MDC pkt hdr
391+
ERROR: Wrong key or corrupt data
392+
-- Routine text/binary mismatch.
393+
select pgp_sym_decrypt(pgp_sym_encrypt_bytea('P', 'key'), 'key', 'debug=1');
394+
NOTICE: dbg: parse_literal_data: data type=b
395+
ERROR: Not text data
396+
-- Decryption with a certain incorrect key yields an apparent BZip2-compressed
397+
-- plaintext. Ciphertext source: iterative pgp_sym_encrypt('secret', 'key')
398+
-- until the random prefix gave rise to that property.
399+
select pgp_sym_decrypt(dearmor('
400+
-----BEGIN PGP MESSAGE-----
401+
402+
ww0EBwMC9rK/dMkF5Zlt0jcBlzAQ1mQY2qYbKYbw8h3EZ5Jk0K2IiY92R82TRhWzBIF/8cmXDPtP
403+
GXsd65oYJZp3Khz0qfyn
404+
=Nmpq
405+
-----END PGP MESSAGE-----
406+
'), 'wrong-key', 'debug=1');
407+
NOTICE: dbg: prefix_init: corrupt prefix
408+
NOTICE: dbg: parse_compressed_data: bzip2 unsupported
409+
NOTICE: dbg: mdcbuf_finish: bad MDC pkt hdr
410+
ERROR: Wrong key or corrupt data
411+
-- Routine use of BZip2 compression. Ciphertext source:
412+
-- echo x | gpg --homedir /nonexistent --personal-compress-preferences bzip2 \
413+
-- --personal-cipher-preferences aes --no-emit-version --batch \
414+
-- --symmetric --passphrase key --armor
415+
select pgp_sym_decrypt(dearmor('
416+
-----BEGIN PGP MESSAGE-----
417+
418+
jA0EBwMCRhFrAKNcLVJg0mMBLJG1cCASNk/x/3dt1zJ+2eo7jHfjgg3N6wpB3XIe
419+
QCwkWJwlBG5pzbO5gu7xuPQN+TbPJ7aQ2sLx3bAHhtYb0i3vV9RO10Gw++yUyd4R
420+
UCAAw2JRIISttRHMfDpDuZJpvYo=
421+
=AZ9M
422+
-----END PGP MESSAGE-----
423+
'), 'key', 'debug=1');
424+
NOTICE: dbg: parse_compressed_data: bzip2 unsupported
425+
ERROR: Unsupported compression algorithm

contrib/pgcrypto/expected/pgp-pubkey-decrypt.out

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -625,7 +625,7 @@ ERROR: No encryption key found
625625
-- rsa: password-protected secret key, wrong password
626626
select pgp_pub_decrypt(dearmor(data), dearmor(seckey), '123')
627627
from keytbl, encdata where keytbl.id=7 and encdata.id=4;
628-
ERROR: Corrupt data
628+
ERROR: Wrong key or corrupt data
629629
-- rsa: password-protected secret key, right password
630630
select pgp_pub_decrypt(dearmor(data), dearmor(seckey), 'parool')
631631
from keytbl, encdata where keytbl.id=7 and encdata.id=4;
@@ -641,7 +641,7 @@ ERROR: Need password for secret key
641641
-- password-protected secret key, wrong password
642642
select pgp_pub_decrypt(dearmor(data), dearmor(seckey), 'foo')
643643
from keytbl, encdata where keytbl.id=5 and encdata.id=1;
644-
ERROR: Corrupt data
644+
ERROR: Wrong key or corrupt data
645645
-- password-protected secret key, right password
646646
select pgp_pub_decrypt(dearmor(data), dearmor(seckey), 'parool')
647647
from keytbl, encdata where keytbl.id=5 and encdata.id=1;

contrib/pgcrypto/mbuf.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -325,7 +325,7 @@ pullf_read_fixed(PullFilter *src, int len, uint8 *dst)
325325
if (res != len)
326326
{
327327
px_debug("pullf_read_fixed: need=%d got=%d", len, res);
328-
return PXE_MBUF_SHORT_READ;
328+
return PXE_PGP_CORRUPT_DATA;
329329
}
330330
if (p != dst)
331331
memcpy(dst, p, len);

contrib/pgcrypto/pgp-decrypt.c

Lines changed: 52 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -236,6 +236,8 @@ pgp_create_pkt_reader(PullFilter **pf_p, PullFilter *src, int len,
236236

237237
/*
238238
* Prefix check filter
239+
* https://tools.ietf.org/html/rfc4880#section-5.7
240+
* https://tools.ietf.org/html/rfc4880#section-5.13
239241
*/
240242

241243
static int
@@ -264,20 +266,7 @@ prefix_init(void **priv_p, void *arg, PullFilter *src)
264266
if (buf[len - 2] != buf[len] || buf[len - 1] != buf[len + 1])
265267
{
266268
px_debug("prefix_init: corrupt prefix");
267-
268-
/*
269-
* The original purpose of the 2-byte check was to show user a
270-
* friendly "wrong key" message. This made following possible:
271-
*
272-
* "An Attack on CFB Mode Encryption As Used By OpenPGP" by Serge
273-
* Mister and Robert Zuccherato
274-
*
275-
* To avoid being 'oracle', we delay reporting, which basically means
276-
* we prefer to run into corrupt packet header.
277-
*
278-
* We _could_ throw PXE_PGP_CORRUPT_DATA here, but there is
279-
* possibility of attack via timing, so we don't.
280-
*/
269+
/* report error in pgp_decrypt() */
281270
ctx->corrupt_prefix = 1;
282271
}
283272
px_memset(tmpbuf, 0, sizeof(tmpbuf));
@@ -788,12 +777,15 @@ parse_literal_data(PGP_Context *ctx, MBuf *dst, PullFilter *pkt)
788777
}
789778
px_memset(tmpbuf, 0, 4);
790779

791-
/* check if text */
780+
/*
781+
* If called from an SQL function that returns text, pgp_decrypt() rejects
782+
* inputs not self-identifying as text.
783+
*/
792784
if (ctx->text_mode)
793785
if (type != 't' && type != 'u')
794786
{
795787
px_debug("parse_literal_data: data type=%c", type);
796-
return PXE_PGP_NOT_TEXT;
788+
ctx->unexpected_binary = true;
797789
}
798790

799791
ctx->unicode_mode = (type == 'u') ? 1 : 0;
@@ -827,6 +819,7 @@ parse_compressed_data(PGP_Context *ctx, MBuf *dst, PullFilter *pkt)
827819
int res;
828820
uint8 type;
829821
PullFilter *pf_decompr;
822+
uint8 *discard_buf;
830823

831824
GETBYTE(pkt, type);
832825

@@ -850,7 +843,20 @@ parse_compressed_data(PGP_Context *ctx, MBuf *dst, PullFilter *pkt)
850843

851844
case PGP_COMPR_BZIP2:
852845
px_debug("parse_compressed_data: bzip2 unsupported");
853-
res = PXE_PGP_UNSUPPORTED_COMPR;
846+
/* report error in pgp_decrypt() */
847+
ctx->unsupported_compr = 1;
848+
849+
/*
850+
* Discard the compressed data, allowing it to first affect any
851+
* MDC digest computation.
852+
*/
853+
while (1)
854+
{
855+
res = pullf_read(pkt, 32 * 1024, &discard_buf);
856+
if (res <= 0)
857+
break;
858+
}
859+
854860
break;
855861

856862
default:
@@ -1171,8 +1177,36 @@ pgp_decrypt(PGP_Context *ctx, MBuf *msrc, MBuf *mdst)
11711177
if (res < 0)
11721178
return res;
11731179

1180+
/*
1181+
* Report a failure of the prefix_init() "quick check" now, rather than
1182+
* upon detection, to hinder timing attacks. pgcrypto is not generally
1183+
* secure against timing attacks, but this helps.
1184+
*/
11741185
if (!got_data || ctx->corrupt_prefix)
1175-
res = PXE_PGP_CORRUPT_DATA;
1186+
return PXE_PGP_CORRUPT_DATA;
1187+
1188+
/*
1189+
* Code interpreting purportedly-decrypted data prior to this stage shall
1190+
* report no error other than PXE_PGP_CORRUPT_DATA. (PXE_BUG is okay so
1191+
* long as it remains unreachable.) This ensures that an attacker able to
1192+
* choose a ciphertext and receive a corresponding decryption error
1193+
* message cannot use that oracle to gather clues about the decryption
1194+
* key. See "An Attack on CFB Mode Encryption As Used By OpenPGP" by
1195+
* Serge Mister and Robert Zuccherato.
1196+
*
1197+
* A problematic value in the first octet of a Literal Data or Compressed
1198+
* Data packet may indicate a simple user error, such as the need to call
1199+
* pgp_sym_decrypt_bytea instead of pgp_sym_decrypt. Occasionally,
1200+
* though, it is the first symptom of the encryption key not matching the
1201+
* decryption key. When this was the only problem encountered, report a
1202+
* specific error to guide the user; otherwise, we will have reported
1203+
* PXE_PGP_CORRUPT_DATA before now. A key mismatch makes the other errors
1204+
* into red herrings, and this avoids leaking clues to attackers.
1205+
*/
1206+
if (ctx->unsupported_compr)
1207+
return PXE_PGP_UNSUPPORTED_COMPR;
1208+
if (ctx->unexpected_binary)
1209+
return PXE_PGP_NOT_TEXT;
11761210

11771211
return res;
11781212
}

contrib/pgcrypto/pgp.h

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -148,7 +148,9 @@ struct PGP_Context
148148
* internal variables
149149
*/
150150
int mdc_checked;
151-
int corrupt_prefix;
151+
int corrupt_prefix; /* prefix failed RFC 4880 "quick check" */
152+
int unsupported_compr; /* has bzip2 compression */
153+
int unexpected_binary; /* binary data seen in text_mode */
152154
int in_mdc_pkt;
153155
int use_mdcbuf_filter;
154156
PX_MD *mdc_ctx;

contrib/pgcrypto/px.c

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -87,9 +87,6 @@ static const struct error_desc px_err_list[] = {
8787
{PXE_PGP_UNSUPPORTED_PUBALGO, "Unsupported public key algorithm"},
8888
{PXE_PGP_MULTIPLE_SUBKEYS, "Several subkeys not supported"},
8989

90-
/* fake this as PXE_PGP_CORRUPT_DATA */
91-
{PXE_MBUF_SHORT_READ, "Corrupt data"},
92-
9390
{0, NULL},
9491
};
9592

contrib/pgcrypto/px.h

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -80,8 +80,6 @@ void px_free(void *p);
8080
#define PXE_NO_RANDOM -17
8181
#define PXE_DECRYPT_FAILED -18
8282

83-
#define PXE_MBUF_SHORT_READ -50
84-
8583
#define PXE_PGP_CORRUPT_DATA -100
8684
#define PXE_PGP_CORRUPT_ARMOR -101
8785
#define PXE_PGP_UNSUPPORTED_COMPR -102

contrib/pgcrypto/sql/pgp-decrypt.sql

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -268,3 +268,48 @@ a3nsOzKTXUfS9VyaXo8IrncM6n7fdaXpwba/3tNsAhJG4lDv1k4g9v8Ix2dfv6Rs
268268
-- check BUG #11905, problem with messages 6 less than a power of 2.
269269
select pgp_sym_decrypt(pgp_sym_encrypt(repeat('x',65530),'1'),'1') = repeat('x',65530);
270270
-- expected: true
271+
272+
273+
-- Negative tests
274+
275+
-- Decryption with a certain incorrect key yields an apparent Literal Data
276+
-- packet reporting its content to be binary data. Ciphertext source:
277+
-- iterative pgp_sym_encrypt('secret', 'key') until the random prefix gave
278+
-- rise to that property.
279+
select pgp_sym_decrypt(dearmor('
280+
-----BEGIN PGP MESSAGE-----
281+
282+
ww0EBwMCxf8PTrQBmJdl0jcB6y2joE7GSLKRv7trbNsF5Z8ou5NISLUg31llVH/S0B2wl4bvzZjV
283+
VsxxqLSPzNLAeIspJk5G
284+
=mSd/
285+
-----END PGP MESSAGE-----
286+
'), 'wrong-key', 'debug=1');
287+
288+
-- Routine text/binary mismatch.
289+
select pgp_sym_decrypt(pgp_sym_encrypt_bytea('P', 'key'), 'key', 'debug=1');
290+
291+
-- Decryption with a certain incorrect key yields an apparent BZip2-compressed
292+
-- plaintext. Ciphertext source: iterative pgp_sym_encrypt('secret', 'key')
293+
-- until the random prefix gave rise to that property.
294+
select pgp_sym_decrypt(dearmor('
295+
-----BEGIN PGP MESSAGE-----
296+
297+
ww0EBwMC9rK/dMkF5Zlt0jcBlzAQ1mQY2qYbKYbw8h3EZ5Jk0K2IiY92R82TRhWzBIF/8cmXDPtP
298+
GXsd65oYJZp3Khz0qfyn
299+
=Nmpq
300+
-----END PGP MESSAGE-----
301+
'), 'wrong-key', 'debug=1');
302+
303+
-- Routine use of BZip2 compression. Ciphertext source:
304+
-- echo x | gpg --homedir /nonexistent --personal-compress-preferences bzip2 \
305+
-- --personal-cipher-preferences aes --no-emit-version --batch \
306+
-- --symmetric --passphrase key --armor
307+
select pgp_sym_decrypt(dearmor('
308+
-----BEGIN PGP MESSAGE-----
309+
310+
jA0EBwMCRhFrAKNcLVJg0mMBLJG1cCASNk/x/3dt1zJ+2eo7jHfjgg3N6wpB3XIe
311+
QCwkWJwlBG5pzbO5gu7xuPQN+TbPJ7aQ2sLx3bAHhtYb0i3vV9RO10Gw++yUyd4R
312+
UCAAw2JRIISttRHMfDpDuZJpvYo=
313+
=AZ9M
314+
-----END PGP MESSAGE-----
315+
'), 'key', 'debug=1');

doc/src/sgml/pgcrypto.sgml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1224,6 +1224,14 @@ gen_random_bytes(count integer) returns bytea
12241224
<para>
12251225
If you cannot, then better do crypto inside client application.
12261226
</para>
1227+
1228+
<para>
1229+
The implementation does not resist
1230+
<ulink url="http://en.wikipedia.org/wiki/Side-channel_attack">side-channel
1231+
attacks</ulink>. For example, the time required for
1232+
a <filename>pgcrypto</> decryption function to complete varies among
1233+
ciphertexts of a given size.
1234+
</para>
12271235
</sect3>
12281236

12291237
<sect3>

0 commit comments

Comments
 (0)