Skip to content

Commit

Permalink
ssh: add fipsonly mode
Browse files Browse the repository at this point in the history
Fixes golang/go#64769

Change-Id: I4132438bc5586215661c2c1872b5a6c7464badf4
  • Loading branch information
drakkan committed Apr 5, 2024
1 parent 7fbf905 commit 0e61cc6
Show file tree
Hide file tree
Showing 8 changed files with 235 additions and 39 deletions.
9 changes: 2 additions & 7 deletions ssh/cipher_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,9 @@ import (
)

func TestDefaultCiphersExist(t *testing.T) {
for _, cipherAlgo := range SupportedAlgorithms().Ciphers {
for _, cipherAlgo := range allAlgorithms().Ciphers {
if _, ok := cipherModes[cipherAlgo]; !ok {
t.Errorf("supported cipher %q is unknown", cipherAlgo)
}
}
for _, cipherAlgo := range InsecureAlgorithms().Ciphers {
if _, ok := cipherModes[cipherAlgo]; !ok {
t.Errorf("preferred cipher %q is unknown", cipherAlgo)
t.Errorf("cipher %q is unknown", cipherAlgo)
}
}
}
Expand Down
19 changes: 19 additions & 0 deletions ssh/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ import (
"os"
"sync"
"time"

"golang.org/x/crypto/ssh/internal/fips"
)

// Client implements a traditional SSH client that supports shells,
Expand Down Expand Up @@ -71,6 +73,23 @@ func NewClient(c Conn, chans <-chan NewChannel, reqs <-chan *Request) *Client {
func NewClientConn(c net.Conn, addr string, config *ClientConfig) (Conn, <-chan NewChannel, <-chan *Request, error) {
fullConf := *config
fullConf.SetDefaults()
if len(config.HostKeyAlgorithms) == 0 {
if fips.Enabled {
config.HostKeyAlgorithms = fipsHostKeyAlgos
} else {
config.HostKeyAlgorithms = preferredHostKeyAlgos
}
} else {
var hostKeyAlgos []string
supported := allAlgorithms().HostKeys
for _, h := range config.HostKeyAlgorithms {
// Ignore unsupported host key algorithms.
if contains(supported, h) {
hostKeyAlgos = append(hostKeyAlgos, h)
}
}
config.HostKeyAlgorithms = hostKeyAlgos
}
if fullConf.HostKeyCallback == nil {
c.Close()
return nil, nil, nil, errors.New("ssh: must specify HostKeyCallback")
Expand Down
14 changes: 14 additions & 0 deletions ssh/client_auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ import (
"fmt"
"io"
"strings"

"golang.org/x/crypto/ssh/internal/fips"
)

type authResult int
Expand Down Expand Up @@ -311,6 +313,18 @@ func (cb publicKeyCallback) auth(session []byte, user string, c packetConn, rand
origSignersLen := len(signers)
for idx := 0; idx < len(signers); idx++ {
signer := signers[idx]
if fips.Enabled {
if rsaKey, ok := signer.PublicKey().(*rsaPublicKey); ok {
if !isFIPSSupportedRSASize(rsaKey.N.BitLen()) {
continue
}
}
if ecdsaKey, ok := signer.PublicKey().(*ecdsaPublicKey); ok {
if !isFIPSSupportedECDSASize(ecdsaKey.Params().BitSize) {
continue
}
}
}
pub := signer.PublicKey()
as, algo, err := pickSignatureAlgorithm(signer, extensions)
if err != nil && errSigAlgo == nil {
Expand Down
4 changes: 1 addition & 3 deletions ssh/client_auth_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -324,9 +324,7 @@ func TestMethodInvalidAlgorithm(t *testing.T) {
}

func TestClientHMAC(t *testing.T) {
supportedAlgos := SupportedAlgorithms()
insecureAlgos := InsecureAlgorithms()
supportedMACs := append(supportedAlgos.MACs, insecureAlgos.MACs...)
supportedMACs := allAlgorithms().MACs
for _, mac := range supportedMACs {
config := &ClientConfig{
User: "testuser",
Expand Down
127 changes: 105 additions & 22 deletions ssh/common.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ import (
_ "crypto/sha1"
_ "crypto/sha256"
_ "crypto/sha512"

"golang.org/x/crypto/ssh/internal/fips"
)

// These are string constants in the SSH protocol.
Expand Down Expand Up @@ -84,6 +86,9 @@ var (
// package and which have security issues.
insecureKexAlgos = []string{InsecureKeyExchangeDH14SHA1, InsecureKeyExchangeDH1SHA1,
InsecureKeyExchangeDHGEXSHA1}
// fipsKexAlgos specifies FIPS approved key-exchange algorithms implemented
// by this package.
fipsKexAlgos = []string{KeyExchangeECDHP256, KeyExchangeECDHP384}
// supportedCiphers specifies cipher algorithms implemented by this package
// in preference order, excluding those with security issues.
supportedCiphers = []string{
Expand All @@ -100,6 +105,12 @@ var (
InsecureCipherTripleDESCBC,
InsecureCipherRC4256, InsecureCipherRC4128, InsecureCipherRC4,
}
// fipsCiphers specifies FIPS approved cipher algorithms implemented by this
// package.
fipsCiphers = []string{
CipherAES128GCM, CipherAES256GCM,
CipherAES128CTR, CipherAES192CTR, CipherAES256CTR,
}
// supportedMACs specifies MAC algorithms implemented by this package in
// preference order, excluding those with security issues.
supportedMACs = []string{HMACSHA256ETM, HMACSHA512ETM,
Expand All @@ -113,6 +124,11 @@ var (
// insecureMACs specifies MAC algorithms implemented by this
// package and which have security issues.
insecureMACs = []string{InsecureHMACSHA196, InsecureHMACSHA1}
// fipsMACs specifies FIPS approved MAC algorithms implemented by this
// package.
fipsMACs = []string{HMACSHA256ETM, HMACSHA512ETM,
HMACSHA256, HMACSHA512,
}
// supportedHostKeyAlgos specifies the supported host-key algorithms (i.e.
// methods of authenticating servers) implemented by this package in
// preference order, excluding those with security issues.
Expand Down Expand Up @@ -143,6 +159,15 @@ var (
insecureHostKeyAlgos = []string{KeyAlgoRSA, InsecureKeyAlgoDSA,
CertAlgoRSAv01, InsecureCertAlgoDSAv01,
}
// fipsHostKeyAlgos specifies FIPS approved host-key algorithms implemented
// by this package.
fipsHostKeyAlgos = []string{
CertAlgoRSASHA256v01, CertAlgoRSASHA512v01,
CertAlgoECDSA256v01, CertAlgoECDSA384v01,

KeyAlgoECDSA256, KeyAlgoECDSA384,
KeyAlgoRSASHA256, KeyAlgoRSASHA512,
}
// supportedPubKeyAuthAlgos specifies the supported client public key
// authentication algorithms. Note that this doesn't include certificate
// types since those use the underlying algorithm. Order is irrelevant.
Expand All @@ -166,6 +191,12 @@ var (
// insecurePubKeyAuthAlgos specifies client public key authentication
// algorithms implemented by this package and which have security issues.
insecurePubKeyAuthAlgos = []string{KeyAlgoRSA, InsecureKeyAlgoDSA}
// fipsPubKeyAuthAlgos specifies FIPS approved public key authentication
// algorithms implemented by this package.
fipsPubKeyAuthAlgos = []string{
KeyAlgoECDSA256, KeyAlgoECDSA384,
KeyAlgoRSASHA256, KeyAlgoRSASHA512,
}
)

// NegotiatedAlgorithms defines algorithms negotiated between client and server.
Expand All @@ -186,10 +217,31 @@ type Algorithms struct {
PublicKeyAuths []string
}

// isFIPSSupportedRSASize returns true if the specified size (in bits) is
// supported for RSA keys in FIPS mode.
func isFIPSSupportedRSASize(size int) bool {
return size == 2048 || size == 3072 || size == 4096
}

// isFIPSSupportedECDSASize returns true if the specified size (in bits) is
// supported for ECDSA keys in FIPS mode.
func isFIPSSupportedECDSASize(size int) bool {
return size == 256 || size == 384
}

// SupportedAlgorithms returns algorithms currently implemented by this package,
// excluding those with security issues, which are returned by
// InsecureAlgorithms. The algorithms listed here are in preference order.
func SupportedAlgorithms() Algorithms {
if fips.Enabled {
return Algorithms{
Ciphers: fipsCiphers,
MACs: fipsMACs,
KeyExchanges: fipsKexAlgos,
HostKeys: fipsHostKeyAlgos,
PublicKeyAuths: fipsPubKeyAuthAlgos,
}
}
return Algorithms{
Ciphers: supportedCiphers,
MACs: supportedMACs,
Expand All @@ -202,6 +254,9 @@ func SupportedAlgorithms() Algorithms {
// InsecureAlgorithms returns algorithms currently implemented by this package
// and which have security issues.
func InsecureAlgorithms() Algorithms {
if fips.Enabled {
return Algorithms{}
}
return Algorithms{
KeyExchanges: insecureKexAlgos,
Ciphers: insecureCiphers,
Expand All @@ -211,6 +266,19 @@ func InsecureAlgorithms() Algorithms {
}
}

func allAlgorithms() Algorithms {
supported := SupportedAlgorithms()
insecure := InsecureAlgorithms()

return Algorithms{
KeyExchanges: append(supported.KeyExchanges, insecure.KeyExchanges...),
Ciphers: append(supported.Ciphers, insecure.Ciphers...),
MACs: append(supported.MACs, insecure.MACs...),
HostKeys: append(supported.HostKeys, insecure.HostKeys...),
PublicKeyAuths: append(supported.PublicKeyAuths, insecure.PublicKeyAuths...),
}
}

var supportedCompressions = []string{compressionNone}

// hashFuncs keeps the mapping of supported signature algorithms to their
Expand Down Expand Up @@ -397,28 +465,38 @@ type Config struct {
// exported for testing: Configs passed to SSH functions are copied and have
// default values set automatically.
func (c *Config) SetDefaults() {
algos := allAlgorithms()
if c.Rand == nil {
c.Rand = rand.Reader
}
if c.Ciphers == nil {
c.Ciphers = preferredCiphers
}
var ciphers []string
for _, c := range c.Ciphers {
if cipherModes[c] != nil {
// Ignore the cipher if we have no cipherModes definition.
ciphers = append(ciphers, c)
if len(c.Ciphers) == 0 {
if fips.Enabled {
c.Ciphers = fipsCiphers
} else {
c.Ciphers = preferredCiphers
}
} else {
var ciphers []string
for _, c := range c.Ciphers {
// Ignore unsupported ciphers.
if contains(algos.Ciphers, c) {
ciphers = append(ciphers, c)
}
}
c.Ciphers = ciphers
}
c.Ciphers = ciphers

if c.KeyExchanges == nil {
c.KeyExchanges = preferredKexAlgos
if len(c.KeyExchanges) == 0 {
if fips.Enabled {
c.KeyExchanges = fipsKexAlgos
} else {
c.KeyExchanges = preferredKexAlgos
}
}
var kexs []string
for _, k := range c.KeyExchanges {
if kexAlgoMap[k] != nil {
// Ignore the KEX if we have no kexAlgoMap definition.
// Ignore unsupported KEXs.
if contains(algos.KeyExchanges, k) || k == keyExchangeCurve25519SHA256LibSSH {
kexs = append(kexs, k)
if k == KeyExchangeCurve25519SHA256 && !contains(c.KeyExchanges, keyExchangeCurve25519SHA256LibSSH) {
kexs = append(kexs, keyExchangeCurve25519SHA256LibSSH)
Expand All @@ -427,17 +505,22 @@ func (c *Config) SetDefaults() {
}
c.KeyExchanges = kexs

if c.MACs == nil {
c.MACs = preferredMACs
}
var macs []string
for _, m := range c.MACs {
if macModes[m] != nil {
// Ignore the MAC if we have no macModes definition.
macs = append(macs, m)
if len(c.MACs) == 0 {
if fips.Enabled {
c.MACs = fipsMACs
} else {
c.MACs = preferredMACs
}
} else {
var macs []string
for _, m := range c.MACs {
// Ignore unsupported MACs.
if contains(algos.MACs, m) {
macs = append(macs, m)
}
}
c.MACs = macs
}
c.MACs = macs

if c.RekeyThreshold == 0 {
// cipher specific default
Expand Down
20 changes: 20 additions & 0 deletions ssh/fipsonly/fipsonly.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
// Copyright 2023 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

//go:build boringcrypto

// Package fipsonly restricts all SSH configuration to FIPS-approved settings.
//
// The effect is triggered by importing the package anywhere in a program, as in:
//
// import _ "golang.org/x/crypto/ssh/fipsonly"
//
// This package only exists when using Go compiled with GOEXPERIMENT=boringcrypto.
package fipsonly

import "golang.org/x/crypto/ssh/internal/fips"

func init() {
fips.Enabled = true
}
10 changes: 10 additions & 0 deletions ssh/internal/fips/fips.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
// Copyright 2023 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// Package fips allows to restrict all SSH configuration to FIPS-approved
// settings.
package fips

// Enabled defines the FIPS status.
var Enabled = false
Loading

0 comments on commit 0e61cc6

Please sign in to comment.