diff --git a/export_test.go b/export_test.go index d3d10ec..30c49c7 100644 --- a/export_test.go +++ b/export_test.go @@ -3,3 +3,5 @@ package openssl var ErrOpen = errOpen var TestNotMarshalable = &testNotMarshalable + +var IsProviderAvailable = isProviderAvailable diff --git a/openssl.go b/openssl.go index 691bb16..a9df964 100644 --- a/openssl.go +++ b/openssl.go @@ -89,23 +89,33 @@ func VersionText() string { var ( providerNameFips = C.CString("fips") providerNameDefault = C.CString("default") + propFIPS = C.CString("fips=yes") + propNoFIPS = C.CString("-fips") + + 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 } - // 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 + // 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. + return proveSHA256(nil) default: panic(errUnsupportedVersion()) } @@ -113,11 +123,15 @@ func FIPS() bool { // SetFIPS enables or disables FIPS mode. // -// For OpenSSL 3, the `fips` provider is loaded if enabled is true, -// else the `default` provider is loaded. -func SetFIPS(enabled bool) error { +// For OpenSSL 3, if there is no provider available that supports FIPS mode, +// SetFIPS will try to load a built-in provider that supports FIPS mode. +func SetFIPS(enable bool) error { + if FIPS() == enable { + // Already in the desired state. + return nil + } var mode C.int - if enabled { + if enable { mode = C.int(1) } else { mode = C.int(0) @@ -129,25 +143,25 @@ func SetFIPS(enabled bool) error { } return nil case 3: - var provName *C.char - if enabled { + var shaProps, provName *C.char + if enable { + shaProps = propFIPS provName = providerNameFips } else { + shaProps = propNoFIPS provName = providerNameDefault } - // Check if there is any provider that matches props. - if C.go_openssl_OSSL_PROVIDER_available(nil, provName) != 1 { - // If not, fallback to provName provider. - if C.go_openssl_OSSL_PROVIDER_load(nil, provName) == nil { - return newOpenSSLError("OSSL_PROVIDER_try_load") - } - // Make sure we now have a provider available. - if C.go_openssl_OSSL_PROVIDER_available(nil, provName) != 1 { - return fail("SetFIPS(" + strconv.FormatBool(enabled) + ") not supported") + if !proveSHA256(shaProps) { + // There is no provider available that supports the desired FIPS mode. + // Try to load the built-in provider associated with the given mode. + if C.go_openssl_OSSL_PROVIDER_try_load(nil, provName, 1) == nil { + // The built-in provider was not loaded successfully, we can't enable FIPS mode. + C.go_openssl_ERR_clear_error() + return errors.New("openssl: FIPS mode not supported by any provider") } } if C.go_openssl_EVP_default_properties_enable_fips(nil, mode) != 1 { - return newOpenSSLError("openssl: EVP_default_properties_enable_fips") + return newOpenSSLError("EVP_default_properties_enable_fips") } return nil default: @@ -155,6 +169,29 @@ func SetFIPS(enabled bool) error { } } +// proveSHA256 checks if the SHA-256 algorithm is available +// using the given properties. +func proveSHA256(props *C.char) bool { + md := C.go_openssl_EVP_MD_fetch(nil, algorithmSHA256, props) + if md == nil { + C.go_openssl_ERR_clear_error() + return false + } + C.go_openssl_EVP_MD_free(md) + return true +} + +// isProviderAvailable checks if the provider with the given name is available. +// This function is used in export_test.go, but must be defined here as test files can't access C functions. +func isProviderAvailable(name string) bool { + if vMajor == 1 { + return false + } + providerName := C.CString(name) + defer C.free(unsafe.Pointer(providerName)) + return C.go_openssl_OSSL_PROVIDER_available(nil, providerName) == 1 +} + // noescape hides a pointer from escape analysis. noescape is // the identity function but escape analysis doesn't think the // output depends on the input. noescape is inlined and currently diff --git a/openssl_test.go b/openssl_test.go index cbae161..4256dad 100644 --- a/openssl_test.go +++ b/openssl_test.go @@ -78,3 +78,44 @@ func TestCheckVersion(t *testing.T) { t.Fatalf("FIPS mismatch: want %v, got %v", want, fips) } } + +func TestSetFIPS(t *testing.T) { + fipsEnabled := openssl.FIPS() + t.Cleanup(func() { + // Restore the previous FIPS mode. + err := openssl.SetFIPS(fipsEnabled) + if err != nil { + t.Fatal(err) + } + }) + + if err := openssl.SetFIPS(fipsEnabled); err != nil { + // Test that we can set FIPS mode to the current state + // without error. + t.Fatalf("SetFIPS(%v) failed: %v", fipsEnabled, err) + } + if got := openssl.FIPS(); got != fipsEnabled { + // Test that the FIPS mode hasn't been changed by the + // previous SetFIPS call. + t.Fatalf("FIPS mode mismatch: want %v, got %v", fipsEnabled, got) + } + + if fipsEnabled && + openssl.IsProviderAvailable("default") { + // Test that we can disable FIPS mode if it was enabled + // when the built-in provider is available. + err := openssl.SetFIPS(false) + if err != nil { + t.Fatalf("SetFIPS(false) failed: %v", err) + } + } else if !fipsEnabled && openssl.IsProviderAvailable("fips") { + // Test that we can enable FIPS mode if it was disabled + // when the provider is known to support FIPS mode. + err := openssl.SetFIPS(true) + if err != nil { + t.Fatalf("SetFIPS(true) failed: %v", err) + } + } else { + t.Skip("FIPS mode is not supported") + } +} \ No newline at end of file diff --git a/shims.h b/shims.h index a484a49..8194a2f 100644 --- a/shims.h +++ b/shims.h @@ -167,6 +167,7 @@ typedef void* GO_SHA_CTX_PTR; // #endif #define FOR_ALL_OPENSSL_FUNCTIONS \ DEFINEFUNC(void, ERR_error_string_n, (unsigned long e, char *buf, size_t len), (e, buf, len)) \ +DEFINEFUNC(void, ERR_clear_error, (void), ()) \ DEFINEFUNC_LEGACY_1(unsigned long, ERR_get_error_line, (const char **file, int *line), (file, line)) \ DEFINEFUNC_3_0(unsigned long, ERR_get_error_all, (const char **file, int *line, const char **func, const char **data, int *flags), (file, line, func, data, flags)) \ DEFINEFUNC_RENAMED_1_1(const char *, OpenSSL_version, SSLeay_version, (int type), (type)) \ @@ -188,7 +189,7 @@ DEFINEFUNC_LEGACY_1(int, FIPS_mode_set, (int r), (r)) \ DEFINEFUNC_3_0(int, EVP_default_properties_is_fips_enabled, (GO_OSSL_LIB_CTX_PTR libctx), (libctx)) \ DEFINEFUNC_3_0(int, EVP_default_properties_enable_fips, (GO_OSSL_LIB_CTX_PTR libctx, int enable), (libctx, enable)) \ DEFINEFUNC_3_0(int, OSSL_PROVIDER_available, (GO_OSSL_LIB_CTX_PTR libctx, const char *name), (libctx, name)) \ -DEFINEFUNC_3_0(GO_OSSL_PROVIDER_PTR, OSSL_PROVIDER_load, (GO_OSSL_LIB_CTX_PTR libctx, const char *name), (libctx, name)) \ +DEFINEFUNC_3_0(GO_OSSL_PROVIDER_PTR, OSSL_PROVIDER_try_load, (GO_OSSL_LIB_CTX_PTR libctx, const char *name, int retain_fallbacks), (libctx, name, retain_fallbacks)) \ DEFINEFUNC_3_0(const char *, OSSL_PROVIDER_get0_name, (const GO_OSSL_PROVIDER_PTR prov), (prov)) \ DEFINEFUNC_3_0(GO_EVP_MD_PTR, EVP_MD_fetch, (GO_OSSL_LIB_CTX_PTR ctx, const char *algorithm, const char *properties), (ctx, algorithm, properties)) \ DEFINEFUNC_3_0(void, EVP_MD_free, (GO_EVP_MD_PTR md), (md)) \