Skip to content

proposal: crypto/tls: on-disk configuration #60790

Closed
@derekparker

Description

@derekparker

Abstract

This proposal introduces a new mechanism for configuring the standard library default crypto/tls.Config settings using a file on disk. The addition of this on-disk configuration mechanism enables consistent and auditable cryptographic policies to be deployed at scale. This configuration file would be scoped to only the configuration of the crypto/tls default ciphers, curves, and min/max TLS versions allowed. Allowing for this type of on-disk configuration will give those deploying Go applications using the standard library TLS stack greater control over the cryptographic settings used by an application, especially when the one deploying the software is not in direct control of the source code.

Background

Many operating systems allow for the creation of on-disk cryptographic policies which are then consumed by processes running on that system in order to apply default system-wide configuration [1][2]. Allowing for this type of centralized configuration enables those on the operations side to have peace of mind knowing that their systems are secure and auditable.

Proposal

This proposal describes both a way to read an on-disk configuration file and subsequently apply that configuration via the Config object by leveraging the unexported methods of that Config object in order to enforce the loaded cryptographic policy. This section will describe the technical details of the implementation.

Configuration format

The on-disk configuration file will be a JSON file, allowing use of the standard library encoding/json package to easily read and parse the file.

Locating the On-Disk Configuration

This proposal introduces two ways to specify the location of the on disk configuration file. The first would be by way of the environment variable GOTLSCONFIG having been set to a full path to the configuration file, which would then be used to set the value of a variable within the crypto/tls package to be used to load and parse the configuration. The second option is to specify the path directly via -ldflags=”-X crypto/tls.configFilepath=/path/to/config.json”.

Loading the Configuration From Disk

The configuration would be read and parsed using the encoding/json package and would be used to set an internal default configuration variable.

Using the Configuration

The internal configuration would be applied automatically to the lists of default curves, signature algorithms and cipher suites, like so:

// applyAlgorithmPolicyToDefault applies the policy to library defaults.
func applyAlgorithmPolicyToDefault() {
	defaultCurvePreferences =
		filterCurves(defaultCurvePreferences)

	defaultSupportedSignatureAlgorithms =
		filterSignatureAlgorithms(defaultSupportedSignatureAlgorithms)

	defaultCipherSuites =
		filterCipherSuites(defaultCipherSuites)
	defaultCipherSuitesLen = len(defaultCipherSuites)

	defaultCipherSuitesTLS13 =
		filterCipherSuitesTLS13(defaultCipherSuitesTLS13)
	defaultCipherSuitesTLS13NoAES =
		filterCipherSuitesTLS13(defaultCipherSuitesTLS13NoAES)

	if defaultFIPSCurvePreferences != nil {
		defaultFIPSCurvePreferences =
			filterCurves(defaultFIPSCurvePreferences)
	}
	if fipsSupportedSignatureAlgorithms != nil {
		fipsSupportedSignatureAlgorithms =
			filterSignatureAlgorithms(fipsSupportedSignatureAlgorithms)
	}
	if defaultCipherSuitesFIPS != nil {
		defaultCipherSuitesFIPS =
			filterCipherSuites(defaultCipherSuitesFIPS)
	}
}

The configuration would also be applied to any user-created config object via a method (c *Config) applyAlgorithmPolicy(). This would be called when initializing a new TLS server or client, and would be used to enforce the on-disk policy for custom config objects created by users.

Rationale

This approach is a non-invasive way to ensure any Go application using the standard library TLS implementation can be configured using an on-disk cryptographic configuration file. An alternative implementation would be to create an external package which could read and parse a configuration file and produce a corresponding tls.Config object, however that approach is only applicable to code you have access to modify. For those wishing to apply a consistent configuration across all applications on a given system, this alternative solution would still fall short.

Compatibility

This change would maintain backward compatibility by skipping any newly introduced code path in the absence of an on-disk configuration.

Implementation

There is a WIP implementation from a coworker that may be helpful to look at while considering this proposal [3].

The representation of the configuration would be:

type onDiskConfig struct {
	SupportedVersions               []string `json:"supportedVersions"`
	SupportedSignatureAlgorithms    []string `json:"supportedSignatureAlgorithms"`
	SupportedCurves                 []string `json:"supportedCurves"`
	SupportedKeyAgreementAlgorithms []string `json:"supportedKeyAgreementAlgorithms"`
	SupportedHashes                 []string `json:"supportedHashes"`
	SupportedCiphers                []string `json:"supportedCiphers"`
	SupportedMACs                   []string `json:"supportedMACs"`
}

Along with a parsed policy object:

// An algorithmPolicy defines a set of algorithms can be used in TLS,
// populated from the onDiskConfig struct.
type algorithmPolicy struct {
	versions               []uint16
	curves                 []CurveID
	signatureAlgorithms    []SignatureScheme
	keyAgreementAlgorithms []keyAgreementType
	hashes                 []crypto.Hash
	ciphers                []*cipherWithKeyLen
	macs                   []macType
	aeads                  []*aeadWithKeyLen
}

Then, within the crypto/tls package there would be an unexported variable named something along the lines of policy which would be a pointer to the above algorithmPolicy struct. If that variable is non-nil it will be used to provide default configuration values.

This policy would be loaded via an init function in the crypto/tls package, populating the aforementioned unexported variables. This would apply the policy to all internal defaults such as curves, signature algorithms and cipher suites. Additionally, when a user passes a config object into tls.Server or tls.Client, the implementation would call config.applyAlgorithmPolicy to ensure that the policy is enforced.

Open Issues

None.

Links

1: https://access.redhat.com/articles/3666211
2: https://manpages.ubuntu.com/manpages/focal/en/man7/crypto-policies.7.html
3: master...ueno:go:wip/tls-policy

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions