@@ -2,6 +2,8 @@ package client
22
33import (
44 "bytes"
5+ "crypto/sha256"
6+ "encoding/hex"
57 "encoding/json"
68 "errors"
79 "fmt"
@@ -1270,3 +1272,136 @@ func (s *ClientSuite) TestVerifyDigest(c *C) {
12701272
12711273 c .Assert (client .VerifyDigest (hash , "sha256" , size , digest ), IsNil )
12721274}
1275+
1276+ type StateLessSuite struct {}
1277+
1278+ var _ = Suite (& StateLessSuite {})
1279+
1280+ func (s * StateLessSuite ) TestRejectsMultiSignaturesSameKeyDifferentIDs (c * C ) {
1281+ // In this test Alice and Bob want to create a TUF repo
1282+ // where a root key rotation would require both their signatures.
1283+ // Alice uses an old version of Go-TUF where each key gets assigned several IDs.
1284+ // Bob uses a modern version of Go-TUF that does not produce the same list of IDs for a same key.
1285+ // This test checks that the TUF client
1286+ // will not accept a root rotation
1287+ // signed twice with Alice's key with different key IDs each time.
1288+ // This test was failing with https://github.com/theupdateframework/go-tuf/tree/ac7b5d7bce18cca5a84a28b021bd6372f450b35b
1289+ // because the signature verification code was assuming that the key IDs used in the metadata
1290+ // were the same as the one the TUF library of the client would generate,
1291+ // breaking the security of threshold signatures.
1292+
1293+ // The attack works just the same if Alice is malicious from the beginning
1294+ // and convinces Bob to sign an initial "root.json"
1295+ // with additional key IDs for her only key,
1296+ // but this scenario show that the vulnerability can even impact situations
1297+ // where Alice is not malicious at all,
1298+ // she was simply using an old client and an attacker stole her key.
1299+ // The purpose of threshold signatures in TUF is precisely
1300+ // to make sure that an attacker cannot forge signatures
1301+ // if they did not steal a large enough number of keys.
1302+
1303+ alice , err := keys .GenerateEd25519Key ()
1304+ if err != nil {
1305+ panic (err )
1306+ }
1307+
1308+ root := data .NewRoot ()
1309+ root .Version = 1
1310+ root .Roles ["root" ] = & data.Role {
1311+ KeyIDs : []string {},
1312+ Threshold : 2 , // Note the threshold
1313+ }
1314+
1315+ // reproduces how IDs were computed in
1316+ // https://github.com/theupdateframework/go-tuf/blob/8e84384bebe3/data/types.go#L50
1317+ oldTUFIDs := func (k * data.PublicKey ) []string {
1318+ bytes , _ := cjson .EncodeCanonical (k )
1319+ digest := sha256 .Sum256 (bytes )
1320+ ids := []string {hex .EncodeToString (digest [:])}
1321+
1322+ if k .Scheme != "" || len (k .Algorithms ) != 0 {
1323+ bytes , _ = cjson .EncodeCanonical (& data.PublicKey {
1324+ Type : k .Type ,
1325+ Value : k .Value ,
1326+ })
1327+ digest = sha256 .Sum256 (bytes )
1328+ ids = append (ids , hex .EncodeToString (digest [:]))
1329+ }
1330+
1331+ return ids
1332+ }
1333+
1334+ // Alice adds her key using an old version of go-tuf
1335+ // which will use several IDs
1336+ for _ , keyID := range oldTUFIDs (alice .PublicData ()) {
1337+ root .Keys [keyID ] = alice .PublicData ()
1338+ root .Roles ["root" ].KeyIDs = append (root .Roles ["root" ].KeyIDs , keyID )
1339+ }
1340+
1341+ bob , err := keys .GenerateEd25519Key ()
1342+ if err != nil {
1343+ panic (err )
1344+ }
1345+
1346+ root .AddKey (bob .PublicData ())
1347+ root .Roles ["root" ].KeyIDs = append (
1348+ root .Roles ["root" ].KeyIDs ,
1349+ bob .PublicData ().IDs ()... ,
1350+ )
1351+
1352+ // signer for the other roles, not important
1353+ delegatedSigner , _ := keys .GenerateEd25519Key ()
1354+ root .AddKey (delegatedSigner .PublicData ())
1355+ for _ , role := range []string {"targets" , "snapshot" , "timestamp" } {
1356+ root .Roles [role ] = & data.Role {
1357+ KeyIDs : delegatedSigner .PublicData ().IDs (),
1358+ Threshold : 1 ,
1359+ }
1360+ }
1361+
1362+ signedRoot , err := sign .Marshal (root , alice , bob )
1363+ c .Assert (err , IsNil )
1364+ rootJSON , err := json .Marshal (signedRoot )
1365+ c .Assert (err , IsNil )
1366+
1367+ // producing evil root using only Alice's key
1368+
1369+ evilRoot := root
1370+ evilRoot .Version = 2
1371+
1372+ canonical , err := cjson .EncodeCanonical (evilRoot )
1373+ c .Assert (err , IsNil )
1374+ sig , err := alice .SignMessage (canonical )
1375+ c .Assert (err , IsNil )
1376+ signedEvilRoot := & data.Signed {
1377+ Signed : canonical ,
1378+ Signatures : make ([]data.Signature , 0 ),
1379+ }
1380+ for _ , keyID := range oldTUFIDs (alice .PublicData ()) {
1381+ signedEvilRoot .Signatures = append (signedEvilRoot .Signatures , data.Signature {
1382+ Signature : sig ,
1383+ KeyID : keyID ,
1384+ })
1385+ }
1386+ evilRootJSON , err := json .Marshal (signedEvilRoot )
1387+ c .Assert (err , IsNil )
1388+
1389+ // checking that client does not accept root rotation
1390+ // to evil root
1391+
1392+ localStore := MemoryLocalStore ()
1393+ err = localStore .SetMeta ("root.json" , rootJSON )
1394+ c .Assert (err , IsNil )
1395+
1396+ remoteStore := newFakeRemoteStore ()
1397+ remoteStore .meta ["2.root.json" ] = newFakeFile (evilRootJSON )
1398+
1399+ client := NewClient (localStore , remoteStore )
1400+
1401+ err = client .UpdateRoots ()
1402+ if err != nil {
1403+ c .Assert (err , DeepEquals , verify.ErrRoleThreshold {Expected : 2 , Actual : 1 })
1404+ } else {
1405+ c .Fatalf ("client returned no error when updating with evil root" )
1406+ }
1407+ }
0 commit comments