Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
293521eeab |
+3
-2
@@ -153,8 +153,9 @@ that key (or one of its subkeys), and `signer` is that key's fingerprint. The
|
||||
reference implementation refuses to load a manifest that fails these checks;
|
||||
`check` and `fetch` given `--require-signature` then compare the required
|
||||
fingerprint with `signer`. It also refuses a manifest whose `signingPubKey`
|
||||
holds a DSA key or subkey, since checking the self-signatures of a DSA key with
|
||||
very large numbers can take hours.
|
||||
holds a DSA key or subkey, or any secret key or subkey, since checking the
|
||||
self-signatures of a DSA key or the numbers of a secret key can take hours when
|
||||
those numbers are very large.
|
||||
|
||||
## Deterministic Serialization
|
||||
|
||||
|
||||
+20
-7
@@ -46,6 +46,7 @@ const (
|
||||
var (
|
||||
errKeyCount = errors.New("must hold exactly one key")
|
||||
errDSAKey = errors.New("must not hold a DSA key")
|
||||
errSecretKey = errors.New("must not hold a secret key")
|
||||
errNoSecretKey = errors.New("signing key file holds no secret key")
|
||||
errNotV4Key = errors.New("signing key must be an OpenPGP version 4 key, " +
|
||||
"the only kind whose fingerprint --require-signature takes")
|
||||
@@ -126,7 +127,7 @@ func readSigningKey(opts *SigningOptions) (*openpgp.Entity, error) {
|
||||
// readSecretKey returns the one key in secretKey, armored or binary,
|
||||
// which must be a version 4 key and include its secret key.
|
||||
func readSecretKey(secretKey []byte) (*openpgp.Entity, error) {
|
||||
key, err := readOneKey(secretKey, "signing key file")
|
||||
key, err := readOneKey(secretKey, "signing key file", false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -144,9 +145,9 @@ func readSecretKey(secretKey []byte) (*openpgp.Entity, error) {
|
||||
}
|
||||
|
||||
// readOneKey returns the key in data, armored or binary, which must hold
|
||||
// exactly one primary key and no DSA key or subkey. what names data in
|
||||
// errors.
|
||||
func readOneKey(data []byte, what string) (*openpgp.Entity, error) {
|
||||
// exactly one primary key and no DSA key or subkey, and when publicOnly no
|
||||
// secret key or subkey either. what names data in errors.
|
||||
func readOneKey(data []byte, what string, publicOnly bool) (*openpgp.Entity, error) {
|
||||
packets, err := dearmor(data)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("read %s: %w", what, err)
|
||||
@@ -161,8 +162,20 @@ func readOneKey(data []byte, what string) (*openpgp.Entity, error) {
|
||||
return nil, fmt.Errorf("%s %w, found %d", what, errKeyCount, keys)
|
||||
}
|
||||
|
||||
// openpgp.ReadKeyRing checks every self-signature, and a DSA key with
|
||||
// very large numbers makes each check take seconds to minutes.
|
||||
// openpgp.ReadKeyRing checks the numbers of every secret key it reads,
|
||||
// and an ElGamal secret subkey with a very large prime makes that take
|
||||
// minutes.
|
||||
secretKeys, err := countPackets(packets, secretKeyPacketTag, secretSubkeyPacketTag)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("read %s: %w", what, err)
|
||||
}
|
||||
|
||||
if publicOnly && secretKeys != 0 {
|
||||
return nil, fmt.Errorf("%s %w", what, errSecretKey)
|
||||
}
|
||||
|
||||
// openpgp.ReadKeyRing also checks every self-signature, and a DSA key
|
||||
// with very large numbers makes each check take seconds to minutes.
|
||||
dsa, err := holdsDSAKey(packets)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("read %s: %w", what, err)
|
||||
@@ -234,7 +247,7 @@ func fingerprint(key *openpgp.Entity) string {
|
||||
// and returns that primary key's fingerprint. signature and pubKey may each
|
||||
// be armored or binary.
|
||||
func verifySignature(data, signature, pubKey []byte) (string, error) {
|
||||
key, err := readOneKey(pubKey, "embedded public key block")
|
||||
key, err := readOneKey(pubKey, "embedded public key block", true)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@ import (
|
||||
|
||||
"github.com/ProtonMail/go-crypto/openpgp"
|
||||
"github.com/ProtonMail/go-crypto/openpgp/armor"
|
||||
"github.com/ProtonMail/go-crypto/openpgp/elgamal"
|
||||
"github.com/ProtonMail/go-crypto/openpgp/packet"
|
||||
"github.com/spf13/afero"
|
||||
"github.com/stretchr/testify/assert"
|
||||
@@ -607,6 +608,54 @@ func TestManifestRefusesDSAKey(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// elGamalSecretSubkeyPacket returns a secret subkey packet holding an
|
||||
// ElGamal key. Its numbers are not a working key: loading must refuse the
|
||||
// packet before it uses them.
|
||||
func elGamalSecretSubkeyPacket(t *testing.T) []byte {
|
||||
t.Helper()
|
||||
|
||||
key := packet.NewElGamalPrivateKey(time.Now(), &elgamal.PrivateKey{
|
||||
P: big.NewInt(23), G: big.NewInt(4), Y: big.NewInt(8), X: big.NewInt(3),
|
||||
})
|
||||
key.IsSubkey = true
|
||||
|
||||
var buf bytes.Buffer
|
||||
|
||||
require.NoError(t, key.Serialize(&buf))
|
||||
|
||||
return buf.Bytes()
|
||||
}
|
||||
|
||||
// TestManifestRefusesSecretKey loads manifests whose embedded public key
|
||||
// block holds a secret key: the key that signed the manifest with its
|
||||
// secret key, or its public key followed by an ElGamal secret subkey.
|
||||
// openpgp.ReadKeyRing checks the numbers of every secret key it reads,
|
||||
// which for an ElGamal key with a very large prime takes minutes. Loading
|
||||
// must refuse each block before that.
|
||||
func TestManifestRefusesSecretKey(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
signer := newTestKey(t, nil)
|
||||
manifest := signedTestManifest(t,
|
||||
&SigningOptions{SecretKey: armoredSecretKeys(t, signer)})
|
||||
|
||||
var signerKey bytes.Buffer
|
||||
|
||||
require.NoError(t, signer.Serialize(&signerKey))
|
||||
|
||||
for _, block := range [][]byte{
|
||||
armoredSecretKeys(t, signer),
|
||||
slices.Concat(signerKey.Bytes(), elGamalSecretSubkeyPacket(t)),
|
||||
} {
|
||||
embedded := rewriteOuter(t, manifest, func(outer *MFFileOuter) {
|
||||
outer.SigningPubKey = block
|
||||
})
|
||||
|
||||
_, err := NewManifestFromReader(bytes.NewReader(embedded))
|
||||
require.ErrorIs(t, err, errSecretKey)
|
||||
}
|
||||
}
|
||||
|
||||
// TestManifestRefusesTwoSignatures loads a manifest whose signature field
|
||||
// holds its good signature twice, not armored. Loading must refuse it.
|
||||
func TestManifestRefusesTwoSignatures(t *testing.T) {
|
||||
|
||||
Reference in New Issue
Block a user