Skip to content

Commit 74dc17a

Browse files
committed
Add Pairwise Consistency Test (PCT) for FIPS-204 IG compliance
- Implement PCT functionality in mldsa/sign.c with check_pct function - Add PCT configuration in mldsa/config.h - Add CBMC proof and specification for check_pct - Include comprehensive test harness for verification Complete proof of check_pct() Weakens pre-conditions of crypto_sign_verify() and crypto_sign_signature() to deal with the case where ctx == NULL && ctxlen == 0, which is required by check_pct() Signed-off-by: Jake Massimo <[email protected]>
1 parent 8dea0d2 commit 74dc17a

File tree

6 files changed

+201
-6
lines changed

6 files changed

+201
-6
lines changed

mldsa/config.h

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,4 +59,42 @@
5959
#define MLD_CONFIG_FIPS202_BACKEND_FILE "fips202/native/auto.h"
6060
#endif
6161

62+
/******************************************************************************
63+
* Name: MLD_CONFIG_KEYGEN_PCT
64+
*
65+
* Description: Compliance with @[FIPS140_3_IG, p.87] requires a
66+
* Pairwise Consistency Test (PCT) to be carried out on a freshly
67+
* generated keypair before it can be exported.
68+
*
69+
* Set this option if such a check should be implemented.
70+
* In this case, crypto_sign_keypair_internal and
71+
* crypto_sign_keypair will return a non-zero error code if the
72+
* PCT failed.
73+
*
74+
* NOTE: This feature will drastically lower the performance of
75+
* key generation.
76+
*
77+
*****************************************************************************/
78+
/* #define MLD_CONFIG_KEYGEN_PCT */
79+
80+
/******************************************************************************
81+
* Name: MLD_CONFIG_KEYGEN_PCT_BREAKAGE_TEST
82+
*
83+
* Description: If this option is set, the user must provide a runtime
84+
* function `static inline int mld_break_pct() { ... }` to
85+
* indicate whether the PCT should be made fail.
86+
*
87+
* This option only has an effect if MLD_CONFIG_KEYGEN_PCT is set.
88+
*
89+
*****************************************************************************/
90+
/* #define MLD_CONFIG_KEYGEN_PCT_BREAKAGE_TEST
91+
#if !defined(__ASSEMBLER__)
92+
#include "sys.h"
93+
static MLD_INLINE int mld_break_pct(void)
94+
{
95+
... return 0/1 depending on whether PCT should be broken ...
96+
}
97+
#endif
98+
*/
99+
62100
#endif /* !MLD_CONFIG_H */

mldsa/sign.c

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,79 @@
1414
#include "sign.h"
1515
#include "symmetric.h"
1616

17+
18+
static int mld_check_pct(uint8_t const pk[CRYPTO_PUBLICKEYBYTES],
19+
uint8_t const sk[CRYPTO_SECRETKEYBYTES])
20+
__contract__(
21+
requires(memory_no_alias(pk, CRYPTO_PUBLICKEYBYTES))
22+
requires(memory_no_alias(sk, CRYPTO_SECRETKEYBYTES))
23+
ensures(return_value == 0 || return_value == -1)
24+
);
25+
26+
#if defined(MLD_CONFIG_KEYGEN_PCT)
27+
/*************************************************
28+
* [FIPS 140-3 IG]
29+
* (https://csrc.nist.gov/csrc/media/Projects/cryptographic-module-validation-program/documents/fips%20140-3/FIPS%20140-3%20IG.pdf)
30+
*
31+
* TE10.35.02: Pair-wise Consistency Test (PCT) for DSA keypairs
32+
*
33+
* Purpose: Validates that a generated public/private key pair can correctly
34+
* sign and verify data. Test performs signature generation using the private
35+
* key (sk), followed by signature verification using the public key (pk).
36+
* Returns 0 if the signature was successfully verified, non-zero if it cannot.
37+
*
38+
* Note: FIPS 204 requires that public/private key pairs are to be used only for
39+
* the calculation and/of verification of digital signatures.
40+
**************************************************/
41+
static int mld_check_pct(uint8_t const pk[CRYPTO_PUBLICKEYBYTES],
42+
uint8_t const sk[CRYPTO_SECRETKEYBYTES])
43+
{
44+
uint8_t message[1] = {0};
45+
uint8_t signature[CRYPTO_BYTES];
46+
uint8_t pk_test[CRYPTO_PUBLICKEYBYTES];
47+
size_t siglen;
48+
int ret;
49+
50+
/* Copy public key for testing */
51+
memcpy(pk_test, pk, CRYPTO_PUBLICKEYBYTES);
52+
53+
/* Sign a test message using the original secret key */
54+
ret = crypto_sign_signature(signature, &siglen, message, sizeof(message),
55+
NULL, 0, sk);
56+
if (ret == 0)
57+
{
58+
#if defined(MLD_CONFIG_KEYGEN_PCT_BREAKAGE_TEST)
59+
/* Deliberately break public key for testing purposes */
60+
if (mld_break_pct())
61+
{
62+
pk_test[0] = ~pk_test[0];
63+
}
64+
#endif /* MLD_CONFIG_KEYGEN_PCT_BREAKAGE_TEST */
65+
66+
/* Verify the signature using the (potentially corrupted) public key */
67+
ret = crypto_sign_verify(signature, siglen, message, sizeof(message), NULL,
68+
0, pk_test);
69+
}
70+
71+
/* Specification: Partially implements
72+
* @[FIPS204, Section 3.6.3, Destruction of intermediate values]
73+
TODO: add these when the rest of destruction of intermediate values are added
74+
mld_zeroize(signature, sizeof(signature));
75+
mld_zeroize(pk_test, sizeof(pk_test));
76+
*/
77+
return ret;
78+
}
79+
#else /* MLD_CONFIG_KEYGEN_PCT */
80+
static int mld_check_pct(uint8_t const pk[CRYPTO_PUBLICKEYBYTES],
81+
uint8_t const sk[CRYPTO_SECRETKEYBYTES])
82+
{
83+
/* Skip PCT */
84+
((void)pk);
85+
((void)sk);
86+
return 0;
87+
}
88+
#endif /* !MLD_CONFIG_KEYGEN_PCT */
89+
1790
static void mld_sample_s1_s2(mld_polyvecl *s1, mld_polyveck *s2,
1891
const uint8_t seed[MLDSA_CRHBYTES])
1992
__contract__(
@@ -96,6 +169,12 @@ int crypto_sign_keypair_internal(uint8_t *pk, uint8_t *sk,
96169
/* Compute H(rho, t1) and write secret key */
97170
shake256(tr, MLDSA_TRBYTES, pk, CRYPTO_PUBLICKEYBYTES);
98171
mld_pack_sk(sk, rho, tr, key, &t0, &s1, &s2);
172+
173+
/* Pairwise Consistency Test (PCT) @[FIPS140_3_IG, p.87] */
174+
if (mld_check_pct(pk, sk))
175+
{
176+
return -1;
177+
}
99178
return 0;
100179
}
101180

mldsa/sign.h

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@
1818
*
1919
* Description: FIPS 204: Algorithm 6 ML-DSA.KeyGen_internal.
2020
* Generates public and private key. Internal API.
21+
* When MLD_CONFIG_KEYGEN_PCT is set, performs a Pairwise
22+
* Consistency Test (PCT) as required by FIPS 140-3 IG.
2123
*
2224
* Arguments: - uint8_t *pk: pointer to output public key (allocated
2325
* array of CRYPTO_PUBLICKEYBYTES bytes)
@@ -26,7 +28,7 @@
2628
* - uint8_t *seed: pointer to input random seed (MLDSA_SEEDBYTES
2729
* bytes)
2830
*
29-
* Returns 0 (success)
31+
* Returns 0 (success) or -1 (PCT failure)
3032
**************************************************/
3133
int crypto_sign_keypair_internal(uint8_t *pk, uint8_t *sk,
3234
const uint8_t seed[MLDSA_SEEDBYTES])
@@ -36,6 +38,7 @@ __contract__(
3638
requires(memory_no_alias(seed, MLDSA_SEEDBYTES))
3739
assigns(object_whole(pk))
3840
assigns(object_whole(sk))
41+
ensures(return_value == 0 || return_value == -1)
3942
);
4043

4144
#define crypto_sign_keypair MLD_NAMESPACE(keypair)
@@ -44,20 +47,23 @@ __contract__(
4447
*
4548
* Description: FIPS 204: Algorithm 1 ML-DSA.KeyGen
4649
* Generates public and private key.
50+
* When MLD_CONFIG_KEYGEN_PCT is set, performs a Pairwise
51+
* Consistency Test (PCT) as required by FIPS 140-3 IG.
4752
*
4853
* Arguments: - uint8_t *pk: pointer to output public key (allocated
4954
* array of CRYPTO_PUBLICKEYBYTES bytes)
5055
* - uint8_t *sk: pointer to output private key (allocated
5156
* array of CRYPTO_SECRETKEYBYTES bytes)
5257
*
53-
* Returns 0 (success)
58+
* Returns 0 (success) or -1 (PCT failure)
5459
**************************************************/
5560
int crypto_sign_keypair(uint8_t *pk, uint8_t *sk)
5661
__contract__(
5762
requires(memory_no_alias(pk, CRYPTO_PUBLICKEYBYTES))
5863
requires(memory_no_alias(sk, CRYPTO_SECRETKEYBYTES))
5964
assigns(object_whole(pk))
6065
assigns(object_whole(sk))
66+
ensures(return_value == 0 || return_value == -1)
6167
);
6268

6369
#define crypto_sign_signature_internal MLD_NAMESPACE(signature_internal)
@@ -117,8 +123,9 @@ __contract__(
117123
* - size_t *siglen: pointer to output length of signature
118124
* - uint8_t *m: pointer to message to be signed
119125
* - size_t mlen: length of message
120-
* - uint8_t *ctx: pointer to contex string
121-
* - size_t ctxlen: length of contex string. Should be <= 255.
126+
* - uint8_t *ctx: pointer to context string. May be NULL
127+
* iff ctxlen == 0
128+
* - size_t ctxlen: length of context string. Should be <= 255.
122129
* - uint8_t *sk: pointer to bit-packed secret key
123130
*
124131
* Returns 0 (success) or -1 (context string too long OR nonce exhaustion)
@@ -130,7 +137,7 @@ __contract__(
130137
requires(memory_no_alias(sig, CRYPTO_BYTES))
131138
requires(memory_no_alias(siglen, sizeof(size_t)))
132139
requires(memory_no_alias(m, mlen))
133-
requires(memory_no_alias(ctx, ctxlen))
140+
requires((ctx == NULL && ctxlen == 0) || memory_no_alias(ctx, ctxlen))
134141
requires(memory_no_alias(sk, CRYPTO_SECRETKEYBYTES))
135142
assigns(memory_slice(sig, CRYPTO_BYTES))
136143
assigns(object_whole(siglen))
@@ -243,6 +250,7 @@ __contract__(
243250
* - const uint8_t *m: pointer to message
244251
* - size_t mlen: length of message
245252
* - const uint8_t *ctx: pointer to context string
253+
* May be NULL iff ctxlen == 0
246254
* - size_t ctxlen: length of context string
247255
* - const uint8_t *pk: pointer to bit-packed public key
248256
*
@@ -254,7 +262,7 @@ int crypto_sign_verify(const uint8_t *sig, size_t siglen, const uint8_t *m,
254262
__contract__(
255263
requires(memory_no_alias(sig, siglen))
256264
requires(memory_no_alias(m, mlen))
257-
requires(memory_no_alias(ctx, ctxlen))
265+
requires((ctx == NULL && ctxlen == 0) || memory_no_alias(ctx, ctxlen))
258266
requires(memory_no_alias(pk, CRYPTO_PUBLICKEYBYTES))
259267
ensures(return_value == 0 || return_value == -1)
260268
);

proofs/cbmc/check_pct/Makefile

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
# Copyright (c) The mldsa-native project authors
2+
# SPDX-License-Identifier: Apache-2.0 OR ISC OR MIT
3+
4+
include ../Makefile_params.common
5+
6+
HARNESS_ENTRY = harness
7+
HARNESS_FILE = check_pct_harness
8+
9+
# This should be a unique identifier for this proof, and will appear on the
10+
# Litani dashboard. It can be human-readable and contain spaces if you wish.
11+
PROOF_UID = mld_check_pct
12+
13+
DEFINES += -DMLD_CONFIG_KEYGEN_PCT
14+
INCLUDES +=
15+
16+
REMOVE_FUNCTION_BODY +=
17+
UNWINDSET +=
18+
19+
PROOF_SOURCES += $(PROOFDIR)/$(HARNESS_FILE).c
20+
PROJECT_SOURCES += $(SRCDIR)/mldsa/sign.c
21+
22+
CHECK_FUNCTION_CONTRACTS=mld_check_pct
23+
USE_FUNCTION_CONTRACTS=$(MLD_NAMESPACE)signature $(MLD_NAMESPACE)verify
24+
APPLY_LOOP_CONTRACTS=on
25+
USE_DYNAMIC_FRAMES=1
26+
27+
# Disable any setting of EXTERNAL_SAT_SOLVER, and choose SMT backend instead
28+
EXTERNAL_SAT_SOLVER=
29+
CBMCFLAGS=--smt2
30+
31+
FUNCTION_NAME = mld_check_pct
32+
33+
# If this proof is found to consume huge amounts of RAM, you can set the
34+
# EXPENSIVE variable. With new enough versions of the proof tools, this will
35+
# restrict the number of EXPENSIVE CBMC jobs running at once. See the
36+
# documentation in Makefile.common under the "Job Pools" heading for details.
37+
# EXPENSIVE = true
38+
39+
# This function is large enough to need...
40+
CBMC_OBJECT_BITS = 8
41+
42+
# If you require access to a file-local ("static") function or object to conduct
43+
# your proof, set the following (and do not include the original source file
44+
# ("mldsa/sign.c") in PROJECT_SOURCES).
45+
# REWRITTEN_SOURCES = $(PROOFDIR)/<__SOURCE_FILE_BASENAME__>.i
46+
# include ../Makefile.common
47+
# $(PROOFDIR)/<__SOURCE_FILE_BASENAME__>.i_SOURCE = $(SRCDIR)/mldsa/sign.c
48+
# $(PROOFDIR)/<__SOURCE_FILE_BASENAME__>.i_FUNCTIONS = foo bar
49+
# $(PROOFDIR)/<__SOURCE_FILE_BASENAME__>.i_OBJECTS = baz
50+
# Care is required with variables on the left-hand side: REWRITTEN_SOURCES must
51+
# be set before including Makefile.common, but any use of variables on the
52+
# left-hand side requires those variables to be defined. Hence, _SOURCE,
53+
# _FUNCTIONS, _OBJECTS is set after including Makefile.common.
54+
55+
include ../Makefile.common
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
// Copyright (c) The mldsa-native project authors
2+
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3+
// SPDX-License-Identifier: MIT-0
4+
5+
#include "sign.h"
6+
7+
int mld_check_pct(uint8_t const pk[CRYPTO_PUBLICKEYBYTES],
8+
uint8_t const sk[CRYPTO_SECRETKEYBYTES]);
9+
10+
void harness(void)
11+
{
12+
uint8_t *a, *b;
13+
mld_check_pct(a, b);
14+
}

proofs/cbmc/crypto_sign_keypair_internal/Makefile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ USE_FUNCTION_CONTRACTS+=$(MLD_NAMESPACE)polyveck_caddq
3232
USE_FUNCTION_CONTRACTS+=$(MLD_NAMESPACE)polyveck_power2round
3333
USE_FUNCTION_CONTRACTS+=$(MLD_NAMESPACE)pack_pk
3434
USE_FUNCTION_CONTRACTS+=$(MLD_NAMESPACE)pack_sk
35+
USE_FUNCTION_CONTRACTS+=mld_check_pct
3536

3637
APPLY_LOOP_CONTRACTS=on
3738
USE_DYNAMIC_FRAMES=1

0 commit comments

Comments
 (0)