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 }