Skip to main content
Redhat Developers  Logo
  • Products

    Featured

    • Red Hat Enterprise Linux
      Red Hat Enterprise Linux Icon
    • Red Hat OpenShift AI
      Red Hat OpenShift AI
    • Red Hat Enterprise Linux AI
      Linux icon inside of a brain
    • Image mode for Red Hat Enterprise Linux
      RHEL image mode
    • Red Hat OpenShift
      Openshift icon
    • Red Hat Ansible Automation Platform
      Ansible icon
    • Red Hat Developer Hub
      Developer Hub
    • View All Red Hat Products
    • Linux

      • Red Hat Enterprise Linux
      • Image mode for Red Hat Enterprise Linux
      • Red Hat Universal Base Images (UBI)
    • Java runtimes & frameworks

      • JBoss Enterprise Application Platform
      • Red Hat build of OpenJDK
    • Kubernetes

      • Red Hat OpenShift
      • Microsoft Azure Red Hat OpenShift
      • Red Hat OpenShift Virtualization
      • Red Hat OpenShift Lightspeed
    • Integration & App Connectivity

      • Red Hat Build of Apache Camel
      • Red Hat Service Interconnect
      • Red Hat Connectivity Link
    • AI/ML

    • Automation

      • Red Hat Ansible Automation Platform
      • Red Hat Ansible Lightspeed
    • Developer tools

      • Red Hat Trusted Software Supply Chain
      • Podman Desktop
      • Red Hat OpenShift Dev Spaces
    • Developer Sandbox

      Developer Sandbox
      Try Red Hat products and technologies without setup or configuration fees for 30 days with this shared Openshift and Kubernetes cluster.
    • Try at no cost
  • Technologies

    Featured

    • AI/ML
      AI/ML Icon
    • Linux
      Linux Icon
    • Kubernetes
      Cloud icon
    • Automation
      Automation Icon showing arrows moving in a circle around a gear
    • View All Technologies
    • Programming Languages & Frameworks

      • Java
      • Python
      • JavaScript
    • System Design & Architecture

      • Red Hat architecture and design patterns
      • Microservices
      • Event-Driven Architecture
      • Databases
    • Developer Productivity

      • Developer productivity
      • Developer Tools
      • GitOps
    • Secure Development & Architectures

      • Security
      • Secure coding
    • Platform Engineering

      • DevOps
      • DevSecOps
      • Ansible automation for applications and services
    • Automated Data Processing

      • AI/ML
      • Data Science
      • Apache Kafka on Kubernetes
      • View All Technologies
    • Start exploring in the Developer Sandbox for free

      sandbox graphic
      Try Red Hat's products and technologies without setup or configuration.
    • Try at no cost
  • Learn

    Featured

    • Kubernetes & Cloud Native
      Openshift icon
    • Linux
      Rhel icon
    • Automation
      Ansible cloud icon
    • Java
      Java icon
    • AI/ML
      AI/ML Icon
    • View All Learning Resources

    E-Books

    • GitOps Cookbook
    • Podman in Action
    • Kubernetes Operators
    • The Path to GitOps
    • View All E-books

    Cheat Sheets

    • Linux Commands
    • Bash Commands
    • Git
    • systemd Commands
    • View All Cheat Sheets

    Documentation

    • API Catalog
    • Product Documentation
    • Legacy Documentation
    • Red Hat Learning

      Learning image
      Boost your technical skills to expert-level with the help of interactive lessons offered by various Red Hat Learning programs.
    • Explore Red Hat Learning
  • Developer Sandbox

    Developer Sandbox

    • Access Red Hat’s products and technologies without setup or configuration, and start developing quicker than ever before with our new, no-cost sandbox environments.
    • Explore Developer Sandbox

    Featured Developer Sandbox activities

    • Get started with your Developer Sandbox
    • OpenShift virtualization and application modernization using the Developer Sandbox
    • Explore all Developer Sandbox activities

    Ready to start developing apps?

    • Try at no cost
  • Blog
  • Events
  • Videos

Is your Go application FIPS compliant?

May 31, 2022
Antonio Cardace Sam Fowler
Related topics:
GoLinuxSecurity
Related products:
Red Hat Enterprise Linux

Share:

    Red Hat Enterprise Linux (RHEL) ships with several Federal Information Processing Standards (FIPS)-validated cryptography libraries, including OpenSSL. This allows applications that use these libraries to operate in FIPS mode, which means that the cryptographic techniques they use can are in compliance with the FIPS-140-2 standard. Any organization that works with the U.S. Federal government must comply with this standard.

    By default, applications written in Go use cryptographic functions from the Go standard library, which is not FIPS-validated. However, the version of Go shipped in RHEL is based on upstream Go's dev.boringcrypto branch, which is modified to use BoringSSL for crypto primitives. Modifications made in the RHEL version replace BoringSSL with OpenSSL. These modifications allow applications written with RHEL's Go to use crypto functions from a FIPS-validated version of OpenSSL. This article will show you how to verify that your system, including your installation of the Go language, is capable of operating in FIPS mode.

    How to get started

    Begin by building your Go binary with the Go compiler shipped in RHEL. To do this quickly use the ubi8-minimal Universal Base Image (UBI) as your build environment and install the go-toolset package.

    To confirm that the Go compiler and built binary are FIPS-capable, run the command below, modified as appropriate for your environment (e.g, $BINARY could be /usr/bin/go):

    $ go tool nm $BINARY | grep FIPS
    

    If the output looks like the listing below, with references to named FIPS functions, then the binary is FIPS-capable. (If it weren't FIPS-capable, the output would be empty.)

    
    [root@rhel-8-4 ~]# go tool nm ./main | grep FIPS
    
      401210 T _cgo_23e85cd750d7_Cfunc__goboringcrypto_FIPS_mode
    
      5d8d80 d _g_FIPS_mode
    
      4c2680 T crypto/internal/boring._Cfunc__goboringcrypto_FIPS_mode
    
      5a27c0 D crypto/internal/boring._cgo_23e85cd750d7_Cfunc__goboringcrypto_FIPS_mode
    
    

    Next, run the application in a container that includes a FIPS-compliant OpenSSL library. Again, you can use the ubi8-minimal image, which fulfills the requirement. If you're using another image, you can check to see if the OpenSSL installation is FIPS-capable with the following command:

    
    $ openssl version
    
    OpenSSL 1.1.1k  FIPS 25 Mar 2021
    
    

    Note: This can be disabled by enforcing pure Go with a build time flag.

    As we noted above, the version of Go that ships with RHEL is based on the upstream Go's dev.boringcrypto branch, which is modified to use BoringSSL. You can verify this by looking at the source code either in the Go source RPM file or in Fedora's source code manager.

    Grepping over the Go source code shows the first signs of the patched-in BoringSSL support:

    
    $ grep -r boring /usr/lib/golang/src/crypto/ | head
    
    /usr/lib/golang/src/crypto/aes/cipher.go:import "crypto/internal/boring"
    
    /usr/lib/golang/src/crypto/aes/cipher.go:   if boring.Enabled() {
    
    

    The crypto/internal/boring package includes directives that use CGO to dynamically link against libdl:

    
    package boring
    
     
    
    // #include "goboringcrypto.h"
    
    // #cgo LDFLAGS: -ldl
    
    import "C"
    
    

    Linking against libdl allows the use of the dl_open() function, which allows for further loading of shared libraries. The dl_open() function is called to load libcrypto, one of the shared libraries in OpenSSL:

    
    static void*
    
    _goboringcrypto_DLOPEN_OPENSSL(void)
    
    {    
    
        if (handle)    
    
        {    
    
        return handle;    
    
        }    
    
    #if OPENSSL_VERSION_NUMBER < 0x10100000L    
    
        handle = dlopen("libcrypto.so.10", RTLD_NOW | RTLD_GLOBAL);    
    
    #else    
    
        handle = dlopen("libcrypto.so.1.1", RTLD_NOW | RTLD_GLOBAL);    
    
    #endif    
    
        return handle;    
    
    }
    
    

    Because dl_open() is used to load the OpenSSL libraries, rather than linking the actual Go binaries, libcrypto will not appear in the output of ldd when run on binaries built with RHEL Go (though libdl will).

    Although libdl is linked, OpenSSL is not used by default. The crypto/internal/boring package will always load OpenSSL, but it will only use it if FIPS mode is enabled:

    
    func init() { 
    
        runtime.LockOSThread()    
    
        defer runtime.UnlockOSThread()   
    
        
    
        // Check if we can `dlopen` OpenSSL   
    
        if C._goboringcrypto_DLOPEN_OPENSSL() == C.NULL {   
    
        return   
    
        }   
    
        
    
        // Initialize the OpenSSL library.  
    
        C._goboringcrypto_OPENSSL_setup()
    
           
    
        // Check to see if the system is running in FIPS mode, if so   
    
        // enable "boring" mode to call into OpenSSL for FIPS compliance.   
    
        if fipsModeEnabled() {   
    
        enableBoringFIPSMode()   
    
        }   
    
        sig.BoringCrypto()   
    
    }
    
    

    The FIPS_mode_set() and FIPS_mode() functions are defined in OpenSSL, which is why libcrypto is loaded even if it won't be used for crypto functions. OpenSSL is used to check if the system is in FIPS mode.

    How to verify FIPS mode

    Depending on how deep you want to go, there are a couple of different ways in which you can check for FIPS compliance.

    Custom fips-detect tool

    A tool called fips-detect is available to determine whether your system or container and your Golang binary are ready to run in FIPS mode. It accomplishes this by performing checks on the running system and the supplied binary to see if everything is in place to correctly run in FIPS mode.

    Common tools

    If you don't want to install a custom tool, you can use ldd to show that Go binaries are linked against libdl, and inspect the source code to determine if dl_open() is used to load OpenSSL. There are a couple of options available, including go tool nm or readelf -s. However, keep in mind that no checks on the underlying system are performed with these common tools, and it may not be convincing enough to inspect the Go binaries alone.

    If the binary is compiled on a standard Fedora 34 system, when you enter this go tool nm command:

    
    $ go tool nm ./main | grep -i dlopen_openssl
    
    

    The output you'll get will be blank. If the binary is compiled on RHEL 8, however, this is the result:

    
    $ go tool nm ./main | grep -i dlopen_openssl
    
     
    [root@rhel-8-4 ~]# go tool nm ./main | grep -i dlopen_openssl
    
      4018d0 T _cgo_fb383f177a95_Cfunc__goboringcrypto_DLOPEN_OPENSSL
    
    

    From this output, it is clear that, when compiling with the RHEL Golang compiler, the binary is at least able to call into OpenSSL and enable FIPS mode.

    To verify that the program runs in FIPS mode, use the LD_DEBUG=symbols environment variable. This shows the various symbols the binary binds from shared libraries to determine whether the application actually calls into OpenSSL.

    The binary runs in FIPS mode when executed with the OPENSSL_FORCE_FIPS_MODE=1 variable. In the following example, we use a custom Go binary that computes a hash using the SHA1 algorithm:

    
    [root@rhel-8-4 ~]# env OPENSSL_FORCE_FIPS_MODE=1 ./main
    
      5939: symbol=FIPS_mode;  lookup in file=./main [0]
    
       5939: symbol=FIPS_mode;  lookup in file=/lib64/libssl.so.1.1 [0]
    
       5939: symbol=FIPS_mode;  lookup in file=/lib64/libcrypto.so.1.1 [0]
    
       5939: symbol=SHA1_Init;  lookup in file=./main [0]
    
       5939: symbol=SHA1_Init;  lookup in file=/lib64/libssl.so.1.1 [0]
    
       5939: symbol=SHA1_Init;  lookup in file=/lib64/libcrypto.so.1.1 [0]
    
       5939: symbol=SHA1_Update;  lookup in file=./main [0]
    
       5939: symbol=SHA1_Update;  lookup in file=/lib64/libssl.so.1.1 [0]
    
       5939: symbol=SHA1_Update;  lookup in file=/lib64/libcrypto.so.1.1 [0]
    
       5939: symbol=SHA1_Final;  lookup in file=./main [0]
    
       5939: symbol=SHA1_Final;  lookup in file=/lib64/libssl.so.1.1 [0]
    
       5939: symbol=SHA1_Final;  lookup in file=/lib64/libcrypto.so.1.1 [0]
    
    SHA1: C8282111D0FAD11680B3775A36E68DC41E36F911
    
    

    For comparison, this is the result when it runs without this variable:

    
    [root@rhel-8-4 ~]# ./main 
    
      12957: symbol=FIPS_mode_set;  lookup in file=/lib64/libcrypto.so.1.1 [0]
    
      12957: symbol=dlsym;  lookup in file=./main [0]
    
      12957: symbol=dlsym;  lookup in file=/lib64/libdl.so.2 [0]
    
      12957: symbol=OPENSSL_init;  lookup in file=/lib64/libcrypto.so.1.1 [0]
    
      12957: symbol=FIPS_mode;  lookup in file=/lib64/libcrypto.so.1.1 [0]
    
    SHA1: C8282111D0FAD11680B3775A36E68DC41E36F911
    
    

    The differences are:

    • When the binary runs in non-FIPS mode, it uses the Golang standard crypto library, which uses its own routines (these are statically included in the binary) rather than calling into OpenSSL. This is why the dynamic linker doesn't bind OpenSSL symbols.
    • OpenSSL is still loaded even when FIPS mode is disabled. It provides the functions used to check if FIPS mode is enabled or disabled. Only enabled FIPS mode uses OpenSSL for cryptographic functions.

    FIPS mode

    Earlier, we used the OPENSSL_FORCE_FIPS_MODE environment variable to force the binary to behave as if FIPS mode were enabled.

    The following is a snippet of the libcrypto.so shared library constructor. It runs in the lib init phase before the dynamic linker transfers the flow control to main():

    
    # define FIPS_MODE_SWITCH_FILE "/proc/sys/crypto/fips_enabled"
    
    static void init_fips_mode(void)
    
    {
    
    char buf[2] = "0";
    
    int fd;
    
    if (secure_getenv("OPENSSL_FORCE_FIPS_MODE") != NULL) {
    
             buf[0] = '1';
    
    } else if ((fd = open(FIPS_MODE_SWITCH_FILE, O_RDONLY)) >= 0) {
    
             while (read(fd, buf, sizeof(buf)) < 0 && errno == EINTR) ;
    
             close(fd);
    
    }
    
    if (buf[0] != '1' && !FIPS_module_installed())
    
             return;
    
     
       FIPS_mode_set(1);
    
    if (buf[0] != '1') {
    
             /* drop down to non-FIPS mode if it is not requested */
    
             FIPS_mode_set(0);
    
    } else {
    
             /* abort if selftest failed */
    
             FIPS_selftest_check();
    
    }
    
    }
    
     
    void __attribute__ ((constructor)) OPENSSL_init_library(void)
    
    {
    
    static int done = 0;
    
    if (done)
    
             return;
    
    done = 1;
    
    init_fips_mode();
    
    }
    
    
    

    This snippet comes from a patch included in the RHEL and Fedora versions of OpenSSL, so it only applies to RHEL and the UBI container image.

    The shared library constructor shows that, to transparently enable FIPS mode, you must either define the OPENSSL_FORCE_FIPS_MODE variable or ensure that the first byte of the /proc/sys/crypto/fips_enabled file contains 1.

    Containers will detect hosts that are in FIPS mode when /proc/sys/crypto/fips_enabled appears with the host's value in all container PID namespaces.

    Conclusion

    Building Go applications on RHEL allows them to run in two different modes, default and FIPS. The default mode uses the Go standard library, and the FIPS mode uses a FIPS-validated version of OpenSSL. This provides developers an easy way to meet compliance requirements that mandate the use of FIPS-validated libraries, while also preserving consistency with applications built upstream.

    Last updated: June 8, 2022

    Recent Posts

    • How to run AI models in cloud development environments

    • How Trilio secures OpenShift virtual machines and containers

    • How to implement observability with Node.js and Llama Stack

    • How to encrypt RHEL images for Azure confidential VMs

    • How to manage RHEL virtual machines with Podman Desktop

    What’s up next?

    Podman is one of the next-generation container tools (along with buildah and skopeo) included in Red Hat Enterprise Linux 7 and RHEL 8. The Podman Basics Cheat Sheet covers all of the commands that focus on images, containers, and container resources.

    Get the cheat sheet
    Red Hat Developers logo LinkedIn YouTube Twitter Facebook

    Products

    • Red Hat Enterprise Linux
    • Red Hat OpenShift
    • Red Hat Ansible Automation Platform

    Build

    • Developer Sandbox
    • Developer Tools
    • Interactive Tutorials
    • API Catalog

    Quicklinks

    • Learning Resources
    • E-books
    • Cheat Sheets
    • Blog
    • Events
    • Newsletter

    Communicate

    • About us
    • Contact sales
    • Find a partner
    • Report a website issue
    • Site Status Dashboard
    • Report a security problem

    RED HAT DEVELOPER

    Build here. Go anywhere.

    We serve the builders. The problem solvers who create careers with code.

    Join us if you’re a developer, software engineer, web designer, front-end designer, UX designer, computer scientist, architect, tester, product manager, project manager or team lead.

    Sign me up

    Red Hat legal and privacy links

    • About Red Hat
    • Jobs
    • Events
    • Locations
    • Contact Red Hat
    • Red Hat Blog
    • Inclusion at Red Hat
    • Cool Stuff Store
    • Red Hat Summit

    Red Hat legal and privacy links

    • Privacy statement
    • Terms of use
    • All policies and guidelines
    • Digital accessibility

    Report a website issue