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  }