mirror of https://github.com/helm/helm
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
189 lines
5.8 KiB
189 lines
5.8 KiB
/*
|
|
Copyright The Helm Authors.
|
|
Licensed under the Apache License, Version 2.0 (the "License");
|
|
you may not use this file except in compliance with the License.
|
|
You may obtain a copy of the License at
|
|
|
|
http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
Unless required by applicable law or agreed to in writing, software
|
|
distributed under the License is distributed on an "AS IS" BASIS,
|
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
See the License for the specific language governing permissions and
|
|
limitations under the License.
|
|
*/
|
|
|
|
package provenance
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/binary"
|
|
"os"
|
|
"testing"
|
|
|
|
"github.com/ProtonMail/go-crypto/openpgp"
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
const (
|
|
// testKeybox is a GnuPG keybox (pubring.kbx) containing the helm-test
|
|
// public key. Regenerate with testdata/regen-keyring-formats.sh.
|
|
testKeybox = "testdata/helm-test-key.kbx"
|
|
|
|
// testMixedKeybox is a keybox containing the RSA and Ed25519 test keys.
|
|
testMixedKeybox = "testdata/helm-mixed-keyring.kbx"
|
|
|
|
// testArmoredPubfile is the ASCII-armored export of the helm-test key.
|
|
testArmoredPubfile = "testdata/helm-test-key.asc"
|
|
|
|
// testMultiBlockArmored is two concatenated single-key armored exports
|
|
// (cat key1.asc key2.asc), covering the RSA and Ed25519 test keys.
|
|
testMultiBlockArmored = "testdata/helm-mixed-keyring.asc"
|
|
)
|
|
|
|
func TestIsKeybox(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
file string
|
|
want bool
|
|
}{
|
|
{"keybox", testKeybox, true},
|
|
{"mixed keybox", testMixedKeybox, true},
|
|
{"legacy binary keyring", testPubfile, false},
|
|
{"armored keyring", testArmoredPubfile, false},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
data, err := os.ReadFile(tt.file)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, tt.want, isKeybox(data))
|
|
})
|
|
}
|
|
|
|
t.Run("degenerate inputs", func(t *testing.T) {
|
|
assert.False(t, isKeybox(nil))
|
|
assert.False(t, isKeybox([]byte{}))
|
|
assert.False(t, isKeybox([]byte("KBXf")))
|
|
assert.False(t, isKeybox([]byte("garbage that is longer than twelve bytes")))
|
|
})
|
|
}
|
|
|
|
func TestIsArmored(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
file string
|
|
want bool
|
|
}{
|
|
{"armored keyring", testArmoredPubfile, true},
|
|
{"legacy binary keyring", testPubfile, false},
|
|
{"keybox", testKeybox, false},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
data, err := os.ReadFile(tt.file)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, tt.want, isArmored(data))
|
|
})
|
|
}
|
|
|
|
t.Run("degenerate inputs", func(t *testing.T) {
|
|
assert.False(t, isArmored(nil))
|
|
assert.False(t, isArmored([]byte("not a key")))
|
|
assert.True(t, isArmored([]byte("\n\t -----BEGIN PGP PUBLIC KEY BLOCK-----")))
|
|
})
|
|
}
|
|
|
|
func TestKeyboxPublicKeys(t *testing.T) {
|
|
data, err := os.ReadFile(testKeybox)
|
|
require.NoError(t, err)
|
|
|
|
keys, err := keyboxPublicKeys(data)
|
|
require.NoError(t, err)
|
|
|
|
ring, err := openpgp.ReadKeyRing(bytes.NewReader(keys))
|
|
require.NoError(t, err)
|
|
|
|
require.Len(t, ring, 1)
|
|
_, ok := ring[0].Identities[testKeyName]
|
|
assert.True(t, ok, "expected keybox to contain %q", testKeyName)
|
|
}
|
|
|
|
func TestKeyboxPublicKeysEphemeral(t *testing.T) {
|
|
// GnuPG flags in-progress key material as ephemeral (bit 0x0002 of the
|
|
// blob flags at blob offset 6) and hides it from every normal read; the
|
|
// parser must do the same.
|
|
setFlags := func(t *testing.T, data []byte, blobStart int, flags uint16) []byte {
|
|
t.Helper()
|
|
require.Equal(t, byte(kbxBlobTypeOpenPGP), data[blobStart+4])
|
|
mutated := bytes.Clone(data)
|
|
binary.BigEndian.PutUint16(mutated[blobStart+6:], flags)
|
|
return mutated
|
|
}
|
|
|
|
t.Run("all blobs ephemeral means no keys", func(t *testing.T) {
|
|
valid, err := os.ReadFile(testKeybox)
|
|
require.NoError(t, err)
|
|
|
|
_, err = keyboxPublicKeys(setFlags(t, valid, 32, kbxBlobFlagEphemeral))
|
|
assert.ErrorContains(t, err, "no OpenPGP keys")
|
|
})
|
|
|
|
t.Run("ephemeral blob is skipped, others kept", func(t *testing.T) {
|
|
valid, err := os.ReadFile(testMixedKeybox)
|
|
require.NoError(t, err)
|
|
|
|
// Flag only the first OpenPGP blob (the RSA helm-test key).
|
|
keys, err := keyboxPublicKeys(setFlags(t, valid, 32, kbxBlobFlagEphemeral))
|
|
require.NoError(t, err)
|
|
|
|
ring, err := openpgp.ReadKeyRing(bytes.NewReader(keys))
|
|
require.NoError(t, err)
|
|
|
|
require.Len(t, ring, 1)
|
|
_, ok := ring[0].Identities[testKeyName]
|
|
assert.False(t, ok, "expected the ephemeral-flagged %q blob to be skipped", testKeyName)
|
|
})
|
|
}
|
|
|
|
func TestKeyboxPublicKeysMalformed(t *testing.T) {
|
|
valid, err := os.ReadFile(testKeybox)
|
|
require.NoError(t, err)
|
|
|
|
// The mutations below rely on the fixture layout: a 32-byte header blob
|
|
// followed by an OpenPGP blob.
|
|
const blobStart = 32
|
|
require.Greater(t, len(valid), blobStart+16)
|
|
require.Equal(t, byte(kbxBlobTypeOpenPGP), valid[blobStart+4])
|
|
|
|
mutate := func(offset int, value uint32) []byte {
|
|
data := bytes.Clone(valid)
|
|
binary.BigEndian.PutUint32(data[offset:], value)
|
|
return data
|
|
}
|
|
|
|
tests := []struct {
|
|
name string
|
|
data []byte
|
|
}{
|
|
{"header only, no keys", valid[:blobStart]},
|
|
{"truncated inside blob header", valid[:blobStart+2]},
|
|
{"truncated inside blob body", valid[:blobStart+16]},
|
|
{"zero blob length", mutate(blobStart, 0)},
|
|
{"blob length below minimum", mutate(blobStart, 4)},
|
|
{"blob length past end of data", mutate(blobStart, uint32(len(valid))+1)},
|
|
{"keyblock offset out of range", mutate(blobStart+8, uint32(len(valid)))},
|
|
{"keyblock length out of range", mutate(blobStart+12, uint32(len(valid)))},
|
|
{"keyblock offset overflow", mutate(blobStart+8, ^uint32(0))},
|
|
{"keyblock length overflow", mutate(blobStart+12, ^uint32(0))},
|
|
{"openpgp blob shorter than its header", append(bytes.Clone(valid[:blobStart]), 0, 0, 0, 8, kbxBlobTypeOpenPGP, 1, 0, 0)},
|
|
{"empty input", nil},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
_, err := keyboxPublicKeys(tt.data)
|
|
assert.Error(t, err)
|
|
})
|
|
}
|
|
}
|