github.com/gravitational/teleport/api@v0.0.0-20240507183017-3110591cbafc/utils/sshutils/ssh_test.go (about)

     1  /*
     2  Copyright 2021 Gravitational, Inc.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package sshutils
    18  
    19  import (
    20  	"encoding/base64"
    21  	"fmt"
    22  	"testing"
    23  
    24  	"github.com/stretchr/testify/require"
    25  	"golang.org/x/crypto/ssh"
    26  )
    27  
    28  // TestHostKeyCallback verifies that host key callback properly validates
    29  // host certificates.
    30  func TestHostKeyCallback(t *testing.T) {
    31  	ca, err := MakeTestSSHCA()
    32  	require.NoError(t, err)
    33  
    34  	realCert, err := MakeRealHostCert(ca)
    35  	require.NoError(t, err)
    36  
    37  	spoofedCert, err := MakeSpoofedHostCert(ca)
    38  	require.NoError(t, err)
    39  
    40  	knownHosts := [][]byte{[]byte(makeKnownHostsLine("127.0.0.1", ca.PublicKey()))}
    41  	trustedKeys, err := ParseKnownHosts(knownHosts)
    42  	require.NoError(t, err)
    43  
    44  	hostKeyCallback, err := HostKeyCallback(trustedKeys, false)
    45  	require.NoError(t, err)
    46  
    47  	err = hostKeyCallback("127.0.0.1:3022", nil, realCert.PublicKey())
    48  	require.NoError(t, err, "host key callback rejected valid host certificate")
    49  
    50  	err = hostKeyCallback("127.0.0.1:3022", nil, spoofedCert.PublicKey())
    51  	require.Error(t, err, "host key callback accepted spoofed host certificate")
    52  }
    53  
    54  func makeKnownHostsLine(host string, key ssh.PublicKey) string {
    55  	return fmt.Sprintf("%v %v %v", host, key.Type(),
    56  		base64.StdEncoding.EncodeToString(key.Marshal()))
    57  }
    58  
    59  // Tests symmetric equality of key equality.
    60  func TestSSHKeysEqual(t *testing.T) {
    61  	rsaKey1, _, _, _, err := ssh.ParseAuthorizedKey([]byte("ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQClwXUKOp/S4XEtFjgr8mfaCy4OyI7N9ZMibdCGxvk2VHP9+Vn8Al1lUSVwuBxHI7EHiq42RCTBetIpTjzn6yiPNAeGNL5cfl9i6r+P5k7og1hz+2oheWveGodx6Dp+Z4o2dw65NGf5EPaotXF8AcHJc3+OiMS5yp/x2A3tu2I1SPQ6dtPa067p8q1L49BKbFwrFRBCVwkr6kpEQAIjnMESMPGD5Buu/AtyAdEZQSLTt8RZajJZDfXFKMEtQm2UF248NFl3hSMAcbbTxITBbZxX7THbwQz22Yuw7422G5CYBPf6WRXBY84Rs6jCS4I4GMxj+3rF4mGtjvuz0wOE32s3w4eMh9h3bPuEynufjE8henmPCIW49+kuZO4LZut7Zg5BfVDQnZYclwokEIMz+gR02YpyflxQOa98t/0mENu+t4f0LNAdkQEBpYtGKKDth5kLphi2Sdi9JpGO2sTivlxMsGyBqdd0wT9VwQpWf4wro6t09HdZJX1SAuEi/0tNI10= friel@test"))
    62  	require.NoError(t, err)
    63  	// Same as above, but different comment
    64  	rsaKey1Alt, _, _, _, err := ssh.ParseAuthorizedKey([]byte("ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQClwXUKOp/S4XEtFjgr8mfaCy4OyI7N9ZMibdCGxvk2VHP9+Vn8Al1lUSVwuBxHI7EHiq42RCTBetIpTjzn6yiPNAeGNL5cfl9i6r+P5k7og1hz+2oheWveGodx6Dp+Z4o2dw65NGf5EPaotXF8AcHJc3+OiMS5yp/x2A3tu2I1SPQ6dtPa067p8q1L49BKbFwrFRBCVwkr6kpEQAIjnMESMPGD5Buu/AtyAdEZQSLTt8RZajJZDfXFKMEtQm2UF248NFl3hSMAcbbTxITBbZxX7THbwQz22Yuw7422G5CYBPf6WRXBY84Rs6jCS4I4GMxj+3rF4mGtjvuz0wOE32s3w4eMh9h3bPuEynufjE8henmPCIW49+kuZO4LZut7Zg5BfVDQnZYclwokEIMz+gR02YpyflxQOa98t/0mENu+t4f0LNAdkQEBpYtGKKDth5kLphi2Sdi9JpGO2sTivlxMsGyBqdd0wT9VwQpWf4wro6t09HdZJX1SAuEi/0tNI10= other@foo"))
    65  	require.NoError(t, err)
    66  	rsaKey2, _, _, _, err := ssh.ParseAuthorizedKey([]byte("ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQCxNlAqagvmYJSKcSZeTfAJj8ch/wKeTuwOjUOvuudXelAl6ZNslu2a9IrRyu0SIKtHSxgJG7BPuz4bPSkh7/unjCDIRjuxzIEyA2Dud+MDG2QgFrgQqSuDQS/1CJwlqm9i/UdlbrQOrkX7dwIoY2f+bzW1JR3tLjhCQkCZzkViGkY3ELew+Pdu+aagS/BeR+fJNLKvGx8NwAFjZe/YoTJ+vwDNhRP2NyDe1ISSX9PWGvqy4cCQ3WBOsuocpX+XR01tTuxVFsw5fQ+U4vb52aIDUv5VYU5ioG9TLpR1HL9Lu8l0mDI1lGtzHl0/uKEyAFUghyD0ow25vLQzkG/3bjOla6ehQKvSrXly5TbGed+QfphK27hVVctnehO8PyP3ANJK4bzy3fKdq7EmzoGwBIc2QvX3RjdzWJw37kNw44cAsEYVJRJdtaFOB9nFvtCRSPYM0CJj081Sjgqwvnd4cGgUfQjGYdQiMmzLA11PryUQ6ahZVeVhu3TE809ZrJI8HIc= friel@test"))
    67  	require.NoError(t, err)
    68  
    69  	ed25519Key1, _, _, _, err := ssh.ParseAuthorizedKey([]byte("ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIGtqQKEkGIY5+Bc4EmEv7NeSn6aA7KMl5eiNEAOqwTBl friel@test"))
    70  	require.NoError(t, err)
    71  	ed25519Key1Alt, _, _, _, err := ssh.ParseAuthorizedKey([]byte("ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIGtqQKEkGIY5+Bc4EmEv7NeSn6aA7KMl5eiNEAOqwTBl other@foo"))
    72  	require.NoError(t, err)
    73  	ed25519Key2, _, _, _, err := ssh.ParseAuthorizedKey([]byte("ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIAhOF1Yw9LyTcIM1ku2hrqYcJ4e+784zp2XX4oIAWRuZ friel@test"))
    74  	require.NoError(t, err)
    75  
    76  	// The key material says this is RSA, the prefix is irrelevant.
    77  	disguisedRsaKey, _, _, _, err := ssh.ParseAuthorizedKey([]byte("ssh-ed25519 AAAAB3NzaC1yc2EAAAADAQABAAABgQClwXUKOp/S4XEtFjgr8mfaCy4OyI7N9ZMibdCGxvk2VHP9+Vn8Al1lUSVwuBxHI7EHiq42RCTBetIpTjzn6yiPNAeGNL5cfl9i6r+P5k7og1hz+2oheWveGodx6Dp+Z4o2dw65NGf5EPaotXF8AcHJc3+OiMS5yp/x2A3tu2I1SPQ6dtPa067p8q1L49BKbFwrFRBCVwkr6kpEQAIjnMESMPGD5Buu/AtyAdEZQSLTt8RZajJZDfXFKMEtQm2UF248NFl3hSMAcbbTxITBbZxX7THbwQz22Yuw7422G5CYBPf6WRXBY84Rs6jCS4I4GMxj+3rF4mGtjvuz0wOE32s3w4eMh9h3bPuEynufjE8henmPCIW49+kuZO4LZut7Zg5BfVDQnZYclwokEIMz+gR02YpyflxQOa98t/0mENu+t4f0LNAdkQEBpYtGKKDth5kLphi2Sdi9JpGO2sTivlxMsGyBqdd0wT9VwQpWf4wro6t09HdZJX1SAuEi/0tNI10= friel@Zing"))
    78  	require.NoError(t, err)
    79  
    80  	// The key material says this is Ed25519, the prefix is irrelevant.
    81  	disguisedEd25519Key, _, _, _, err := ssh.ParseAuthorizedKey([]byte("ssh-rsa AAAAC3NzaC1lZDI1NTE5AAAAIGtqQKEkGIY5+Bc4EmEv7NeSn6aA7KMl5eiNEAOqwTBl friel@test"))
    82  	require.NoError(t, err)
    83  
    84  	type test struct {
    85  		name    string
    86  		variant string
    87  		key     ssh.PublicKey
    88  	}
    89  
    90  	keys := []test{
    91  		{name: "rsaKey1", key: rsaKey1},
    92  		{name: "rsaKey1", variant: "-alt", key: rsaKey1Alt},
    93  		{name: "rsaKey2", key: rsaKey2},
    94  		{name: "ed25519Key1", variant: "-rsa-prefixed", key: disguisedEd25519Key},
    95  		{name: "ed25519Key1", key: ed25519Key1},
    96  		{name: "ed25519Key1", variant: "-alt", key: ed25519Key1Alt},
    97  		{name: "ed25519Key2", key: ed25519Key2},
    98  		{name: "rsaKey1", variant: "-ed25519-prefixed", key: disguisedRsaKey},
    99  	}
   100  
   101  	for _, ak := range keys {
   102  		for _, bk := range keys {
   103  			expected := ak.name == bk.name
   104  
   105  			var op string
   106  			if expected {
   107  				op = "=="
   108  			} else {
   109  				op = "!="
   110  			}
   111  
   112  			t.Run(fmt.Sprintf("%v%v%v%v%v", ak.name, ak.variant, op, bk.name, bk.variant), func(t *testing.T) {
   113  				actual := KeysEqual(ak.key, bk.key)
   114  				require.Equal(t, expected, actual)
   115  			})
   116  		}
   117  	}
   118  }
   119  
   120  func TestSSHMarshalEd25519(t *testing.T) {
   121  	ak, _, _, _, err := ssh.ParseAuthorizedKey([]byte("ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIGtqQKEkGIY5+Bc4EmEv7NeSn6aA7KMl5eiNEAOqwTBl friel@test"))
   122  	require.NoError(t, err)
   123  
   124  	bk, _, _, _, err := ssh.ParseAuthorizedKey([]byte("ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIGtqQKEkGIY5+Bc4EmEv7NeSn6aA7KMl5eiNEAOqwTBl friel@test"))
   125  	require.NoError(t, err)
   126  
   127  	result := KeysEqual(ak, bk)
   128  	require.True(t, result)
   129  }
   130  
   131  func TestMatchesWildcard(t *testing.T) {
   132  	t.Parallel()
   133  	t.Run("Wildcard match", func(t *testing.T) {
   134  		require.True(t, matchesWildcard("foo.example.com", "*.example.com"))
   135  		require.True(t, matchesWildcard("bar.example.com", "*.example.com"))
   136  		require.True(t, matchesWildcard("bar.example.com.", "*.example.com"))
   137  		require.True(t, matchesWildcard("bar.foo", "*.foo"))
   138  	})
   139  
   140  	t.Run("Wildcard mismatch", func(t *testing.T) {
   141  		require.False(t, matchesWildcard("foo.example.com", "example.com"), "Not a wildcard pattern")
   142  		require.False(t, matchesWildcard("foo.example.org", "*.example.com"), "Wildcard pattern shouldn't match different suffix")
   143  		require.False(t, matchesWildcard("a.b.example.com", "*.example.com"), "Wildcard pattern shouldn't match multiple prefixes")
   144  
   145  		t.Run("Single part hostname", func(t *testing.T) {
   146  			require.False(t, matchesWildcard("example", "*.example.com"))
   147  			require.False(t, matchesWildcard("example", "*.example"))
   148  			require.False(t, matchesWildcard("example", "example"))
   149  			require.False(t, matchesWildcard("example", "*."))
   150  		})
   151  	})
   152  }