github.com/Venafi/vcert/v5@v5.10.2/pkg/playbook/app/domain/installation.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 domain
    18  
    19  import (
    20  	"fmt"
    21  	"runtime"
    22  	"strings"
    23  
    24  	"go.uber.org/zap"
    25  )
    26  
    27  const (
    28  	// JKSMinPasswordLength represents the minimum length a JKS password must have per the JKS specification
    29  	JKSMinPasswordLength = 6
    30  
    31  	capiLocationCurrentUser  = "currentuser"
    32  	capiLocationLocalMachine = "localmachine"
    33  )
    34  
    35  var validStoreNames = []string{"addressbook", "authroot", "certificateauthority", "disallowed", "my", "root",
    36  	"trustedpeople", "trustedpublisher"}
    37  
    38  // Installation represents a location in which a certificate will be installed,
    39  // along with the format in which it will be installed
    40  type Installation struct {
    41  	AfterAction         string `yaml:"afterInstallAction,omitempty"`
    42  	BackupFiles         bool   `yaml:"backupFiles,omitempty"`
    43  	CAPIFriendlyName    string `yaml:"capiFriendlyName,omitempty"` // In a future version of vCert this will become REQUIRED!
    44  	CAPIIsNonExportable bool   `yaml:"capiIsNonExportable,omitempty"`
    45  	CAPILocation        string `yaml:"capiLocation,omitempty"` // This is an alias for Location
    46  	ChainFile           string `yaml:"chainFile,omitempty"`
    47  	File                string `yaml:"file,omitempty"`
    48  	InstallValidation   string `yaml:"installValidationAction,omitempty"`
    49  	JKSAlias            string `yaml:"jksAlias,omitempty"`
    50  	JKSPassword         string `yaml:"jksPassword,omitempty"`
    51  	KeyFile             string `yaml:"keyFile,omitempty"`
    52  	KeyPassword         string `yaml:"keyPassword,omitempty"`
    53  	// Deprecated: Location is deprecated in favor of CAPILocation. It will be removed on a future release
    54  	Location     string             `yaml:"location,omitempty"`
    55  	P12Password  string             `yaml:"p12Password,omitempty"`
    56  	UseLegacyP12 bool               `yaml:"useLegacyP12,omitempty"`
    57  	Type         InstallationFormat `yaml:"format,omitempty"`
    58  }
    59  
    60  // Installations is a slice of Installation
    61  type Installations []Installation
    62  
    63  // IsValid returns true if the Installation type is supported by vcert
    64  func (installation Installation) IsValid() (bool, error) {
    65  	switch installation.Type {
    66  	case FormatJKS:
    67  		if err := validateJKS(installation); err != nil {
    68  			return false, fmt.Errorf("\t\t\t%w", err)
    69  		}
    70  	case FormatPEM:
    71  		if err := validatePEM(installation); err != nil {
    72  			return false, fmt.Errorf("\t\t\t%w", err)
    73  		}
    74  	case FormatPKCS12:
    75  		if err := validateP12(installation); err != nil {
    76  			return false, fmt.Errorf("\t\t\t%w", err)
    77  		}
    78  	case FormatCAPI:
    79  		if err := validateCAPI(installation); err != nil {
    80  			return false, fmt.Errorf("\t\t\t%w", err)
    81  		}
    82  	case FormatUnknown:
    83  		fallthrough
    84  	default:
    85  		return false, fmt.Errorf("\t\t\t%w", ErrUndefinedInstallationFormat)
    86  	}
    87  
    88  	return true, nil
    89  }
    90  
    91  func validateCAPI(installation Installation) error {
    92  	if runtime.GOOS != "windows" {
    93  		return ErrCAPIOnNonWindows
    94  	}
    95  
    96  	location := installation.CAPILocation
    97  	if location == "" {
    98  		location = installation.Location
    99  	}
   100  
   101  	// Ensure there is a location specified
   102  	if location == "" {
   103  		return ErrNoCAPILocation
   104  	}
   105  
   106  	// Throw warning if using deprecated field
   107  	if installation.Location != "" {
   108  		zap.L().Warn(WarningLocationFieldDeprecated)
   109  	}
   110  
   111  	// Throw warning if no friendly name set
   112  	if installation.CAPIFriendlyName == "" {
   113  		zap.L().Warn(WarningNoCAPIFriendlyName)
   114  	}
   115  
   116  	// Ensure proper location specified
   117  	segments := strings.Split(location, "\\")
   118  
   119  	// CAPI Location must be in form of <string>\<string>
   120  	if len(segments) != 2 {
   121  		return ErrMalformedCAPILocation
   122  	}
   123  
   124  	capiLocation := strings.ToLower(segments[0])
   125  	if capiLocation != capiLocationCurrentUser && capiLocation != capiLocationLocalMachine {
   126  		return ErrInvalidCAPILocation
   127  	}
   128  
   129  	// valid store names from https://learn.microsoft.com/en-us/dotnet/api/system.security.cryptography.x509certificates.storename?view=net-7.0
   130  	// Although it is unlikely that you'd want to install a certificate and private key in anything but "my", here for completeness
   131  	isValidStoreName := false
   132  	for _, v := range validStoreNames {
   133  		if v == strings.ToLower(segments[1]) {
   134  			isValidStoreName = true
   135  			break
   136  		}
   137  	}
   138  
   139  	if !isValidStoreName {
   140  		return ErrInvalidCAPIStoreName
   141  	}
   142  
   143  	return nil
   144  }
   145  
   146  func validateJKS(installation Installation) error {
   147  	if installation.File == "" {
   148  		return ErrNoInstallationFile
   149  	}
   150  
   151  	if installation.JKSAlias == "" {
   152  		return ErrNoJKSAlias
   153  	}
   154  	if installation.JKSPassword == "" {
   155  		return ErrNoJKSPassword
   156  	}
   157  	if len(installation.JKSPassword) < JKSMinPasswordLength {
   158  		return ErrJKSPasswordLength
   159  	}
   160  
   161  	if installation.KeyPassword == "" {
   162  		zap.L().Warn("no keyPassword set. Using JKSPassword as password for the Private Key")
   163  	} else {
   164  		if len(installation.KeyPassword) < JKSMinPasswordLength {
   165  			return ErrKeyPasswordLength
   166  		}
   167  	}
   168  
   169  	return nil
   170  }
   171  
   172  func validatePEM(installation Installation) error {
   173  	if installation.File == "" {
   174  		return ErrNoInstallationFile
   175  	}
   176  
   177  	if installation.ChainFile == "" {
   178  		return ErrNoChainFile
   179  	}
   180  	if installation.KeyFile == "" {
   181  		return ErrNoKeyFile
   182  	}
   183  	return nil
   184  }
   185  
   186  func validateP12(installation Installation) error {
   187  	if installation.File == "" {
   188  		return ErrNoInstallationFile
   189  	}
   190  	if installation.P12Password == "" {
   191  		return ErrNoP12Password
   192  	}
   193  	return nil
   194  }