istio.io/istio@v0.0.0-20240520182934-d79c90f27776/security/pkg/pki/ca/ca_test.go (about)

     1  // Copyright Istio Authors
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package ca
    16  
    17  import (
    18  	"bytes"
    19  	"context"
    20  	"crypto/tls"
    21  	"crypto/x509"
    22  	"os"
    23  	"reflect"
    24  	"sync"
    25  	"testing"
    26  	"time"
    27  
    28  	v1 "k8s.io/api/core/v1"
    29  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    30  	"k8s.io/client-go/kubernetes/fake"
    31  
    32  	caerror "istio.io/istio/security/pkg/pki/error"
    33  	"istio.io/istio/security/pkg/pki/util"
    34  )
    35  
    36  var (
    37  	cert1Pem = `
    38  -----BEGIN CERTIFICATE-----
    39  MIIC3jCCAcagAwIBAgIJAMwyWk0iqlOoMA0GCSqGSIb3DQEBCwUAMBwxGjAYBgNV
    40  BAoMEWs4cy5jbHVzdGVyLmxvY2FsMB4XDTE4MDkyMTAyMjAzNFoXDTI4MDkxODAy
    41  MjAzNFowHDEaMBgGA1UECgwRazhzLmNsdXN0ZXIubG9jYWwwggEiMA0GCSqGSIb3
    42  DQEBAQUAA4IBDwAwggEKAoIBAQC8TDtfy23OKCRnkSYrKZwuHG5lOmTZgLwoFR1h
    43  3NDTkjR9406CjnAy6Gl73CRG3zRYVgY/2dGNqTzAKRCeKZlOzBlK6Kilb0NIJ6it
    44  s6ooMAxwXlr7jOKiSn6xbaexVMrP0VPUbCgJxQtGs3++hQ14D6WnyfdzPBZJLKbI
    45  tVdDnAcl/FJXKVV9gIg+MM0gETWOYj5Yd8Ye0FTvoFcgs8NKkxhEZe/LeYa7XYsk
    46  S0PymwbHwNZcfC4znp2bzu28LUmUe6kL97YU8ubvhR0muRy6h5MnQNMQrRG5Q5j4
    47  A2+tkO0vto8gOb6/lacEUVYuQdSkMZJiqWEjWgWKeAYdkTJDAgMBAAGjIzAhMA4G
    48  A1UdDwEB/wQEAwICBDAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IB
    49  AQAxWP3MT0IelJcb+e7fNTfMS0r3UhpiNkRU368Z7gJ4tDNOGRPzntW6CLnaE+3g
    50  IjOMAE8jlXeEmNuXtDQqQoZwWc1D5ma3jyc83E5H9LJzjfmn5rAHafr29YH85Ms2
    51  VlKdpP+teYg8Cag9u4ar/AUR4zMUEpGK5U+T9IH44lVqVH23T+DxAT+btsyuGiB0
    52  DsM76XVDj4g3OKCUalu7a8FHvgTkBpUJBl7vwh9kqo9HwCaj4iC2CwveOm0WtSgy
    53  K9PpVDxTGNSxqsxKn7DJQ15NTOP+gr29ABqFKwRr+S8ggw6evzHbABQTUMebaRSr
    54  iH7cSgrzZBiUvJmZRi7/BrYU
    55  -----END CERTIFICATE-----`
    56  
    57  	key1Pem = `
    58  -----BEGIN PRIVATE KEY-----
    59  MIIEwAIBADANBgkqhkiG9w0BAQEFAASCBKowggSmAgEAAoIBAQC8TDtfy23OKCRn
    60  kSYrKZwuHG5lOmTZgLwoFR1h3NDTkjR9406CjnAy6Gl73CRG3zRYVgY/2dGNqTzA
    61  KRCeKZlOzBlK6Kilb0NIJ6its6ooMAxwXlr7jOKiSn6xbaexVMrP0VPUbCgJxQtG
    62  s3++hQ14D6WnyfdzPBZJLKbItVdDnAcl/FJXKVV9gIg+MM0gETWOYj5Yd8Ye0FTv
    63  oFcgs8NKkxhEZe/LeYa7XYskS0PymwbHwNZcfC4znp2bzu28LUmUe6kL97YU8ubv
    64  hR0muRy6h5MnQNMQrRG5Q5j4A2+tkO0vto8gOb6/lacEUVYuQdSkMZJiqWEjWgWK
    65  eAYdkTJDAgMBAAECggEBAJTemFqmVQwWxKF1Kn4ZibcTF1zFDBLCKwBtoStMD3YW
    66  M5YL7nhd8OruwOcCJ1Q5CAOHD63PolOjp7otPUwui1y3FJAa3areCo2zfTLHxxG6
    67  2zrD/p6+xjeVOhFBJsGWzjn7v5FEaWs/9ChTpf2U6A8yH8BGd3MN4Hi96qboaDO0
    68  fFz3zOu7sgjkDNZiapZpUuqs7a6MCCr2T3FPwdWUiILZF2t5yWd/l8KabP+3QvvR
    69  tDU6sNv4j8e+dsF2l9ZT81JLkN+f6HvWcLVAADvcBqMcd8lmMSPgxSbytzKanx7o
    70  wtzIiGkNZBCVKGO7IK2ByCluiyHDpGul60Th7HUluDECgYEA9/Q1gT8LTHz1n6vM
    71  2n2umQN9R+xOaEYN304D5DQqptN3S0BCJ4dihD0uqEB5osstRTf4QpP/qb2hMDP4
    72  qWbWyrc7Z5Lyt6HI1ly6VpVnYKb3HDeJ9M+5Se1ttdwyRCzuT4ZBhT5bbqBatsOU
    73  V7+dyrJKbk8r9K4qy29UFozz/38CgYEAwmhzPVak99rVmqTpe0gPERW//n+PdW3P
    74  Ta6ongU8zkkw9LAFwgjGtNpd4nlk0iQigiM4jdJDFl6edrRXv2cisEfJ9+s53AOb
    75  hXui4HAn2rusPK+Dq2InkHYTGjEGDpx94zC/bjYR1GBIsthIh0w2G9ql8yvLatxG
    76  x6oXEsb7Lz0CgYEA7Oj+/mDYUNrMbSVfdBvF6Rl2aHQWbncQ5h3Khg55+i/uuY3K
    77  J66pqKQ0ojoIfk0XEh3qLOLv0qUHD+F4Y5OJAuOT9OBo3J/OH1M2D2hs/+JIFUPT
    78  on+fEE21F6AuvwkXIhCrJb5w6gB47Etuv3CsOXGkwEURQJXw+bODapB+yc0CgYEA
    79  t7zoTay6NdcJ0yLR2MZ+FvOrhekhuSaTqyPMEa15jq32KwzCJGUPCJbp7MY217V3
    80  N+/533A+H8JFmoNP+4KKcnknFb2n7Z0rO7licyUNRdniK2jm1O/r3Mj7vOFgjCaz
    81  hCnqg0tvBn4Jt55aziTlbuXzuiRGGTUfYE4NiJ2vgTECgYEA8di9yqGhETYQkoT3
    82  E70JpEmkCWiHl/h2ClLcDkj0gXKFxmhzmvs8G5On4S8toNiJ6efmz0KlHN1F7Ldi
    83  2iVd9LZnFVP1YwG0mvTJxxc5P5Uy5q/EhCLBAetqoTkWYlPcpkcathmCbCpJG4/x
    84  iOmuuOfQWnMfcVk8I0YDL5+G9Pg=
    85  -----END PRIVATE KEY-----`
    86  )
    87  
    88  // TODO (myidpt): Test Istio CA can load plugin key/certs from secret.
    89  
    90  func TestCreateSelfSignedIstioCAWithoutSecret(t *testing.T) {
    91  	caCertTTL := time.Hour
    92  	defaultCertTTL := 30 * time.Minute
    93  	maxCertTTL := time.Hour
    94  	org := "test.ca.Org"
    95  	const caNamespace = "default"
    96  	client := fake.NewSimpleClientset()
    97  	rootCertFile := ""
    98  	rootCertCheckInverval := time.Hour
    99  	rsaKeySize := 2048
   100  
   101  	caopts, err := NewSelfSignedIstioCAOptions(context.Background(),
   102  		0, caCertTTL, rootCertCheckInverval, defaultCertTTL,
   103  		maxCertTTL, org, false, false, caNamespace, client.CoreV1(),
   104  		rootCertFile, false, rsaKeySize)
   105  	if err != nil {
   106  		t.Fatalf("Failed to create a self-signed CA Options: %v", err)
   107  	}
   108  
   109  	ca, err := NewIstioCA(caopts)
   110  	if err != nil {
   111  		t.Errorf("Got error while creating self-signed CA: %v", err)
   112  	}
   113  	if ca == nil {
   114  		t.Fatalf("Failed to create a self-signed CA.")
   115  	}
   116  
   117  	signingCert, _, certChainBytes, rootCertBytes := ca.GetCAKeyCertBundle().GetAll()
   118  	rootCert, err := util.ParsePemEncodedCertificate(rootCertBytes)
   119  	if err != nil {
   120  		t.Error(err)
   121  	}
   122  	// Root cert and siging cert are the same for self-signed CA.
   123  	if !rootCert.Equal(signingCert) {
   124  		t.Error("CA root cert does not match signing cert")
   125  	}
   126  
   127  	if ttl := rootCert.NotAfter.Sub(rootCert.NotBefore); ttl != caCertTTL {
   128  		t.Errorf("Unexpected CA certificate TTL (expecting %v, actual %v)", caCertTTL, ttl)
   129  	}
   130  
   131  	if certOrg := rootCert.Issuer.Organization[0]; certOrg != org {
   132  		t.Errorf("Unexpected CA certificate organization (expecting %v, actual %v)", org, certOrg)
   133  	}
   134  
   135  	if len(certChainBytes) != 0 {
   136  		t.Errorf("Cert chain should be empty")
   137  	}
   138  
   139  	// Check the signing cert stored in K8s secret.
   140  	caSecret, err := client.CoreV1().Secrets("default").Get(context.TODO(), CASecret, metav1.GetOptions{})
   141  	if err != nil {
   142  		t.Errorf("Failed to get secret (error: %s)", err)
   143  	}
   144  
   145  	signingCertFromSecret, err := util.ParsePemEncodedCertificate(caSecret.Data[CACertFile])
   146  	if err != nil {
   147  		t.Errorf("Failed to parse cert (error: %s)", err)
   148  	}
   149  
   150  	if !signingCertFromSecret.Equal(signingCert) {
   151  		t.Error("CA signing cert does not match the K8s secret")
   152  	}
   153  }
   154  
   155  func TestCreateSelfSignedIstioCAWithoutSecretAndUseCacertsEnabled(t *testing.T) {
   156  	caCertTTL := time.Hour
   157  	defaultCertTTL := 30 * time.Minute
   158  	maxCertTTL := time.Hour
   159  	org := "test.ca.Org"
   160  	const caNamespace = "default"
   161  	client := fake.NewSimpleClientset()
   162  	rootCertFile := ""
   163  	rootCertCheckInverval := time.Hour
   164  	rsaKeySize := 2048
   165  
   166  	caopts, err := NewSelfSignedIstioCAOptions(context.Background(),
   167  		0, caCertTTL, rootCertCheckInverval, defaultCertTTL,
   168  		maxCertTTL, org, true, false, caNamespace, client.CoreV1(),
   169  		rootCertFile, false, rsaKeySize)
   170  	if err != nil {
   171  		t.Fatalf("Failed to create a self-signed CA Options: %v", err)
   172  	}
   173  
   174  	ca, err := NewIstioCA(caopts)
   175  	if err != nil {
   176  		t.Errorf("Got error while creating self-signed CA: %v", err)
   177  	}
   178  	if ca == nil {
   179  		t.Fatalf("Failed to create a self-signed CA.")
   180  	}
   181  
   182  	signingCert, _, certChainBytes, rootCertBytes := ca.GetCAKeyCertBundle().GetAll()
   183  	rootCert, err := util.ParsePemEncodedCertificate(rootCertBytes)
   184  	if err != nil {
   185  		t.Error(err)
   186  	}
   187  	// Root cert and siging cert are the same for self-signed CA.
   188  	if !rootCert.Equal(signingCert) {
   189  		t.Error("CA root cert does not match signing cert")
   190  	}
   191  
   192  	if ttl := rootCert.NotAfter.Sub(rootCert.NotBefore); ttl != caCertTTL {
   193  		t.Errorf("Unexpected CA certificate TTL (expecting %v, actual %v)", caCertTTL, ttl)
   194  	}
   195  
   196  	if certOrg := rootCert.Issuer.Organization[0]; certOrg != org {
   197  		t.Errorf("Unexpected CA certificate organization (expecting %v, actual %v)", org, certOrg)
   198  	}
   199  
   200  	if len(certChainBytes) != 0 {
   201  		t.Errorf("Cert chain should be empty")
   202  	}
   203  
   204  	// Check the signing cert stored in K8s secret.
   205  	caSecret, err := client.CoreV1().Secrets("default").Get(context.TODO(), CACertsSecret, metav1.GetOptions{})
   206  	if err != nil {
   207  		t.Errorf("Failed to get secret (error: %s)", err)
   208  	}
   209  
   210  	signingCertFromSecret, err := util.ParsePemEncodedCertificate(caSecret.Data[CACertFile])
   211  	if err != nil {
   212  		t.Errorf("Failed to parse cert (error: %s)", err)
   213  	}
   214  
   215  	if !signingCertFromSecret.Equal(signingCert) {
   216  		t.Error("CA signing cert does not match the K8s secret")
   217  	}
   218  }
   219  
   220  func TestCreateSelfSignedIstioCAWithSecret(t *testing.T) {
   221  	rootCertPem := cert1Pem
   222  	// Use the same signing cert and root cert for self-signed CA.
   223  	signingCertPem := []byte(cert1Pem)
   224  	signingKeyPem := []byte(key1Pem)
   225  
   226  	client := fake.NewSimpleClientset()
   227  	initSecret := BuildSecret(CASecret, "default", nil, nil, nil, signingCertPem, signingKeyPem, istioCASecretType)
   228  	_, err := client.CoreV1().Secrets("default").Create(context.TODO(), initSecret, metav1.CreateOptions{})
   229  	if err != nil {
   230  		t.Errorf("Failed to create secret (error: %s)", err)
   231  	}
   232  
   233  	caCertTTL := time.Hour
   234  	defaultCertTTL := 30 * time.Minute
   235  	maxCertTTL := time.Hour
   236  	org := "test.ca.Org"
   237  	caNamespace := "default"
   238  	const rootCertFile = ""
   239  	rootCertCheckInverval := time.Hour
   240  	rsaKeySize := 2048
   241  
   242  	caopts, err := NewSelfSignedIstioCAOptions(context.Background(),
   243  		0, caCertTTL, rootCertCheckInverval, defaultCertTTL, maxCertTTL,
   244  		org, false, false, caNamespace, client.CoreV1(),
   245  		rootCertFile, false, rsaKeySize)
   246  	if err != nil {
   247  		t.Fatalf("Failed to create a self-signed CA Options: %v", err)
   248  	}
   249  
   250  	ca, err := NewIstioCA(caopts)
   251  	if err != nil {
   252  		t.Errorf("Got error while creating self-signed CA: %v", err)
   253  	}
   254  	if ca == nil {
   255  		t.Fatalf("Failed to create a self-signed CA.")
   256  	}
   257  
   258  	signingCert, err := util.ParsePemEncodedCertificate(signingCertPem)
   259  	if err != nil {
   260  		t.Errorf("Failed to parse cert (error: %s)", err)
   261  	}
   262  
   263  	signingCertFromCA, _, certChainBytesFromCA, rootCertBytesFromCA := ca.GetCAKeyCertBundle().GetAll()
   264  
   265  	if !signingCert.Equal(signingCertFromCA) {
   266  		t.Error("Signing cert does not match")
   267  	}
   268  
   269  	if !bytes.Equal(rootCertBytesFromCA, []byte(rootCertPem)) {
   270  		t.Error("Root cert does not match")
   271  	}
   272  
   273  	if len(certChainBytesFromCA) != 0 {
   274  		t.Errorf("Cert chain should be empty")
   275  	}
   276  }
   277  
   278  func TestCreateSelfSignedIstioCAReadSigningCertOnly(t *testing.T) {
   279  	caCertTTL := time.Hour
   280  	defaultCertTTL := 30 * time.Minute
   281  	maxCertTTL := time.Hour
   282  	org := "test.ca.Org"
   283  	caNamespace := "default"
   284  	const rootCertFile = ""
   285  	rootCertCheckInverval := time.Hour
   286  	rsaKeySize := 2048
   287  
   288  	client := fake.NewSimpleClientset()
   289  
   290  	// succeed creating a self-signed cert
   291  	ctx0, cancel0 := context.WithTimeout(context.Background(), time.Millisecond*50)
   292  	defer cancel0()
   293  	_, err := NewSelfSignedIstioCAOptions(ctx0, 0,
   294  		caCertTTL, defaultCertTTL, rootCertCheckInverval, maxCertTTL, org, false, false,
   295  		caNamespace, client.CoreV1(), rootCertFile, false, rsaKeySize)
   296  	if err != nil {
   297  		t.Errorf("Got unexpected error: %v", err)
   298  	}
   299  
   300  	// Using existing CASecret.
   301  	secret, err := client.CoreV1().Secrets("default").Get(context.TODO(), CASecret, metav1.GetOptions{})
   302  	if err != nil {
   303  		t.Errorf("Got unexpected error %v", err)
   304  	}
   305  	signingCertPem := secret.Data[CACertFile]
   306  
   307  	ctx1, cancel1 := context.WithCancel(context.Background())
   308  	defer cancel1()
   309  	caopts, err := NewSelfSignedIstioCAOptions(ctx1, 0,
   310  		caCertTTL, defaultCertTTL, rootCertCheckInverval, maxCertTTL, org, false, false,
   311  		caNamespace, client.CoreV1(), rootCertFile, false, rsaKeySize)
   312  	if err != nil {
   313  		t.Errorf("Unexpected error: %v", err)
   314  	}
   315  
   316  	ca, err := NewIstioCA(caopts)
   317  	if err != nil {
   318  		t.Errorf("Got error while creating self-signed CA: %v", err)
   319  	}
   320  	if ca == nil {
   321  		t.Fatalf("Failed to create a self-signed CA.")
   322  	}
   323  
   324  	signingCert, err := util.ParsePemEncodedCertificate(signingCertPem)
   325  	if err != nil {
   326  		t.Errorf("Failed to parse cert (error: %s)", err)
   327  	}
   328  
   329  	signingCertFromCA, _, _, rootCertBytesFromCA := ca.GetCAKeyCertBundle().GetAll()
   330  	if !signingCert.Equal(signingCertFromCA) {
   331  		t.Error("Signing cert does not match")
   332  	}
   333  	if !bytes.Equal(rootCertBytesFromCA, secret.Data[CACertFile]) {
   334  		t.Error("Root cert does not match")
   335  	}
   336  }
   337  
   338  func TestConcurrentCreateSelfSignedIstioCA(t *testing.T) {
   339  	caCertTTL := time.Hour
   340  	defaultCertTTL := 30 * time.Minute
   341  	maxCertTTL := time.Hour
   342  	org := "test.ca.Org"
   343  	caNamespace := "default"
   344  	const rootCertFile = ""
   345  	rootCertCheckInverval := time.Hour
   346  	rsaKeySize := 2048
   347  
   348  	client := fake.NewSimpleClientset()
   349  
   350  	parallel := 10
   351  	wg := sync.WaitGroup{}
   352  	wg.Add(parallel)
   353  	rootCertCh := make(chan []byte, parallel)
   354  	privateKeyCh := make(chan []byte, parallel)
   355  
   356  	for i := 0; i < parallel; i++ {
   357  		go func() {
   358  			defer wg.Done()
   359  			// succeed creating a self-signed cert
   360  			ctx0, cancel0 := context.WithTimeout(context.Background(), 20*time.Second)
   361  			defer cancel0()
   362  			caOpts, err := NewSelfSignedIstioCAOptions(ctx0, 0,
   363  				caCertTTL, defaultCertTTL, rootCertCheckInverval, maxCertTTL, org, false, false,
   364  				caNamespace, client.CoreV1(), rootCertFile, false, rsaKeySize)
   365  			if err != nil {
   366  				t.Errorf("NewSelfSignedIstioCAOptions got unexpected error: %v", err)
   367  			}
   368  			cert, privateKey, certChain, rootCert := caOpts.KeyCertBundle.GetAllPem()
   369  			if !bytes.Equal(cert, rootCert) {
   370  				t.Error("Root cert and cert do not match")
   371  			}
   372  			// self signed certs do not contain cert chain
   373  			if len(certChain) > 0 {
   374  				t.Error("Cert chain should not exist")
   375  			}
   376  			rootCertCh <- rootCert
   377  			privateKeyCh <- privateKey
   378  		}()
   379  	}
   380  	wg.Wait()
   381  	caSecret, err := client.CoreV1().Secrets(caNamespace).Get(context.TODO(), CASecret, metav1.GetOptions{})
   382  	if err != nil || caSecret == nil {
   383  		t.Errorf("failed getting ca secret %v", err)
   384  	}
   385  	rootCert := caSecret.Data[CACertFile]
   386  	privateKey := caSecret.Data[CAPrivateKeyFile]
   387  
   388  	for {
   389  		select {
   390  		case current := <-rootCertCh:
   391  			if !bytes.Equal(rootCert, current) {
   392  				t.Error("Root cert does not match")
   393  			}
   394  		case current := <-privateKeyCh:
   395  			if !bytes.Equal(privateKey, current) {
   396  				t.Error("Private key does not match", string(privateKey), "\n", string(current))
   397  			}
   398  		default:
   399  			return
   400  		}
   401  	}
   402  }
   403  
   404  func TestCreatePluggedCertCA(t *testing.T) {
   405  	rootCertFile := "../testdata/multilevelpki/root-cert.pem"
   406  	certChainFile := []string{"../testdata/multilevelpki/int2-cert-chain.pem"}
   407  	signingCertFile := "../testdata/multilevelpki/int2-cert.pem"
   408  	signingKeyFile := "../testdata/multilevelpki/int2-key.pem"
   409  	rsaKeySize := 2048
   410  
   411  	defaultWorkloadCertTTL := 99999 * time.Hour
   412  	maxWorkloadCertTTL := time.Hour
   413  
   414  	caopts, err := NewPluggedCertIstioCAOptions(SigningCAFileBundle{rootCertFile, certChainFile, signingCertFile, signingKeyFile},
   415  		defaultWorkloadCertTTL, maxWorkloadCertTTL, rsaKeySize)
   416  	if err != nil {
   417  		t.Fatalf("Failed to create a plugged-cert CA Options: %v", err)
   418  	}
   419  
   420  	t0 := time.Now()
   421  	ca, err := NewIstioCA(caopts)
   422  	if err != nil {
   423  		t.Errorf("Got error while creating plugged-cert CA: %v", err)
   424  	}
   425  	if ca == nil {
   426  		t.Fatalf("Failed to create a plugged-cert CA.")
   427  	}
   428  
   429  	signingCertBytes, signingKeyBytes, certChainBytes, rootCertBytes := ca.GetCAKeyCertBundle().GetAllPem()
   430  	if !comparePem(signingCertBytes, signingCertFile) {
   431  		t.Errorf("Failed to verify loading of signing cert pem.")
   432  	}
   433  	if !comparePem(signingKeyBytes, signingKeyFile) {
   434  		t.Errorf("Failed to verify loading of signing key pem.")
   435  	}
   436  	if !comparePem(certChainBytes, certChainFile[0]) {
   437  		t.Errorf("Failed to verify loading of cert chain pem.")
   438  	}
   439  	if !comparePem(rootCertBytes, rootCertFile) {
   440  		t.Errorf("Failed to verify loading of root cert pem.")
   441  	}
   442  
   443  	certChain, err := util.ParsePemEncodedCertificate(certChainBytes)
   444  	if err != nil {
   445  		t.Fatalf("Failed to parse cert chain pem.")
   446  	}
   447  	// if CA cert becomes invalid before workload cert it's going to cause workload cert to be invalid too,
   448  	// however citatel won't rotate if that happens
   449  	delta := certChain.NotAfter.Sub(t0.Add(ca.defaultCertTTL))
   450  	if delta >= time.Second*2 {
   451  		t.Errorf("Invalid default cert TTL, should be the same as cert chain: %v VS (expected) %v",
   452  			t0.Add(ca.defaultCertTTL),
   453  			certChain.NotAfter)
   454  	}
   455  }
   456  
   457  func TestSignCSR(t *testing.T) {
   458  	subjectID := "spiffe://example.com/ns/foo/sa/bar"
   459  	cases := map[string]struct {
   460  		forCA         bool
   461  		certOpts      util.CertOptions
   462  		maxTTL        time.Duration
   463  		requestedTTL  time.Duration
   464  		verifyFields  util.VerifyFields
   465  		expectedError string
   466  	}{
   467  		"Workload uses RSA": {
   468  			forCA: false,
   469  			certOpts: util.CertOptions{
   470  				// This value is not used, instead, subjectID should be used in certificate.
   471  				Host:       "spiffe://different.com/test",
   472  				RSAKeySize: 2048,
   473  				IsCA:       false,
   474  			},
   475  			maxTTL:       time.Hour,
   476  			requestedTTL: 30 * time.Minute,
   477  			verifyFields: util.VerifyFields{
   478  				ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth, x509.ExtKeyUsageServerAuth},
   479  				KeyUsage:    x509.KeyUsageDigitalSignature | x509.KeyUsageKeyEncipherment,
   480  				IsCA:        false,
   481  				Host:        subjectID,
   482  			},
   483  			expectedError: "",
   484  		},
   485  		"Workload uses EC": {
   486  			forCA: false,
   487  			certOpts: util.CertOptions{
   488  				// This value is not used, instead, subjectID should be used in certificate.
   489  				Host:     "spiffe://different.com/test",
   490  				ECSigAlg: util.EcdsaSigAlg,
   491  				IsCA:     false,
   492  			},
   493  			maxTTL:       time.Hour,
   494  			requestedTTL: 30 * time.Minute,
   495  			verifyFields: util.VerifyFields{
   496  				ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth, x509.ExtKeyUsageServerAuth},
   497  				KeyUsage:    x509.KeyUsageDigitalSignature | x509.KeyUsageKeyEncipherment,
   498  				IsCA:        false,
   499  				Host:        subjectID,
   500  			},
   501  			expectedError: "",
   502  		},
   503  		"CA uses RSA": {
   504  			forCA: true,
   505  			certOpts: util.CertOptions{
   506  				RSAKeySize: 2048,
   507  				IsCA:       true,
   508  			},
   509  			maxTTL:       365 * 24 * time.Hour,
   510  			requestedTTL: 30 * 24 * time.Hour,
   511  			verifyFields: util.VerifyFields{
   512  				KeyUsage: x509.KeyUsageCertSign,
   513  				IsCA:     true,
   514  				Host:     subjectID,
   515  			},
   516  			expectedError: "",
   517  		},
   518  		"CA uses EC": {
   519  			forCA: true,
   520  			certOpts: util.CertOptions{
   521  				ECSigAlg: util.EcdsaSigAlg,
   522  				IsCA:     true,
   523  			},
   524  			maxTTL:       365 * 24 * time.Hour,
   525  			requestedTTL: 30 * 24 * time.Hour,
   526  			verifyFields: util.VerifyFields{
   527  				KeyUsage: x509.KeyUsageCertSign,
   528  				IsCA:     true,
   529  				Host:     subjectID,
   530  			},
   531  			expectedError: "",
   532  		},
   533  		"CSR uses RSA TTL error": {
   534  			forCA: false,
   535  			certOpts: util.CertOptions{
   536  				Org:        "istio.io",
   537  				RSAKeySize: 2048,
   538  			},
   539  			maxTTL:        2 * time.Hour,
   540  			requestedTTL:  3 * time.Hour,
   541  			expectedError: "requested TTL 3h0m0s is greater than the max allowed TTL 2h0m0s",
   542  		},
   543  		"CSR uses EC TTL error": {
   544  			forCA: false,
   545  			certOpts: util.CertOptions{
   546  				Org:      "istio.io",
   547  				ECSigAlg: util.EcdsaSigAlg,
   548  			},
   549  			maxTTL:        2 * time.Hour,
   550  			requestedTTL:  3 * time.Hour,
   551  			expectedError: "requested TTL 3h0m0s is greater than the max allowed TTL 2h0m0s",
   552  		},
   553  	}
   554  
   555  	for id, tc := range cases {
   556  		csrPEM, keyPEM, err := util.GenCSR(tc.certOpts)
   557  		if err != nil {
   558  			t.Errorf("%s: GenCSR error: %v", id, err)
   559  		}
   560  
   561  		ca, err := createCA(tc.maxTTL, tc.certOpts.ECSigAlg)
   562  		if err != nil {
   563  			t.Errorf("%s: createCA error: %v", id, err)
   564  		}
   565  
   566  		caCertOpts := CertOpts{
   567  			SubjectIDs: []string{subjectID},
   568  			TTL:        tc.requestedTTL,
   569  			ForCA:      tc.forCA,
   570  		}
   571  		certPEM, signErr := ca.Sign(csrPEM, caCertOpts)
   572  		if signErr != nil {
   573  			if tc.expectedError == "" {
   574  				t.Errorf("%s: Sign error: %v", id, err)
   575  			}
   576  			if certPEM != nil {
   577  				t.Errorf("%s: Expected null cert be obtained a non-null cert.", id)
   578  			}
   579  			if signErr.(*caerror.Error).Error() != tc.expectedError {
   580  				t.Errorf("%s: Expected error: %s but got error: %s.", id, tc.expectedError, signErr.(*caerror.Error).Error())
   581  			}
   582  			continue
   583  		}
   584  
   585  		_, _, certChainBytes, rootCertBytes := ca.GetCAKeyCertBundle().GetAll()
   586  		if err = util.VerifyCertificate(
   587  			keyPEM, append(certPEM, certChainBytes...), rootCertBytes, &tc.verifyFields); err != nil {
   588  			t.Errorf("%s: VerifyCertificate error: %v", id, err)
   589  		}
   590  
   591  		cert, err := util.ParsePemEncodedCertificate(certPEM)
   592  		if err != nil {
   593  			t.Errorf("%s: ParsePemEncodedCertificate error: %v", id, err)
   594  		}
   595  
   596  		if ttl := cert.NotAfter.Sub(cert.NotBefore) - util.ClockSkewGracePeriod; ttl != tc.requestedTTL {
   597  			t.Errorf("%s: Unexpected certificate TTL (expecting %v, actual %v)", id, tc.requestedTTL, ttl)
   598  		}
   599  		san := util.ExtractSANExtension(cert.Extensions)
   600  		if san == nil {
   601  			t.Errorf("%s: No SAN extension is found in the certificate", id)
   602  		}
   603  		expected, err := util.BuildSubjectAltNameExtension(subjectID)
   604  		if err != nil {
   605  			t.Errorf("%s: BuildSubjectAltNameExtension error: %v", id, err)
   606  		}
   607  		if !reflect.DeepEqual(expected, san) {
   608  			t.Errorf("%s: Unexpected extensions: wanted %v but got %v", id, expected, san)
   609  		}
   610  	}
   611  }
   612  
   613  func TestAppendRootCerts(t *testing.T) {
   614  	root1 := "root-cert-1"
   615  	expRootCerts := `root-cert-1
   616  root-cert-2
   617  root-cert-3`
   618  	rootCerts, err := util.AppendRootCerts([]byte(root1), "./root-certs-for-testing.pem")
   619  	if err != nil {
   620  		t.Errorf("AppendRootCerts() returns an error: %v", err)
   621  	} else if expRootCerts != string(rootCerts) {
   622  		t.Errorf("the root certificates do not match. Expect:%v. Actual:%v.",
   623  			expRootCerts, string(rootCerts))
   624  	}
   625  }
   626  
   627  func TestAppendRootCertsToNullCert(t *testing.T) {
   628  	// nil certificate
   629  	var root1 []byte
   630  	expRootCerts := `root-cert-2
   631  root-cert-3`
   632  	rootCerts, err := util.AppendRootCerts(root1, "./root-certs-for-testing.pem")
   633  	if err != nil {
   634  		t.Errorf("AppendRootCerts() returns an error: %v", err)
   635  	} else if expRootCerts != string(rootCerts) {
   636  		t.Errorf("the root certificates do not match. Expect:%v. Actual:%v.",
   637  			expRootCerts, string(rootCerts))
   638  	}
   639  }
   640  
   641  func TestSignWithCertChain(t *testing.T) {
   642  	rootCertFile := "../testdata/multilevelpki/root-cert.pem"
   643  	certChainFile := []string{"../testdata/multilevelpki/int-cert-chain.pem"}
   644  	signingCertFile := "../testdata/multilevelpki/int-cert.pem"
   645  	signingKeyFile := "../testdata/multilevelpki/int-key.pem"
   646  	rsaKeySize := 2048
   647  
   648  	defaultWorkloadCertTTL := 30 * time.Minute
   649  	maxWorkloadCertTTL := time.Hour
   650  
   651  	caopts, err := NewPluggedCertIstioCAOptions(SigningCAFileBundle{rootCertFile, certChainFile, signingCertFile, signingKeyFile},
   652  		defaultWorkloadCertTTL, maxWorkloadCertTTL, rsaKeySize)
   653  	if err != nil {
   654  		t.Fatalf("Failed to create a plugged-cert CA Options: %v", err)
   655  	}
   656  
   657  	ca, err := NewIstioCA(caopts)
   658  	if err != nil {
   659  		t.Errorf("Got error while creating plugged-cert CA: %v", err)
   660  	}
   661  	if ca == nil {
   662  		t.Fatalf("Failed to create a plugged-cert CA.")
   663  	}
   664  
   665  	opts := util.CertOptions{
   666  		// This value is not used, instead, subjectID should be used in certificate.
   667  		Host:       "spiffe://different.com/test",
   668  		RSAKeySize: 2048,
   669  		IsCA:       false,
   670  	}
   671  	csrPEM, privPEM, err := util.GenCSR(opts)
   672  	if err != nil {
   673  		t.Error(err)
   674  	}
   675  
   676  	caCertOpts := CertOpts{
   677  		SubjectIDs: []string{"localhost"},
   678  		TTL:        time.Hour,
   679  		ForCA:      false,
   680  	}
   681  	certPEM, signErr := ca.signWithCertChain(csrPEM, caCertOpts.SubjectIDs, caCertOpts.TTL, true, caCertOpts.ForCA)
   682  
   683  	if signErr != nil {
   684  		t.Error(err)
   685  	}
   686  
   687  	cert, err := tls.X509KeyPair(certPEM, privPEM)
   688  	if err != nil {
   689  		t.Error(err)
   690  	}
   691  
   692  	if len(cert.Certificate) != 3 {
   693  		t.Errorf("Unexpected number of certificates returned: %d (expected 4)", len(cert.Certificate))
   694  	}
   695  }
   696  
   697  func TestGenKeyCert(t *testing.T) {
   698  	cases := map[string]struct {
   699  		rootCertFile      string
   700  		certChainFile     []string
   701  		signingCertFile   string
   702  		signingKeyFile    string
   703  		certLifetime      time.Duration
   704  		checkCertLifetime bool
   705  		expectedError     string
   706  	}{
   707  		"RSA cryptography": {
   708  			rootCertFile:      "../testdata/multilevelpki/root-cert.pem",
   709  			certChainFile:     []string{"../testdata/multilevelpki/int-cert-chain.pem"},
   710  			signingCertFile:   "../testdata/multilevelpki/int-cert.pem",
   711  			signingKeyFile:    "../testdata/multilevelpki/int-key.pem",
   712  			certLifetime:      3650 * 24 * time.Hour,
   713  			checkCertLifetime: false,
   714  			expectedError:     "",
   715  		},
   716  		"EC cryptography": {
   717  			rootCertFile:      "../testdata/multilevelpki/ecc-root-cert.pem",
   718  			certChainFile:     []string{"../testdata/multilevelpki/ecc-int-cert-chain.pem"},
   719  			signingCertFile:   "../testdata/multilevelpki/ecc-int-cert.pem",
   720  			signingKeyFile:    "../testdata/multilevelpki/ecc-int-key.pem",
   721  			certLifetime:      3650 * 24 * time.Hour,
   722  			checkCertLifetime: false,
   723  			expectedError:     "",
   724  		},
   725  		"Pass lifetime check": {
   726  			rootCertFile:      "../testdata/multilevelpki/ecc-root-cert.pem",
   727  			certChainFile:     []string{"../testdata/multilevelpki/ecc-int-cert-chain.pem"},
   728  			signingCertFile:   "../testdata/multilevelpki/ecc-int-cert.pem",
   729  			signingKeyFile:    "../testdata/multilevelpki/ecc-int-key.pem",
   730  			certLifetime:      24 * time.Hour,
   731  			checkCertLifetime: true,
   732  			expectedError:     "",
   733  		},
   734  		"Error lifetime check": {
   735  			rootCertFile:      "../testdata/multilevelpki/ecc-root-cert.pem",
   736  			certChainFile:     []string{"../testdata/multilevelpki/ecc-int-cert-chain.pem"},
   737  			signingCertFile:   "../testdata/multilevelpki/ecc-int-cert.pem",
   738  			signingKeyFile:    "../testdata/multilevelpki/ecc-int-key.pem",
   739  			certLifetime:      25 * time.Hour,
   740  			checkCertLifetime: true,
   741  			expectedError:     "requested TTL 25h0m0s is greater than the max allowed TTL 24h0m0s",
   742  		},
   743  	}
   744  	defaultWorkloadCertTTL := 30 * time.Minute
   745  	maxWorkloadCertTTL := 24 * time.Hour
   746  	rsaKeySize := 2048
   747  
   748  	for id, tc := range cases {
   749  		caopts, err := NewPluggedCertIstioCAOptions(SigningCAFileBundle{tc.rootCertFile, tc.certChainFile, tc.signingCertFile, tc.signingKeyFile},
   750  			defaultWorkloadCertTTL, maxWorkloadCertTTL, rsaKeySize)
   751  		if err != nil {
   752  			t.Fatalf("%s: failed to create a plugged-cert CA Options: %v", id, err)
   753  		}
   754  
   755  		ca, err := NewIstioCA(caopts)
   756  		if err != nil {
   757  			t.Fatalf("%s: got error while creating plugged-cert CA: %v", id, err)
   758  		}
   759  		if ca == nil {
   760  			t.Fatalf("failed to create a plugged-cert CA.")
   761  		}
   762  
   763  		certPEM, privPEM, err := ca.GenKeyCert([]string{"host1", "host2"}, tc.certLifetime, tc.checkCertLifetime)
   764  		if err != nil {
   765  			if tc.expectedError == "" {
   766  				t.Fatalf("[%s] Unexpected error: %v", id, err)
   767  			}
   768  			if err.Error() != tc.expectedError {
   769  				t.Fatalf("[%s] Error returned does not match expectation: %v VS (expected) %v", id, err, tc.expectedError)
   770  			}
   771  			continue
   772  		} else if tc.expectedError != "" {
   773  			t.Fatalf("[%s] GenKeyCert succeeded but expected error: %v", id, tc.expectedError)
   774  		}
   775  
   776  		cert, err := tls.X509KeyPair(certPEM, privPEM)
   777  		if err != nil {
   778  			t.Fatalf("[%s] X509KeyPair error: %v", id, err)
   779  		}
   780  
   781  		if len(cert.Certificate) != 3 {
   782  			t.Fatalf("[%s] unexpected number of certificates returned: %d (expected 3)", id, len(cert.Certificate))
   783  		}
   784  	}
   785  }
   786  
   787  // TestBuildSecret verifies that BuildSecret returns expected secret.
   788  func TestBuildSecret(t *testing.T) {
   789  	CertPem := []byte(cert1Pem)
   790  	KeyPem := []byte(key1Pem)
   791  	namespace := "default"
   792  	secretType := "secret-type"
   793  
   794  	caSecret := BuildSecret(CASecret, namespace, nil, nil, nil, CertPem, KeyPem, v1.SecretType(secretType))
   795  	if caSecret.ObjectMeta.Annotations != nil {
   796  		t.Fatalf("Annotation should be nil but got %v", caSecret)
   797  	}
   798  	if _, ok := caSecret.Data[IstioGenerated]; ok {
   799  		t.Fatal("IstioGenerated key should not exist")
   800  	}
   801  	if caSecret.Data[CertChainFile] != nil {
   802  		t.Fatalf("Cert chain should be nil but got %v", caSecret.Data[CertChainFile])
   803  	}
   804  	if caSecret.Data[PrivateKeyFile] != nil {
   805  		t.Fatalf("Private key should be nil but got %v", caSecret.Data[PrivateKeyFile])
   806  	}
   807  	if !bytes.Equal(caSecret.Data[CACertFile], CertPem) {
   808  		t.Fatalf("CA cert does not match, want %v got %v", CertPem, caSecret.Data[CACertFile])
   809  	}
   810  	if !bytes.Equal(caSecret.Data[CAPrivateKeyFile], KeyPem) {
   811  		t.Fatalf("CA cert does not match, want %v got %v", KeyPem, caSecret.Data[CAPrivateKeyFile])
   812  	}
   813  	serverSecret := BuildSecret(CACertsSecret, namespace, CertPem, KeyPem, nil, nil, nil, v1.SecretType(secretType))
   814  	if serverSecret.ObjectMeta.Annotations != nil {
   815  		t.Fatalf("Annotation should be nil but got %v", serverSecret)
   816  	}
   817  	if _, ok := serverSecret.Data[IstioGenerated]; !ok {
   818  		t.Fatal("IstioGenerated key should exist")
   819  	}
   820  	if serverSecret.Data[CACertFile] != nil {
   821  		t.Fatalf("CA Cert should be nil but got %v", serverSecret.Data[CACertFile])
   822  	}
   823  	if serverSecret.Data[CAPrivateKeyFile] != nil {
   824  		t.Fatalf("CA private key should be nil but got %v", serverSecret.Data[CAPrivateKeyFile])
   825  	}
   826  	if !bytes.Equal(serverSecret.Data[CertChainFile], CertPem) {
   827  		t.Fatalf("Cert chain does not match, want %v got %v", CertPem, serverSecret.Data[CertChainFile])
   828  	}
   829  	if !bytes.Equal(serverSecret.Data[PrivateKeyFile], KeyPem) {
   830  		t.Fatalf("Private key does not match, want %v got %v", KeyPem, serverSecret.Data[PrivateKeyFile])
   831  	}
   832  }
   833  
   834  func createCA(maxTTL time.Duration, ecSigAlg util.SupportedECSignatureAlgorithms) (*IstioCA, error) {
   835  	// Generate root CA key and cert.
   836  	rootCAOpts := util.CertOptions{
   837  		IsCA:         true,
   838  		IsSelfSigned: true,
   839  		TTL:          time.Hour,
   840  		Org:          "Root CA",
   841  		RSAKeySize:   2048,
   842  		ECSigAlg:     ecSigAlg,
   843  	}
   844  
   845  	rootCertBytes, rootKeyBytes, err := util.GenCertKeyFromOptions(rootCAOpts)
   846  	if err != nil {
   847  		return nil, err
   848  	}
   849  
   850  	rootCert, err := util.ParsePemEncodedCertificate(rootCertBytes)
   851  	if err != nil {
   852  		return nil, err
   853  	}
   854  
   855  	rootKey, err := util.ParsePemEncodedKey(rootKeyBytes)
   856  	if err != nil {
   857  		return nil, err
   858  	}
   859  
   860  	intermediateCAOpts := util.CertOptions{
   861  		IsCA:         true,
   862  		IsSelfSigned: false,
   863  		TTL:          time.Hour,
   864  		Org:          "Intermediate CA",
   865  		RSAKeySize:   2048,
   866  		SignerCert:   rootCert,
   867  		SignerPriv:   rootKey,
   868  		ECSigAlg:     ecSigAlg,
   869  	}
   870  
   871  	intermediateCert, intermediateKey, err := util.GenCertKeyFromOptions(intermediateCAOpts)
   872  	if err != nil {
   873  		return nil, err
   874  	}
   875  
   876  	bundle, err := util.NewVerifiedKeyCertBundleFromPem(
   877  		intermediateCert, intermediateKey, intermediateCert, rootCertBytes)
   878  	if err != nil {
   879  		return nil, err
   880  	}
   881  	// Disable root cert rotator by setting root cert check interval to 0ns.
   882  	rootCertCheckInverval := time.Duration(0)
   883  	caOpts := &IstioCAOptions{
   884  		DefaultCertTTL: time.Hour,
   885  		MaxCertTTL:     maxTTL,
   886  		KeyCertBundle:  bundle,
   887  		RotatorConfig: &SelfSignedCARootCertRotatorConfig{
   888  			CheckInterval: rootCertCheckInverval,
   889  		},
   890  	}
   891  
   892  	return NewIstioCA(caOpts)
   893  }
   894  
   895  func comparePem(expectedBytes []byte, file string) bool {
   896  	fileBytes, err := os.ReadFile(file)
   897  	if err != nil {
   898  		return false
   899  	}
   900  	if !bytes.Equal(fileBytes, expectedBytes) {
   901  		return false
   902  	}
   903  	return true
   904  }