diff --git a/ssh/client.go b/ssh/client.go index eb6c035..43f8647 100644 --- a/ssh/client.go +++ b/ssh/client.go @@ -62,8 +62,8 @@ func (c *ClientConn) handshake() error { ServerHostKeyAlgos: supportedHostKeyAlgos, CiphersClientServer: c.config.Crypto.ciphers(), CiphersServerClient: c.config.Crypto.ciphers(), - MACsClientServer: supportedMACs, - MACsServerClient: supportedMACs, + MACsClientServer: c.config.Crypto.macs(), + MACsServerClient: c.config.Crypto.macs(), CompressionClientServer: supportedCompressions, CompressionServerClient: supportedCompressions, } diff --git a/ssh/client_auth_test.go b/ssh/client_auth_test.go index e51bdc3..0df51c3 100644 --- a/ssh/client_auth_test.go +++ b/ssh/client_auth_test.go @@ -255,3 +255,24 @@ func TestClientAuthRSAandDSA(t *testing.T) { } c.Close() } + +func TestClientHMAC(t *testing.T) { + kc := new(keychain) + kc.keys = append(kc.keys, rsakey) + for _, mac := range DefaultMACOrder { + config := &ClientConfig{ + User: "testuser", + Auth: []ClientAuth{ + ClientAuthKeyring(kc), + }, + Crypto: CryptoConfig{ + MACs: []string{mac}, + }, + } + c, err := Dial("tcp", newMockAuthServer(t), config) + if err != nil { + t.Fatalf("client could not authenticate with mac algo %s: %v", mac, err) + } + c.Close() + } +} diff --git a/ssh/common.go b/ssh/common.go index e8652dd..6c65b95 100644 --- a/ssh/common.go +++ b/ssh/common.go @@ -17,7 +17,6 @@ const ( kexAlgoDH14SHA1 = "diffie-hellman-group14-sha1" hostAlgoRSA = "ssh-rsa" hostAlgoDSA = "ssh-dss" - macSHA196 = "hmac-sha1-96" compressionNone = "none" serviceUserAuth = "ssh-userauth" serviceSSH = "ssh-connection" @@ -25,7 +24,6 @@ const ( var supportedKexAlgos = []string{kexAlgoDH14SHA1} var supportedHostKeyAlgos = []string{hostAlgoRSA} -var supportedMACs = []string{macSHA196} var supportedCompressions = []string{compressionNone} // dhGroup is a multiplicative group suitable for implementing Diffie-Hellman key agreement. @@ -134,6 +132,9 @@ type CryptoConfig struct { // The allowed cipher algorithms. If unspecified then DefaultCipherOrder is // used. Ciphers []string + + // The allowed MAC algorithms. If unspecified then DefaultMACOrder is used. + MACs []string } func (c *CryptoConfig) ciphers() []string { @@ -143,6 +144,13 @@ func (c *CryptoConfig) ciphers() []string { return c.Ciphers } +func (c *CryptoConfig) macs() []string { + if c.MACs == nil { + return DefaultMACOrder + } + return c.MACs +} + // serialize a signed slice according to RFC 4254 6.6. func serializeSignature(algoname string, sig []byte) []byte { switch algoname { diff --git a/ssh/mac.go b/ssh/mac.go new file mode 100644 index 0000000..5b1922d --- /dev/null +++ b/ssh/mac.go @@ -0,0 +1,58 @@ +// Copyright 2012 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 ssh + +// Message authentication support + +import ( + "crypto/hmac" + "crypto/sha1" + "hash" +) + +type macMode struct { + keySize int + new func(key []byte) hash.Hash +} + +// truncatingMAC wraps around a hash.Hash and truncates the output digest to +// a given size. +type truncatingMAC struct { + length int + hmac hash.Hash +} + +func (t truncatingMAC) Write(data []byte) (int, error) { + return t.hmac.Write(data) +} + +func (t truncatingMAC) Sum(in []byte) []byte { + out := t.hmac.Sum(in) + return out[:len(in)+t.length] +} + +func (t truncatingMAC) Reset() { + t.hmac.Reset() +} + +func (t truncatingMAC) Size() int { + return t.length +} + +func (t truncatingMAC) BlockSize() int { return t.hmac.BlockSize() } + +// Specifies a default set of MAC algorithms and a preference order. +// This is based on RFC 4253, section 6.4, with the removal of the +// hmac-md5 variants as they have reached the end of their useful life. +var DefaultMACOrder = []string{"hmac-sha1", "hmac-sha1-96"} + +var macModes = map[string]*macMode{ + "hmac-sha1": {20, func(key []byte) hash.Hash { + return hmac.New(sha1.New, key) + }}, + "hmac-sha1-96": {20, func(key []byte) hash.Hash { + return truncatingMAC{12, hmac.New(sha1.New, key)} + }}, +} diff --git a/ssh/server.go b/ssh/server.go index ec367c8..7c2ee23 100644 --- a/ssh/server.go +++ b/ssh/server.go @@ -228,8 +228,8 @@ func (s *ServerConn) Handshake() error { ServerHostKeyAlgos: supportedHostKeyAlgos, CiphersClientServer: s.config.Crypto.ciphers(), CiphersServerClient: s.config.Crypto.ciphers(), - MACsClientServer: supportedMACs, - MACsServerClient: supportedMACs, + MACsClientServer: s.config.Crypto.macs(), + MACsServerClient: s.config.Crypto.macs(), CompressionClientServer: supportedCompressions, CompressionServerClient: supportedCompressions, } diff --git a/ssh/transport.go b/ssh/transport.go index e21bc4b..c76116a 100644 --- a/ssh/transport.go +++ b/ssh/transport.go @@ -8,8 +8,6 @@ import ( "bufio" "crypto" "crypto/cipher" - "crypto/hmac" - "crypto/sha1" "crypto/subtle" "errors" "hash" @@ -255,28 +253,22 @@ var ( // (to setup server->client keys) or clientKeys (for client->server keys). func (c *common) setupKeys(d direction, K, H, sessionId []byte, hashFunc crypto.Hash) error { cipherMode := cipherModes[c.cipherAlgo] - - macKeySize := 20 + macMode := macModes[c.macAlgo] iv := make([]byte, cipherMode.ivSize) key := make([]byte, cipherMode.keySize) - macKey := make([]byte, macKeySize) + macKey := make([]byte, macMode.keySize) h := hashFunc.New() generateKeyMaterial(iv, d.ivTag, K, H, sessionId, h) generateKeyMaterial(key, d.keyTag, K, H, sessionId, h) generateKeyMaterial(macKey, d.macKeyTag, K, H, sessionId, h) - c.mac = truncatingMAC{12, hmac.New(sha1.New, macKey)} + c.mac = macMode.new(macKey) - cipher, err := cipherMode.createCipher(key, iv) - if err != nil { - return err - } - - c.cipher = cipher - - return nil + var err error + c.cipher, err = cipherMode.createCipher(key, iv) + return err } // generateKeyMaterial fills out with key material generated from tag, K, H @@ -305,32 +297,6 @@ func generateKeyMaterial(out, tag []byte, K, H, sessionId []byte, h hash.Hash) { } } -// truncatingMAC wraps around a hash.Hash and truncates the output digest to -// a given size. -type truncatingMAC struct { - length int - hmac hash.Hash -} - -func (t truncatingMAC) Write(data []byte) (int, error) { - return t.hmac.Write(data) -} - -func (t truncatingMAC) Sum(in []byte) []byte { - out := t.hmac.Sum(in) - return out[:len(in)+t.length] -} - -func (t truncatingMAC) Reset() { - t.hmac.Reset() -} - -func (t truncatingMAC) Size() int { - return t.length -} - -func (t truncatingMAC) BlockSize() int { return t.hmac.BlockSize() } - // maxVersionStringBytes is the maximum number of bytes that we'll accept as a // version string. In the event that the client is talking a different protocol // we need to set a limit otherwise we will keep using more and more memory