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