github.com/keybase/client/go@v0.0.0-20240309051027-028f7c731f8b/engine/pgp_kid_reuse_test.go (about)

     1  package engine
     2  
     3  import (
     4  	"crypto"
     5  	"crypto/ecdsa"
     6  	"crypto/elliptic"
     7  	"crypto/rand"
     8  	"testing"
     9  	"time"
    10  
    11  	"github.com/keybase/client/go/libkb"
    12  	"github.com/keybase/client/go/protocol/keybase1"
    13  	"github.com/stretchr/testify/require"
    14  
    15  	"github.com/keybase/go-crypto/curve25519"
    16  	"github.com/keybase/go-crypto/openpgp"
    17  	"github.com/keybase/go-crypto/openpgp/ecdh"
    18  	"github.com/keybase/go-crypto/openpgp/packet"
    19  )
    20  
    21  func genKeyWithMaterial(uid *packet.UserId, currentTime time.Time, signingPriv *ecdsa.PrivateKey,
    22  	encryptPriv *ecdh.PrivateKey) *openpgp.Entity {
    23  
    24  	// We will be manipulating key creation time while keeping the eddsa
    25  	// private key bytes same. This will yield different PGP fingerprint
    26  	// every time (and different full hash), but same Keybase KID.
    27  
    28  	signingPrivKey := packet.NewECDSAPrivateKey(currentTime, signingPriv)
    29  	entity := &openpgp.Entity{
    30  		PrimaryKey: &signingPrivKey.PublicKey,
    31  		PrivateKey: signingPrivKey,
    32  		Identities: make(map[string]*openpgp.Identity),
    33  	}
    34  	isPrimaryID := true
    35  	entity.Identities[uid.Id] = &openpgp.Identity{
    36  		Name:   uid.Name,
    37  		UserId: uid,
    38  		SelfSignature: &packet.Signature{
    39  			CreationTime: currentTime,
    40  			SigType:      packet.SigTypePositiveCert,
    41  			PubKeyAlgo:   packet.PubKeyAlgoECDSA,
    42  			Hash:         crypto.SHA512,
    43  			IsPrimaryId:  &isPrimaryID,
    44  			FlagsValid:   true,
    45  			FlagSign:     true,
    46  			FlagCertify:  true,
    47  			IssuerKeyId:  &entity.PrimaryKey.KeyId,
    48  		},
    49  	}
    50  	encryptPrivKey := packet.NewECDHPrivateKey(currentTime, encryptPriv)
    51  	subkey := openpgp.Subkey{
    52  		PublicKey:  &encryptPrivKey.PublicKey,
    53  		PrivateKey: encryptPrivKey,
    54  		Sig: &packet.Signature{
    55  			CreationTime:              currentTime,
    56  			SigType:                   packet.SigTypeSubkeyBinding,
    57  			PubKeyAlgo:                packet.PubKeyAlgoECDSA,
    58  			Hash:                      crypto.SHA512,
    59  			FlagsValid:                true,
    60  			FlagEncryptStorage:        true,
    61  			FlagEncryptCommunications: true,
    62  			IssuerKeyId:               &entity.PrimaryKey.KeyId,
    63  		},
    64  	}
    65  	subkey.PrivateKey.IsSubkey = true
    66  	subkey.PublicKey.IsSubkey = true
    67  	entity.Subkeys = append(entity.Subkeys, subkey)
    68  	return entity
    69  }
    70  
    71  func TestY2K1178(t *testing.T) {
    72  	// Test PGP KID reuse by using the same key material for primary key.
    73  
    74  	// PGP keys are not uniquely identified by KID. KID is only generated by
    75  	// hashing the primary key public key material. The rest is malleable.
    76  	// Different uids, subkeys, and signatures will yields the same key but
    77  	// also the same PGP fingerprint. However, it is possible to generate new
    78  	// PGP key with the same key material but different creation time. This
    79  	// will yield a new PGP fingerprint but same Keybase KID.
    80  
    81  	// There was an issue where we weren't properly setting `ActivePGPHash`
    82  	// when new PGP key was being delegated after key with the same KID was
    83  	// revoked.
    84  
    85  	// During sigchain parsing, we would go through:
    86  	// 1) Delegate key A with kid=X - new key, ActivePGPHash is set to A.
    87  	// 2) Revoke kid=X. keys[X] is set to Revoked, and ActivePGPHash to "".
    88  	// 3) Delegate key B with kid=X - there already is X, set status to
    89  	//    Uncancelled (forgot about ActivePGPHash here).
    90  
    91  	// This resulted in sigchain not being replayable because we were taking
    92  	// PGP key A to verify reverse sig of PGP key B in link 3.
    93  
    94  	signingPriv, err := ecdsa.GenerateKey(elliptic.P521(), rand.Reader)
    95  	require.NoError(t, err)
    96  	// Encryption key material doesn't matter, we can keep it the same or make
    97  	// a new one for each generated key. KID is only based on primary key
    98  	// material.
    99  	encPriv, err := ecdh.GenerateKey(curve25519.Cv25519(), rand.Reader)
   100  	require.NoError(t, err)
   101  
   102  	uid := packet.NewUserId("Keybase PGP Test", "Test Only Do Not Use", "alice@example.com")
   103  
   104  	encode := func(entity *openpgp.Entity) []byte {
   105  		buf, err := encodeArmoredPrivatePGP(entity)
   106  		require.NoError(t, err)
   107  		return buf.Bytes()
   108  	}
   109  
   110  	currentTime := time.Now()
   111  
   112  	entity1 := genKeyWithMaterial(uid, currentTime, signingPriv, encPriv)
   113  	privKey1 := encode(entity1)
   114  
   115  	currentTime2 := currentTime.Add(-24 * time.Hour)
   116  	entity2 := genKeyWithMaterial(uid, currentTime2, signingPriv, encPriv)
   117  	privKey2 := encode(entity2)
   118  
   119  	tc := SetupEngineTest(t, "pgp")
   120  	defer tc.Cleanup()
   121  
   122  	user := CreateAndSignupFakeUser(tc, "pgp")
   123  	secui := &libkb.TestSecretUI{Passphrase: user.Passphrase}
   124  	uis := libkb.UIs{LogUI: tc.G.UI.GetLogUI(), SecretUI: secui}
   125  
   126  	mctx := NewMetaContextForTest(tc).WithUIs(uis)
   127  
   128  	var kid keybase1.KID
   129  
   130  	{
   131  		// Add first PGP key.
   132  		eng, err := NewPGPKeyImportEngineFromBytes(tc.G, privKey1, false)
   133  		require.NoError(t, err)
   134  		err = RunEngine2(mctx, eng)
   135  		require.NoError(t, err)
   136  		kid = eng.bundle.GetKID()
   137  	}
   138  
   139  	{
   140  		// Revoke that key.
   141  		eng := NewRevokeKeyEngine(tc.G, kid)
   142  		err := RunEngine2(mctx, eng)
   143  		require.NoError(t, err)
   144  	}
   145  
   146  	var delegate2Err error
   147  	{
   148  		// Add second key. It should have the same KID as the first one.
   149  		eng, err := NewPGPKeyImportEngineFromBytes(tc.G, privKey2, false)
   150  		require.NoError(t, err)
   151  		// Do not care about an error from this engine immediately, keep going.
   152  		delegate2Err = RunEngine2(mctx, eng)
   153  		kid2 := eng.bundle.GetKID()
   154  		require.Equal(t, kid, kid2)
   155  	}
   156  
   157  	{
   158  		// Try to identify that user
   159  		tc := SetupEngineTest(t, "pgp")
   160  		defer tc.Cleanup()
   161  
   162  		idUI := &FakeIdentifyUI{}
   163  		arg := keybase1.Identify2Arg{
   164  			UserAssertion:    user.Username,
   165  			UseDelegateUI:    false,
   166  			CanSuppressUI:    true,
   167  			IdentifyBehavior: keybase1.TLFIdentifyBehavior_CLI,
   168  		}
   169  
   170  		uis := libkb.UIs{
   171  			LogUI:      tc.G.UI.GetLogUI(),
   172  			IdentifyUI: idUI,
   173  		}
   174  		eng := NewResolveThenIdentify2(tc.G, &arg)
   175  		mctx := NewMetaContextForTest(tc).WithUIs(uis)
   176  		err = RunEngine2(mctx, eng)
   177  		require.NoError(t, err)
   178  	}
   179  
   180  	// Check PGP import engine error. When this bug was first reported, that
   181  	// engine was erroring out but the key was still being added.
   182  	require.NoError(t, delegate2Err)
   183  }