Skip to content

Commit

Permalink
support third-party providers in FIPS and SetFIPS (#223)
Browse files Browse the repository at this point in the history
  • Loading branch information
qmuntal authored Nov 14, 2024
1 parent ca714e7 commit fd82fff
Show file tree
Hide file tree
Showing 4 changed files with 106 additions and 25 deletions.
2 changes: 2 additions & 0 deletions export_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,5 @@ package openssl
var ErrOpen = errOpen

var TestNotMarshalable = &testNotMarshalable

var IsProviderAvailable = isProviderAvailable
85 changes: 61 additions & 24 deletions openssl.go
Original file line number Diff line number Diff line change
Expand Up @@ -89,35 +89,49 @@ 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())
}
}

// 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)
Expand All @@ -129,32 +143,55 @@ 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:
panic(errUnsupportedVersion())
}
}

// 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
Expand Down
41 changes: 41 additions & 0 deletions openssl_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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")
}
}
3 changes: 2 additions & 1 deletion shims.h
Original file line number Diff line number Diff line change
Expand Up @@ -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)) \
Expand All @@ -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)) \
Expand Down

0 comments on commit fd82fff

Please sign in to comment.