github.com/gravitational/teleport/api@v0.0.0-20240507183017-3110591cbafc/client/credentials_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 client 18 19 import ( 20 "crypto/rand" 21 "crypto/rsa" 22 "crypto/tls" 23 "crypto/x509" 24 "crypto/x509/pkix" 25 "encoding/pem" 26 "math/big" 27 "os" 28 "path" 29 "path/filepath" 30 "testing" 31 32 "github.com/google/go-cmp/cmp" 33 "github.com/google/go-cmp/cmp/cmpopts" 34 "github.com/stretchr/testify/require" 35 "golang.org/x/crypto/ssh" 36 37 "github.com/gravitational/teleport/api/constants" 38 "github.com/gravitational/teleport/api/identityfile" 39 "github.com/gravitational/teleport/api/profile" 40 "github.com/gravitational/teleport/api/utils/keys" 41 "github.com/gravitational/teleport/api/utils/sshutils" 42 ) 43 44 func TestLoadTLS(t *testing.T) { 45 t.Parallel() 46 47 // Load expected tls.Config. 48 expectedTLSConfig := getExpectedTLSConfig(t) 49 // Load TLSConfigCreds. 50 creds := LoadTLS(expectedTLSConfig) 51 // Build tls.Config and compare to expected tls.Config. 52 tlsConfig, err := creds.TLSConfig() 53 require.NoError(t, err) 54 requireEqualTLSConfig(t, expectedTLSConfig, tlsConfig) 55 56 // Load invalid tls.Config. 57 invalidTLSCreds := LoadTLS(nil) 58 _, err = invalidTLSCreds.TLSConfig() 59 require.Error(t, err) 60 _, err = invalidTLSCreds.SSHClientConfig() 61 require.Error(t, err) 62 } 63 64 func TestLoadIdentityFile(t *testing.T) { 65 t.Parallel() 66 67 // Load expected tls.Config and ssh.ClientConfig. 68 expectedTLSConfig := getExpectedTLSConfig(t) 69 expectedSSHConfig := getExpectedSSHConfig(t) 70 71 // Write identity file to disk. 72 path := filepath.Join(t.TempDir(), "file") 73 idFile := &identityfile.IdentityFile{ 74 PrivateKey: keyPEM, 75 Certs: identityfile.Certs{ 76 TLS: tlsCert, 77 SSH: sshCert, 78 }, 79 CACerts: identityfile.CACerts{ 80 TLS: [][]byte{tlsCACert}, 81 SSH: [][]byte{sshCACert}, 82 }, 83 } 84 err := identityfile.Write(idFile, path) 85 require.NoError(t, err) 86 87 // Load identity file from disk. 88 creds := LoadIdentityFile(path) 89 // Build tls.Config and compare to expected tls.Config. 90 tlsConfig, err := creds.TLSConfig() 91 require.NoError(t, err) 92 requireEqualTLSConfig(t, expectedTLSConfig, tlsConfig) 93 94 // Build ssh.ClientConfig and compare to expected ssh.ClientConfig. 95 sshConfig, err := creds.SSHClientConfig() 96 require.NoError(t, err) 97 requireEqualSSHConfig(t, expectedSSHConfig, sshConfig) 98 99 // Load invalid identity. 100 creds = LoadIdentityFile("invalid_path") 101 _, err = creds.TLSConfig() 102 require.Error(t, err) 103 _, err = creds.SSHClientConfig() 104 require.Error(t, err) 105 } 106 107 func TestLoadIdentityFileFromString(t *testing.T) { 108 t.Parallel() 109 110 // Load expected tls.Config and ssh.ClientConfig. 111 expectedTLSConfig := getExpectedTLSConfig(t) 112 expectedSSHConfig := getExpectedSSHConfig(t) 113 114 // Write identity file to disk. 115 path := filepath.Join(t.TempDir(), "file") 116 idFile := &identityfile.IdentityFile{ 117 PrivateKey: keyPEM, 118 Certs: identityfile.Certs{ 119 TLS: tlsCert, 120 SSH: sshCert, 121 }, 122 CACerts: identityfile.CACerts{ 123 TLS: [][]byte{tlsCACert}, 124 SSH: [][]byte{sshCACert}, 125 }, 126 } 127 err := identityfile.Write(idFile, path) 128 require.NoError(t, err) 129 130 b, err := os.ReadFile(path) 131 require.NoError(t, err) 132 133 // Load identity file from disk. 134 creds := LoadIdentityFileFromString(string(b)) 135 // Build tls.Config and compare to expected tls.Config. 136 tlsConfig, err := creds.TLSConfig() 137 require.NoError(t, err) 138 requireEqualTLSConfig(t, expectedTLSConfig, tlsConfig) 139 140 // Build ssh.ClientConfig and compare to expected ssh.ClientConfig. 141 sshConfig, err := creds.SSHClientConfig() 142 require.NoError(t, err) 143 requireEqualSSHConfig(t, expectedSSHConfig, sshConfig) 144 145 // Load invalid identity. 146 creds = LoadIdentityFileFromString("invalid_creds") 147 _, err = creds.TLSConfig() 148 require.Error(t, err) 149 _, err = creds.SSHClientConfig() 150 require.Error(t, err) 151 } 152 153 func TestLoadKeyPair(t *testing.T) { 154 t.Parallel() 155 156 // Load expected tls.Config. 157 expectedTLSConfig := getExpectedTLSConfig(t) 158 159 // Write key pair and CAs files from bytes. 160 path := t.TempDir() + "username" 161 certPath, keyPath, caPath := path+".crt", path+".key", path+".cas" 162 err := os.WriteFile(certPath, tlsCert, 0600) 163 require.NoError(t, err) 164 err = os.WriteFile(keyPath, keyPEM, 0600) 165 require.NoError(t, err) 166 err = os.WriteFile(caPath, tlsCACert, 0600) 167 require.NoError(t, err) 168 169 // Load key pair from disk. 170 creds := LoadKeyPair(certPath, keyPath, caPath) 171 // Build tls.Config and compare to expected tls.Config. 172 tlsConfig, err := creds.TLSConfig() 173 require.NoError(t, err) 174 requireEqualTLSConfig(t, expectedTLSConfig, tlsConfig) 175 176 // Load invalid keypairs. 177 invalidIdentityCreds := LoadKeyPair("invalid_path", "invalid_path", "invalid_path") 178 _, err = invalidIdentityCreds.TLSConfig() 179 require.Error(t, err) 180 } 181 182 func TestLoadProfile(t *testing.T) { 183 t.Parallel() 184 profileName := "proxy.example.com" 185 186 t.Run("normal profile", func(t *testing.T) { 187 t.Parallel() 188 dir := t.TempDir() 189 writeProfile(t, &profile.Profile{ 190 WebProxyAddr: profileName + ":3080", 191 SiteName: "example.com", 192 Username: "testUser", 193 Dir: dir, 194 }) 195 testProfileContents(t, dir, profileName) 196 }) 197 198 t.Run("non existent profile", func(t *testing.T) { 199 t.Parallel() 200 // Load non existent profile. 201 creds := LoadProfile("invalid_dir", "invalid_name") 202 _, err := creds.TLSConfig() 203 require.Error(t, err) 204 _, err = creds.SSHClientConfig() 205 require.Error(t, err) 206 }) 207 } 208 209 func testProfileContents(t *testing.T, dir, name string) { 210 // Load expected tls.Config and ssh.ClientConfig. 211 expectedTLSConfig := getExpectedTLSConfig(t) 212 expectedSSHConfig := getExpectedSSHConfig(t) 213 214 // Load profile from disk. 215 creds := LoadProfile(dir, name) 216 217 // Build tls.Config and compare to expected tls.Config. 218 tlsConfig, err := creds.TLSConfig() 219 require.NoError(t, err) 220 requireEqualTLSConfig(t, expectedTLSConfig, tlsConfig) 221 // Build ssh.ClientConfig and compare to expected ssh.ClientConfig. 222 sshConfig, err := creds.SSHClientConfig() 223 require.NoError(t, err) 224 requireEqualSSHConfig(t, expectedSSHConfig, sshConfig) 225 } 226 227 func writeProfile(t *testing.T, p *profile.Profile) { 228 // Save profile and keys to disk. 229 require.NoError(t, p.SaveToDir(p.Dir, true)) 230 require.NoError(t, os.MkdirAll(p.KeyDir(), 0700)) 231 require.NoError(t, os.MkdirAll(p.ProxyKeyDir(), 0700)) 232 require.NoError(t, os.MkdirAll(p.TLSClusterCASDir(), 0700)) 233 require.NoError(t, os.WriteFile(p.UserKeyPath(), keyPEM, 0600)) 234 require.NoError(t, os.WriteFile(p.TLSCertPath(), tlsCert, 0600)) 235 require.NoError(t, os.WriteFile(p.TLSCAPathCluster(p.SiteName), tlsCACert, 0600)) 236 require.NoError(t, os.WriteFile(p.KnownHostsPath(), sshCACert, 0600)) 237 require.NoError(t, os.MkdirAll(p.SSHDir(), 0700)) 238 require.NoError(t, os.WriteFile(p.SSHCertPath(), sshCert, 0600)) 239 require.NoError(t, os.WriteFile(p.PPKFilePath(), ppkFile, 0600)) 240 } 241 242 func getExpectedTLSConfig(t *testing.T) *tls.Config { 243 cert, err := tls.X509KeyPair(tlsCert, keyPEM) 244 require.NoError(t, err) 245 246 pool := x509.NewCertPool() 247 require.True(t, pool.AppendCertsFromPEM(tlsCACert)) 248 249 return configureTLS(&tls.Config{ 250 Certificates: []tls.Certificate{cert}, 251 RootCAs: pool, 252 }) 253 } 254 255 func getExpectedSSHConfig(t *testing.T) *ssh.ClientConfig { 256 cert, err := sshutils.ParseCertificate(sshCert) 257 require.NoError(t, err) 258 259 priv, err := keys.ParsePrivateKey(keyPEM) 260 require.NoError(t, err) 261 262 config, err := sshutils.ProxyClientSSHConfig(cert, priv, sshCACert) 263 require.NoError(t, err) 264 265 return config 266 } 267 268 func requireEqualTLSConfig(t *testing.T, expected *tls.Config, actual *tls.Config) { 269 require.Empty(t, cmp.Diff(expected, actual, 270 cmpopts.IgnoreFields(tls.Config{}, "GetClientCertificate"), 271 cmpopts.IgnoreUnexported(tls.Config{}, x509.CertPool{}), 272 )) 273 } 274 275 func requireEqualSSHConfig(t *testing.T, expected *ssh.ClientConfig, actual *ssh.ClientConfig) { 276 require.Empty(t, cmp.Diff(expected, actual, 277 cmpopts.IgnoreFields(ssh.ClientConfig{}, "Auth", "HostKeyCallback"), 278 )) 279 } 280 281 var ( 282 tlsCert = []byte(`-----BEGIN CERTIFICATE----- 283 MIIDyzCCArOgAwIBAgIQD3MiJ2Au8PicJpCNFbvcETANBgkqhkiG9w0BAQsFADBe 284 MRQwEgYDVQQKEwtleGFtcGxlLmNvbTEUMBIGA1UEAxMLZXhhbXBsZS5jb20xMDAu 285 BgNVBAUTJzIwNTIxNzE3NzMzMTIxNzQ2ODMyNjA5NjAxODEwODc0NTAzMjg1ODAe 286 Fw0yMTAyMTcyMDI3MjFaFw0yMTAyMTgwODI4MjFaMIGCMRUwEwYDVQQHEwxhY2Nl 287 c3MtYWRtaW4xCTAHBgNVBAkTADEYMBYGA1UEEQwPeyJsb2dpbnMiOm51bGx9MRUw 288 EwYDVQQKEwxhY2Nlc3MtYWRtaW4xFTATBgNVBAMTDGFjY2Vzcy1hZG1pbjEWMBQG 289 BSvODwEHEwtleGFtcGxlLmNvbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC 290 ggEBAM5FFaCeK59lwIthyXgSCMZbHTDxsy66Cbm/XhwFbKQLngyS0oKkHbh06INN 291 UfTAAEaFlMG0CzdAyGyRSu9FK8BE127kRHBs6hb1pTgy2f6TFkFo/h4WTWW4GQSi 292 O8Al7A2tuRjc3mAnk71q+kvpQYS7tnkhmFCYE8jKxMtlYG39x4kQ6btll7P9zI6X 293 Zv5RRrlzqADuwZpEcLYVi0TjITqPbx3rDZT4l+EmslhaoG+xE5Vu+GYXLlvwB9E/ 294 amfN1Z9Kps4Ob6Jxxse9kjeMir9mwiNkBWVyhH/LETDA9Xa6sTQ2e75MYM7yXJLY 295 OmBKV4g176Qf1T1ye7a/Ggn4t2UCAwEAAaNgMF4wDgYDVR0PAQH/BAQDAgWgMB0G 296 A1UdJQQWMBQGCCsGAQUFBwMBBggrBgEFBQcDAjAMBgNVHRMBAf8EAjAAMB8GA1Ud 297 IwQYMBaAFJWqMooE05nf263F341pOO+mPMSqMA0GCSqGSIb3DQEBCwUAA4IBAQCK 298 s0yPzkSuCY/LFeHJoJeNJ1SR+EKbk4zoAnD0nbbIsd2quyYIiojshlfehhuZE+8P 299 bzpUNG2aYKq+8lb0NO+OdZW7kBEDWq7ZwC8OG8oMDrX385fLcicm7GfbGCmZ6286 300 m1gfG9yqEte7pxv3yWM+7X2bzEjCBds4feahuKPNxOAOSfLUZiTpmOVlRzrpRIhu 301 2XxiuH+E8n4AP8jf/9bGvKd8PyHohtHVf8HWuKLZxWznQhoKkcfmUmlz5q8ci4Bq 302 WQdM2NXAMABGAofGrVklPIiraUoHzr0Xxpia4vQwRewYXv8bCPHW+8g8vGBGvoG2 303 gtLit9DL5DR5ac/CRGJt 304 -----END CERTIFICATE-----`) 305 306 keyPEM = []byte(`-----BEGIN RSA PRIVATE KEY----- 307 MIIEowIBAAKCAQEAzkUVoJ4rn2XAi2HJeBIIxlsdMPGzLroJub9eHAVspAueDJLS 308 gqQduHTog01R9MAARoWUwbQLN0DIbJFK70UrwETXbuREcGzqFvWlODLZ/pMWQWj+ 309 HhZNZbgZBKI7wCXsDa25GNzeYCeTvWr6S+lBhLu2eSGYUJgTyMrEy2Vgbf3HiRDp 310 u2WXs/3Mjpdm/lFGuXOoAO7BmkRwthWLROMhOo9vHesNlPiX4SayWFqgb7ETlW74 311 ZhcuW/AH0T9qZ83Vn0qmzg5vonHGx72SN4yKv2bCI2QFZXKEf8sRMMD1drqxNDZ7 312 vkxgzvJcktg6YEpXiDXvpB/VPXJ7tr8aCfi3ZQIDAQABAoIBAE1Vk207wAksAgt/ 313 5yQwRr/vizs9czuSnnDYsbT5x6idfm0iYvB+DXKJyl7oD1Ee5zuJe6NAGHBnxn0F 314 4D1jBqs4ZDj8NjicbQucn4w5bIfIp7BwZ83p+KypYB/fn11EGoNqXZpXvLv6Oqbq 315 w9rQIjNcmWZC1TNqQQioFS5Y3NV/gw5uYCRXZlSLMsRCvcX2+LN2EP76ZbkpIVpT 316 CidC2TxwFPPbyMsG774Olfz4U2IDgX1mO+milF7RIa/vPADSeHAX6tJHmZ13GsyP 317 0GAdPbFa0Ls/uykeGi1uGPFkdkNEqbWlDf1Z9IG0dr/ck2eh8G2X8E+VFgzsKp4k 318 WtH9nGECgYEA53lFodLiKQjQR7IoUmGp+P6qnrDwOdU1RfT9jse35xOb9tYvZs3X 319 kUXU+MEGAMW1Pvmo1v9xOjZbdFYB9I/tIYTSyjYQNaFjgJMPMLSx2qjMzhFXAY5f 320 8t20/CBt2V1q46aa8tR2ll//QvY4mqvJUaaB0pkuasFbKMXJcGKdvdkCgYEA5CAo 321 UI8NVA9GqAJfs7hkGHQwpX1X1+JpFhF4dZKsV40NReqaK0vd/mWTYjlMOPO6oolr 322 PoCDUlQYU6poIDtEnfJ6KkYuLMgxZKnS2OlDthKoZJe9aUTCP1RhTVHyyABRXbGg 323 tNMKFYkZ38C9+JM+X5T0eKZTHeK+wjiZd55+sm0CgYAmyp0PxI6gP9jf2wyE2dcp 324 YkxnsdFgb8mwwqDnl7LLJ+8gS76/5Mk2kFRjp72AzaFVP3O7LC3miouDEJLdUG12 325 C5NjzfGjezt4payLBg00Tsub0S4alaigw+T7x9eA8PXj1tzqyw5gnw/hQfA0g4uG 326 gngJOiCcRXEogRUEH5K96QKBgFUnB8ViUHhTJ22pTS3Zo0tZe5saWYLVGaLKLKu+ 327 byRTG2RAuQF2VUwTgFtGxgPwPndTUjvHXr2JdHcugaWeWfOXQjCrd6rxozZPCcw7 328 7jF1b3P1DBfSOavIBHYHI9ex/q05k6JLsFTvkz/pQ0AZPkwRXtv2QcpDDC+VTvvO 329 pr5VAoGBAJBhNjs9wAu+ZoPcMZcjIXT/BAj2tQYiHoRnNpvQjDYbQueUBeI0Ry8d 330 5QnKS2k9D278P6BiDBz1c+fS8UErOxY6CS0pi4x3fjMliPwXj/w7AzjlXgDBhRcp 331 90Ns/9SamlBo9j8ETm9g9D3EVir9zF5XvoR13OdN9gabGy1GuubT 332 -----END RSA PRIVATE KEY-----`) 333 334 tlsCACert = []byte(`-----BEGIN CERTIFICATE----- 335 MIIDiTCCAnGgAwIBAgIRAJlp/39yg8U604bjsxgcoC0wDQYJKoZIhvcNAQELBQAw 336 XjEUMBIGA1UEChMLZXhhbXBsZS5jb20xFDASBgNVBAMTC2V4YW1wbGUuY29tMTAw 337 LgYDVQQFEycyMDM5MjIyNTY2MzcxMDQ0NDc3MzYxNjA0MTk0NjU2MTgzMDA5NzMw 338 HhcNMjEwMjAzMDAyOTQ2WhcNMzEwMjAxMDAyOTQ2WjBeMRQwEgYDVQQKEwtleGFt 339 cGxlLmNvbTEUMBIGA1UEAxMLZXhhbXBsZS5jb20xMDAuBgNVBAUTJzIwMzkyMjI1 340 NjYzNzEwNDQ0NzczNjE2MDQxOTQ2NTYxODMwMDk3MzCCASIwDQYJKoZIhvcNAQEB 341 BQADggEPADCCAQoCggEBAKnIJmcKgzj/FbvF6/OYkw3owsS3XU6AcJZ7HmTfYpZF 342 ozqTDVJdHMFQVfu6cp/6hkzoZ/t7hKT6Nd/O2mlIZdBCfT5ZKESRvTGAeCUANKA5 343 /D4+6PDdW6AutOFUGbHQ1nYLB7HRgaXF/aZmzFPsPNwX8Wm8EByL+Dws61EmSBBv 344 Soado5rPG78mAnRpFvyYbzBDkxzsgLIfv0EPw9jhSjrT3OVjCXnBv53u2S+UbJfR 345 jmI7MutjNbJ/rIBp7JpRHJASmW7oj65WPH0SE0+67XwXYKbs0b7CcSuYW+1S+l9R 346 uGswW4hqwMloP9sTZoWzgT+nCXQSYUavQF+UJZ/dklMCAwEAAaNCMEAwDgYDVR0P 347 AQH/BAQDAgKkMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFC4555Otcq4GAYcc 348 QQDJgh1TKrFvMA0GCSqGSIb3DQEBCwUAA4IBAQBYsEMJYmSD6Dc1suUEnkWo7kOw 349 va/aaOu0Phy9SK3hCjg+tatHVVDHO2dZdVCAvCe36BcLiZL1ovFZAXzEzOovwLx/ 350 AVjXpMXTJj52RSMOAtRVSkk3/WOHrGOGIBW2bCKxF4ORXJfWJrdtaObwPPV5sbDC 351 ACdlNMujdBfUM8EDNmvREI/sVmqL6FK9l6elO/bWLJoiaRTxI+CMixpfIYq8pAwJ 352 UpgZGjcwco4eqXm7rgbQ4wLaMU6hyk8OE5Glk5E6qpnbVzlrL/jl2iE6EqvI6GJn 353 Na6B0YR7mdrrL+lyzymnOr6UOrT5nUWRAB1QeY7dhBNnsvoZwaS3VLSc1KCk 354 -----END CERTIFICATE-----`) 355 356 sshCert = []byte("ssh-rsa-cert-v01@openssh.com AAAAHHNzaC1yc2EtY2VydC12MDFAb3BlbnNzaC5jb20AAAAg8C10PShw+GxCadSlC4nFURIAyvDtgWRvHPabpL5wzDQAAAADAQABAAABAQDORRWgniufZcCLYcl4EgjGWx0w8bMuugm5v14cBWykC54MktKCpB24dOiDTVH0wABGhZTBtAs3QMhskUrvRSvARNdu5ERwbOoW9aU4Mtn+kxZBaP4eFk1luBkEojvAJewNrbkY3N5gJ5O9avpL6UGEu7Z5IZhQmBPIysTLZWBt/ceJEOm7ZZez/cyOl2b+UUa5c6gA7sGaRHC2FYtE4yE6j28d6w2U+JfhJrJYWqBvsROVbvhmFy5b8AfRP2pnzdWfSqbODm+iccbHvZI3jIq/ZsIjZAVlcoR/yxEwwPV2urE0Nnu+TGDO8lyS2DpgSleINe+kH9U9cnu2vxoJ+LdlAAAAAAAAAAAAAAABAAAADGFjY2Vzcy1hZG1pbgAAABAAAAAMYWNjZXNzLWFkbWluAAAAAGAtfCkAAAAAYC4lJQAAAAAAAACdAAAAFnBlcm1pdC1wb3J0LWZvcndhcmRpbmcAAAAAAAAACnBlcm1pdC1wdHkAAAAAAAAADnRlbGVwb3J0LXJvbGVzAAAALQAAACl7InZlcnNpb24iOiJ2MSIsInJvbGVzIjpbImFjY2Vzcy1hZG1pbiJdfQAAAA90ZWxlcG9ydC10cmFpdHMAAAATAAAAD3sibG9naW5zIjpudWxsfQAAAAAAAAEXAAAAB3NzaC1yc2EAAAADAQABAAABAQD3VbuNmR0h3tjYIkTVG+HfNByigp6tuNl8XVylIWx7a7ojRA1nJVzAtNs9QQMut8XY+7jxf4Ue83eIaE0e0QKA0GZlRdbSG0zaYzK8CDAcPVN6Ywt8jnGKuuMhBAckGkN/9nyuJHgTAKeHYgdgQgijPuW/D59s3Sk3vCRHryZzJfZDQ52i40B1q2zLvCcQa6UBvPblHAF3usRa08DnsNkgLey1EkkyvBazqt1amH2Epl3uJRHHUtRVSp2a+0597leT58RZNFfFfB9pccPJfD7cn+iiDmN62T/8YslLYl/O6xCJ43Or7wIRHwJ1tY5hq/Bw7LYn29zeBrIkxIvsH8WtAAABFAAAAAxyc2Etc2hhMi01MTIAAAEAhIz0X+wgA0B8Bi67ALpTEA3kHVWaQY3aT+Ig8obof9upq51H0YlySPJph8h6pVzfSJzQYtuGbmzQ/XAGRMn541mnSUGoy0WCHzscyCowaj9VgjFyVpct7Nz98dB3PnRocNTajGGla+AteZEU3d6KXv/CaA4NGwO3k0rYB+UfX0AAaatAwwxnzYehpCvwSqPdrq/OIyb0aljZHADoNRrcnmYDbB1V76WWY6eTCxYGXx1QyU4A8kH9U8pIZ1fVif/i8dSTbBTftTtv5bmO4WUbVscRw/xIqgZ8v6StNLGHPTt/+Zn+iUoiIrwcnpy+yQp2SRTv7+Lg2SSvJO818x3NNg==") 357 358 sshCACert = []byte("@cert-authority *.example.com ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDMIgxZpT5362npj0x6NQA76IB73bcK85K8cEyKURuHtFC83RjBzvzqtUz6X02+6ohVZiR2MdmsXkCLznzwEIZ0NtoxgnLTZLmduPLeAuYW2vIFpd0G17y6Yog9vxhQ0BLdlhU5Y3JYjRYjmQMfe1iD/RXWD6rEvgWlz+c3HMQR33JqkVIEFH34upfkC2RQG3TXjMe5t14l3yCTtyF5YGzN7+6z/4+/EDto/F3zVtSEp+k8XE/m0ddTGo7usa8ErAom31RwrgkNRmgJmPleDwEflybEsgGKApJXkfFxmG2wu20JoEt/CFjY3fIIa/5aqIGJPpMH4aEdLcj/iyNCog8D type=host") 359 360 ppkFile = []byte(`PuTTY-User-Key-File-3: ssh-rsa 361 Encryption: none 362 Comment: test.com 363 Public-Lines: 6 364 AAAAB3NzaC1yc2EAAAADAQABAAABAQDORRWgniufZcCLYcl4EgjGWx0w8bMuugm5 365 v14cBWykC54MktKCpB24dOiDTVH0wABGhZTBtAs3QMhskUrvRSvARNdu5ERwbOoW 366 9aU4Mtn+kxZBaP4eFk1luBkEojvAJewNrbkY3N5gJ5O9avpL6UGEu7Z5IZhQmBPI 367 ysTLZWBt/ceJEOm7ZZez/cyOl2b+UUa5c6gA7sGaRHC2FYtE4yE6j28d6w2U+Jfh 368 JrJYWqBvsROVbvhmFy5b8AfRP2pnzdWfSqbODm+iccbHvZI3jIq/ZsIjZAVlcoR/ 369 yxEwwPV2urE0Nnu+TGDO8lyS2DpgSleINe+kH9U9cnu2vxoJ+Ld 370 Private-Lines: 14 371 AAABAE1Vk207wAksAgt/5yQwRr/vizs9czuSnnDYsbT5x6idfm0iYvB+DXKJyl7o 372 D1Ee5zuJe6NAGHBnxn0F4D1jBqs4ZDj8NjicbQucn4w5bIfIp7BwZ83p+KypYB/f 373 n11EGoNqXZpXvLv6Oqbqw9rQIjNcmWZC1TNqQQioFS5Y3NV/gw5uYCRXZlSLMsRC 374 vcX2+LN2EP76ZbkpIVpTCidC2TxwFPPbyMsG774Olfz4U2IDgX1mO+milF7RIa/v 375 PADSeHAX6tJHmZ13GsyP0GAdPbFa0Ls/uykeGi1uGPFkdkNEqbWlDf1Z9IG0dr/c 376 k2eh8G2X8E+VFgzsKp4kWtH9nGEAAACBAOQgKFCPDVQPRqgCX7O4ZBh0MKV9V9fi 377 aRYReHWSrFeNDUXqmitL3f5lk2I5TDjzuqKJaz6Ag1JUGFOqaCA7RJ3yeipGLizI 378 MWSp0tjpQ7YSqGSXvWlEwj9UYU1R8sgAUV2xoLTTChWJGd/AvfiTPl+U9HimUx3i 379 vsI4mXeefrJtAAAAgQDneUWh0uIpCNBHsihSYan4/qqesPA51TVF9P2Ox7fnE5v2 380 1i9mzdeRRdT4wQYAxbU++ajW/3E6Nlt0VgH0j+0hhNLKNhA1oWOAkw8wtLHaqMzO 381 EVcBjl/y3bT8IG3ZXWrjppry1HaWX/9C9jiaq8lRpoHSmS5qwVsoxclwYp292QAA 382 AIBV1ZA8WqvC+xZrPwmtmN87BHwGjqpE52kbUfcD94k8IqqhPR9oN9uOlcoBzZiS 383 3SkunUpmzKlcXe63RQYOEqEVlTNOafcYNc5gW8NXKrgF7vBE91VsfmOGJvLt3pIv 384 k53lH1qmEOm9+vrhNwNzpHk4AqDkP+0YDG++B4n0BtJJpw== 385 Private-MAC: 8951bbe929e0714a61df01bc8fbc5223e3688f174aee29339931984fb9224c7d`) 386 ) 387 388 func TestDynamicIdentityFileCreds(t *testing.T) { 389 dir := t.TempDir() 390 identityPath := path.Join(dir, "identity") 391 392 idFile := &identityfile.IdentityFile{ 393 PrivateKey: keyPEM, 394 Certs: identityfile.Certs{ 395 TLS: tlsCert, 396 SSH: sshCert, 397 }, 398 CACerts: identityfile.CACerts{ 399 TLS: [][]byte{tlsCACert}, 400 SSH: [][]byte{sshCACert}, 401 }, 402 } 403 require.NoError(t, identityfile.Write(idFile, identityPath)) 404 405 cred, err := NewDynamicIdentityFileCreds(identityPath) 406 require.NoError(t, err) 407 408 // Check the initial TLS certificate/key has been loaded. 409 tlsConfig, err := cred.TLSConfig() 410 require.NoError(t, err) 411 gotTLSCert, err := tlsConfig.GetClientCertificate(&tls.CertificateRequestInfo{ 412 // We always return the same cert so this can be empty 413 }) 414 require.NoError(t, err) 415 wantTLSCert, err := tls.X509KeyPair(tlsCert, keyPEM) 416 require.NoError(t, err) 417 require.Equal(t, wantTLSCert, *gotTLSCert) 418 419 tlsCACertPEM, _ := pem.Decode(tlsCACert) 420 tlsCACertDER, err := x509.ParseCertificate(tlsCACertPEM.Bytes) 421 require.NoError(t, err) 422 wantCertPool := x509.NewCertPool() 423 wantCertPool.AddCert(tlsCACertDER) 424 require.True(t, wantCertPool.Equal(tlsConfig.RootCAs), "tlsconfig.RootCAs mismatch") 425 426 // Generate a new TLS certificate that contains the same private key as 427 // the original. 428 template := &x509.Certificate{ 429 SerialNumber: big.NewInt(0), 430 Subject: pkix.Name{ 431 CommonName: "example", 432 }, 433 KeyUsage: x509.KeyUsageDigitalSignature, 434 BasicConstraintsValid: true, 435 DNSNames: []string{constants.APIDomain}, 436 } 437 secondTLSCert, err := x509.CreateCertificate( 438 rand.Reader, template, template, &wantTLSCert.PrivateKey.(*rsa.PrivateKey).PublicKey, wantTLSCert.PrivateKey, 439 ) 440 require.NoError(t, err) 441 secondTLSCertPem := pem.EncodeToMemory(&pem.Block{ 442 Type: "CERTIFICATE", 443 Bytes: secondTLSCert, 444 }) 445 446 // Write the new TLS certificate as part of the identity file and reload. 447 secondIDFile := &identityfile.IdentityFile{ 448 PrivateKey: keyPEM, 449 Certs: identityfile.Certs{ 450 TLS: secondTLSCertPem, 451 SSH: sshCert, 452 }, 453 CACerts: identityfile.CACerts{ 454 TLS: [][]byte{tlsCACert}, 455 SSH: [][]byte{sshCACert}, 456 }, 457 } 458 require.NoError(t, identityfile.Write(secondIDFile, identityPath)) 459 require.NoError(t, cred.Reload()) 460 461 // Test that calling GetClientCertificate on the original tls.Config now 462 // returns the new certificate we wrote and reloaded. 463 gotTLSCert, err = tlsConfig.GetClientCertificate(&tls.CertificateRequestInfo{ 464 // We always return the same cert so this can be empty 465 }) 466 require.NoError(t, err) 467 wantTLSCert, err = tls.X509KeyPair(secondTLSCertPem, keyPEM) 468 require.NoError(t, err) 469 require.Equal(t, wantTLSCert, *gotTLSCert) 470 471 }