Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor openssl.FIPS to support third-party FIPS providers #184

Merged
merged 5 commits into from
Oct 21, 2024
Merged
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
38 changes: 30 additions & 8 deletions goopenssl.c
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,19 @@ FOR_ALL_OPENSSL_FUNCTIONS
#undef DEFINEFUNC_RENAMED_1_1
#undef DEFINEFUNC_RENAMED_3_0

// go_openssl_fips_enabled returns 1 if FIPS mode is enabled, 0 otherwise.
// As a special case, it returns -1 if it cannot determine if FIPS mode is enabled.
// See openssl.FIPS for details about its implementation.
//
// This function is reimplemented here because openssl.FIPS assumes that
// all the OpenSSL bindings are loaded, that is, go_openssl_load_functions has
// already been called. On the other hand, go_openssl_fips_enabled is called from
// openssl.CheckVersion, which is used to check if a given OpenSSL shared library
// exists and is FIPS compliant. That shared library might not be the one that
// was passed to go_openssl_load_functions, or it might not even have been called at all.
//
// It is written in C because it is not possible to directly call C function pointers
// retrieved using dlsym from Go.
int
go_openssl_fips_enabled(void* handle)
{
Expand All @@ -45,15 +58,24 @@ go_openssl_fips_enabled(void* handle)
return FIPS_mode();

// For OpenSSL 3.x.
int (*EVP_default_properties_is_fips_enabled)(void*);
int (*OSSL_PROVIDER_available)(void*, const char*);
EVP_default_properties_is_fips_enabled = (int (*)(void*))dlsym(handle, "EVP_default_properties_is_fips_enabled");
OSSL_PROVIDER_available = (int (*)(void*, const char*))dlsym(handle, "OSSL_PROVIDER_available");
if (EVP_default_properties_is_fips_enabled != NULL && OSSL_PROVIDER_available != NULL &&
EVP_default_properties_is_fips_enabled(NULL) == 1 && OSSL_PROVIDER_available(NULL, "fips") == 1)
return 1;
int (*EVP_default_properties_is_fips_enabled)(void*) = (int (*)(void*))dlsym(handle, "EVP_default_properties_is_fips_enabled");
void *(*EVP_MD_fetch)(void*, const char*, const char*) = (void* (*)(void*, const char*, const char*))dlsym(handle, "EVP_MD_fetch");
void (*EVP_MD_free)(void*) = (void (*)(void*))dlsym(handle, "EVP_MD_free");

return 0;
if (EVP_default_properties_is_fips_enabled == NULL || EVP_MD_fetch == NULL || EVP_MD_free == NULL) {
// Shouldn't happen, but if it does, we can't determine if FIPS mode is enabled.
return -1;
}

if (EVP_default_properties_is_fips_enabled(NULL) != 1)
return 0;

void *md = EVP_MD_fetch(NULL, "SHA2-256", NULL);
if (md == NULL)
return 0;

EVP_MD_free(md);
return 1;
}

// Load all the functions stored in FOR_ALL_OPENSSL_FUNCTIONS
Expand Down
39 changes: 29 additions & 10 deletions openssl.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,14 @@ func CheckVersion(version string) (exists, fips bool) {
return false, false
}
defer dlclose(handle)
fips = C.go_openssl_fips_enabled(handle) == 1
return true, fips
enabled := C.go_openssl_fips_enabled(handle)
fips = enabled == 1
// If go_openssl_fips_enabled returns -1, it means that all or some of the necessary
// functions are not available. This can be due to the version of OpenSSL being too old,
// too incompatible, or the shared library not being an OpenSSL library. In any case,
// we shouldn't consider this library to be valid for our purposes.
exists = enabled != -1
return
}

// Init loads and initializes OpenSSL from the shared library at path.
Expand Down Expand Up @@ -96,23 +102,36 @@ func VersionText() string {
var (
providerNameFips = C.CString("fips")
providerNameDefault = C.CString("default")

algorithmSHA256 = C.CString("SHA2-256")
)

// FIPS returns true if OpenSSL is running in FIPS mode, else returns false.
// FIPS returns true if OpenSSL is running in FIPS mode and there is
// a provider available that supports FIPS. It returns false otherwise.
func FIPS() bool {
switch vMajor {
case 1:
return C.go_openssl_FIPS_mode() == 1
case 3:
// If FIPS is not enabled via default properties, then we are sure FIPS is not used.
if C.go_openssl_EVP_default_properties_is_fips_enabled(nil) == 0 {
// Check if the default properties contain `fips=1`.
if C.go_openssl_EVP_default_properties_is_fips_enabled(nil) != 1 {
// Note that it is still possible that the provider used by default is FIPS-compliant,
// but that wouldn't be a system or user requirement.
return false
}
// Check if the SHA-256 algorithm is available. If it is, then we can be sure that there is a provider available that matches
// the `fips=1` query. Most notably, this works for the common case of using the built-in FIPS provider.
//
// Note that this approach has a small chance of false negative if the FIPS provider doesn't provide the SHA-256 algorithm,
// but that is highly unlikely because SHA-256 is one of the most common algorithms and fundamental to many cryptographic operations.
// It also has a small chance of false positive if the FIPS provider implements the SHA-256 algorithm but not the other algorithms
// used by the caller application, but that is also unlikely because the FIPS provider should provide all common algorithms.
md := C.go_openssl_EVP_MD_fetch(nil, algorithmSHA256, nil)
if md == nil {
return false
}
// EVP_default_properties_is_fips_enabled can return true even if the FIPS provider isn't loaded,
// it is only based on the default properties.
// We can be sure that the FIPS provider is available if we can fetch an algorithm, e.g., SHA2-256,
// explicitly setting `fips=yes`.
return C.go_openssl_OSSL_PROVIDER_available(nil, providerNameFips) == 1
C.go_openssl_EVP_MD_free(md)
return true
default:
panic(errUnsupportedVersion())
}
Expand Down
Loading