diff --git a/goopenssl.c b/goopenssl.c index 1e428d5..c3385b9 100644 --- a/goopenssl.c +++ b/goopenssl.c @@ -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) { @@ -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 diff --git a/openssl.go b/openssl.go index 1562cee..fb4b65b 100644 --- a/openssl.go +++ b/openssl.go @@ -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. @@ -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()) }