diff --git a/calypso/api_test.go b/calypso/api_test.go index a57a56bd6..c718e91aa 100644 --- a/calypso/api_test.go +++ b/calypso/api_test.go @@ -2,6 +2,7 @@ package calypso import ( "encoding/binary" + "go.dedis.ch/kyber/v3/util/key" "testing" "time" @@ -137,8 +138,6 @@ func TestClient_Calypso(t *testing.T) { ltsReply, err := calypsoClient.CreateLTS(roster, gDarc.GetBaseID(), []darc.Signer{admin}, []uint64{adminCt}) adminCt++ require.NoError(t, err) - //If no error, assign it - calypsoClient.ltsReply = ltsReply //Create a signer, darc for data point #1 darc1 := darc.NewDarc(darc.InitRules([]darc.Identity{provider1.Identity()}, @@ -166,11 +165,12 @@ func TestClient_Calypso(t *testing.T) { _, err = calypsoClient.SpawnDarc(admin, adminCt, gDarc, *darc2, 10) adminCt++ require.NoError(t, err) + //Create a secret key key1 := []byte("secret key 1") //Create a Write instance - write1 := NewWrite(cothority.Suite, calypsoClient.ltsReply.InstanceID, - darc1.GetBaseID(), calypsoClient.ltsReply.X, key1) + write1 := NewWrite(cothority.Suite, ltsReply.InstanceID, + darc1.GetBaseID(), ltsReply.X, key1) //Write it to calypso wr1, err := calypsoClient.AddWrite(write1, provider1, 1, *darc1, 10) require.NoError(t, err) @@ -188,8 +188,8 @@ func TestClient_Calypso(t *testing.T) { key2 := []byte("secret key 2") //Create a Write instance - write2 := NewWrite(cothority.Suite, calypsoClient.ltsReply.InstanceID, - darc2.GetBaseID(), calypsoClient.ltsReply.X, key2) + write2 := NewWrite(cothority.Suite, ltsReply.InstanceID, + darc2.GetBaseID(), ltsReply.X, key2) wr2, err := calypsoClient.AddWrite(write2, provider2, 1, *darc2, 10) require.NoError(t, err) prWr2, err := calypsoClient.WaitProof(wr2.InstanceID, time.Second, nil) @@ -210,10 +210,131 @@ func TestClient_Calypso(t *testing.T) { // Make sure you can actually decrypt dk1, err := calypsoClient.DecryptKey(&DecryptKey{Read: *prRe1, Write: *prWr1}) require.NoError(t, err) - require.True(t, dk1.X.Equal(calypsoClient.ltsReply.X)) + require.True(t, dk1.X.Equal(ltsReply.X)) keyCopy1, err := dk1.RecoverKey(reader1.Ed25519.Secret) require.NoError(t, err) require.Equal(t, key1, keyCopy1) // use keyCopy to unlock the stuff in writeInstance.Data } + +// Tests the Calypso system with a simple write/read scenario. +// But it does the signing outside of the `Read` and `Write` methods for +// integration of the MPC signing by OneKey. +func TestClient_Calypso_Simple(t *testing.T) { + l := onet.NewTCPTest(cothority.Suite) + _, roster, _ := l.GenTree(3, true) + defer l.CloseAll() + + admin := darc.NewSignerEd25519(nil, nil) + adminCt := uint64(1) + user := darc.NewSignerEd25519(nil, nil) + // Initialise the genesis message and send it to the service. + // The admin has the privilege to spawn darcs + msg, err := byzcoin.DefaultGenesisMsg(byzcoin.CurrentVersion, roster, + []string{"spawn:" + ContractLongTermSecretID}, + admin.Identity()) + + msg.BlockInterval = 500 * time.Millisecond + require.NoError(t, err) + // The darc inside it should be valid. + gDarc := msg.GenesisDarc + require.Nil(t, gDarc.Verify(true)) + //Create Ledger + c, _, err := byzcoin.NewLedger(msg, false) + require.NoError(t, err) + //Create a Calypso Client (Byzcoin + Onet) + calypsoClient := NewClient(c) + + //Create the LTS + for _, who := range roster.List { + err := calypsoClient.Authorize(who, c.ID) + require.NoError(t, err) + } + ltsReply, err := calypsoClient.CreateLTS(roster, gDarc.GetBaseID(), []darc.Signer{admin}, []uint64{adminCt}) + adminCt++ + require.NoError(t, err) + + //Create a signer darc + userDarc := darc.NewDarc(darc.InitRules([]darc.Identity{user.Identity()}, + []darc.Identity{user.Identity()}), []byte("Provider1")) + // user can read and write. + // This can be changed to two different public keys. + err = userDarc.Rules.AddRule(darc.Action("spawn:"+ContractWriteID), + expression.InitOrExpr(user.Identity().String())) + require.NoError(t, err) + err = userDarc.Rules.AddRule(darc.Action("spawn:"+ContractReadID), + expression.InitOrExpr(user.Identity().String())) + require.NoError(t, err) + require.NotNil(t, userDarc) + _, err = calypsoClient.SpawnDarc(admin, adminCt, gDarc, *userDarc, 10) + adminCt++ + require.NoError(t, err) + + data := []byte("Some secret data - or the user's private key") + // Create a Write structure + write1, err := NewWriteData(cothority.Suite, + ltsReply.InstanceID, + userDarc.GetBaseID(), ltsReply.X, data) + require.NoError(t, err) + + // Create a write-instance and send it to Byzcoin - here + // the instruction and the transaction is created manually, + // so that an external signer can sign the hash of the instruction. + wrInst, err := ContractWriteSpawnInstruction(write1, userDarc) + require.NoError(t, err) + wrInst.SignerCounter = []uint64{1} + wrInst.SignerIdentities = []darc.Identity{user.Identity()} + wrTx, err := calypsoClient.bcClient.CreateTransaction(*wrInst) + require.NoError(t, err) + digest := wrTx.Instructions.Hash() + + // This signature can be replaced by an external signature. + signature, err := user.Sign(digest) + require.NoError(t, err) + wrTx.Instructions[0].Signatures = [][]byte{signature} + + // Send the transaction to ByzCoin + _, err = calypsoClient.bcClient.AddTransactionAndWait(wrTx, 10) + require.NoError(t, err) + wrID := wrTx.Instructions[0].DeriveID("") + proofWr, err := calypsoClient.WaitProof(wrID, time.Second, nil) + require.NoError(t, err) + + // Create a read-instance and send it to ByzCoin. + ephemeral := key.NewKeyPair(cothority.Suite) + readInst, err := ContractReadSpawnInstruction(wrID, ephemeral.Public) + require.NoError(t, err) + readInst.SignerCounter = []uint64{2} + readInst.SignerIdentities = []darc.Identity{user.Identity()} + readTx, err := calypsoClient.bcClient.CreateTransaction(*readInst) + require.NoError(t, err) + digest = readTx.Instructions.Hash() + + // This signature can be replaced by an external signature + signature, err = user.Sign(digest) + require.NoError(t, err) + readTx.Instructions[0].Signatures = [][]byte{signature} + readID := readTx.Instructions[0].DeriveID("") + + // Send the transaction to ByzCoin + _, err = calypsoClient.bcClient.AddTransactionAndWait(readTx, 10) + require.NoError(t, err) + proofRd, err := calypsoClient.WaitProof(readID, time.Second, + nil) + require.NoError(t, err) + + // Make sure you can actually decrypt + dk, err := calypsoClient.DecryptKey(&DecryptKey{Read: *proofRd, + Write: *proofWr}) + require.NoError(t, err) + require.True(t, dk.X.Equal(ltsReply.X)) + keyCopy, err := dk.RecoverKey(ephemeral.Private) + require.NoError(t, err) + var wrCopy Write + require.NoError(t, proofWr.VerifyAndDecode(cothority.Suite, ContractWriteID, + &wrCopy)) + dataDecrypt, err := wrCopy.Decrypt(keyCopy) + require.NoError(t, err) + require.Equal(t, data, dataDecrypt) +} diff --git a/calypso/contracts.go b/calypso/contracts.go index ef5ba8df2..293a18db1 100644 --- a/calypso/contracts.go +++ b/calypso/contracts.go @@ -2,6 +2,7 @@ package calypso import ( "fmt" + "go.dedis.ch/kyber/v3" "strings" "go.dedis.ch/cothority/v3" @@ -278,3 +279,48 @@ func (c ContractWrite) VerifyInstruction(rst byzcoin.ReadOnlyStateTrie, inst byz } return inst.VerifyWithOption(rst, ctxHash, nil) } + +// ContractWriteSpawnInstruction returns the spawn instruction for a Write +// contract. +func ContractWriteSpawnInstruction(wr *Write, + d *darc.Darc) (*byzcoin.Instruction, error) { + writeBuf, err := protobuf.Encode(wr) + if err != nil { + return nil, xerrors.Errorf("couldn't encode write: %v", err) + } + return &byzcoin.Instruction{ + InstanceID: byzcoin.NewInstanceID(d.GetBaseID()), + Spawn: &byzcoin.Spawn{ + ContractID: ContractWriteID, + Args: byzcoin.Arguments{ + { + Name: "write", + Value: writeBuf, + }, + }, + }, + }, nil +} + +// ContractReadSpawnInstruction returns the spawn instruction for a Read +// contract. +func ContractReadSpawnInstruction(wrID byzcoin.InstanceID, + xc kyber.Point) (*byzcoin.Instruction, error) { + var readBuf []byte + read := &Read{ + Write: wrID, + Xc: xc, + } + readBuf, err := protobuf.Encode(read) + if err != nil { + return nil, xerrors.Errorf("encoding Read message: %v", err) + } + + return &byzcoin.Instruction{ + InstanceID: wrID, + Spawn: &byzcoin.Spawn{ + ContractID: ContractReadID, + Args: byzcoin.Arguments{{Name: "read", Value: readBuf}}, + }, + }, nil +} diff --git a/calypso/struct.go b/calypso/struct.go index 415a9c026..fb87397d0 100644 --- a/calypso/struct.go +++ b/calypso/struct.go @@ -1,15 +1,21 @@ package calypso import ( + "crypto/aes" + "crypto/cipher" + "crypto/rand" "crypto/sha256" "fmt" - + "go.dedis.ch/cothority/v3" "go.dedis.ch/cothority/v3/byzcoin" "go.dedis.ch/cothority/v3/darc" "go.dedis.ch/kyber/v3" "go.dedis.ch/kyber/v3/suites" + "go.dedis.ch/kyber/v3/util/random" "go.dedis.ch/kyber/v3/xof/keccak" "go.dedis.ch/onet/v3/network" + "golang.org/x/xerrors" + "io" ) func init() { @@ -68,6 +74,20 @@ func NewWrite(suite suites.Suite, ltsid byzcoin.InstanceID, writeDarc darc.ID, X return wr } +// NewWriteData is like NewWrite, +// but it encrypts the data with a random key and adds the data to the Write +// structure. +func NewWriteData(suite suites.Suite, ltsid byzcoin.InstanceID, + writeDarc darc.ID, X kyber.Point, data []byte) (wr *Write, err error) { + key := random.Bits(192, true, random.New()) + wr = NewWrite(suite, ltsid, writeDarc, X, key) + wr.Data, err = AeadSeal(key, data) + if err != nil { + return nil, xerrors.Errorf("couldn't seal data: %v", err) + } + return +} + // CheckProof verifies that the write-request has actually been created with // somebody having access to the secret key. func (wr *Write) CheckProof(suite suite, writeID darc.ID) error { @@ -97,6 +117,60 @@ func (wr *Write) CheckProof(suite suite, writeID darc.ID) error { "%s\n%s", e.String(), wr.E.String()) } +// Decrypt calls AeadOpen to decrypt the data with the given key. +func (wr *Write) Decrypt(key []byte) ([]byte, error) { + return AeadOpen(key, wr.Data) +} + +// This suggested length is from https://godoc.org/crypto/cipher#NewGCM example +const nonceLen = 12 + +// AeadSeal encrypts the given plaintext with the given key. +// It adds a 12-byte nonce to the ciphertext. +func AeadSeal(symKey, data []byte) ([]byte, error) { + block, err := aes.NewCipher(symKey) + if err != nil { + return nil, + xerrors.Errorf("creating aes cipher block instance: %v", err) + } + + // Never use more than 2^32 random nonces with a given key because of the risk of a repeat. + nonce := make([]byte, nonceLen) + _, err = io.ReadFull(rand.Reader, nonce) + if err != nil { + return nil, xerrors.Errorf("reading nonce: %v", err) + } + + aesgcm, err := cipher.NewGCM(block) + if err != nil { + return nil, xerrors.Errorf("creating aesgcm instance: %v", err) + } + encData := aesgcm.Seal(nil, nonce, data, nil) + encData = append(encData, nonce...) + return encData, nil +} + +// AeadOpen decrypts a given ciphertext with the given key. +func AeadOpen(key, ciphertext []byte) ([]byte, error) { + block, err := aes.NewCipher(key) + if err != nil { + return nil, + xerrors.Errorf("creating aes cipher block instance: %v", err) + } + + aesgcm, err := cipher.NewGCM(block) + if err != nil { + return nil, xerrors.Errorf("creating aesgcm instance: %v", err) + } + + if len(ciphertext) < 12 { + return nil, xerrors.New("ciphertext too short") + } + nonce := ciphertext[len(ciphertext)-nonceLen:] + out, err := aesgcm.Open(nil, nonce, ciphertext[0:len(ciphertext)-nonceLen], nil) + return out, cothority.ErrorOrNil(err, "decrypting ciphertext") +} + type newLtsConfig struct { byzcoin.Proof }