Skip to content

Commit 3abb411

Browse files
WillChilds-Kleinamirhosvgeedo0
authored
Add build option for exposing self-test failure messages (#433)
*Issue #, if available:* ACCP-129 _**THIS FEATURE IS NOT INTENDED FOR PRODUCTION AND IS THUS LEFT UNDOCUMENTED IN OUR README**_ ## Notes This PR builds on prior work from @amirhosv on the `fips-experimentation` branch to provide an alternative mode of handling AWS-LC self test failures. We call this (non-default) mode `FIPS_SELF_TEST_SKIP_ABORT`, and it is only usable when ACCP is built in FIPS mode. By default, AWS-LC will `abort()` on self-test failures. However, as of AWS-LC v1.47, when built with the `AWSLC_FIPS_FAILURE_CALLBACK` build flag AWS-LC will [call][1] a [weak symbol][2] `AWS_LC_fips_failure_callback` function to handle self test failures instead of aborting. When ACCP is built with `-DFIPS_SELF_TEST_SKIP_ABORT`, ACCP defines `AWS_LC_fips_failure_callback` such that it appends to a queue of error strings in ACCP's native heap. To manage the accumulated error strings, we add a native `std::vector` wrapper `ConcurrentStringVector` providing a minimal, threadsafe API. Once the error queue is non-empty, all subsequent `getInstance` calls on an algorithm provided by ACCP will throw `FipsStatusException`. We provide two functions for callers to query fips self test error state on an `AmazonCorrettoCryptoProvider` instance: - `public boolean isFipsStatusOk()` - `public List<String> getFipsSelfTestFailures()` We could get away with only the latter function, but we provide `isFipsStatusOk()` to avoid performance costs of copying error strings over the JNI. [1]: https://github.com/aws/aws-lc/blob/1d8b807ed1ae75c89beda6c73a4ae27c404fa46f/crypto/fipsmodule/bcm.c#L416 [2]: https://github.com/aws/aws-lc/blob/1d8b807ed1ae75c89beda6c73a4ae27c404fa46f/crypto/internal.h#L1427-L1432 ## Testing To adequately test the new mode, we need to build AWS-LC with `FIPS_BREAK_TEST=TESTS` to programmatically break AWS-LC's pairwise consistency tests (PCTs). We test against each available PCT breakage during key generation by setting the appropriate environment variable, indicating which PCT to break. Unfortunately, Java doesn't appear to have a standard utility for manipulating process environment variables at runtime, so we create our own `TestUtil.setEnv` that calls POSIX `setenv`/`unsetenv` over the JNI. In addition to CI tests, I've also executed `run_accp_basic_tests.sh` with the new `--fips-self-test-failure-no-abort` flag, which will eventually be incorporated into our GitHub CI. ``` $ TEST_JAVA_HOME=/usr/lib/jvm/java-17-amazon-corretto.x86_64 ./tests/ci/run_accp_basic_tests.sh --fips-self-test-failure-no-abort ... BUILD SUCCESSFUL in 40m 15s 18 actionable tasks: 13 executed, 5 up-to-date ``` --- By submitting this pull request, I confirm that you can use, modify, copy, and redistribute this contribution, under the terms of your choice. --------- Co-authored-by: Amir Vakili <[email protected]> Co-authored-by: Amir Vakili <[email protected]> Co-authored-by: Gerardo Ravago 🇵🇭 <[email protected]>
1 parent eb936b1 commit 3abb411

18 files changed

+476
-19
lines changed

CHANGELOG.md

+5
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
11
# Changelog
22

3+
## 2.6.0
4+
5+
### Minor
6+
* [PR 433:](https://github.com/corretto/amazon-corretto-crypto-provider/pull/433) Add build option for exposing self-test failure messages
7+
38
## 2.5.0
49

510
### Minor

CMakeLists.txt

+22-3
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ set(TEST_DATA_DIR ${PROJECT_SOURCE_DIR}/test-data/ CACHE STRING "Path to directo
4242
set(ORIG_SRCROOT ${PROJECT_SOURCE_DIR} CACHE STRING "Path to root of original package")
4343
set(PROVIDER_VERSION_STRING "" CACHE STRING "X.Y.Z formatted version of the provider")
4444
set(EXPERIMENTAL_FIPS NO CACHE BOOL "Determines if this build is for FIPS mode with extra features from a non-FIPS branch of AWS-LC.")
45+
set(FIPS_SELF_TEST_SKIP_ABORT NO CACHE BOOL "Determines whether ACCP throws exceptions on self-test failure, or AWS-LC aborts. If NO, AWS-LC aborts. If YES, ACCP will provide error messages.")
4546
set(FIPS NO CACHE BOOL "Determine if this build is for FIPS mode")
4647
set(ALWAYS_ALLOW_EXTERNAL_LIB NO CACHE BOOL "Always permit tests to load ACCP shared objects from the library path")
4748
set(AWS_LC_VERSION_STRING "" CACHE STRING "Git version of AWS-LC used in this build")
@@ -51,6 +52,10 @@ if (EXPERIMENTAL_FIPS)
5152
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DEXPERIMENTAL_FIPS_BUILD")
5253
endif()
5354

55+
if (FIPS_SELF_TEST_SKIP_ABORT)
56+
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DFIPS_SELF_TEST_SKIP_ABORT")
57+
endif()
58+
5459
if (USE_CLANG_TIDY)
5560
# https://releases.llvm.org/9.0.0/tools/clang/tools/extra/docs/clang-tidy/checks/list.html
5661
# https://clang.llvm.org/extra/clang-tidy/#suppressing-undesired-diagnostics
@@ -294,6 +299,7 @@ set(C_SRC
294299
csrc/util.cpp
295300
csrc/util_class.cpp
296301
csrc/fips_kat_self_test.cpp
302+
csrc/fips_status.cpp
297303
${JNI_HEADER_DIR}/generated-headers.h)
298304

299305
if(FIPS)
@@ -504,7 +510,7 @@ if(NOT ENABLE_NATIVE_TEST_HOOKS)
504510
CHECK_LINKER_FLAG_SUPPORT(USE_VERSION_SCRIPT "-Wl,--version-script -Wl,${CMAKE_CURRENT_SOURCE_DIR}/final-link.version")
505511

506512
# This does the same thing as the version script, but works on Darwin platforms
507-
CHECK_LINKER_FLAG_SUPPORT(USE_EXPORTED_SYMBOL "-Wl,-exported_symbol '-Wl,_Java_*' -Wl,-exported_symbol '-Wl,_JNI_*'")
513+
CHECK_LINKER_FLAG_SUPPORT(USE_EXPORTED_SYMBOL "-Wl,-exported_symbol '-Wl,_Java_*' '-Wl,_AWS_LC_fips_failure_callback' -Wl,-exported_symbol '-Wl,_JNI_*'")
508514
endif()
509515

510516
# Attempt to drop unused sections; the idea here is to exclude unreferenced
@@ -681,6 +687,7 @@ add_custom_target(check-junit
681687
--select-package=com.amazon.corretto.crypto.provider.test
682688
--exclude-package=com.amazon.corretto.crypto.provider.test.integration
683689
--exclude-classname=com.amazon.corretto.crypto.provider.test.SecurityManagerTest
690+
--exclude-classname=com.amazon.corretto.crypto.provider.test.FipsStatusTest
684691

685692
DEPENDS accp-jar tests-jar)
686693

@@ -702,6 +709,15 @@ add_custom_target(check-junit-SecurityManager
702709

703710
DEPENDS accp-jar tests-jar)
704711

712+
add_custom_target(check-junit-FipsStatus
713+
COMMAND ${TEST_JAVA_EXECUTABLE}
714+
${TEST_RUNNER_ARGUMENTS}
715+
--select-class=com.amazon.corretto.crypto.provider.test.AesTest # Force loading ciphers
716+
--select-class=com.amazon.corretto.crypto.provider.test.SHA1Test # Force loading digests
717+
--select-class=com.amazon.corretto.crypto.provider.test.FipsStatusTest
718+
719+
DEPENDS accp-jar tests-jar)
720+
705721
add_custom_target(check-with-jni-flag
706722
COMMAND ${TEST_JAVA_EXECUTABLE}
707723
-Xcheck:jni
@@ -749,6 +765,7 @@ add_custom_target(check-junit-extra-checks
749765
${TEST_RUNNER_ARGUMENTS}
750766
--select-package=com.amazon.corretto.crypto.provider.test
751767
--exclude-package=com.amazon.corretto.crypto.provider.test.integration
768+
--exclude-classname=com.amazon.corretto.crypto.provider.test.FipsStatusTest
752769
--exclude-classname=com.amazon.corretto.crypto.provider.test.SecurityManagerTest
753770

754771
DEPENDS accp-jar tests-jar)
@@ -845,7 +862,8 @@ add_custom_target(check-junit-edKeyFactory
845862

846863
DEPENDS accp-jar tests-jar)
847864

848-
set(check_targets check-recursive-init
865+
set(check_targets
866+
check-recursive-init
849867
check-install-via-properties
850868
check-install-via-properties-with-debug
851869
check-junit
@@ -854,7 +872,8 @@ set(check_targets check-recursive-init
854872
check-junit-AesLazy
855873
check-junit-AesEager
856874
check-junit-DifferentTempDir
857-
check-junit-edKeyFactory)
875+
check-junit-edKeyFactory
876+
check-junit-FipsStatus)
858877

859878
if(${CMAKE_SYSTEM_NAME} MATCHES "Linux")
860879
set(check_targets ${check_targets} check-with-jni-flag)

build.gradle

+24-2
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,16 @@ if (System.properties["AWSLC_GITVERSION"]) {
3232
}
3333

3434
ext.isLegacyBuild = Boolean.getBoolean('LEGACY_BUILD')
35+
ext.allowFipsTestBreak = Boolean.getBoolean('ALLOW_FIPS_TEST_BREAK')
36+
ext.isFipsSelfTestFailureSkipAbort = Boolean.getBoolean('FIPS_SELF_TEST_SKIP_ABORT')
37+
38+
if (allowFipsTestBreak && !isFips) {
39+
throw new GradleException("ALLOW_FIPS_TEST_BREAK can only be set if FIPS is also set to true")
40+
}
41+
42+
if (isFipsSelfTestFailureSkipAbort && !isFips) {
43+
throw new GradleException("FIPS_SELF_TEST_SKIP_ABORT can only be set if FIPS is also set to true")
44+
}
3545

3646
ext.lcovIgnore = System.properties['LCOV_IGNORE']
3747
if (ext.lcovIgnore == null) {
@@ -253,9 +263,20 @@ task buildAwsLc {
253263

254264

255265
if (isFips) {
266+
println "Building AWS-LC in FIPS mode"
256267
args '-DFIPS=1'
257268
}
258269

270+
if (allowFipsTestBreak) {
271+
println "Building AWS-LC with hooks to break FIPS tests"
272+
args '-DFIPS_BREAK_TEST=TESTS'
273+
}
274+
275+
if (isFipsSelfTestFailureSkipAbort) {
276+
println "Building AWS-LC to call callback instead of aborting on self-test failure"
277+
args '-DCMAKE_C_FLAGS="-DAWSLC_FIPS_FAILURE_CALLBACK"'
278+
}
279+
259280
args '.'
260281
}
261282
}
@@ -342,6 +363,9 @@ task executeCmake(type: Exec) {
342363
if (isExperimentalFips) {
343364
args '-DEXPERIMENTAL_FIPS=ON'
344365
}
366+
if (isFipsSelfTestFailureSkipAbort) {
367+
args '-DFIPS_SELF_TEST_SKIP_ABORT=ON'
368+
}
345369

346370
if (prebuiltJar != null) {
347371
args '-DSIGNED_JAR=' + prebuiltJar
@@ -555,11 +579,9 @@ task coverage_cmake(type: Exec) {
555579
if (isExperimentalFips) {
556580
args '-DEXPERIMENTAL_FIPS=ON'
557581
}
558-
559582
if (System.properties['JAVA_HOME'] != null) {
560583
args '-DJAVA_HOME=' + System.properties['JAVA_HOME']
561584
}
562-
563585
if (System.properties['SINGLE_TEST'] != null) {
564586
args '-DSINGLE_TEST=' + System.properties['SINGLE_TEST']
565587

csrc/ed_gen.cpp

+2-2
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,12 @@
77

88
using namespace AmazonCorrettoCryptoProvider;
99

10-
void generateEdKey(EVP_PKEY_auto& key)
10+
static void generateEdKey(EVP_PKEY_auto& key)
1111
{
1212
EVP_PKEY_CTX_auto ctx = EVP_PKEY_CTX_auto::from(EVP_PKEY_CTX_new_id(EVP_PKEY_ED25519, nullptr));
1313
CHECK_OPENSSL(ctx.isInitialized());
1414
CHECK_OPENSSL(EVP_PKEY_keygen_init(ctx) == 1);
15-
CHECK_OPENSSL(EVP_PKEY_keygen(ctx, key.getAddressOfPtr()));
15+
CHECK_OPENSSL(EVP_PKEY_keygen(ctx, key.getAddressOfPtr()) == 1);
1616
}
1717

1818
JNIEXPORT jlong JNICALL Java_com_amazon_corretto_crypto_provider_EdGen_generateEvpEdKey(JNIEnv* pEnv, jclass)

csrc/fips_status.cpp

+87
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved.
2+
// SPDX-License-Identifier: Apache-2.0
3+
#include <cstdio>
4+
#include <functional>
5+
#include <jni.h>
6+
#include <string.h>
7+
#include <vector>
8+
9+
#include "string_vector.h"
10+
11+
static AmazonCorrettoCryptoProvider::ConcurrentStringVector fipsStatusErrors(1024);
12+
13+
// To have this symbol exported, one needs to modify the final-link.version and the CMakeLists.txt
14+
extern "C" void AWS_LC_fips_failure_callback(char const* message);
15+
16+
#if defined(FIPS_SELF_TEST_SKIP_ABORT)
17+
void AWS_LC_fips_failure_callback(char const* message)
18+
{
19+
const size_t char_limit = 128;
20+
if (strnlen(message, char_limit + 1) > char_limit) {
21+
fprintf(stderr, "AWS_LC_fips_failure_callback invoked with message message exceeding %lu chars\n", char_limit);
22+
return;
23+
}
24+
fprintf(stderr, "AWS_LC_fips_failure_callback invoked with message: '%s'\n", message);
25+
fipsStatusErrors.push_back(message);
26+
}
27+
#else
28+
void AWS_LC_fips_failure_callback(char const* message)
29+
{
30+
fprintf(stderr, "AWS_LC_fips_failure_callback invoked with message: '%s'\n", message);
31+
abort();
32+
}
33+
#endif
34+
35+
extern "C" JNIEXPORT jobject JNICALL
36+
Java_com_amazon_corretto_crypto_provider_AmazonCorrettoCryptoProvider_getFipsSelfTestFailuresInternal(
37+
JNIEnv* env, jobject thisObj)
38+
{
39+
std::vector<std::string> errors = fipsStatusErrors.to_std();
40+
41+
// Construct a Java ArrayList, get a handle for the |add| method
42+
jclass arrayListClass = env->FindClass("java/util/ArrayList");
43+
if (arrayListClass == NULL) {
44+
abort();
45+
}
46+
jmethodID constructor = env->GetMethodID(arrayListClass, "<init>", "()V");
47+
if (constructor == NULL) {
48+
abort();
49+
}
50+
jobject arrayList = env->NewObject(arrayListClass, constructor);
51+
if (arrayList == NULL) {
52+
abort();
53+
}
54+
jmethodID addMethod = env->GetMethodID(arrayListClass, "add", "(Ljava/lang/Object;)Z");
55+
if (addMethod == NULL) {
56+
abort();
57+
}
58+
59+
// Copy the errors over to |arrayList| and clean up temporary local reference
60+
for (size_t i = 0; i < errors.size(); i++) {
61+
jstring javaString = env->NewStringUTF(errors[i].c_str());
62+
env->CallVoidMethod(arrayList, addMethod, javaString);
63+
env->DeleteLocalRef(javaString);
64+
}
65+
66+
return arrayList;
67+
}
68+
69+
extern "C" JNIEXPORT int JNICALL
70+
Java_com_amazon_corretto_crypto_provider_AmazonCorrettoCryptoProvider_fipsStatusErrorCount(JNIEnv* env, jobject thisObj)
71+
{
72+
return fipsStatusErrors.size();
73+
}
74+
75+
// TEST methods below
76+
77+
extern "C" JNIEXPORT void JNICALL Java_com_amazon_corretto_crypto_provider_test_NativeTestHooks_resetFipsStatus(
78+
JNIEnv*, jclass)
79+
{
80+
fipsStatusErrors.clear();
81+
}
82+
83+
extern "C" JNIEXPORT void JNICALL
84+
Java_com_amazon_corretto_crypto_provider_test_NativeTestHooks_callAwsLcFipsFailureCallback(JNIEnv*, jclass)
85+
{
86+
AWS_LC_fips_failure_callback("called by a test");
87+
}

csrc/loader.cpp

+10
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,16 @@ JNIEXPORT jboolean JNICALL Java_com_amazon_corretto_crypto_provider_Loader_isExp
5656
#endif
5757
}
5858

59+
JNIEXPORT jboolean JNICALL Java_com_amazon_corretto_crypto_provider_Loader_isFipsSelfTestFailureSkipAbort(
60+
JNIEnv*, jclass)
61+
{
62+
#ifdef FIPS_SELF_TEST_SKIP_ABORT
63+
return JNI_TRUE;
64+
#else
65+
return JNI_FALSE;
66+
#endif
67+
}
68+
5969
JNIEXPORT jstring JNICALL Java_com_amazon_corretto_crypto_provider_Loader_getNativeLibraryVersion(JNIEnv* pEnv, jclass)
6070
{
6171
try {

csrc/string_vector.h

+71
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved.
2+
// SPDX-License-Identifier: Apache-2.0
3+
#ifndef STRING_VECTOR_H
4+
#define STRING_VECTOR_H
5+
6+
#include "compiler.h"
7+
8+
#include <cstdio>
9+
#include <cstdlib>
10+
#include <deque>
11+
#include <pthread.h>
12+
#include <string>
13+
14+
namespace AmazonCorrettoCryptoProvider {
15+
16+
class ConcurrentStringVector {
17+
private:
18+
std::deque<std::string> vec;
19+
const size_t limit;
20+
mutable pthread_rwlock_t lock;
21+
22+
public:
23+
ConcurrentStringVector(size_t limit)
24+
: limit(limit)
25+
{
26+
pthread_rwlock_init(&lock, nullptr);
27+
}
28+
29+
~ConcurrentStringVector() { pthread_rwlock_destroy(&lock); }
30+
31+
// Disable copy constructors
32+
ConcurrentStringVector(const ConcurrentStringVector&);
33+
ConcurrentStringVector& operator=(const ConcurrentStringVector&);
34+
35+
void push_back(const std::string& value)
36+
{
37+
pthread_rwlock_wrlock(&lock);
38+
if (vec.size() >= limit) { // If we're at the limit, pop FIFO
39+
vec.pop_front();
40+
}
41+
vec.push_back(std::string(value));
42+
pthread_rwlock_unlock(&lock);
43+
}
44+
45+
void clear()
46+
{
47+
pthread_rwlock_wrlock(&lock);
48+
vec.clear();
49+
pthread_rwlock_unlock(&lock);
50+
}
51+
52+
size_t size() const
53+
{
54+
pthread_rwlock_rdlock(&lock);
55+
size_t vec_size = vec.size();
56+
pthread_rwlock_unlock(&lock);
57+
return vec_size;
58+
}
59+
60+
std::vector<std::string> to_std() const
61+
{
62+
pthread_rwlock_rdlock(&lock);
63+
std::vector<std::string> out(vec.begin(), vec.end()); // Use copy constructor, no references
64+
pthread_rwlock_unlock(&lock);
65+
return out;
66+
}
67+
};
68+
69+
} // namespace AmazonCorrettoCryptoProvider
70+
71+
#endif // STRING_VECTOR_H

csrc/test_util.cpp

+28
Original file line numberDiff line numberDiff line change
@@ -64,4 +64,32 @@ extern "C" JNIEXPORT jbyteArray JNICALL Java_com_amazon_corretto_crypto_provider
6464
}
6565
}
6666

67+
/*
68+
* Class: com_amazon_corretto_crypto_provider_test_TestUtil
69+
* Method: setEnv
70+
* Signature: (Ljava/lang/String;Ljava/lang/String;)V
71+
*/
72+
extern "C" JNIEXPORT void JNICALL Java_com_amazon_corretto_crypto_provider_test_TestUtil_setEnv(
73+
JNIEnv* pEnv, jclass, jstring nameJava, jstring valueJava)
74+
{
75+
int ret;
76+
try {
77+
raii_env env(pEnv);
78+
const char* name = env->GetStringUTFChars(nameJava, 0);
79+
if (valueJava == nullptr) {
80+
ret = unsetenv(name);
81+
} else {
82+
const char* value = env->GetStringUTFChars(valueJava, 0);
83+
ret = setenv(name, value, /*overwrite*/ 1);
84+
env->ReleaseStringUTFChars(valueJava, value);
85+
}
86+
env->ReleaseStringUTFChars(nameJava, name);
87+
if (ret != 0) { // non-zero indicates failure https://man7.org/linux/man-pages/man3/setenv.3.html
88+
throw_java_ex(EX_RUNTIME_CRYPTO, "Error calling POSIX setenv or unsetenv");
89+
}
90+
} catch (java_ex& ex) {
91+
ex.throw_to_java(pEnv);
92+
}
93+
}
94+
6795
} // namespace

final-link.version

+1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
{
22
global:
33
Java_*;
4+
AWS_LC_fips_failure_callback;
45
JNI_*;
56
hook_*;
67
local:

src/com/amazon/corretto/crypto/provider/AesGcmSpi.java

-1
Original file line numberDiff line numberDiff line change
@@ -316,7 +316,6 @@ protected void engineInit(
316316
final AlgorithmParameterSpec algorithmParameterSpec,
317317
final SecureRandom secureRandom)
318318
throws InvalidKeyException, InvalidAlgorithmParameterException {
319-
320319
final int opMode = checkOperation(jceOpMode);
321320

322321
final GCMParameterSpec spec = checkSpecAndTag(algorithmParameterSpec);

0 commit comments

Comments
 (0)