github.com/Venafi/vcert/v5@v5.10.2/pkg/playbook/app/installer/pkcs12.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  	"crypto/x509"
    21  	"encoding/pem"
    22  	"fmt"
    23  	"os"
    24  
    25  	"go.uber.org/zap"
    26  	"software.sslmate.com/src/go-pkcs12"
    27  
    28  	"github.com/Venafi/vcert/v5/pkg/certificate"
    29  	"github.com/Venafi/vcert/v5/pkg/playbook/app/domain"
    30  	"github.com/Venafi/vcert/v5/pkg/playbook/util"
    31  )
    32  
    33  // PKCS12Installer represents an installation that will use the PKCS12 format for the certificate bundle
    34  type PKCS12Installer struct {
    35  	domain.Installation
    36  }
    37  
    38  // NewPKCS12Installer returns a new installer of type PKCS12 with the values defined in inst
    39  func NewPKCS12Installer(inst domain.Installation) PKCS12Installer {
    40  	return PKCS12Installer{inst}
    41  }
    42  
    43  // Check is the method in charge of making the validations to install a new certificate:
    44  // 1. Does the certificate exists? > Install if it doesn't.
    45  // 2. Does the certificate is about to expire? Renew if about to expire.
    46  // Returns true if the certificate needs to be installed.
    47  func (r PKCS12Installer) Check(renewBefore string, _ domain.PlaybookRequest) (bool, error) {
    48  	zap.L().Info("checking certificate health", zap.String("format", r.Type.String()), zap.String("location", r.File))
    49  
    50  	// Check certificate file exists
    51  	certExists, err := util.FileExists(r.File)
    52  	if err != nil {
    53  		return false, err
    54  	}
    55  	if !certExists {
    56  		return true, nil
    57  	}
    58  
    59  	// Load Certificate
    60  	cert, err := loadPKCS12(r.File, r.P12Password)
    61  	if err != nil {
    62  		return false, err
    63  	}
    64  
    65  	// Check certificate expiration
    66  	renew := needRenewal(cert, renewBefore)
    67  
    68  	return renew, nil
    69  }
    70  
    71  // Backup takes the certificate request and backs up the current version prior to overwriting
    72  func (r PKCS12Installer) Backup() error {
    73  	zap.L().Debug("backing up certificate", zap.String("location", r.File))
    74  
    75  	// Check certificate file exists
    76  	certExists, err := util.FileExists(r.File)
    77  	if err != nil {
    78  		return err
    79  	}
    80  	if !certExists {
    81  		zap.L().Info("new certificate location specified, no back up taken")
    82  		return nil
    83  	}
    84  
    85  	newLocation := fmt.Sprintf("%s.bak", r.File)
    86  
    87  	err = util.CopyFile(r.File, newLocation)
    88  	if err != nil {
    89  		return err
    90  	}
    91  
    92  	zap.L().Info("certificate backed up", zap.String("location", r.File), zap.String("backupLocation", newLocation))
    93  	return err
    94  }
    95  
    96  // Install takes the certificate bundle and moves it to the location specified in the installer
    97  func (r PKCS12Installer) Install(pcc certificate.PEMCollection) error {
    98  	zap.L().Debug("installing certificate", zap.String("location", r.File))
    99  
   100  	if r.P12Password == "" {
   101  		return domain.ErrNoP12Password
   102  	}
   103  
   104  	content, err := packageAsPKCS12(pcc, r.P12Password, r.UseLegacyP12)
   105  	if err != nil {
   106  		zap.L().Error("could not package certificate as PKCS12")
   107  		return err
   108  	}
   109  
   110  	err = util.WriteFile(r.File, content)
   111  	if err != nil {
   112  		return err
   113  	}
   114  
   115  	return nil
   116  }
   117  
   118  // AfterInstallActions runs any instructions declared in the Installer on a terminal.
   119  //
   120  // No validations happen over the content of the AfterAction string, so caution is advised
   121  func (r PKCS12Installer) AfterInstallActions() (string, error) {
   122  	zap.L().Debug("running after-install actions", zap.String("location", r.File))
   123  
   124  	result, err := util.ExecuteScript(r.AfterAction)
   125  	return result, err
   126  }
   127  
   128  // InstallValidationActions runs any instructions declared in the Installer on a terminal and expects
   129  // "0" for successful validation and "1" for a validation failure
   130  // No validations happen over the content of the InstallValidation string, so caution is advised
   131  func (r PKCS12Installer) InstallValidationActions() (string, error) {
   132  	zap.L().Debug("running install validation actions", zap.String("location", r.File))
   133  
   134  	validationResult, err := util.ExecuteScript(r.InstallValidation)
   135  	if err != nil {
   136  		return "", err
   137  	}
   138  
   139  	return validationResult, err
   140  }
   141  
   142  func loadPKCS12(pkcs12File string, keyPassword string) (*x509.Certificate, error) {
   143  	//Open file
   144  	data, err := os.ReadFile(pkcs12File)
   145  	if err != nil {
   146  		zap.L().Error("could not read PKCS12 file", zap.String("location", pkcs12File))
   147  		return nil, err
   148  	}
   149  
   150  	// Due to limitations in pkcs12
   151  	_, cert, _, err := pkcs12.DecodeChain(data, keyPassword)
   152  	if err != nil {
   153  		return nil, err
   154  	}
   155  
   156  	return cert, nil
   157  }
   158  
   159  func packageAsPKCS12(pcc certificate.PEMCollection, keyPassword string, legacyPkcs12 bool) ([]byte, error) {
   160  	if len(pcc.Certificate) == 0 || len(pcc.PrivateKey) == 0 {
   161  		return nil, fmt.Errorf("certificate and Private Key are required for PKCS12")
   162  	}
   163  
   164  	//Getting the certificate in bytes
   165  	certBlock, _ := pem.Decode([]byte(pcc.Certificate))
   166  	if certBlock == nil || certBlock.Type != "CERTIFICATE" {
   167  		return nil, fmt.Errorf("missing Certificate PEM")
   168  	}
   169  
   170  	//Getting X509.Certificate object
   171  	cert, err := x509.ParseCertificate(certBlock.Bytes)
   172  	if err != nil {
   173  		return nil, fmt.Errorf("failed to parse Certificate bytes to X509.Certificate object")
   174  	}
   175  
   176  	//Getting Chain as X509.Certificate objects
   177  	chainList, err := getX509CertChain(pcc.Chain)
   178  	if err != nil {
   179  		return nil, err
   180  	}
   181  
   182  	//Getting the Private Key
   183  	privateKey, err := getPrivateKey(pcc.PrivateKey, keyPassword)
   184  	if err != nil {
   185  		return nil, err
   186  	}
   187  	var bytes []byte
   188  
   189  	if legacyPkcs12 {
   190  		bytes, err = pkcs12.Legacy.Encode(privateKey, cert, chainList, keyPassword)
   191  	} else {
   192  		bytes, err = pkcs12.Modern2023.Encode(privateKey, cert, chainList, keyPassword)
   193  	}
   194  
   195  	if err != nil {
   196  		return nil, fmt.Errorf("PKCS12 encode error: %w", err)
   197  	}
   198  
   199  	return bytes, nil
   200  }
   201  
   202  func getX509CertChain(chain []string) ([]*x509.Certificate, error) {
   203  	chainList := make([]*x509.Certificate, 0)
   204  	for _, chainCertStr := range chain {
   205  		chainBlock, _ := pem.Decode([]byte(chainCertStr))
   206  		chainCert, err := x509.ParseCertificate(chainBlock.Bytes)
   207  		if err != nil {
   208  			return nil, fmt.Errorf("failed to parse Chain Certificate bytes to X509.Certificate")
   209  		}
   210  		chainList = append(chainList, chainCert)
   211  	}
   212  
   213  	return chainList, nil
   214  }