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 }