Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
236 changes: 236 additions & 0 deletions tests/api/test_pkcs7.c
Original file line number Diff line number Diff line change
Expand Up @@ -3826,6 +3826,242 @@ int test_wc_PKCS7_DecodeAuthEnvelopedData_truncated(void)
} /* END test_wc_PKCS7_DecodeAuthEnvelopedData_truncated() */


#if defined(HAVE_PKCS7) && !defined(NO_RSA) && !defined(NO_AES) && \
defined(WOLFSSL_AES_128) && (defined(HAVE_AESGCM) || defined(HAVE_AESCCM))
/* Encode an AuthEnvelopedData bundle, then cut its tag down to tagSz bytes
* and fix up the stated tag size and the outer lengths. Returns the new size. */
static int pkcs7_shortTagBundle(byte* out, word32 outSz, int encryptOID,
word32 tagSz, int contentOID)
{
PKCS7* pkcs7 = NULL;
byte data[] = "short authTag authEnvelopedData test";
int encSz;
word32 cut;
word32 lenIdx[3];
word32 found = 0;
word32 i;
word32 n;
word32 len;

if (tagSz == 0 || tagSz > (word32)WC_AES_BLOCK_SIZE)
return -1;
cut = (word32)WC_AES_BLOCK_SIZE - tagSz;

pkcs7 = wc_PKCS7_New(HEAP_HINT, testDevId);
if (pkcs7 == NULL)
return -1;
if (wc_PKCS7_InitWithCert(pkcs7, (byte*)client_cert_der_2048,
sizeof_client_cert_der_2048) != 0) {
wc_PKCS7_Free(pkcs7);
return -1;
}
pkcs7->content = data;
pkcs7->contentSz = (word32)sizeof(data);
/* a contentOID other than DATA makes the encoder add authenticated
* attributes, which the decoder walks in its own state */
pkcs7->contentOID = contentOID;
pkcs7->encryptOID = encryptOID;
encSz = wc_PKCS7_EncodeAuthEnvelopedData(pkcs7, out, outSz);
wc_PKCS7_Free(pkcs7);
if (encSz <= 32)
return -1;

/* Tag is last: 04 10 <16 bytes>. Keep only its first tagSz bytes. */
if (out[encSz - (WC_AES_BLOCK_SIZE + 2)] != ASN_OCTET_STRING ||
out[encSz - (WC_AES_BLOCK_SIZE + 1)] != WC_AES_BLOCK_SIZE)
return -1;
out[encSz - (WC_AES_BLOCK_SIZE + 1)] = (byte)tagSz;
encSz -= (int)cut;

/* Tag size field follows the nonce: 04 <n> <nonce> 02 01 10 */
for (i = 0; i + 20 < (word32)encSz; i++) {
n = out[i + 1];
/* 04 <n> <nonce> 02 01 10, then the encryptedContent [0] tag */
if (out[i] == 0x04 && n >= 7 && n <= 13 &&
out[i + n + 2] == 0x02 && out[i + n + 3] == 0x01 &&
out[i + n + 4] == 0x10 &&
(out[i + n + 5] == 0x80 || out[i + n + 5] == 0xA0)) {
out[i + n + 4] = (byte)tagSz;
found = 1;
break;
}
}
if (!found)
return -1;

/* Outer SEQUENCE, [0] and inner SEQUENCE lengths are 82 hi lo. */
lenIdx[0] = 1;
lenIdx[1] = 6 + (word32)out[5] + 1;
lenIdx[2] = lenIdx[1] + 4;
for (i = 0; i < 3; i++) {
if (out[lenIdx[i]] != 0x82)
return -1;
len = ((word32)out[lenIdx[i] + 1] << 8) | out[lenIdx[i] + 2];
len -= cut;
out[lenIdx[i] + 1] = (byte)(len >> 8);
out[lenIdx[i] + 2] = (byte)len;
}

return encSz;
}

/* Decode a bundle whose tag was cut short, expecting it to be refused. */
static int pkcs7_decodeShortTag(byte* enveloped, int encSz)
{
PKCS7* pkcs7 = NULL;
byte decoded[256];
int ret;

pkcs7 = wc_PKCS7_New(HEAP_HINT, testDevId);
if (pkcs7 == NULL)
return -1;
if (wc_PKCS7_InitWithCert(pkcs7, (byte*)client_cert_der_2048,
sizeof_client_cert_der_2048) != 0) {
wc_PKCS7_Free(pkcs7);
return -1;
}
pkcs7->privateKey = (byte*)client_key_der_2048;
pkcs7->privateKeySz = sizeof_client_key_der_2048;
ret = wc_PKCS7_DecodeAuthEnvelopedData(pkcs7, enveloped, (word32)encSz,
decoded, sizeof(decoded));
wc_PKCS7_Free(pkcs7);

return ret;
}
#endif


/* A GCM tag under 12 bytes must be refused, even when the build allows
* short tags for plain AES-GCM calls. */
int test_wc_PKCS7_DecodeAuthEnvelopedData_shortTag(void)
{
EXPECT_DECLS;
#if defined(HAVE_PKCS7) && defined(HAVE_AESGCM) && !defined(NO_RSA) && \
!defined(NO_AES) && defined(WOLFSSL_AES_128)
byte enveloped[2048];
int encSz = 0;

ExpectIntGT(encSz = pkcs7_shortTagBundle(enveloped, sizeof(enveloped),
AES128GCMb, 8, DATA), 0);
if (EXPECT_SUCCESS()) {
ExpectIntEQ(pkcs7_decodeShortTag(enveloped, encSz),
WC_NO_ERR_TRACE(ASN_PARSE_E));
}

/* These accept a truncated tag, which needs the in-tree AES-GCM. A v5, v6 or
* selftest build pins an older aes.c, so they are left out there. */
#if (!defined(HAVE_FIPS) || FIPS_VERSION3_GE(7,0,0)) && !defined(HAVE_SELFTEST)
#if WOLFSSL_MIN_AUTH_TAG_SZ <= 12
/* a tag at the floor still decodes, a GCM tag being the leading bytes
* of the full one */
ExpectIntGT(encSz = pkcs7_shortTagBundle(enveloped, sizeof(enveloped),
AES128GCMb, 12, DATA), 0);
if (EXPECT_SUCCESS()) {
ExpectIntGT(pkcs7_decodeShortTag(enveloped, encSz), 0);
}
#endif
#if WOLFSSL_MIN_AUTH_TAG_SZ <= 13
/* SP 800-38D section 5.2.1.2 approves 104 bits, so an odd GCM ICV is
* allowed here where the CCM list has none */
ExpectIntGT(encSz = pkcs7_shortTagBundle(enveloped, sizeof(enveloped),
AES128GCMb, 13, DATA), 0);
if (EXPECT_SUCCESS()) {
ExpectIntGT(pkcs7_decodeShortTag(enveloped, encSz), 0);
}
#endif
#endif /* in-tree AES-GCM */
#endif
return EXPECT_RESULT();
} /* END test_wc_PKCS7_DecodeAuthEnvelopedData_shortTag() */


/* A CCM tag under 8 bytes must be refused, even when the build allows
* short tags for plain AES-CCM calls. */
int test_wc_PKCS7_DecodeAuthEnvelopedData_shortTagCcm(void)
{
EXPECT_DECLS;
#if defined(HAVE_PKCS7) && defined(HAVE_AESCCM) && !defined(NO_RSA) && \
!defined(NO_AES) && defined(WOLFSSL_AES_128)
byte enveloped[2048];
int encSz = 0;

ExpectIntGT(encSz = pkcs7_shortTagBundle(enveloped, sizeof(enveloped),
AES128CCMb, 6, DATA), 0);
if (EXPECT_SUCCESS()) {
ExpectIntEQ(pkcs7_decodeShortTag(enveloped, encSz),
WC_NO_ERR_TRACE(ASN_PARSE_E));
}

/* Odd sizes are the only way to reach the parity rule, and RFC 5084 section
* 3.1 stops at 16, so 13 and 15 are the only candidates. */
#if WOLFSSL_MIN_AUTH_TAG_SZ <= 13
#define PKCS7_TEST_CCM_ODD_SZ 13
#elif WOLFSSL_MIN_AUTH_TAG_SZ <= 15
#define PKCS7_TEST_CCM_ODD_SZ 15
#endif

#ifdef PKCS7_TEST_CCM_ODD_SZ
/* RFC 5084 section 3.1 has no odd ICV size, so this is refused while
* parsing rather than later by the cipher */
ExpectIntGT(encSz = pkcs7_shortTagBundle(enveloped, sizeof(enveloped),
AES128CCMb, PKCS7_TEST_CCM_ODD_SZ, DATA), 0);
if (EXPECT_SUCCESS()) {
ExpectIntEQ(pkcs7_decodeShortTag(enveloped, encSz),
WC_NO_ERR_TRACE(ASN_PARSE_E));
}
#endif
#endif
return EXPECT_RESULT();
} /* END test_wc_PKCS7_DecodeAuthEnvelopedData_shortTagCcm() */


/* Feeding the bundle in chunks makes the decoder come back in at the tag
* state, where it must still refuse a short tag. */
int test_wc_PKCS7_DecodeAuthEnvelopedData_shortTagChunked(void)
{
EXPECT_DECLS;
#if defined(HAVE_PKCS7) && defined(HAVE_AESGCM) && !defined(NO_RSA) && \
!defined(NO_AES) && defined(WOLFSSL_AES_128) && !defined(NO_PKCS7_STREAM)
PKCS7* pkcs7 = NULL;
byte enveloped[2048];
byte decoded[256];
int encSz = 0;
int ret = 0;
int idx;
int chunk = 1;

/* authenticated attributes plus one byte at a time make the decoder stop
* in its own state, so it comes back in at the tag state instead of
* falling through to it with the cipher still in hand */
ExpectIntGT(encSz = pkcs7_shortTagBundle(enveloped, sizeof(enveloped),
AES128GCMb, 8, FIRMWARE_PKG_DATA), 0);

ExpectNotNull(pkcs7 = wc_PKCS7_New(HEAP_HINT, testDevId));
ExpectIntEQ(wc_PKCS7_InitWithCert(pkcs7, (byte*)client_cert_der_2048,
sizeof_client_cert_der_2048), 0);
if (pkcs7 != NULL) {
pkcs7->privateKey = (byte*)client_key_der_2048;
pkcs7->privateKeySz = sizeof_client_key_der_2048;
}

if (EXPECT_SUCCESS()) {
for (idx = 0; idx < encSz; idx += chunk) {
int sz = (encSz - idx < chunk) ? encSz - idx : chunk;

ret = wc_PKCS7_DecodeAuthEnvelopedData(pkcs7, enveloped + idx,
(word32)sz, decoded, sizeof(decoded));
if (ret != WC_NO_ERR_TRACE(WC_PKCS7_WANT_READ_E))
break;
}
ExpectIntEQ(ret, WC_NO_ERR_TRACE(ASN_PARSE_E));
}

wc_PKCS7_Free(pkcs7);
#endif
return EXPECT_RESULT();
} /* END test_wc_PKCS7_DecodeAuthEnvelopedData_shortTagChunked() */


/* Tearing down a PKCS7 whose AuthEnvelopedData decode stopped part-way must
* not leak the encryptedContent buffer.
*
Expand Down
6 changes: 6 additions & 0 deletions tests/api/test_pkcs7.h
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,9 @@ int test_wc_PKCS7_DecodeEnvelopedData_multiple_recipients(void);
int test_wc_PKCS7_DecodeEnvelopedData_forgedRecipientSetLen(void);
int test_wc_PKCS7_DecodeEnvelopedData_constructedDefiniteOctet(void);
int test_wc_PKCS7_DecodeAuthEnvelopedData_truncated(void);
int test_wc_PKCS7_DecodeAuthEnvelopedData_shortTag(void);
int test_wc_PKCS7_DecodeAuthEnvelopedData_shortTagCcm(void);
int test_wc_PKCS7_DecodeAuthEnvelopedData_shortTagChunked(void);
int test_wc_PKCS7_AuthEnvelopedData_stream_leak(void);
int test_wc_PKCS7_VerifySignedData_PKCS7ContentSeq(void);
int test_wc_PKCS7_VerifySignedData_IndefLenOOB(void);
Expand Down Expand Up @@ -171,6 +174,9 @@ int test_wc_PKCS7_VerifySignedData_NoDigestParams(void);
TEST_DECL_GROUP("pkcs7_ed", test_wc_PKCS7_DecodeEnvelopedData_forgedRecipientSetLen), \
TEST_DECL_GROUP("pkcs7_ed", test_wc_PKCS7_DecodeEnvelopedData_constructedDefiniteOctet), \
TEST_DECL_GROUP("pkcs7_ed", test_wc_PKCS7_DecodeAuthEnvelopedData_truncated), \
TEST_DECL_GROUP("pkcs7_ed", test_wc_PKCS7_DecodeAuthEnvelopedData_shortTag), \
TEST_DECL_GROUP("pkcs7_ed", test_wc_PKCS7_DecodeAuthEnvelopedData_shortTagCcm), \
TEST_DECL_GROUP("pkcs7_ed", test_wc_PKCS7_DecodeAuthEnvelopedData_shortTagChunked), \
TEST_DECL_GROUP("pkcs7_ed", test_wc_PKCS7_AuthEnvelopedData_stream_leak)

#define TEST_PKCS7_SIGNED_ENCRYPTED_DATA_DECLS \
Expand Down
38 changes: 22 additions & 16 deletions wolfcrypt/src/pkcs7.c
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,11 @@ struct PKCS7SignerInfo {
#define WOLFSSL_PKCS7_MAX_DECOMPRESSION 1031
#endif

/* RFC 5084 section 3.2 and SP 800-38C appendix B.2: smallest ICV each mode
* may carry in a bundle. */
#define PKCS7_GCM_MIN_ICV_SZ 12
#define PKCS7_CCM_MIN_ICV_SZ 8

#ifndef NO_PKCS7_STREAM

/* Hard upper bound on a single PKCS7 streaming buffer allocation. Guards
Expand Down Expand Up @@ -16289,6 +16294,8 @@ int wc_PKCS7_DecodeAuthEnvelopedData(wc_PKCS7* pkcs7, byte* in,
encodedAttribs = pkcs7->stream->aad;
}
macSz = (int)pkcs7->stream->icvSz;
/* re-entry here skips the earlier states, so get the cipher back */
wc_PKCS7_StreamGetVar(pkcs7, &encOID, NULL, NULL);
#endif


Expand Down Expand Up @@ -16318,28 +16325,27 @@ int wc_PKCS7_DecodeAuthEnvelopedData(wc_PKCS7* pkcs7, byte* in,
WOLFSSL_MSG("AuthEnvelopedData authTag size mismatch");
ret = ASN_PARSE_E;
}
/* RFC 5084 section 3.2: AES-GCM ICV is 12 to 16 bytes, and macSz
* already bounds the top. The floor is raised to the build minimum
* when that is larger. */
if (ret == 0 &&
(encOID == AES128GCMb || encOID == AES192GCMb ||
encOID == AES256GCMb)) {
#if (defined(HAVE_FIPS) && FIPS_VERSION3_LT(7,0,0)) || \
defined(HAVE_SELFTEST) || !defined(HAVE_AESGCM)
if (authTagSz < WOLFSSL_MIN_AUTH_TAG_SZ) {
WOLFSSL_MSG("AuthEnvelopedData GCM authTag too small");
ret = ASN_PARSE_E;
}
#else
ret = wc_local_AesGcmCheckTagSz(authTagSz);
if (ret != 0) {
ret = ASN_PARSE_E;
WOLFSSL_MSG("AuthEnvelopedData GCM authTag invalid size");
}
#endif
encOID == AES256GCMb) &&
(authTagSz < PKCS7_GCM_MIN_ICV_SZ ||
authTagSz < WOLFSSL_MIN_AUTH_TAG_SZ)) {
WOLFSSL_MSG("AuthEnvelopedData GCM authTag invalid size");
ret = ASN_PARSE_E;
}
/* RFC 5084 section 3.1 lists even ICV sizes only, and SP 800-38C
* appendix B.2 wants 8 bytes or more. The floor is raised to the
* build minimum when that is larger. */
if (ret == 0 &&
(encOID == AES128CCMb || encOID == AES192CCMb ||
encOID == AES256CCMb) &&
authTagSz < WOLFSSL_MIN_AUTH_TAG_SZ) {
WOLFSSL_MSG("AuthEnvelopedData CCM authTag too small");
(authTagSz < PKCS7_CCM_MIN_ICV_SZ ||
authTagSz < WOLFSSL_MIN_AUTH_TAG_SZ ||
(authTagSz & 1) != 0)) {
WOLFSSL_MSG("AuthEnvelopedData CCM authTag invalid size");
ret = ASN_PARSE_E;
}

Expand Down
Loading