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

Suggesting to replace syscall.Getenv("GOFIPS") with Read registry key #1429

Open
lmaliniak opened this issue Dec 4, 2024 · 9 comments
Open
Labels
question This issue is a question about the project

Comments

@lmaliniak
Copy link

Hi,

In the documentation you recommend using GOFIPS and set at runtime to support both option of running in FIPS mode or non-FIPS.

The problem is that unless set before service installation, adding GOFIPS during the installation, requires restart of the Windows in order for the GOFIPS change to be visible to the service.

I want to suggest to replace syscall.Getenv("GOFIPS") with read of the registry GOFIPS key to check to decide how to run a service and avoid the restart.

Path: Computer\HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Session Manager\Environment

Similar to enableSystemWideFIPS(), microsoft-go/eng/_util/cmd/run-builder/systemfips_windows.go

Thanks,
Lilach

@dagood
Copy link
Member

dagood commented Dec 4, 2024

I might not be understanding your suggestion, but I think it might already work in the way you want. On Windows, FIPS mode cannot be enabled or disabled for a specific application: it's always a system-wide setting, and changing that registry key is one way to configure it.

On Windows, GOFIPS only checks that the system is running in the desired mode. It will never try to change the mode. https://github.com/microsoft/go/tree/microsoft/main/eng/doc/fips#windows-fips-mode-cng. To make sure it's clear: GOFIPS has two options on Windows (different from OpenSSL/Linux):

  • 1: panic if systemwide FIPS mode isn't enabled.
  • <anything else>: don't run a check, simply use what the system provides.

+func init() {
+ // 1: FIPS required: abort the process if the system is not in FIPS mode.
+ // other values: continue regardless of system-configured FIPS mode.
+ if v, _, ok := envGoFIPS(); ok && v == "1" {
+ enabled, err := cng.FIPS()
+ if err != nil {
+ panic("cngcrypto: unknown FIPS mode: " + err.Error())
+ }
+ if !enabled {
+ panic("cngcrypto: not in FIPS mode")
+ }
+ }

cng.FIPS() uses BCryptGetFipsAlgorithmMode, which is affected by that registry key.

It sounds like GOFIPS isn't useful for your case (a Windows service), and that's fine--there's no need to use it for every program. On Windows, it's just a small feature that lets you potentially detect misconfigured systems.

@dagood dagood added the question This issue is a question about the project label Dec 4, 2024
@lmaliniak
Copy link
Author

According to the documentation Usage Common Configuration

Using GOEXPERIMENT=systemcrypto which is the default, can be used to create a compliant app. FIPS mode is automatically enabled at runtime if it is configured systemwide or GOFIPS=1.
GOEXPERIMENT=systemcrypto and in runtime, during app start it sets GOFIPS=1 this replaces the need to define GOFIPS system wide. The app either enables FIPS mode or ensures it is already enabled. Otherwise, the app panics.
GOEXPERIMENT=systemcrypto and in runtime, the app sets GOFIPS=0 to run as non-FIPS. Crypto usage is not FIPS compliant. The app attempts to disable FIPS mode and panics if it isn't possible.

To run as FIPS compliant it is required that the Windows FIPS policy will be enabled as a prerequisite. Is that correct?
In addition either GOFIPS is added system wide or at runtime.

In our use case we install and run a Windows service. Assuming that the FIPS policy is enabled but GOFIPS var is not defined system wide the question was what are the options in order for the service to immediately start running using CNG, FIPS compliant?

Windows services spawned by SCM. SCM starts on early boot stage and acquires the environment, all services will inherit SCM's environment. Even the newly added/installed service might get a stale SCM environment.
Thus adding system wide GOFIPS environment variable requires a system reboot for SCM to be aware of it and propagate it to the services.

Please consider the following scenario:
Installing a Go-based service on a FIPS-compliant Windows host.
The system administrator may not know the Go prerequisite of having a GOFIPS=1 environment variable, thus the variable might be missing.
Attempts to set this variable from the MSI context will not affect the newly added services - as the parent SCM process is unaware of the new variable. Thus a reboot of the host is required to make it work properly.
Go reading the value directly from HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Session Manager\Environment will have an immediate effect.

The option of dynamically adding GOFIPS=1 after the service was started will have the same affect as having a predefined system wide GOFIPS=1 var?
Thanks

@dagood
Copy link
Member

dagood commented Dec 5, 2024

FIPS mode is automatically enabled at runtime if it is configured systemwide or GOFIPS=1.

Ah, apologies, that only describes the OpenSSL backend, not CNG/Windows. I'll get that fixed. That particular note might not have been revised since the CNG backend was added--OpenSSL was first.

On Windows, the Go application is not responsible for enabling FIPS mode. https://github.com/microsoft/go/tree/microsoft/main/eng/doc/fips#windows-fips-mode-cng is correct, sorry about the conflicting information.

In addition either GOFIPS is added system wide or at runtime.

The Go application just reads the environment variable upon startup (during an init func). The app doesn't try to determine if GOFIPS is defined system-wide or specifically in the Go process or its parent process. They are equivalent as far as the app is concerned.

For clarity: none of the language in the doc is intended to say anything about defining environment variables system-wide or specific to a certain process. I'll give the doc a pass-through to try to make sure this is clear. (In my experience, on Linux it's a little clearer that this concept won't affect anything, so some of the current wording might be muddy when reading for Windows.)

Assuming that the FIPS policy is enabled but GOFIPS var is not defined system wide the question was what are the options in order for the service to immediately start running using CNG, FIPS compliant?

There are two factors here:

  • A Windows Go app built using the cngcrypto backend (or the alias systemcrypto) will always use CNG calls.
  • If the FIPS policy is enabled while a cngcrypto (or systemcrypto) app is initializing, the app will be in FIPS mode.
    • We use a CNG API call to check FIPS policy. We expect this to be very accurate and futureproof.

GOFIPS doesn't affect either of those. The only effect is that during app init, if GOFIPS is 1 and the FIPS policy is not enabled, the app will panic.

@dagood
Copy link
Member

dagood commented Dec 5, 2024

Missed this Q, I think it's a good one for me to answer directly to make sure everything makes sense together: 🙂

To run as FIPS compliant it is required that the Windows FIPS policy will be enabled as a prerequisite. Is that correct?

Yes, and the only other prerequisite is that the app is built with cngcrypto or systemcrypto.

@lmaliniak
Copy link
Author

lmaliniak commented Dec 7, 2024

Thanks @dagood, it is clear now.
Beside using GOFIPS env var to detect whether the FIPS mode policy, in local policy is enabled or not, what are other straightforward ways to know that my Go app is running in FIPS mode?
Is there an event logged to Event Viewer?
If on runtime I want to add an indication in the app UI whether it is running in FIPS mode or not what is the best approach?
Thanks

@dagood
Copy link
Member

dagood commented Dec 9, 2024

We haven't provided a supported API to check FIPS mode--a bit more background about why in this issue:

There also aren't any external indicators like event viewer, etc.

(Note that #999 also mentions that upstream has accepted a proposal that includes adding a built-in function to check the mode, crypto/fips140.Enabled(). Once upstream does that, we will follow. Upstream was previously against adding this API, but a lot is changing.)

Summary: for now, there is no straightforward way. In 1.24 there might be.

@lmaliniak
Copy link
Author

Thanks @dagood.
What is your opinion about checking whether FIPS local security policy is enabled to conclude the app is running is FIPS mode?

Also, does the changes from building with the goexperiment.systemcrypto or goexperiment.cngcrypto tags, replace the standard crypto package https://pkg.go.dev/crypto including the https://pkg.go.dev/crypto/tls in the vendor and vendor-patched modules?

What is the expected impact of running in FIPS mode if the app is using weak hash such as MD5?
What is the expected impact on the TLS handshake?
Thanks

@dagood
Copy link
Member

dagood commented Dec 11, 2024

What is your opinion about checking whether FIPS local security policy is enabled to conclude the app is running is FIPS mode?

I think that if you need to check FIPS mode, the workaround in the description of #999 is currently the best way to do it (if using an ordinary build of Microsoft Go). I think that checking whether FIPS local security policy is enabled by some other means and then making a conclusion about FIPS mode could maybe end up ok, but I'm not familiar with that and the potential caveats.

Also, does the changes from building with the goexperiment.systemcrypto or goexperiment.cngcrypto tags, replace the standard crypto package https://pkg.go.dev/crypto including the https://pkg.go.dev/crypto/tls in the vendor and vendor-patched modules?

I'm not sure I understand "the vendor and vendor-patched modules". If you mean you have an app, it imports some packages from external modules, and you've called go mod vendor, yes, they use the same crypto package as the app's code does, and the crypto package's logic is different when the app is built by Microsoft Go. Modifying the files in vendor/ ("vendor-patched?") also doesn't affect how the imports work.

What is the expected impact of running in FIPS mode if the app is using weak hash such as MD5?

I would expect it to succeed, but the app would not be considered FIPS compliant if the MD5 call was performed for a cryptographic purpose.

https://github.com/microsoft/go/tree/microsoft/main/eng/doc/fips#microsoft-go-fork-fips-compliance

[...] The modified crypto runtime will fall back to Go standard library crypto if it cannot provide a FIPS-compliant implementation, e.g. when hashing a message using crypto/md5 hashes or when using an AES-GCM cipher with a non-standard nonce size.

What is the expected impact on the TLS handshake?

It should be restricted to FIPS approved settings (and I see this happen with my example program at #1434 (comment)):

https://github.com/microsoft/go/tree/microsoft/main/eng/doc/fips#tls-with-fips-compliant-settings

Since Go 1.22, the Microsoft Go runtime automatically enforces that crypto/tls and crypto/x509 only use FIPS-compliant settings when running in FIPS mode.

@lmaliniak
Copy link
Author

I've read #999 and linked issue there and understand that in the next release, 1.24, there will be a function that can be called to check whether the app is running in FIPS mode.
Regarding the TLS. I'll continue the discussion in #1434.
Thanks @dagood.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
question This issue is a question about the project
Projects
None yet
Development

No branches or pull requests

2 participants