diff --git a/openssl.go b/openssl.go index 145b082..ec39bf1 100644 --- a/openssl.go +++ b/openssl.go @@ -134,6 +134,37 @@ func FIPS() bool { } } +// FIPSCapable returns true if the provider used by default matches the `fips=yes` query. +// It is useful for checking whether OpenSSL is capable of running in FIPS mode regardless +// of whether FIPS mode is explicitly enabled. For example, Azure Linux 3 doesn't set the +// `fips=yes` query in the default properties, but sets the default provider to be SCOSSL, +// which is FIPS-capable. +// +// Considerations: +// - Multiple calls to FIPSCapable can return different values if [SetFIPS] is called in between. +// - Can return true even if [FIPS] returns false, because [FIPS] also checks whether +// the default properties contain `fips=yes`. +// - When using OpenSSL 3, will always return true if [FIPS] returns true. +// - When using OpenSSL 1, Will always return the same value as [FIPS]. +// - OpenSSL 3 doesn't provide a way to know if a provider is FIPS-capable. This function uses +// some heuristics that should be treated as an implementation detail that may change in the future. +func FIPSCapable() bool { + if FIPS() { + return true + } + if vMajor == 3 { + // Load the provider with and without the `fips=yes` query. + // If the providers are the same, then the default provider is FIPS-capable. + provFIPS := sha256Provider(propFIPS) + if provFIPS == nil { + return false + } + provDefault := sha256Provider(nil) + return provFIPS == provDefault + } + return false +} + // 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 { @@ -193,16 +224,22 @@ func SetFIPS(enable bool) error { } } -// proveSHA256 checks if the SHA-256 algorithm is available +// sha256Provider returns the provider for the SHA-256 algorithm // using the given properties. -func proveSHA256(props *C.char) bool { +func sha256Provider(props *C.char) C.GO_OSSL_PROVIDER_PTR { md := C.go_openssl_EVP_MD_fetch(nil, algorithmSHA256, props) if md == nil { C.go_openssl_ERR_clear_error() - return false + return nil } - C.go_openssl_EVP_MD_free(md) - return true + defer C.go_openssl_EVP_MD_free(md) + return C.go_openssl_EVP_MD_get0_provider(md) +} + +// proveSHA256 checks if the SHA-256 algorithm is available +// using the given properties. +func proveSHA256(props *C.char) bool { + return sha256Provider(props) != nil } // noescape hides a pointer from escape analysis. noescape is diff --git a/openssl_test.go b/openssl_test.go index 4c723fc..22acfe1 100644 --- a/openssl_test.go +++ b/openssl_test.go @@ -62,6 +62,7 @@ func TestMain(m *testing.M) { _ = openssl.SetFIPS(true) // Skip the error as we still want to run the tests on machines without FIPS support. fmt.Println("OpenSSL version:", openssl.VersionText()) fmt.Println("FIPS enabled:", openssl.FIPS()) + fmt.Println("FIPS capable:", openssl.FIPSCapable()) status := m.Run() for range 5 { // Run GC a few times to avoid false positives in leak detection. @@ -133,3 +134,15 @@ func TestSetFIPS(t *testing.T) { t.Skip("FIPS mode is not supported") } } + +func TestFIPSCapable(t *testing.T) { + got := openssl.FIPSCapable() + want := openssl.FIPS() + if !want && openssl.SymCryptProviderAvailable() { + // The SymCrypt provider is FIPS-capable. + want = true + } + if got != want { + t.Fatalf("FIPSCapable mismatch: want %v, got %v", want, got) + } +}