github.com/Venafi/vcert/v5@v5.10.2/pkg/playbook/app/installer/jks.go (about)

     1  /*
     2   * Copyright 2023 Venafi, 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 installer
    18  
    19  import (
    20  	"bytes"
    21  	"crypto/x509"
    22  	"encoding/pem"
    23  	"fmt"
    24  	"os"
    25  	"time"
    26  
    27  	"github.com/pavel-v-chernykh/keystore-go/v4"
    28  	"go.uber.org/zap"
    29  
    30  	"github.com/Venafi/vcert/v5/pkg/certificate"
    31  	"github.com/Venafi/vcert/v5/pkg/playbook/app/domain"
    32  	"github.com/Venafi/vcert/v5/pkg/playbook/util"
    33  )
    34  
    35  // JKSInstaller represents an installation that will use the Java KeyStore format for the certificate bundle
    36  type JKSInstaller struct {
    37  	domain.Installation
    38  }
    39  
    40  // NewJKSInstaller returns a new installer of type JKS with the values defined in inst
    41  func NewJKSInstaller(inst domain.Installation) JKSInstaller {
    42  	return JKSInstaller{inst}
    43  }
    44  
    45  // Check is the method in charge of making the validations to install a new certificate:
    46  // 1. Does the certificate exists? > Install if it doesn't.
    47  // 2. Does the certificate is about to expire? Renew if about to expire.
    48  // Returns true if the certificate needs to be installed.
    49  func (r JKSInstaller) Check(renewBefore string, _ domain.PlaybookRequest) (bool, error) {
    50  	zap.L().Info("checking certificate health", zap.String("format", r.Type.String()), zap.String("location", r.File))
    51  
    52  	// Check certificate file exists
    53  	certExists, err := util.FileExists(r.File)
    54  	if err != nil {
    55  		return false, err
    56  	}
    57  	if !certExists {
    58  		return true, nil
    59  	}
    60  
    61  	keyPassword := r.KeyPassword
    62  	if keyPassword == "" {
    63  		keyPassword = r.JKSPassword
    64  	}
    65  
    66  	// Load Certificate
    67  	cert, err := loadJKS(r.File, r.JKSAlias, r.JKSPassword, keyPassword)
    68  	if err != nil {
    69  		return false, err
    70  	}
    71  
    72  	// Check certificate expiration
    73  	renew := needRenewal(cert, renewBefore)
    74  
    75  	return renew, nil
    76  }
    77  
    78  // Backup takes the certificate request and backs up the current version prior to overwriting
    79  func (r JKSInstaller) Backup() error {
    80  	zap.L().Debug("backing up certificate", zap.String("location", r.File))
    81  
    82  	// Check certificate file exists
    83  	certExists, err := util.FileExists(r.File)
    84  	if err != nil {
    85  		return err
    86  	}
    87  	if !certExists {
    88  		zap.L().Info("New certificate location specified, no back up taken")
    89  		return nil
    90  	}
    91  
    92  	newLocation := fmt.Sprintf("%s.bak", r.File)
    93  
    94  	err = util.CopyFile(r.File, newLocation)
    95  	if err != nil {
    96  		return err
    97  	}
    98  
    99  	zap.L().Info("certificate backed up", zap.String("location", r.File), zap.String("backupLocation", newLocation))
   100  	return nil
   101  }
   102  
   103  // Install takes the certificate bundle and moves it to the location specified in the installer
   104  func (r JKSInstaller) Install(pcc certificate.PEMCollection) error {
   105  	zap.L().Debug("installing certificate", zap.String("location", r.File))
   106  
   107  	// If no password is set for the Private Key, use the JKSPassword
   108  	keyPassword := r.KeyPassword
   109  	if keyPassword == "" {
   110  		keyPassword = r.JKSPassword
   111  	}
   112  
   113  	content, err := packageAsJKS(pcc, keyPassword, r.JKSAlias, r.JKSPassword)
   114  	if err != nil {
   115  		zap.L().Error("could not package certificate as JKS", zap.Error(err))
   116  		return err
   117  	}
   118  
   119  	err = util.WriteFile(r.File, content)
   120  	if err != nil {
   121  		return err
   122  	}
   123  
   124  	return nil
   125  }
   126  
   127  // AfterInstallActions runs any instructions declared in the Installer on a terminal.
   128  //
   129  // No validations happen over the content of the AfterAction string, so caution is advised
   130  func (r JKSInstaller) AfterInstallActions() (string, error) {
   131  	zap.L().Debug("running after-install actions", zap.String("location", r.File))
   132  
   133  	result, err := util.ExecuteScript(r.AfterAction)
   134  	return result, err
   135  }
   136  
   137  // InstallValidationActions runs any instructions declared in the Installer on a terminal and expects
   138  // "0" for successful validation and "1" for a validation failure
   139  // No validations happen over the content of the InstallValidation string, so caution is advised
   140  func (r JKSInstaller) InstallValidationActions() (string, error) {
   141  	zap.L().Debug("running install validation actions", zap.String("location", r.File))
   142  
   143  	validationResult, err := util.ExecuteScript(r.InstallValidation)
   144  	if err != nil {
   145  		return "", err
   146  	}
   147  
   148  	return validationResult, err
   149  }
   150  
   151  func loadJKS(jksFile string, jksAlias string, jksPassword string, pkPassword string) (*x509.Certificate, error) {
   152  	//Open file
   153  	f, err := os.Open(jksFile)
   154  	if err != nil {
   155  		zap.L().Error("could not read JKS file", zap.String("jksFile", jksFile), zap.Error(err))
   156  		return nil, err
   157  	}
   158  	defer func() {
   159  		if err = f.Close(); err != nil {
   160  			zap.L().Fatal("could not close JKS file", zap.String("jksFile", jksFile))
   161  		}
   162  	}()
   163  
   164  	// Load JKS
   165  	ks := keystore.New()
   166  	err = ks.Load(f, []byte(jksPassword))
   167  	if err != nil {
   168  		zap.L().Error("could not load JKS resource", zap.String("jksFile", jksFile))
   169  		return nil, err
   170  	}
   171  
   172  	//Load Private Key and Certificate chain
   173  	pkEntry, err := ks.GetPrivateKeyEntry(jksAlias, []byte(pkPassword))
   174  	if err != nil {
   175  		zap.L().Error("could not retrieve Private Key from JKS", zap.String("jksAlias", jksAlias))
   176  		return nil, err
   177  	}
   178  
   179  	certData := pkEntry.CertificateChain[0]
   180  
   181  	cert, err := x509.ParseCertificate(certData.Content)
   182  	if err != nil {
   183  		return nil, fmt.Errorf("could not parse certificate: %w", err)
   184  	}
   185  
   186  	return cert, nil
   187  }
   188  
   189  func packageAsJKS(pcc certificate.PEMCollection, keyPassword string, jksAlias string, jksPassword string) ([]byte, error) {
   190  	if len(pcc.Certificate) == 0 || len(pcc.PrivateKey) == 0 {
   191  		return nil, fmt.Errorf("certificate and Private Key are required for JKS")
   192  	}
   193  
   194  	//Getting the certificate in bytes
   195  	certBlock, _ := pem.Decode([]byte(pcc.Certificate))
   196  	if certBlock == nil || certBlock.Type != "CERTIFICATE" {
   197  		return nil, fmt.Errorf("no Certificate found on Certificate content")
   198  	}
   199  
   200  	//Adding the certificates to the slice of Certificates
   201  	certificateChain := make([]keystore.Certificate, 0)
   202  	certificateChain = append(certificateChain, keystore.Certificate{
   203  		Type:    "X509",
   204  		Content: certBlock.Bytes,
   205  	})
   206  
   207  	//Getting chain as keystore.Certificate objects
   208  	certificateChain = append(certificateChain, getJKSCertChain(pcc.Chain)...)
   209  
   210  	//Getting the Private Key
   211  	privateKey, err := getPrivateKey(pcc.PrivateKey, keyPassword)
   212  	if err != nil {
   213  		return nil, err
   214  	}
   215  
   216  	//Marshalling the Private Key to PKCS8, which is mandatory for JKS format
   217  	pkcs8DER, err := x509.MarshalPKCS8PrivateKey(privateKey)
   218  	if err != nil {
   219  		return nil, fmt.Errorf("error marshalling the private key to PKCS8: %w", err)
   220  	}
   221  
   222  	//creating a Private Key entry
   223  	pkEntry := keystore.PrivateKeyEntry{
   224  		CreationTime:     time.Now(),
   225  		PrivateKey:       pkcs8DER,
   226  		CertificateChain: certificateChain,
   227  	}
   228  
   229  	//Adding the Private Key entry to the JKS
   230  	keyStore := keystore.New()
   231  	err = keyStore.SetPrivateKeyEntry(jksAlias, pkEntry, []byte(keyPassword))
   232  	if err != nil {
   233  		return nil, fmt.Errorf("JKS private key error: %w", err)
   234  	}
   235  
   236  	//Setting storePassword as keyPassword if jksPassword not defined
   237  	var storePassword []byte
   238  	if jksPassword != "" {
   239  		storePassword = []byte(jksPassword)
   240  	} else {
   241  		storePassword = []byte(keyPassword)
   242  	}
   243  
   244  	//Storing the JKS to the buffer
   245  	buffer := new(bytes.Buffer)
   246  	err = keyStore.Store(buffer, storePassword)
   247  	if err != nil {
   248  		return nil, fmt.Errorf("JKS keystore error: %w", err)
   249  	}
   250  
   251  	return buffer.Bytes(), nil
   252  }
   253  
   254  func getJKSCertChain(chain []string) []keystore.Certificate {
   255  	certificateChain := make([]keystore.Certificate, 0)
   256  	//Getting each certificate in the chain and adding their bytes to the JKS chain
   257  	for _, chainCert := range chain {
   258  		chainBlock, _ := pem.Decode([]byte(chainCert))
   259  		certificateChain = append(certificateChain, keystore.Certificate{
   260  			Type:    "X509",
   261  			Content: chainBlock.Bytes,
   262  		})
   263  	}
   264  
   265  	return certificateChain
   266  }