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

Prioritize FIPS-enabled versions when automatically selecting a version #53

Merged
merged 7 commits into from
Jun 2, 2023
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
2 changes: 2 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ jobs:
run: go test -gcflags=all=-d=checkptr -count 10 -v ./...
env:
GO_OPENSSL_VERSION_OVERRIDE: ${{ matrix.openssl-version-build }}
- name: Run Test - Build - No version override
run: go test -gcflags=all=-d=checkptr -v ./...
- name: Run Test - Misc
run: go test -gcflags=all=-d=checkptr -v .
working-directory: ./misc
Expand Down
21 changes: 21 additions & 0 deletions openssl/goopenssl.c
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,27 @@
#include <dlfcn.h>
#include <stdio.h>

int
go_openssl_fips_enabled(void* handle)
{
// For OpenSSL 1.x.
int (*FIPS_mode)(void);
FIPS_mode = (int (*)(void))dlsym(handle, "FIPS_mode");
if (FIPS_mode != NULL)
return FIPS_mode();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, on second look, I think this might be going too deep? If systemwide/opensslwide FIPS isn't enabled, it seems like this means that even if FIPS mode is supported, this will return 0, so this version of OpenSSL won't get picked. So if we e.g. have 3.0 that doesn't support FIPS and 1.0.2 that supports FIPS but FIPS isn't enabled by default, we would pick 3.0 as the "fallback", not have FIPS, and (e.g.) fail to turn on FIPS mode later when we really should have been able to.

Copy link
Contributor Author

@qmuntal qmuntal May 31, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep, that was may intention, pick the systemwide enabled OpenSSL version, not just one that is FIPS-capable but not enabled (should have documented it better in the PR description). IMO it makes more sense this way, production environments that require FIPS must have it enable systemwide, as not all tooling and framework provides something like GOFIPS=1.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Interesting, yeah, that makes sense.

My next thought would be "what if you want to test FIPS mode on a non-FIPS machine with multiple OpenSSL versions etc." but in that case, setting GO_OPENSSL_VERSION_OVERRIDE is perfectly fine if the fallback isn't the right version--and it's probably something you would end up doing anyway to test each version intentionally. 😄


// 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;

return 0;
}

static unsigned long
version_num(void* handle)
{
Expand Down
1 change: 1 addition & 0 deletions openssl/goopenssl.h
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

#include "openssl_funcs.h"

int go_openssl_fips_enabled(void* handle);
int go_openssl_version_major(void* handle);
int go_openssl_version_minor(void* handle);
int go_openssl_thread_setup(void);
Expand Down
75 changes: 38 additions & 37 deletions openssl/openssl.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,6 @@ import (
var (
providerNameFips = C.CString("fips")
providerNameDefault = C.CString("default")
propFipsYes = C.CString("fips=yes")
propFipsNo = C.CString("fips=no")
algProve = C.CString("SHA2-256")
)

var (
Expand Down Expand Up @@ -126,26 +123,33 @@ func loadLibrary(version string) (unsafe.Pointer, error) {
}
return handle, nil
}
var fallbackHandle unsafe.Pointer
for _, v := range knownVersions {
handle := dlopen(v)
if handle != nil {
if handle == nil {
continue
}
if C.go_openssl_fips_enabled(handle) == 1 {
// Found a FIPS enabled version, use it.
if fallbackHandle != nil {
// If we found a FIPS enabled version but we already have a fallback
// version, close the fallback version.
C.dlclose(fallbackHandle)
}
return handle, nil
}
if fallbackHandle == nil {
// Remember the first version that exists but is not FIPS enabled
// in case we don't find any FIPS enabled version.
fallbackHandle = handle
} else {
C.dlclose(handle)
}
}
return nil, errors.New("openssl: can't load libcrypto.so using any known version suffix")
}

// providerAvailable looks through provider's digests
// checking if there is any that matches the props query.
func providerAvailable(props *C.char) bool {
C.go_openssl_ERR_set_mark()
md := C.go_openssl_EVP_MD_fetch(nil, algProve, props)
C.go_openssl_ERR_pop_to_mark()
if md == nil {
return false
if fallbackHandle != nil {
return fallbackHandle, nil
}
C.go_openssl_EVP_MD_free(md)
return true
return nil, errors.New("openssl: can't load libcrypto.so using any known version suffix")
}

// FIPS returns true if OpenSSL is running in FIPS mode, else returns false.
Expand All @@ -159,53 +163,49 @@ func FIPS() bool {
}
// 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.
return providerAvailable(propFipsYes)
return C.go_openssl_OSSL_PROVIDER_available(nil, providerNameFips) == 1
default:
panic(errUnsuportedVersion())
}
}

// SetFIPS enables or disables FIPS mode.
//
// It implements the following provider fallback logic for OpenSSL 3:
// - The "fips" provider is loaded if enabled=true and no loaded provider matches "fips=yes".
// - The "default" provider is loaded if enabled=false and no loaded provider matches "fips=no".
// This logic allows advanced users to define their own providers that match "fips=yes" and "fips=no" using the OpenSSL config file.
// On OpenSSL 3, the `fips` provider is loaded if enabled is true,
// else the `default` provider is loaded.
func SetFIPS(enabled bool) error {
var mode C.int
if enabled {
mode = C.int(1)
} else {
mode = C.int(0)
}
switch vMajor {
case 1:
var mode C.int
if enabled {
mode = C.int(1)
} else {
mode = C.int(0)
}
if C.go_openssl_FIPS_mode_set(mode) != 1 {
return newOpenSSLError("openssl: FIPS_mode_set")
}
return nil
case 3:
var props, provName *C.char
var provName *C.char
if enabled {
props = propFipsYes
provName = providerNameFips
} else {
props = propFipsNo
provName = providerNameDefault
}
// Check if there is any provider that matches props.
if !providerAvailable(props) {
// Check if provName is not loaded.
if C.go_openssl_OSSL_PROVIDER_available(nil, provName) == 0 {
// If not, fallback to provName provider.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This doesn't seem to line up with the behavior mentioned in the SetFIPS func comment--it seems like there isn't room for a config-file-based provider to sneak in anymore.

Copy link
Contributor Author

@qmuntal qmuntal May 29, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Did that on purpose, but forgot to remove the comment. I now think that supporting FIPS providers not named fips (as the official one) is overthinking, and the implications of that are unknown to me, I haven't seen that support elsewhere. For example, nodejs and python checks if the fips provider can be loaded and if is enabled by default, and ruby only checks if it is enabled by default.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Notice that I've implemented things a bit different in golang-fips/openssl#66. That's because I don't want to break the go-crypto-openssl API, in case we decide to backport this.

if C.go_openssl_OSSL_PROVIDER_load(nil, provName) == nil {
return newOpenSSLError("openssl: OSSL_PROVIDER_try_load")
return newOpenSSLError("openssl: OSSL_PROVIDER_load")
}
// Make sure we now have a provider available.
if !providerAvailable(props) {
if C.go_openssl_OSSL_PROVIDER_available(nil, provName) == 0 {
return fail("SetFIPS(" + strconv.FormatBool(enabled) + ") not supported")
}
}
if C.go_openssl_EVP_set_default_properties(nil, props) != 1 {
return newOpenSSLError("openssl: EVP_set_default_properties")
if C.go_openssl_EVP_default_properties_enable_fips(nil, mode) != 1 {
return newOpenSSLError("openssl: EVP_default_properties_enable_fips")
}
return nil
default:
Expand Down Expand Up @@ -281,6 +281,7 @@ func bnToBig(bn C.GO_BIGNUM_PTR) BigInt {
// output depends on the input. noescape is inlined and currently
// compiles down to zero instructions.
// USE CAREFULLY!
//
//go:nosplit
func noescape(p unsafe.Pointer) unsafe.Pointer {
x := uintptr(p)
Expand Down
7 changes: 2 additions & 5 deletions openssl/openssl_funcs.h
Original file line number Diff line number Diff line change
Expand Up @@ -150,8 +150,6 @@ typedef struct {
// #include <openssl/provider.h>
// #endif
#define FOR_ALL_OPENSSL_FUNCTIONS \
DEFINEFUNC(int, ERR_set_mark, (void), ()) \
DEFINEFUNC(int, ERR_pop_to_mark, (void), ()) \
DEFINEFUNC(unsigned long, ERR_get_error, (void), ()) \
DEFINEFUNC(void, ERR_error_string_n, (unsigned long e, char *buf, size_t len), (e, buf, len)) \
DEFINEFUNC_RENAMED_1_1(const char *, OpenSSL_version, SSLeay_version, (int type), (type)) \
Expand All @@ -165,8 +163,9 @@ DEFINEFUNC_1_1(int, OPENSSL_init_crypto, (uint64_t ops, const GO_OPENSSL_INIT_SE
DEFINEFUNC_LEGACY_1(int, FIPS_mode, (void), ()) \
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_set_default_properties, (GO_OSSL_LIB_CTX_PTR libctx, const char *propq), (libctx, propq)) \
DEFINEFUNC_3_0(int, EVP_default_properties_enable_fips, (GO_OSSL_LIB_CTX_PTR libctx, int enable), (libctx, enable)) \
DEFINEFUNC_3_0(GO_OSSL_PROVIDER_PTR, OSSL_PROVIDER_load, (GO_OSSL_LIB_CTX_PTR libctx, const char *name), (libctx, name)) \
DEFINEFUNC_3_0(int, OSSL_PROVIDER_available, (GO_OSSL_LIB_CTX_PTR libctx, const char *name), (libctx, name)) \
DEFINEFUNC(int, RAND_bytes, (unsigned char* arg0, int arg1), (arg0, arg1)) \
DEFINEFUNC(int, EVP_DigestInit, (GO_EVP_MD_CTX_PTR ctx, const GO_EVP_MD_PTR type), (ctx, type)) \
DEFINEFUNC(int, EVP_DigestInit_ex, (GO_EVP_MD_CTX_PTR ctx, const GO_EVP_MD_PTR type, GO_ENGINE_PTR impl), (ctx, type, impl)) \
Expand All @@ -186,8 +185,6 @@ DEFINEFUNC(const GO_EVP_MD_PTR, EVP_sha256, (void), ()) \
DEFINEFUNC(const GO_EVP_MD_PTR, EVP_sha384, (void), ()) \
DEFINEFUNC(const GO_EVP_MD_PTR, EVP_sha512, (void), ()) \
DEFINEFUNC_1_1(const GO_EVP_MD_PTR, EVP_md5_sha1, (void), ()) \
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)) \
DEFINEFUNC_RENAMED_3_0(int, EVP_MD_get_size, EVP_MD_size, (const GO_EVP_MD_PTR arg0), (arg0)) \
DEFINEFUNC_LEGACY_1_0(void, HMAC_CTX_init, (GO_HMAC_CTX_PTR arg0), (arg0)) \
DEFINEFUNC_LEGACY_1_0(void, HMAC_CTX_cleanup, (GO_HMAC_CTX_PTR arg0), (arg0)) \
Expand Down
11 changes: 9 additions & 2 deletions scripts/openssl.sh
Original file line number Diff line number Diff line change
Expand Up @@ -13,24 +13,28 @@ case "$version" in
sha256="82fa58e3f273c53128c6fe7e3635ec8cda1319a10ce1ad50a987c3df0deeef05"
config="shared"
make="build_libs"
install=""
;;
"1.1.0")
tag="OpenSSL_1_1_0l"
sha256="e2acf0cf58d9bff2b42f2dc0aee79340c8ffe2c5e45d3ca4533dd5d4f5775b1d"
config="shared"
make="build_libs"
install=""
;;
"1.1.1")
tag="OpenSSL_1_1_1m"
sha256="36ae24ad7cf0a824d0b76ac08861262e47ec541e5d0f20e6d94bab90b2dab360"
config="shared"
make="build_libs"
install=""
;;
"3.0.1")
tag="openssl-3.0.1";
sha256="2a9dcf05531e8be96c296259e817edc41619017a4bf3e229b4618a70103251d5"
config="shared enable-fips"
make="install_fips"
config="enable-fips"
make="build_libs"
install="install_fips"
;;
*)
echo >&2 "error: unsupported OpenSSL version '$version'"
Expand All @@ -49,5 +53,8 @@ mv "openssl-$tag" "openssl-$version"
cd "openssl-$version"
./config $config
make -j$(nproc) $make
if [ -n "$install" ]; then
make $install
fi

cp -H ./libcrypto.so "/usr/lib/libcrypto.so.${version}"
Loading