internal/files/sign.go (79 lines of code) (raw):
// Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
// or more contributor license agreements. Licensed under the Elastic License;
// you may not use this file except in compliance with the Elastic License.
package files
import (
"fmt"
"os"
"github.com/ProtonMail/gopenpgp/v2/armor"
"github.com/ProtonMail/gopenpgp/v2/constants"
"github.com/ProtonMail/gopenpgp/v2/crypto"
"github.com/elastic/elastic-package/internal/environment"
"github.com/elastic/elastic-package/internal/logger"
)
const signatureComment = "Signed with elastic-package (using GopenPGP: https://gopenpgp.org)"
var (
signerPrivateKeyfileEnv = environment.WithElasticPackagePrefix("SIGNER_PRIVATE_KEYFILE")
signerPassphraseEnv = environment.WithElasticPackagePrefix("SIGNER_PASSPHRASE")
)
type SignOptions struct {
PackageName string
PackageVersion string
}
// VerifySignerConfiguration function verifies if the signer configuration is complete.
func VerifySignerConfiguration() error {
signerPrivateKeyfile := os.Getenv(signerPrivateKeyfileEnv)
if signerPrivateKeyfile == "" {
return fmt.Errorf("signer private keyfile is required. Please define it with environment variable %s", signerPrivateKeyfileEnv)
}
_, err := os.Stat(signerPrivateKeyfile)
if err != nil {
return fmt.Errorf("can't access the signer private keyfile: %w", err)
}
signerPassphrase := os.Getenv(signerPassphraseEnv)
if signerPassphrase == "" {
return fmt.Errorf("signer passphrase is required. Please define it with environment variable %s", signerPassphraseEnv)
}
return nil
}
// Sign function signs the target file using provided private ket. It creates the {targetFile}.sig file for the given
// {targetFile}.
func Sign(targetFile string, options SignOptions) error {
signerPrivateKeyfile := os.Getenv(signerPrivateKeyfileEnv)
logger.Debugf("Read signer private keyfile: %s", signerPrivateKeyfile)
signerPrivateKey, err := os.ReadFile(signerPrivateKeyfile)
if err != nil {
return fmt.Errorf("can't read the signer private keyfile (path: %s): %w", signerPrivateKeyfile, err)
}
signerPassphrase := []byte(os.Getenv(signerPassphraseEnv))
logger.Debug("Start the signing routine")
signingKey, err := crypto.NewKeyFromArmored(string(signerPrivateKey))
if err != nil {
return fmt.Errorf("crypto.NewKeyFromArmored failed: %w", err)
}
unlockedKey, err := signingKey.Unlock(signerPassphrase)
if err != nil {
return fmt.Errorf("signingKey.Unlock failed: %w", err)
}
defer unlockedKey.ClearPrivateParams()
keyRing, err := crypto.NewKeyRing(unlockedKey)
if err != nil {
return fmt.Errorf("crypto.NewKeyRing failed: %w", err)
}
messageReader, err := os.Open(targetFile)
if err != nil {
return fmt.Errorf("os.Open failed (targetFile: %s): %w", targetFile, err)
}
defer messageReader.Close()
signature, err := keyRing.SignDetachedStream(messageReader)
if err != nil {
return fmt.Errorf("keyRing.SignDetached failed: %w", err)
}
armoredSignature, err := armor.ArmorWithTypeAndCustomHeaders(signature.Data, constants.PGPSignatureHeader,
fmt.Sprintf("%s-%s", options.PackageName, options.PackageVersion), signatureComment)
if err != nil {
return fmt.Errorf("signature.GetArmored failed: %w", err)
}
logger.Debug("Signature generated for the target file, writing the .sig file")
targetSigFile := targetFile + ".sig"
err = os.WriteFile(targetSigFile, []byte(armoredSignature), 0644)
if err != nil {
return fmt.Errorf("can't write the signature file: %w", err)
}
logger.Infof("Signature file written: %s", targetSigFile)
return nil
}