Skip to content

Commit 66c3d8c

Browse files
committed
ssh: add support for FIPS mode
Unsupported algoritms are silently ignored and not negotiated, or rejected Fixes golang/go#75061 Change-Id: I08d50d10a97c08e78aedead89ca61beceff88918 Reviewed-on: https://go-review.googlesource.com/c/crypto/+/698795 Reviewed-by: Mio Mio <miomio0086@gmail.com> Reviewed-by: Junyang Shao <shaojunyang@google.com> Reviewed-by: Filippo Valsorda <filippo@golang.org> Reviewed-by: Michael Knyszek <mknyszek@google.com> LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
1 parent ddb4e80 commit 66c3d8c

File tree

8 files changed

+166
-72
lines changed

8 files changed

+166
-72
lines changed

ssh/certs_test.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -338,7 +338,9 @@ func TestCertTypes(t *testing.T) {
338338
CertType: UserCert,
339339
Key: priv.PublicKey(),
340340
}
341-
cert.SignCert(rand.Reader, priv)
341+
if err := cert.SignCert(rand.Reader, priv); err != nil {
342+
t.Fatalf("error signing certificate: %v", err)
343+
}
342344

343345
certSigner, err := NewCertSigner(cert, priv)
344346
if err != nil {

ssh/cipher.go

Lines changed: 32 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,15 @@ import (
88
"crypto/aes"
99
"crypto/cipher"
1010
"crypto/des"
11+
"crypto/fips140"
1112
"crypto/rc4"
1213
"crypto/subtle"
1314
"encoding/binary"
1415
"errors"
1516
"fmt"
1617
"hash"
1718
"io"
19+
"slices"
1820

1921
"golang.org/x/crypto/chacha20"
2022
"golang.org/x/crypto/internal/poly1305"
@@ -93,41 +95,41 @@ func streamCipherMode(skip int, createFunc func(key, iv []byte) (cipher.Stream,
9395
}
9496

9597
// cipherModes documents properties of supported ciphers. Ciphers not included
96-
// are not supported and will not be negotiated, even if explicitly requested in
97-
// ClientConfig.Crypto.Ciphers.
98-
var cipherModes = map[string]*cipherMode{
99-
// Ciphers from RFC 4344, which introduced many CTR-based ciphers. Algorithms
100-
// are defined in the order specified in the RFC.
101-
CipherAES128CTR: {16, aes.BlockSize, streamCipherMode(0, newAESCTR)},
102-
CipherAES192CTR: {24, aes.BlockSize, streamCipherMode(0, newAESCTR)},
103-
CipherAES256CTR: {32, aes.BlockSize, streamCipherMode(0, newAESCTR)},
104-
105-
// Ciphers from RFC 4345, which introduces security-improved arcfour ciphers.
106-
// They are defined in the order specified in the RFC.
107-
InsecureCipherRC4128: {16, 0, streamCipherMode(1536, newRC4)},
108-
InsecureCipherRC4256: {32, 0, streamCipherMode(1536, newRC4)},
109-
110-
// Cipher defined in RFC 4253, which describes SSH Transport Layer Protocol.
111-
// Note that this cipher is not safe, as stated in RFC 4253: "Arcfour (and
112-
// RC4) has problems with weak keys, and should be used with caution."
113-
// RFC 4345 introduces improved versions of Arcfour.
114-
InsecureCipherRC4: {16, 0, streamCipherMode(0, newRC4)},
115-
116-
// AEAD ciphers
117-
CipherAES128GCM: {16, 12, newGCMCipher},
118-
CipherAES256GCM: {32, 12, newGCMCipher},
119-
CipherChaCha20Poly1305: {64, 0, newChaCha20Cipher},
120-
98+
// are not supported and will not be negotiated, even if explicitly configured.
99+
// When FIPS mode is enabled, only FIPS-approved algorithms are included.
100+
var cipherModes = map[string]*cipherMode{}
101+
102+
func init() {
103+
cipherModes[CipherAES128CTR] = &cipherMode{16, aes.BlockSize, streamCipherMode(0, newAESCTR)}
104+
cipherModes[CipherAES192CTR] = &cipherMode{24, aes.BlockSize, streamCipherMode(0, newAESCTR)}
105+
cipherModes[CipherAES256CTR] = &cipherMode{32, aes.BlockSize, streamCipherMode(0, newAESCTR)}
106+
// Use of GCM with arbitrary IVs is not allowed in FIPS 140-only mode,
107+
// we'll wire it up to NewGCMForSSH in Go 1.26.
108+
//
109+
// For now it means we'll work with fips140=on but not fips140=only.
110+
cipherModes[CipherAES128GCM] = &cipherMode{16, 12, newGCMCipher}
111+
cipherModes[CipherAES256GCM] = &cipherMode{32, 12, newGCMCipher}
112+
113+
if fips140.Enabled() {
114+
defaultCiphers = slices.DeleteFunc(defaultCiphers, func(algo string) bool {
115+
_, ok := cipherModes[algo]
116+
return !ok
117+
})
118+
return
119+
}
120+
121+
cipherModes[CipherChaCha20Poly1305] = &cipherMode{64, 0, newChaCha20Cipher}
122+
// Insecure ciphers not included in the default configuration.
123+
cipherModes[InsecureCipherRC4128] = &cipherMode{16, 0, streamCipherMode(1536, newRC4)}
124+
cipherModes[InsecureCipherRC4256] = &cipherMode{32, 0, streamCipherMode(1536, newRC4)}
125+
cipherModes[InsecureCipherRC4] = &cipherMode{16, 0, streamCipherMode(0, newRC4)}
121126
// CBC mode is insecure and so is not included in the default config.
122127
// (See https://www.ieee-security.org/TC/SP2013/papers/4977a526.pdf). If absolutely
123128
// needed, it's possible to specify a custom Config to enable it.
124129
// You should expect that an active attacker can recover plaintext if
125130
// you do.
126-
InsecureCipherAES128CBC: {16, aes.BlockSize, newAESCBCCipher},
127-
128-
// 3des-cbc is insecure and is not included in the default
129-
// config.
130-
InsecureCipherTripleDESCBC: {24, des.BlockSize, newTripleDESCBCCipher},
131+
cipherModes[InsecureCipherAES128CBC] = &cipherMode{16, aes.BlockSize, newAESCBCCipher}
132+
cipherModes[InsecureCipherTripleDESCBC] = &cipherMode{24, des.BlockSize, newTripleDESCBCCipher}
131133
}
132134

133135
// prefixLen is the length of the packet prefix that contains the packet length

ssh/common.go

Lines changed: 35 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ package ssh
66

77
import (
88
"crypto"
9+
"crypto/fips140"
910
"crypto/rand"
1011
"fmt"
1112
"io"
@@ -256,6 +257,40 @@ type Algorithms struct {
256257
PublicKeyAuths []string
257258
}
258259

260+
func init() {
261+
if fips140.Enabled() {
262+
defaultHostKeyAlgos = slices.DeleteFunc(defaultHostKeyAlgos, func(algo string) bool {
263+
_, err := hashFunc(underlyingAlgo(algo))
264+
return err != nil
265+
})
266+
defaultPubKeyAuthAlgos = slices.DeleteFunc(defaultPubKeyAuthAlgos, func(algo string) bool {
267+
_, err := hashFunc(underlyingAlgo(algo))
268+
return err != nil
269+
})
270+
}
271+
}
272+
273+
func hashFunc(format string) (crypto.Hash, error) {
274+
switch format {
275+
case KeyAlgoRSASHA256, KeyAlgoECDSA256, KeyAlgoSKED25519, KeyAlgoSKECDSA256:
276+
return crypto.SHA256, nil
277+
case KeyAlgoECDSA384:
278+
return crypto.SHA384, nil
279+
case KeyAlgoRSASHA512, KeyAlgoECDSA521:
280+
return crypto.SHA512, nil
281+
case KeyAlgoED25519:
282+
// KeyAlgoED25519 doesn't pre-hash.
283+
return 0, nil
284+
case KeyAlgoRSA, InsecureKeyAlgoDSA:
285+
if fips140.Enabled() {
286+
return 0, fmt.Errorf("ssh: hash algorithm for format %q not allowed in FIPS 140 mode", format)
287+
}
288+
return crypto.SHA1, nil
289+
default:
290+
return 0, fmt.Errorf("ssh: hash algorithm for format %q not mapped", format)
291+
}
292+
}
293+
259294
// SupportedAlgorithms returns algorithms currently implemented by this package,
260295
// excluding those with security issues, which are returned by
261296
// InsecureAlgorithms. The algorithms listed here are in preference order.
@@ -283,21 +318,6 @@ func InsecureAlgorithms() Algorithms {
283318

284319
var supportedCompressions = []string{compressionNone}
285320

286-
// hashFuncs keeps the mapping of supported signature algorithms to their
287-
// respective hashes needed for signing and verification.
288-
var hashFuncs = map[string]crypto.Hash{
289-
KeyAlgoRSA: crypto.SHA1,
290-
KeyAlgoRSASHA256: crypto.SHA256,
291-
KeyAlgoRSASHA512: crypto.SHA512,
292-
InsecureKeyAlgoDSA: crypto.SHA1,
293-
KeyAlgoECDSA256: crypto.SHA256,
294-
KeyAlgoECDSA384: crypto.SHA384,
295-
KeyAlgoECDSA521: crypto.SHA512,
296-
// KeyAlgoED25519 doesn't pre-hash.
297-
KeyAlgoSKECDSA256: crypto.SHA256,
298-
KeyAlgoSKED25519: crypto.SHA256,
299-
}
300-
301321
// algorithmsForKeyFormat returns the supported signature algorithms for a given
302322
// public key format (PublicKey.Type), in order of preference. See RFC 8332,
303323
// Section 2. See also the note in sendKexInit on backwards compatibility.

ssh/doc.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,18 @@ References:
1717
[PROTOCOL.certkeys]: http://cvsweb.openbsd.org/cgi-bin/cvsweb/src/usr.bin/ssh/PROTOCOL.certkeys?rev=HEAD
1818
[SSH-PARAMETERS]: http://www.iana.org/assignments/ssh-parameters/ssh-parameters.xml#ssh-parameters-1
1919
[SSH-CERTS]: https://datatracker.ietf.org/doc/html/draft-miller-ssh-cert-01
20+
[FIPS 140-3 mode]: https://go.dev/doc/security/fips140
2021
2122
This package does not fall under the stability promise of the Go language itself,
2223
so its API may be changed when pressing needs arise.
24+
25+
# FIPS 140-3 mode
26+
27+
When the program is in [FIPS 140-3 mode], this package behaves as if only SP
28+
800-140C and SP 800-140D approved cipher suites, signature algorithms,
29+
certificate public key types and sizes, and key exchange and derivation
30+
algorithms were implemented. Others are silently ignored and not negotiated, or
31+
rejected. This set may depend on the algorithms supported by the FIPS 140-3 Go
32+
Cryptographic Module selected with GOFIPS140, and may change across Go versions.
2333
*/
2434
package ssh

ssh/kex.go

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,14 @@ import (
88
"crypto"
99
"crypto/ecdsa"
1010
"crypto/elliptic"
11+
"crypto/fips140"
1112
"crypto/rand"
1213
"encoding/binary"
1314
"errors"
1415
"fmt"
1516
"io"
1617
"math/big"
18+
"slices"
1719

1820
"golang.org/x/crypto/curve25519"
1921
)
@@ -395,9 +397,27 @@ func ecHash(curve elliptic.Curve) crypto.Hash {
395397
return crypto.SHA512
396398
}
397399

400+
// kexAlgoMap defines the supported KEXs. KEXs not included are not supported
401+
// and will not be negotiated, even if explicitly configured. When FIPS mode is
402+
// enabled, only FIPS-approved algorithms are included.
398403
var kexAlgoMap = map[string]kexAlgorithm{}
399404

400405
func init() {
406+
// mlkem768x25519-sha256 we'll work with fips140=on but not fips140=only
407+
// until Go 1.26.
408+
kexAlgoMap[KeyExchangeMLKEM768X25519] = &mlkem768WithCurve25519sha256{}
409+
kexAlgoMap[KeyExchangeECDHP521] = &ecdh{elliptic.P521()}
410+
kexAlgoMap[KeyExchangeECDHP384] = &ecdh{elliptic.P384()}
411+
kexAlgoMap[KeyExchangeECDHP256] = &ecdh{elliptic.P256()}
412+
413+
if fips140.Enabled() {
414+
defaultKexAlgos = slices.DeleteFunc(defaultKexAlgos, func(algo string) bool {
415+
_, ok := kexAlgoMap[algo]
416+
return !ok
417+
})
418+
return
419+
}
420+
401421
p, _ := new(big.Int).SetString(oakleyGroup2, 16)
402422
kexAlgoMap[InsecureKeyExchangeDH1SHA1] = &dhGroup{
403423
g: new(big.Int).SetInt64(2),
@@ -431,14 +451,10 @@ func init() {
431451
hashFunc: crypto.SHA512,
432452
}
433453

434-
kexAlgoMap[KeyExchangeECDHP521] = &ecdh{elliptic.P521()}
435-
kexAlgoMap[KeyExchangeECDHP384] = &ecdh{elliptic.P384()}
436-
kexAlgoMap[KeyExchangeECDHP256] = &ecdh{elliptic.P256()}
437454
kexAlgoMap[KeyExchangeCurve25519] = &curve25519sha256{}
438455
kexAlgoMap[keyExchangeCurve25519LibSSH] = &curve25519sha256{}
439456
kexAlgoMap[InsecureKeyExchangeDHGEXSHA1] = &dhGEXSHA{hashFunc: crypto.SHA1}
440457
kexAlgoMap[KeyExchangeDHGEXSHA256] = &dhGEXSHA{hashFunc: crypto.SHA256}
441-
kexAlgoMap[KeyExchangeMLKEM768X25519] = &mlkem768WithCurve25519sha256{}
442458
}
443459

444460
// curve25519sha256 implements the curve25519-sha256 (formerly known as

ssh/keys.go

Lines changed: 33 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -504,7 +504,10 @@ func (r *rsaPublicKey) Verify(data []byte, sig *Signature) error {
504504
if !slices.Contains(supportedAlgos, sig.Format) {
505505
return fmt.Errorf("ssh: signature type %s for key type %s", sig.Format, r.Type())
506506
}
507-
hash := hashFuncs[sig.Format]
507+
hash, err := hashFunc(sig.Format)
508+
if err != nil {
509+
return err
510+
}
508511
h := hash.New()
509512
h.Write(data)
510513
digest := h.Sum(nil)
@@ -621,7 +624,11 @@ func (k *dsaPublicKey) Verify(data []byte, sig *Signature) error {
621624
if sig.Format != k.Type() {
622625
return fmt.Errorf("ssh: signature type %s for key type %s", sig.Format, k.Type())
623626
}
624-
h := hashFuncs[sig.Format].New()
627+
hash, err := hashFunc(sig.Format)
628+
if err != nil {
629+
return err
630+
}
631+
h := hash.New()
625632
h.Write(data)
626633
digest := h.Sum(nil)
627634

@@ -666,7 +673,11 @@ func (k *dsaPrivateKey) SignWithAlgorithm(rand io.Reader, data []byte, algorithm
666673
return nil, fmt.Errorf("ssh: unsupported signature algorithm %s", algorithm)
667674
}
668675

669-
h := hashFuncs[k.PublicKey().Type()].New()
676+
hash, err := hashFunc(k.PublicKey().Type())
677+
if err != nil {
678+
return nil, err
679+
}
680+
h := hash.New()
670681
h.Write(data)
671682
digest := h.Sum(nil)
672683
r, s, err := dsa.Sign(rand, k.PrivateKey, digest)
@@ -816,8 +827,11 @@ func (k *ecdsaPublicKey) Verify(data []byte, sig *Signature) error {
816827
if sig.Format != k.Type() {
817828
return fmt.Errorf("ssh: signature type %s for key type %s", sig.Format, k.Type())
818829
}
819-
820-
h := hashFuncs[sig.Format].New()
830+
hash, err := hashFunc(sig.Format)
831+
if err != nil {
832+
return err
833+
}
834+
h := hash.New()
821835
h.Write(data)
822836
digest := h.Sum(nil)
823837

@@ -920,8 +934,11 @@ func (k *skECDSAPublicKey) Verify(data []byte, sig *Signature) error {
920934
if sig.Format != k.Type() {
921935
return fmt.Errorf("ssh: signature type %s for key type %s", sig.Format, k.Type())
922936
}
923-
924-
h := hashFuncs[sig.Format].New()
937+
hash, err := hashFunc(sig.Format)
938+
if err != nil {
939+
return err
940+
}
941+
h := hash.New()
925942
h.Write([]byte(k.application))
926943
appDigest := h.Sum(nil)
927944

@@ -1024,7 +1041,11 @@ func (k *skEd25519PublicKey) Verify(data []byte, sig *Signature) error {
10241041
return fmt.Errorf("invalid size %d for Ed25519 public key", l)
10251042
}
10261043

1027-
h := hashFuncs[sig.Format].New()
1044+
hash, err := hashFunc(sig.Format)
1045+
if err != nil {
1046+
return err
1047+
}
1048+
h := hash.New()
10281049
h.Write([]byte(k.application))
10291050
appDigest := h.Sum(nil)
10301051

@@ -1131,7 +1152,10 @@ func (s *wrappedSigner) SignWithAlgorithm(rand io.Reader, data []byte, algorithm
11311152
return nil, fmt.Errorf("ssh: unsupported signature algorithm %q for key format %q", algorithm, s.pubKey.Type())
11321153
}
11331154

1134-
hashFunc := hashFuncs[algorithm]
1155+
hashFunc, err := hashFunc(algorithm)
1156+
if err != nil {
1157+
return nil, err
1158+
}
11351159
var digest []byte
11361160
if hashFunc != 0 {
11371161
h := hashFunc.New()

0 commit comments

Comments
 (0)