Compare --require-signature with the key that signed (closes #167)
check / check (push) Waiting to run
check / check (push) Waiting to run
check and fetch --require-signature compared the required fingerprint with the first key in the manifest's embedded public key block, while gpg accepted a good signature by any key in that block. Loading a signed manifest now refuses one whose embedded block holds more than one primary key, counted as gpg reads the block, or whose signer field is not the primary key fingerprint gpg reports for the signature. --require-signature compares with the signer field, which loading has checked. Signing names and embeds the key gpg reports it signed with, so a key ID matching several keys still writes a manifest that loads. docs/FORMAT.md states what a verifier checks. Model: opus-5-5
This commit was merged in pull request #171.
This commit is contained in:
+39
-13
@@ -9,12 +9,14 @@ import (
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"slices"
|
||||
"testing"
|
||||
|
||||
"github.com/spf13/afero"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
urfcli "github.com/urfave/cli/v3"
|
||||
"google.golang.org/protobuf/proto"
|
||||
"sneak.berlin/go/mfer/mfer"
|
||||
)
|
||||
|
||||
@@ -86,8 +88,7 @@ func TestVerifyRequiredSignerMessages(t *testing.T) {
|
||||
t.Run("invalid fingerprint length", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
err := verifyRequiredSigner(context.Background(),
|
||||
unsignedChecker(t), "12345678")
|
||||
err := verifyRequiredSigner(unsignedChecker(t), "12345678")
|
||||
require.ErrorIs(t, err, errInvalidFingerprint)
|
||||
assert.EqualError(t, err,
|
||||
"invalid fingerprint: must be exactly 40 hex characters, got 8")
|
||||
@@ -96,8 +97,7 @@ func TestVerifyRequiredSignerMessages(t *testing.T) {
|
||||
t.Run("manifest not signed", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
err := verifyRequiredSigner(context.Background(),
|
||||
unsignedChecker(t), msgFpA)
|
||||
err := verifyRequiredSigner(unsignedChecker(t), msgFpA)
|
||||
require.ErrorIs(t, err, errManifestNotSigned)
|
||||
assert.EqualError(t, err,
|
||||
"manifest is not signed, but signature from "+msgFpA+" is required")
|
||||
@@ -105,23 +105,21 @@ func TestVerifyRequiredSignerMessages(t *testing.T) {
|
||||
}
|
||||
|
||||
// TestSignerMismatchMessage drives verifyRequiredSigner against a real signed
|
||||
// manifest. The embedded fingerprint is whatever the generated key produced,
|
||||
// so it is read back from the checker and substituted into the expected
|
||||
// string; the required signer is a fixed value that cannot match it. Requires
|
||||
// gpg and is skipped where it is absent, as the other signing tests are.
|
||||
// manifest. The signing key's fingerprint is whatever the generated key
|
||||
// produced, so it is read back from the checker and substituted into the
|
||||
// expected string; the required signer is a fixed value that cannot match
|
||||
// it. Requires gpg and is skipped where it is absent, as the other signing
|
||||
// tests are.
|
||||
//
|
||||
//nolint:paralleltest // signedManifest calls t.Setenv, which bars t.Parallel
|
||||
func TestSignerMismatchMessage(t *testing.T) {
|
||||
chk := signedChecker(t,
|
||||
signedManifest(t, map[string][]byte{"f.txt": []byte("signed file")}))
|
||||
|
||||
embeddedFP, err := chk.ExtractEmbeddedSigningKeyFP(context.Background())
|
||||
require.NoError(t, err)
|
||||
|
||||
err = verifyRequiredSigner(context.Background(), chk, msgFpB)
|
||||
err := verifyRequiredSigner(chk, msgFpB)
|
||||
require.ErrorIs(t, err, errSignerMismatch)
|
||||
assert.EqualError(t, err,
|
||||
"embedded signing key fingerprint "+embeddedFP+
|
||||
"embedded signing key fingerprint "+string(chk.Signer())+
|
||||
" does not match required "+msgFpB)
|
||||
}
|
||||
|
||||
@@ -191,6 +189,34 @@ func signedChecker(t *testing.T, manifest []byte) *mfer.Checker {
|
||||
return chk
|
||||
}
|
||||
|
||||
// manifestSignedByAnotherKey returns a manifest of files and the
|
||||
// fingerprint of a throwaway key, the required key, that did not sign it.
|
||||
// The manifest is signed by a second throwaway key; its embedded public key
|
||||
// block holds the required key followed by the second key, and its signer
|
||||
// field names the required key.
|
||||
func manifestSignedByAnotherKey(
|
||||
t *testing.T, files map[string][]byte,
|
||||
) ([]byte, string) {
|
||||
t.Helper()
|
||||
|
||||
required := new(mfer.MFFileOuter)
|
||||
require.NoError(t, proto.Unmarshal(
|
||||
signedManifest(t, files)[len(mfer.MAGIC):], required))
|
||||
|
||||
outer := new(mfer.MFFileOuter)
|
||||
require.NoError(t, proto.Unmarshal(
|
||||
signedManifest(t, files)[len(mfer.MAGIC):], outer))
|
||||
|
||||
outer.SigningPubKey = slices.Concat(
|
||||
required.GetSigningPubKey(), outer.GetSigningPubKey())
|
||||
outer.Signer = required.GetSigner()
|
||||
|
||||
data, err := proto.Marshal(outer)
|
||||
require.NoError(t, err)
|
||||
|
||||
return append([]byte(mfer.MAGIC), data...), string(required.GetSigner())
|
||||
}
|
||||
|
||||
func TestPathDoesNotExistMessage(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
|
||||
Reference in New Issue
Block a user