mirror of https://github.com/helm/helm.git
Remove unnecessary file i/o operations from signing and verifying
Signed-off-by: Scott Rigby <scott@r6by.com>
This commit is contained in:
parent
9ea35da0d0
commit
e814ff3c38
|
@ -38,7 +38,8 @@ type HTTPInstaller struct {
|
|||
base
|
||||
extractor Extractor
|
||||
getter getter.Getter
|
||||
// Provenance data to save after installation
|
||||
// Cached data to avoid duplicate downloads
|
||||
pluginData []byte
|
||||
provData []byte
|
||||
}
|
||||
|
||||
|
@ -74,15 +75,18 @@ func NewHTTPInstaller(source string) (*HTTPInstaller, error) {
|
|||
//
|
||||
// Implements Installer.
|
||||
func (i *HTTPInstaller) Install() error {
|
||||
// Ensure plugin data is cached
|
||||
if i.pluginData == nil {
|
||||
pluginData, err := i.getter.Get(i.Source)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
i.pluginData = pluginData.Bytes()
|
||||
}
|
||||
|
||||
// Save the original tarball to plugins directory for verification
|
||||
// Extract metadata to get the actual plugin name and version
|
||||
pluginBytes := pluginData.Bytes()
|
||||
metadata, err := plugin.ExtractPluginMetadataFromReader(bytes.NewReader(pluginBytes))
|
||||
metadata, err := plugin.ExtractTgzPluginMetadata(bytes.NewReader(i.pluginData))
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to extract plugin metadata from tarball: %w", err)
|
||||
}
|
||||
|
@ -91,20 +95,28 @@ func (i *HTTPInstaller) Install() error {
|
|||
if err := os.MkdirAll(filepath.Dir(tarballPath), 0755); err != nil {
|
||||
return fmt.Errorf("failed to create plugins directory: %w", err)
|
||||
}
|
||||
if err := os.WriteFile(tarballPath, pluginBytes, 0644); err != nil {
|
||||
if err := os.WriteFile(tarballPath, i.pluginData, 0644); err != nil {
|
||||
return fmt.Errorf("failed to save tarball: %w", err)
|
||||
}
|
||||
|
||||
// Ensure prov data is cached if available
|
||||
if i.provData == nil {
|
||||
// Try to download .prov file if it exists
|
||||
provURL := i.Source + ".prov"
|
||||
if provData, err := i.getter.Get(provURL); err == nil {
|
||||
i.provData = provData.Bytes()
|
||||
}
|
||||
}
|
||||
|
||||
// Save prov file if we have the data
|
||||
if i.provData != nil {
|
||||
provPath := tarballPath + ".prov"
|
||||
if err := os.WriteFile(provPath, provData.Bytes(), 0644); err != nil {
|
||||
if err := os.WriteFile(provPath, i.provData, 0644); err != nil {
|
||||
slog.Debug("failed to save provenance file", "error", err)
|
||||
}
|
||||
}
|
||||
|
||||
if err := i.extractor.Extract(pluginData, i.CacheDir); err != nil {
|
||||
if err := i.extractor.Extract(bytes.NewBuffer(i.pluginData), i.CacheDir); err != nil {
|
||||
return fmt.Errorf("extracting files from archive: %w", err)
|
||||
}
|
||||
|
||||
|
@ -148,51 +160,32 @@ func (i *HTTPInstaller) SupportsVerification() bool {
|
|||
return strings.HasSuffix(i.Source, ".tgz") || strings.HasSuffix(i.Source, ".tar.gz")
|
||||
}
|
||||
|
||||
// PrepareForVerification downloads the plugin and signature files for verification
|
||||
func (i *HTTPInstaller) PrepareForVerification() (string, func(), error) {
|
||||
// GetVerificationData returns cached plugin and provenance data for verification
|
||||
func (i *HTTPInstaller) GetVerificationData() (archiveData, provData []byte, filename string, err error) {
|
||||
if !i.SupportsVerification() {
|
||||
return "", nil, fmt.Errorf("verification not supported for this source")
|
||||
return nil, nil, "", fmt.Errorf("verification not supported for this source")
|
||||
}
|
||||
|
||||
// Create temporary directory for downloads
|
||||
tempDir, err := os.MkdirTemp("", "helm-plugin-verify-*")
|
||||
// Download plugin data once and cache it
|
||||
if i.pluginData == nil {
|
||||
data, err := i.getter.Get(i.Source)
|
||||
if err != nil {
|
||||
return "", nil, fmt.Errorf("failed to create temp directory: %w", err)
|
||||
return nil, nil, "", fmt.Errorf("failed to download plugin: %w", err)
|
||||
}
|
||||
i.pluginData = data.Bytes()
|
||||
}
|
||||
|
||||
cleanup := func() {
|
||||
os.RemoveAll(tempDir)
|
||||
}
|
||||
|
||||
// Download plugin tarball
|
||||
pluginFile := filepath.Join(tempDir, filepath.Base(i.Source))
|
||||
|
||||
g, err := getter.All(new(cli.EnvSettings)).ByScheme("http")
|
||||
// Download prov data once and cache it if available
|
||||
if i.provData == nil {
|
||||
provData, err := i.getter.Get(i.Source + ".prov")
|
||||
if err != nil {
|
||||
cleanup()
|
||||
return "", nil, err
|
||||
}
|
||||
|
||||
data, err := g.Get(i.Source, getter.WithURL(i.Source))
|
||||
if err != nil {
|
||||
cleanup()
|
||||
return "", nil, fmt.Errorf("failed to download plugin: %w", err)
|
||||
}
|
||||
|
||||
if err := os.WriteFile(pluginFile, data.Bytes(), 0644); err != nil {
|
||||
cleanup()
|
||||
return "", nil, fmt.Errorf("failed to write plugin file: %w", err)
|
||||
}
|
||||
|
||||
// Try to download signature file - don't fail if it doesn't exist
|
||||
if provData, err := g.Get(i.Source+".prov", getter.WithURL(i.Source+".prov")); err == nil {
|
||||
if err := os.WriteFile(pluginFile+".prov", provData.Bytes(), 0644); err == nil {
|
||||
// Store the provenance data so we can save it after installation
|
||||
// If provenance file doesn't exist, set provData to nil
|
||||
// The verification logic will handle this gracefully
|
||||
i.provData = nil
|
||||
} else {
|
||||
i.provData = provData.Bytes()
|
||||
}
|
||||
}
|
||||
// Note: We don't fail if .prov file can't be downloaded - the verification logic
|
||||
// in InstallWithOptions will handle missing .prov files appropriately
|
||||
|
||||
return pluginFile, cleanup, nil
|
||||
return i.pluginData, i.provData, filepath.Base(i.Source), nil
|
||||
}
|
||||
|
|
|
@ -55,8 +55,8 @@ type Installer interface {
|
|||
type Verifier interface {
|
||||
// SupportsVerification returns true if this installer can verify plugins
|
||||
SupportsVerification() bool
|
||||
// PrepareForVerification downloads necessary files for verification
|
||||
PrepareForVerification() (pluginPath string, cleanup func(), err error)
|
||||
// GetVerificationData returns plugin and provenance data for verification
|
||||
GetVerificationData() (archiveData, provData []byte, filename string, err error)
|
||||
}
|
||||
|
||||
// Install installs a plugin.
|
||||
|
@ -91,28 +91,19 @@ func InstallWithOptions(i Installer, opts Options) (*VerificationResult, error)
|
|||
return nil, fmt.Errorf("--verify is only supported for plugin tarballs (.tgz files)")
|
||||
}
|
||||
|
||||
// Prepare for verification (download files if needed)
|
||||
pluginPath, cleanup, err := verifier.PrepareForVerification()
|
||||
// Get verification data (works for both memory and file-based installers)
|
||||
archiveData, provData, filename, err := verifier.GetVerificationData()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to prepare for verification: %w", err)
|
||||
}
|
||||
if cleanup != nil {
|
||||
defer cleanup()
|
||||
return nil, fmt.Errorf("failed to get verification data: %w", err)
|
||||
}
|
||||
|
||||
// Check if provenance file exists
|
||||
provFile := pluginPath + ".prov"
|
||||
if _, err := os.Stat(provFile); err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
// Check if provenance data exists
|
||||
if len(provData) == 0 {
|
||||
// No .prov file found - emit warning but continue installation
|
||||
fmt.Fprintf(os.Stderr, "WARNING: No provenance file found for plugin. Plugin is not signed and cannot be verified.\n")
|
||||
} else {
|
||||
// Other error accessing .prov file
|
||||
return nil, fmt.Errorf("failed to access provenance file: %w", err)
|
||||
}
|
||||
} else {
|
||||
// Provenance file exists - verify the plugin
|
||||
verification, err := plugin.VerifyPlugin(pluginPath, opts.Keyring)
|
||||
// Provenance data exists - verify the plugin
|
||||
verification, err := plugin.VerifyPlugin(archiveData, provData, filename, opts.Keyring)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("plugin verification failed: %w", err)
|
||||
}
|
||||
|
|
|
@ -37,7 +37,8 @@ type LocalInstaller struct {
|
|||
base
|
||||
isArchive bool
|
||||
extractor Extractor
|
||||
provData []byte // Provenance data to save after installation
|
||||
pluginData []byte // Cached plugin data
|
||||
provData []byte // Cached provenance data
|
||||
}
|
||||
|
||||
// NewLocalInstaller creates a new LocalInstaller.
|
||||
|
@ -110,7 +111,7 @@ func (i *LocalInstaller) installFromArchive() error {
|
|||
|
||||
// Copy the original tarball to plugins directory for verification
|
||||
// Extract metadata to get the actual plugin name and version
|
||||
metadata, err := plugin.ExtractPluginMetadataFromReader(bytes.NewReader(data))
|
||||
metadata, err := plugin.ExtractTgzPluginMetadata(bytes.NewReader(data))
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to extract plugin metadata from tarball: %w", err)
|
||||
}
|
||||
|
@ -184,21 +185,35 @@ func (i *LocalInstaller) SupportsVerification() bool {
|
|||
return i.isArchive
|
||||
}
|
||||
|
||||
// PrepareForVerification returns the local path for verification
|
||||
func (i *LocalInstaller) PrepareForVerification() (string, func(), error) {
|
||||
// GetVerificationData loads plugin and provenance data from local files for verification
|
||||
func (i *LocalInstaller) GetVerificationData() (archiveData, provData []byte, filename string, err error) {
|
||||
if !i.SupportsVerification() {
|
||||
return "", nil, fmt.Errorf("verification not supported for directories")
|
||||
return nil, nil, "", fmt.Errorf("verification not supported for directories")
|
||||
}
|
||||
|
||||
// For local files, try to read the .prov file if it exists
|
||||
// Read and cache the plugin archive file
|
||||
if i.pluginData == nil {
|
||||
i.pluginData, err = os.ReadFile(i.Source)
|
||||
if err != nil {
|
||||
return nil, nil, "", fmt.Errorf("failed to read plugin file: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Read and cache the provenance file if it exists
|
||||
if i.provData == nil {
|
||||
provFile := i.Source + ".prov"
|
||||
if provData, err := os.ReadFile(provFile); err == nil {
|
||||
// Store the provenance data so we can save it after installation
|
||||
i.provData = provData
|
||||
i.provData, err = os.ReadFile(provFile)
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
// If provenance file doesn't exist, set provData to nil
|
||||
// The verification logic will handle this gracefully
|
||||
i.provData = nil
|
||||
} else {
|
||||
// If file exists but can't be read (permissions, etc), return error
|
||||
return nil, nil, "", fmt.Errorf("failed to access provenance file %s: %w", provFile, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
// Note: We don't fail if .prov file doesn't exist - the verification logic
|
||||
// in InstallWithOptions will handle missing .prov files appropriately
|
||||
|
||||
// Return the source path directly, no cleanup needed
|
||||
return i.Source, nil, nil
|
||||
return i.pluginData, i.provData, filepath.Base(i.Source), nil
|
||||
}
|
||||
|
|
|
@ -44,6 +44,9 @@ type OCIInstaller struct {
|
|||
base
|
||||
settings *cli.EnvSettings
|
||||
getter getter.Getter
|
||||
// Cached data to avoid duplicate downloads
|
||||
pluginData []byte
|
||||
provData []byte
|
||||
}
|
||||
|
||||
// NewOCIInstaller creates a new OCIInstaller with optional getter options
|
||||
|
@ -83,18 +86,17 @@ func NewOCIInstaller(source string, options ...getter.Option) (*OCIInstaller, er
|
|||
func (i *OCIInstaller) Install() error {
|
||||
slog.Debug("pulling OCI plugin", "source", i.Source)
|
||||
|
||||
// Use getter to download the plugin
|
||||
// Ensure plugin data is cached
|
||||
if i.pluginData == nil {
|
||||
pluginData, err := i.getter.Get(i.Source)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to pull plugin from %s: %w", i.Source, err)
|
||||
}
|
||||
|
||||
// Save the original tarball to plugins directory for verification
|
||||
// For OCI plugins, extract version from plugin.yaml inside the tarball
|
||||
pluginBytes := pluginData.Bytes()
|
||||
i.pluginData = pluginData.Bytes()
|
||||
}
|
||||
|
||||
// Extract metadata to get the actual plugin name and version
|
||||
metadata, err := plugin.ExtractPluginMetadataFromReader(bytes.NewReader(pluginBytes))
|
||||
metadata, err := plugin.ExtractTgzPluginMetadata(bytes.NewReader(i.pluginData))
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to extract plugin metadata from tarball: %w", err)
|
||||
}
|
||||
|
@ -104,21 +106,29 @@ func (i *OCIInstaller) Install() error {
|
|||
if err := os.MkdirAll(filepath.Dir(tarballPath), 0755); err != nil {
|
||||
return fmt.Errorf("failed to create plugins directory: %w", err)
|
||||
}
|
||||
if err := os.WriteFile(tarballPath, pluginBytes, 0644); err != nil {
|
||||
if err := os.WriteFile(tarballPath, i.pluginData, 0644); err != nil {
|
||||
return fmt.Errorf("failed to save tarball: %w", err)
|
||||
}
|
||||
|
||||
// Try to download and save .prov file alongside the tarball
|
||||
// Ensure prov data is cached if available
|
||||
if i.provData == nil {
|
||||
// Try to download .prov file if it exists
|
||||
provSource := i.Source + ".prov"
|
||||
if provData, err := i.getter.Get(provSource); err == nil {
|
||||
i.provData = provData.Bytes()
|
||||
}
|
||||
}
|
||||
|
||||
// Save prov file if we have the data
|
||||
if i.provData != nil {
|
||||
provPath := tarballPath + ".prov"
|
||||
if err := os.WriteFile(provPath, provData.Bytes(), 0644); err != nil {
|
||||
if err := os.WriteFile(provPath, i.provData, 0644); err != nil {
|
||||
slog.Debug("failed to save provenance file", "error", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Check if this is a gzip compressed file
|
||||
if len(pluginBytes) < 2 || pluginBytes[0] != 0x1f || pluginBytes[1] != 0x8b {
|
||||
if len(i.pluginData) < 2 || i.pluginData[0] != 0x1f || i.pluginData[1] != 0x8b {
|
||||
return fmt.Errorf("plugin data is not a gzip compressed archive")
|
||||
}
|
||||
|
||||
|
@ -128,7 +138,7 @@ func (i *OCIInstaller) Install() error {
|
|||
}
|
||||
|
||||
// Extract as gzipped tar
|
||||
if err := extractTarGz(bytes.NewReader(pluginBytes), i.CacheDir); err != nil {
|
||||
if err := extractTarGz(bytes.NewReader(i.pluginData), i.CacheDir); err != nil {
|
||||
return fmt.Errorf("failed to extract plugin: %w", err)
|
||||
}
|
||||
|
||||
|
@ -251,55 +261,41 @@ func (i *OCIInstaller) SupportsVerification() bool {
|
|||
return true
|
||||
}
|
||||
|
||||
// PrepareForVerification downloads the plugin tarball and provenance to a temporary directory
|
||||
func (i *OCIInstaller) PrepareForVerification() (pluginPath string, cleanup func(), err error) {
|
||||
slog.Debug("preparing OCI plugin for verification", "source", i.Source)
|
||||
// GetVerificationData downloads and caches plugin and provenance data from OCI registry for verification
|
||||
func (i *OCIInstaller) GetVerificationData() (archiveData, provData []byte, filename string, err error) {
|
||||
slog.Debug("getting verification data for OCI plugin", "source", i.Source)
|
||||
|
||||
// Create temporary directory for verification
|
||||
tempDir, err := os.MkdirTemp("", "helm-oci-verify-")
|
||||
// Download plugin data once and cache it
|
||||
if i.pluginData == nil {
|
||||
pluginDataBuffer, err := i.getter.Get(i.Source)
|
||||
if err != nil {
|
||||
return "", nil, fmt.Errorf("failed to create temp directory: %w", err)
|
||||
return nil, nil, "", fmt.Errorf("failed to pull plugin from %s: %w", i.Source, err)
|
||||
}
|
||||
i.pluginData = pluginDataBuffer.Bytes()
|
||||
}
|
||||
|
||||
cleanup = func() {
|
||||
os.RemoveAll(tempDir)
|
||||
}
|
||||
|
||||
// Download the plugin tarball
|
||||
pluginData, err := i.getter.Get(i.Source)
|
||||
if err != nil {
|
||||
cleanup()
|
||||
return "", nil, fmt.Errorf("failed to pull plugin from %s: %w", i.Source, err)
|
||||
}
|
||||
|
||||
// Extract metadata to get the actual plugin name and version
|
||||
pluginBytes := pluginData.Bytes()
|
||||
metadata, err := plugin.ExtractPluginMetadataFromReader(bytes.NewReader(pluginBytes))
|
||||
if err != nil {
|
||||
cleanup()
|
||||
return "", nil, fmt.Errorf("failed to extract plugin metadata from tarball: %w", err)
|
||||
}
|
||||
filename := fmt.Sprintf("%s-%s.tgz", metadata.Name, metadata.Version)
|
||||
|
||||
// Save plugin tarball to temp directory
|
||||
pluginTarball := filepath.Join(tempDir, filename)
|
||||
if err := os.WriteFile(pluginTarball, pluginBytes, 0644); err != nil {
|
||||
cleanup()
|
||||
return "", nil, fmt.Errorf("failed to save plugin tarball: %w", err)
|
||||
}
|
||||
|
||||
// Try to download the provenance file - don't fail if it doesn't exist
|
||||
// Download prov data once and cache it if available
|
||||
if i.provData == nil {
|
||||
provSource := i.Source + ".prov"
|
||||
if provData, err := i.getter.Get(provSource); err == nil {
|
||||
// Save provenance to temp directory
|
||||
provFile := filepath.Join(tempDir, filename+".prov")
|
||||
if err := os.WriteFile(provFile, provData.Bytes(), 0644); err == nil {
|
||||
slog.Debug("prepared plugin for verification", "plugin", pluginTarball, "provenance", provFile)
|
||||
// Calling getter.Get again is reasonable because: 1. The OCI registry client already optimizes the underlying network calls
|
||||
// 2. Both calls use the same underlying manifest and memory store 3. The second .prov call is very fast since the data is already pulled
|
||||
provDataBuffer, err := i.getter.Get(provSource)
|
||||
if err != nil {
|
||||
// If provenance file doesn't exist, set provData to nil
|
||||
// The verification logic will handle this gracefully
|
||||
i.provData = nil
|
||||
} else {
|
||||
i.provData = provDataBuffer.Bytes()
|
||||
}
|
||||
}
|
||||
// Note: We don't fail if .prov file can't be downloaded - the verification logic
|
||||
// in InstallWithOptions will handle missing .prov files appropriately
|
||||
|
||||
slog.Debug("prepared plugin for verification", "plugin", pluginTarball)
|
||||
return pluginTarball, cleanup, nil
|
||||
// Extract metadata to get the filename
|
||||
metadata, err := plugin.ExtractTgzPluginMetadata(bytes.NewReader(i.pluginData))
|
||||
if err != nil {
|
||||
return nil, nil, "", fmt.Errorf("failed to extract plugin metadata from tarball: %w", err)
|
||||
}
|
||||
filename = fmt.Sprintf("%s-%s.tgz", metadata.Name, metadata.Version)
|
||||
|
||||
slog.Debug("got verification data for OCI plugin", "filename", filename)
|
||||
return i.pluginData, i.provData, filename, nil
|
||||
}
|
||||
|
|
|
@ -17,6 +17,7 @@ package plugin
|
|||
|
||||
import (
|
||||
"archive/tar"
|
||||
"bytes"
|
||||
"compress/gzip"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
@ -29,14 +30,14 @@ import (
|
|||
"helm.sh/helm/v4/pkg/provenance"
|
||||
)
|
||||
|
||||
// SignPlugin signs a plugin using the SHA256 hash of the tarball.
|
||||
// SignPlugin signs a plugin using the SHA256 hash of the tarball data.
|
||||
//
|
||||
// This is used when packaging and signing a plugin from a tarball file.
|
||||
// This is used when packaging and signing a plugin from tarball data.
|
||||
// It creates a signature that includes the tarball hash and plugin metadata,
|
||||
// allowing verification of the original tarball later.
|
||||
func SignPlugin(tarballPath string, signer *provenance.Signatory) (string, error) {
|
||||
// Extract plugin metadata from tarball
|
||||
pluginMeta, err := extractPluginMetadata(tarballPath)
|
||||
func SignPlugin(tarballData []byte, filename string, signer *provenance.Signatory) (string, error) {
|
||||
// Extract plugin metadata from tarball data
|
||||
pluginMeta, err := ExtractTgzPluginMetadata(bytes.NewReader(tarballData))
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to extract plugin metadata: %w", err)
|
||||
}
|
||||
|
@ -48,22 +49,11 @@ func SignPlugin(tarballPath string, signer *provenance.Signatory) (string, error
|
|||
}
|
||||
|
||||
// Use the generic provenance signing function
|
||||
return signer.ClearSign(tarballPath, metadataBytes)
|
||||
return signer.ClearSign(tarballData, filename, metadataBytes)
|
||||
}
|
||||
|
||||
// extractPluginMetadata extracts plugin metadata from a tarball
|
||||
func extractPluginMetadata(tarballPath string) (*Metadata, error) {
|
||||
f, err := os.Open(tarballPath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
return ExtractPluginMetadataFromReader(f)
|
||||
}
|
||||
|
||||
// ExtractPluginMetadataFromReader extracts plugin metadata from a tarball reader
|
||||
func ExtractPluginMetadataFromReader(r io.Reader) (*Metadata, error) {
|
||||
// ExtractTgzPluginMetadata extracts plugin metadata from a gzipped tarball reader
|
||||
func ExtractTgzPluginMetadata(r io.Reader) (*Metadata, error) {
|
||||
gzr, err := gzip.NewReader(r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
|
|
@ -69,8 +69,14 @@ runtimeConfig:
|
|||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Read the tarball data
|
||||
tarballData, err := os.ReadFile(tarballPath)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to read tarball: %v", err)
|
||||
}
|
||||
|
||||
// Sign the plugin tarball
|
||||
sig, err := SignPlugin(tarballPath, signer)
|
||||
sig, err := SignPlugin(tarballData, filepath.Base(tarballPath), signer)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to sign plugin: %v", err)
|
||||
}
|
||||
|
|
|
@ -16,57 +16,24 @@ limitations under the License.
|
|||
package plugin
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"helm.sh/helm/v4/pkg/provenance"
|
||||
)
|
||||
|
||||
// VerifyPlugin verifies a plugin tarball against a signature.
|
||||
//
|
||||
// This function verifies that a plugin tarball has a valid provenance file
|
||||
// and that the provenance file is signed by a trusted entity.
|
||||
func VerifyPlugin(pluginPath, keyring string) (*provenance.Verification, error) {
|
||||
// Verify the plugin path exists
|
||||
fi, err := os.Stat(pluginPath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Only support tarball verification
|
||||
if fi.IsDir() {
|
||||
return nil, errors.New("directory verification not supported - only plugin tarballs can be verified")
|
||||
}
|
||||
|
||||
// Verify it's a tarball
|
||||
if !isTarball(pluginPath) {
|
||||
return nil, errors.New("plugin file must be a gzipped tarball (.tar.gz or .tgz)")
|
||||
}
|
||||
|
||||
// Look for provenance file
|
||||
provFile := pluginPath + ".prov"
|
||||
if _, err := os.Stat(provFile); err != nil {
|
||||
return nil, fmt.Errorf("could not find provenance file %s: %w", provFile, err)
|
||||
}
|
||||
|
||||
// VerifyPlugin verifies plugin data against a signature using data in memory.
|
||||
func VerifyPlugin(archiveData, provData []byte, filename, keyring string) (*provenance.Verification, error) {
|
||||
// Create signatory from keyring
|
||||
sig, err := provenance.NewFromKeyring(keyring, "")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return verifyPluginTarball(pluginPath, provFile, sig)
|
||||
}
|
||||
|
||||
// verifyPluginTarball verifies a plugin tarball against its signature
|
||||
func verifyPluginTarball(pluginPath, provPath string, sig *provenance.Signatory) (*provenance.Verification, error) {
|
||||
// Reuse chart verification logic from pkg/provenance
|
||||
return sig.Verify(pluginPath, provPath)
|
||||
// Use the new VerifyData method directly
|
||||
return sig.Verify(archiveData, provData, filename)
|
||||
}
|
||||
|
||||
// isTarball checks if a file has a tarball extension
|
||||
func isTarball(filename string) bool {
|
||||
func IsTarball(filename string) bool {
|
||||
return filepath.Ext(filename) == ".gz" || filepath.Ext(filename) == ".tgz"
|
||||
}
|
||||
|
|
|
@ -18,7 +18,6 @@ package plugin
|
|||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"helm.sh/helm/v4/pkg/provenance"
|
||||
|
@ -74,7 +73,13 @@ func TestVerifyPlugin(t *testing.T) {
|
|||
t.Fatal(err)
|
||||
}
|
||||
|
||||
sig, err := SignPlugin(tarballPath, signer)
|
||||
// Read the tarball data
|
||||
tarballData, err := os.ReadFile(tarballPath)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
sig, err := SignPlugin(tarballData, filepath.Base(tarballPath), signer)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
@ -85,8 +90,19 @@ func TestVerifyPlugin(t *testing.T) {
|
|||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Read the files for verification
|
||||
archiveData, err := os.ReadFile(tarballPath)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
provData, err := os.ReadFile(provFile)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Now verify the plugin
|
||||
verification, err := VerifyPlugin(tarballPath, testPubFile)
|
||||
verification, err := VerifyPlugin(archiveData, provData, filepath.Base(tarballPath), testPubFile)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to verify plugin: %v", err)
|
||||
}
|
||||
|
@ -146,8 +162,19 @@ InvalidSignatureData
|
|||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Read the files
|
||||
archiveData, err := os.ReadFile(tarballPath)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
provData, err := os.ReadFile(provFile)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Try to verify - should fail
|
||||
_, err = VerifyPlugin(tarballPath, testPubFile)
|
||||
_, err = VerifyPlugin(archiveData, provData, filepath.Base(tarballPath), testPubFile)
|
||||
if err == nil {
|
||||
t.Error("Expected verification to fail with bad signature")
|
||||
}
|
||||
|
@ -162,40 +189,26 @@ func TestVerifyPluginMissingProvenance(t *testing.T) {
|
|||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Try to verify without .prov file
|
||||
_, err := VerifyPlugin(tarballPath, testPubFile)
|
||||
if err == nil {
|
||||
t.Error("Expected verification to fail without provenance file")
|
||||
}
|
||||
}
|
||||
|
||||
func TestVerifyPluginDirectory(t *testing.T) {
|
||||
// Create a test plugin directory
|
||||
tempDir := t.TempDir()
|
||||
pluginDir := filepath.Join(tempDir, "test-plugin")
|
||||
if err := os.MkdirAll(pluginDir, 0755); err != nil {
|
||||
// Read the tarball data
|
||||
archiveData, err := os.ReadFile(tarballPath)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Create a plugin.yaml file
|
||||
if err := os.WriteFile(filepath.Join(pluginDir, "plugin.yaml"), []byte(testPluginYAML), 0644); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Attempt to verify the directory - should fail
|
||||
_, err := VerifyPlugin(pluginDir, testPubFile)
|
||||
// Try to verify with empty provenance data
|
||||
_, err = VerifyPlugin(archiveData, nil, filepath.Base(tarballPath), testPubFile)
|
||||
if err == nil {
|
||||
t.Error("Expected directory verification to fail, but it succeeded")
|
||||
}
|
||||
|
||||
expectedError := "directory verification not supported"
|
||||
if !containsString(err.Error(), expectedError) {
|
||||
t.Errorf("Expected error to contain %q, got %q", expectedError, err.Error())
|
||||
t.Error("Expected verification to fail with empty provenance data")
|
||||
}
|
||||
}
|
||||
|
||||
func containsString(s, substr string) bool {
|
||||
return len(s) >= len(substr) && (s == substr || len(s) > len(substr) &&
|
||||
(s[:len(substr)] == substr || s[len(s)-len(substr):] == substr ||
|
||||
strings.Contains(s, substr)))
|
||||
func TestVerifyPluginMalformedData(t *testing.T) {
|
||||
// Test with malformed tarball data - should fail
|
||||
malformedData := []byte("not a tarball")
|
||||
provData := []byte("fake provenance")
|
||||
|
||||
_, err := VerifyPlugin(malformedData, provData, "malformed.tar.gz", testPubFile)
|
||||
if err == nil {
|
||||
t.Error("Expected malformed data verification to fail, but it succeeded")
|
||||
}
|
||||
}
|
||||
|
|
|
@ -21,6 +21,7 @@ import (
|
|||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"syscall"
|
||||
|
||||
"github.com/Masterminds/semver/v3"
|
||||
|
@ -156,8 +157,14 @@ func (p *Package) Clearsign(filename string) error {
|
|||
return fmt.Errorf("failed to marshal chart metadata: %w", err)
|
||||
}
|
||||
|
||||
// Read the chart archive file
|
||||
archiveData, err := os.ReadFile(filename)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to read chart archive: %w", err)
|
||||
}
|
||||
|
||||
// Use the generic provenance signing function
|
||||
sig, err := signer.ClearSign(filename, metadataBytes)
|
||||
sig, err := signer.ClearSign(archiveData, filepath.Base(filename), metadataBytes)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
|
@ -142,8 +142,15 @@ func (o *pluginPackageOptions) run(out io.Writer) error {
|
|||
|
||||
// If signing was requested, sign the tarball
|
||||
if o.sign {
|
||||
// Sign the plugin tarball (not the source directory)
|
||||
sig, err := plugin.SignPlugin(tarballPath, signer)
|
||||
// Read the tarball data
|
||||
tarballData, err := os.ReadFile(tarballPath)
|
||||
if err != nil {
|
||||
os.Remove(tarballPath)
|
||||
return fmt.Errorf("failed to read tarball for signing: %w", err)
|
||||
}
|
||||
|
||||
// Sign the plugin tarball data
|
||||
sig, err := plugin.SignPlugin(tarballData, filepath.Base(tarballPath), signer)
|
||||
if err != nil {
|
||||
os.Remove(tarballPath)
|
||||
return fmt.Errorf("failed to sign plugin: %w", err)
|
||||
|
|
|
@ -18,6 +18,8 @@ package cmd
|
|||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
|
@ -65,8 +67,41 @@ func newPluginVerifyCmd(out io.Writer) *cobra.Command {
|
|||
}
|
||||
|
||||
func (o *pluginVerifyOptions) run(out io.Writer) error {
|
||||
// Verify the plugin
|
||||
verification, err := plugin.VerifyPlugin(o.pluginPath, o.keyring)
|
||||
// Verify the plugin path exists
|
||||
fi, err := os.Stat(o.pluginPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Only support tarball verification
|
||||
if fi.IsDir() {
|
||||
return fmt.Errorf("directory verification not supported - only plugin tarballs can be verified")
|
||||
}
|
||||
|
||||
// Verify it's a tarball
|
||||
if !plugin.IsTarball(o.pluginPath) {
|
||||
return fmt.Errorf("plugin file must be a gzipped tarball (.tar.gz or .tgz)")
|
||||
}
|
||||
|
||||
// Look for provenance file
|
||||
provFile := o.pluginPath + ".prov"
|
||||
if _, err := os.Stat(provFile); err != nil {
|
||||
return fmt.Errorf("could not find provenance file %s: %w", provFile, err)
|
||||
}
|
||||
|
||||
// Read the files
|
||||
archiveData, err := os.ReadFile(o.pluginPath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to read plugin file: %w", err)
|
||||
}
|
||||
|
||||
provData, err := os.ReadFile(provFile)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to read provenance file: %w", err)
|
||||
}
|
||||
|
||||
// Verify the plugin using data
|
||||
verification, err := plugin.VerifyPlugin(archiveData, provData, filepath.Base(o.pluginPath), o.keyring)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
|
@ -493,7 +493,18 @@ func VerifyChart(path, provfile, keyring string) (*provenance.Verification, erro
|
|||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to load keyring: %w", err)
|
||||
}
|
||||
return sig.Verify(path, provfile)
|
||||
|
||||
// Read archive and provenance files
|
||||
archiveData, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to read chart archive: %w", err)
|
||||
}
|
||||
provData, err := os.ReadFile(provfile)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to read provenance file: %w", err)
|
||||
}
|
||||
|
||||
return sig.Verify(archiveData, provData, filepath.Base(path))
|
||||
}
|
||||
|
||||
// isTar tests whether the given file is a tar file.
|
||||
|
|
|
@ -23,7 +23,6 @@ import (
|
|||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"golang.org/x/crypto/openpgp" //nolint
|
||||
|
@ -194,29 +193,20 @@ func (s *Signatory) DecryptKey(fn PassphraseFetcher) error {
|
|||
return s.Entity.PrivateKey.Decrypt(p)
|
||||
}
|
||||
|
||||
// ClearSign signs a package with the given key and pre-marshalled metadata.
|
||||
// ClearSign signs package data with the given key and pre-marshalled metadata.
|
||||
//
|
||||
// This takes the path to a package archive file, a key, and marshalled metadata bytes.
|
||||
// This allows both charts and plugins to use the same signing infrastructure.
|
||||
//
|
||||
// The Signatory must have a valid Entity.PrivateKey for this to work. If it does
|
||||
// not, an error will be returned.
|
||||
func (s *Signatory) ClearSign(packagePath string, metadataBytes []byte) (string, error) {
|
||||
// This is the core signing method that works with data in memory.
|
||||
// The Signatory must have a valid Entity.PrivateKey for this to work.
|
||||
func (s *Signatory) ClearSign(archiveData []byte, filename string, metadataBytes []byte) (string, error) {
|
||||
if s.Entity == nil {
|
||||
return "", errors.New("private key not found")
|
||||
} else if s.Entity.PrivateKey == nil {
|
||||
return "", errors.New("provided key is not a private key. Try providing a keyring with secret keys")
|
||||
}
|
||||
|
||||
if fi, err := os.Stat(packagePath); err != nil {
|
||||
return "", err
|
||||
} else if fi.IsDir() {
|
||||
return "", errors.New("cannot sign a directory")
|
||||
}
|
||||
|
||||
out := bytes.NewBuffer(nil)
|
||||
|
||||
b, err := messageBlock(packagePath, metadataBytes)
|
||||
b, err := messageBlock(archiveData, filename, metadataBytes)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
@ -246,69 +236,47 @@ func (s *Signatory) ClearSign(packagePath string, metadataBytes []byte) (string,
|
|||
return out.String(), nil
|
||||
}
|
||||
|
||||
// Verify checks a signature and verifies that it is legit for a package.
|
||||
func (s *Signatory) Verify(packagePath, sigpath string) (*Verification, error) {
|
||||
// Verify checks a signature and verifies that it is legit for package data.
|
||||
// This is the core verification method that works with data in memory.
|
||||
func (s *Signatory) Verify(archiveData, provData []byte, filename string) (*Verification, error) {
|
||||
ver := &Verification{}
|
||||
for _, fname := range []string{packagePath, sigpath} {
|
||||
if fi, err := os.Stat(fname); err != nil {
|
||||
return ver, err
|
||||
} else if fi.IsDir() {
|
||||
return ver, fmt.Errorf("%s cannot be a directory", fname)
|
||||
}
|
||||
}
|
||||
|
||||
// First verify the signature
|
||||
sig, err := s.decodeSignature(sigpath)
|
||||
if err != nil {
|
||||
return ver, fmt.Errorf("failed to decode signature: %w", err)
|
||||
block, _ := clearsign.Decode(provData)
|
||||
if block == nil {
|
||||
return ver, errors.New("signature block not found")
|
||||
}
|
||||
|
||||
by, err := s.verifySignature(sig)
|
||||
by, err := s.verifySignature(block)
|
||||
if err != nil {
|
||||
return ver, err
|
||||
}
|
||||
ver.SignedBy = by
|
||||
|
||||
// Second, verify the hash of the tarball.
|
||||
sum, err := DigestFile(packagePath)
|
||||
// Second, verify the hash of the data.
|
||||
sum, err := Digest(bytes.NewBuffer(archiveData))
|
||||
if err != nil {
|
||||
return ver, err
|
||||
}
|
||||
sums, err := parseMessageBlock(sig.Plaintext)
|
||||
sums, err := parseMessageBlock(block.Plaintext)
|
||||
if err != nil {
|
||||
return ver, err
|
||||
}
|
||||
|
||||
sum = "sha256:" + sum
|
||||
basename := filepath.Base(packagePath)
|
||||
if sha, ok := sums.Files[basename]; !ok {
|
||||
return ver, fmt.Errorf("provenance does not contain a SHA for a file named %q", basename)
|
||||
if sha, ok := sums.Files[filename]; !ok {
|
||||
return ver, fmt.Errorf("provenance does not contain a SHA for a file named %q", filename)
|
||||
} else if sha != sum {
|
||||
return ver, fmt.Errorf("sha256 sum does not match for %s: %q != %q", basename, sha, sum)
|
||||
return ver, fmt.Errorf("sha256 sum does not match for %s: %q != %q", filename, sha, sum)
|
||||
}
|
||||
ver.FileHash = sum
|
||||
ver.FileName = basename
|
||||
ver.FileName = filename
|
||||
|
||||
// TODO: when image signing is added, verify that here.
|
||||
|
||||
return ver, nil
|
||||
}
|
||||
|
||||
func (s *Signatory) decodeSignature(filename string) (*clearsign.Block, error) {
|
||||
data, err := os.ReadFile(filename)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
block, _ := clearsign.Decode(data)
|
||||
if block == nil {
|
||||
// There was no sig in the file.
|
||||
return nil, errors.New("signature block not found")
|
||||
}
|
||||
|
||||
return block, nil
|
||||
}
|
||||
|
||||
// verifySignature verifies that the given block is validly signed, and returns the signer.
|
||||
func (s *Signatory) verifySignature(block *clearsign.Block) (*openpgp.Entity, error) {
|
||||
return openpgp.CheckDetachedSignature(
|
||||
|
@ -318,18 +286,17 @@ func (s *Signatory) verifySignature(block *clearsign.Block) (*openpgp.Entity, er
|
|||
)
|
||||
}
|
||||
|
||||
// messageBlock creates a message block from a package path and pre-marshalled metadata
|
||||
func messageBlock(packagePath string, metadataBytes []byte) (*bytes.Buffer, error) {
|
||||
// Checksum the archive
|
||||
chash, err := DigestFile(packagePath)
|
||||
// messageBlock creates a message block from archive data and pre-marshalled metadata
|
||||
func messageBlock(archiveData []byte, filename string, metadataBytes []byte) (*bytes.Buffer, error) {
|
||||
// Checksum the archive data
|
||||
chash, err := Digest(bytes.NewBuffer(archiveData))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
base := filepath.Base(packagePath)
|
||||
sums := &SumCollection{
|
||||
Files: map[string]string{
|
||||
base: "sha256:" + chash,
|
||||
filename: "sha256:" + chash,
|
||||
},
|
||||
}
|
||||
|
||||
|
|
|
@ -98,7 +98,13 @@ func loadChartMetadataForSigning(t *testing.T, chartPath string) []byte {
|
|||
func TestMessageBlock(t *testing.T) {
|
||||
metadataBytes := loadChartMetadataForSigning(t, testChartfile)
|
||||
|
||||
out, err := messageBlock(testChartfile, metadataBytes)
|
||||
// Read the chart file data
|
||||
archiveData, err := os.ReadFile(testChartfile)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
out, err := messageBlock(archiveData, filepath.Base(testChartfile), metadataBytes)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
@ -243,7 +249,13 @@ func TestClearSign(t *testing.T) {
|
|||
|
||||
metadataBytes := loadChartMetadataForSigning(t, testChartfile)
|
||||
|
||||
sig, err := signer.ClearSign(testChartfile, metadataBytes)
|
||||
// Read the chart file data
|
||||
archiveData, err := os.ReadFile(testChartfile)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
sig, err := signer.ClearSign(archiveData, filepath.Base(testChartfile), metadataBytes)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
@ -276,7 +288,13 @@ func TestClearSignError(t *testing.T) {
|
|||
|
||||
metadataBytes := loadChartMetadataForSigning(t, testChartfile)
|
||||
|
||||
sig, err := signer.ClearSign(testChartfile, metadataBytes)
|
||||
// Read the chart file data
|
||||
archiveData, err := os.ReadFile(testChartfile)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
sig, err := signer.ClearSign(archiveData, filepath.Base(testChartfile), metadataBytes)
|
||||
if err == nil {
|
||||
t.Fatal("didn't get an error from ClearSign but expected one")
|
||||
}
|
||||
|
@ -286,56 +304,25 @@ func TestClearSignError(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestDecodeSignature(t *testing.T) {
|
||||
// Unlike other tests, this does a round-trip test, ensuring that a signature
|
||||
// generated by the library can also be verified by the library.
|
||||
|
||||
signer, err := NewFromFiles(testKeyfile, testPubfile)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
metadataBytes := loadChartMetadataForSigning(t, testChartfile)
|
||||
|
||||
sig, err := signer.ClearSign(testChartfile, metadataBytes)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
f, err := os.CreateTemp(t.TempDir(), "helm-test-sig-")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
tname := f.Name()
|
||||
defer func() {
|
||||
os.Remove(tname)
|
||||
}()
|
||||
f.WriteString(sig)
|
||||
f.Close()
|
||||
|
||||
sig2, err := signer.decodeSignature(tname)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
by, err := signer.verifySignature(sig2)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if _, ok := by.Identities[testKeyName]; !ok {
|
||||
t.Errorf("Expected identity %q", testKeyName)
|
||||
}
|
||||
}
|
||||
|
||||
func TestVerify(t *testing.T) {
|
||||
signer, err := NewFromFiles(testKeyfile, testPubfile)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if ver, err := signer.Verify(testChartfile, testSigBlock); err != nil {
|
||||
// Read the chart file data
|
||||
archiveData, err := os.ReadFile(testChartfile)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Read the signature file data
|
||||
sigData, err := os.ReadFile(testSigBlock)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if ver, err := signer.Verify(archiveData, sigData, filepath.Base(testChartfile)); err != nil {
|
||||
t.Errorf("Failed to pass verify. Err: %s", err)
|
||||
} else if len(ver.FileHash) == 0 {
|
||||
t.Error("Verification is missing hash.")
|
||||
|
@ -345,7 +332,13 @@ func TestVerify(t *testing.T) {
|
|||
t.Errorf("FileName is unexpectedly %q", ver.FileName)
|
||||
}
|
||||
|
||||
if _, err = signer.Verify(testChartfile, testTamperedSigBlock); err == nil {
|
||||
// Read the tampered signature file data
|
||||
tamperedSigData, err := os.ReadFile(testTamperedSigBlock)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if _, err = signer.Verify(archiveData, tamperedSigData, filepath.Base(testChartfile)); err == nil {
|
||||
t.Errorf("Expected %s to fail.", testTamperedSigBlock)
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue