From 5ebbd2cdd36530d3f98fcc5b0861560eedfd88ce Mon Sep 17 00:00:00 2001 From: Filip Burlacu Date: Mon, 23 Sep 2019 11:14:30 -0400 Subject: [PATCH] Implements the legacy authcrypt decrypter. Follows on from #139 Part of #36 Closes #294 Signed-off-by: Filip Burlacu --- .../crypto/legacy/authcrypt/authcrypt.go | 6 +- .../crypto/legacy/authcrypt/authcrypt_test.go | 269 +++++++++++++++++- .../crypto/legacy/authcrypt/decrypt.go | 148 +++++++++- .../crypto/legacy/authcrypt/encrypt.go | 4 +- 4 files changed, 412 insertions(+), 15 deletions(-) diff --git a/pkg/didcomm/crypto/legacy/authcrypt/authcrypt.go b/pkg/didcomm/crypto/legacy/authcrypt/authcrypt.go index d38270122d..b369795483 100644 --- a/pkg/didcomm/crypto/legacy/authcrypt/authcrypt.go +++ b/pkg/didcomm/crypto/legacy/authcrypt/authcrypt.go @@ -7,6 +7,7 @@ SPDX-License-Identifier: Apache-2.0 package authcrypt import ( + "crypto/ed25519" "crypto/rand" "errors" "fmt" @@ -14,7 +15,6 @@ import ( "github.com/agl/ed25519/extra25519" "golang.org/x/crypto/blake2b" - "golang.org/x/crypto/ed25519" "golang.org/x/crypto/nacl/box" ) @@ -78,8 +78,8 @@ func isKeyPairValid(kp keyPairEd25519) bool { return true } -// envelope is the full payload envelope for the JSON message -type envelope struct { +// legacyEnvelope is the full payload envelope for the JSON message +type legacyEnvelope struct { Protected string `json:"protected,omitempty"` IV string `json:"iv,omitempty"` CipherText string `json:"ciphertext,omitempty"` diff --git a/pkg/didcomm/crypto/legacy/authcrypt/authcrypt_test.go b/pkg/didcomm/crypto/legacy/authcrypt/authcrypt_test.go index 86258d5346..a3afcee11b 100644 --- a/pkg/didcomm/crypto/legacy/authcrypt/authcrypt_test.go +++ b/pkg/didcomm/crypto/legacy/authcrypt/authcrypt_test.go @@ -257,17 +257,272 @@ func TestEncryptComponents(t *testing.T) { } func TestDecrypt(t *testing.T) { - t.Run("Fail, Decrypt not implemented", func(t *testing.T) { - sender := getB58EdKey("9NKZ9pHL9YVS7BzqJsz3e9uVvk44rJodKfLKbq4hmeUw", - "2VZLugb22G3iovUvGrecKj3VHFUNeCetkApeB4Fn4zkgBqYaMSFTW2nvF395voJ76vHkfnUXH2qvJoJnFydRoQBR") - rec1 := getB58EdKey("DDk4ac2ZA19P8qXjk8XaCY9Fx7WwAmCtELkxeDNqS6Vs", - "33SjJwDk7vKL4tEyVT45XwzebdAVEpRqXJHRrT228pet7hhTFCSdFgnhvrSPX6PfsELM94Wrwwp6JdaFBFReZTBB") + t.Run("Success: encrypt then decrypt, same crypter", func(t *testing.T) { + sender, err := randEdKeyPair(rand.Reader) + require.NoError(t, err) + rec1, err := randEdKeyPair(rand.Reader) + require.NoError(t, err) crypter, err := New(*sender, []*publicEd25519{rec1.pub}) require.NoError(t, err) - _, err = crypter.Decrypt([]byte{}, rec1.priv) - require.Errorf(t, err, "not implemented") + msgIn := []byte("Junky qoph-flags vext crwd zimb.") + + enc, err := crypter.Encrypt(msgIn) + require.NoError(t, err) + msgOut, err := crypter.Decrypt(enc, rec1) + require.NoError(t, err) + + require.ElementsMatch(t, msgIn, msgOut) + }) + + t.Run("Success: encrypt and decrypt, different crypters, including fail recipient who wasn't sent the message", func(t *testing.T) { // nolint: lll + sender, err := randEdKeyPair(rand.Reader) + require.NoError(t, err) + rec1, err := randEdKeyPair(rand.Reader) + require.NoError(t, err) + rec2, err := randEdKeyPair(rand.Reader) + require.NoError(t, err) + rec3, err := randEdKeyPair(rand.Reader) + require.NoError(t, err) + rec4, err := randEdKeyPair(rand.Reader) + require.NoError(t, err) + + sendCrypter, err := New(*sender, []*publicEd25519{rec1.pub, rec2.pub, rec3.pub}) + require.NoError(t, err) + rec2Crypter, err := New(*sender, []*publicEd25519{rec2.pub}) + require.NoError(t, err) + rec4Crypter, err := New(*sender, []*publicEd25519{rec4.pub}) + require.NoError(t, err) + + msgIn := []byte("Junky qoph-flags vext crwd zimb.") + + enc, err := sendCrypter.Encrypt(msgIn) + require.NoError(t, err) + msgOut, err := rec2Crypter.Decrypt(enc, rec2) + require.NoError(t, err) + require.ElementsMatch(t, msgIn, msgOut) + + _, err = rec4Crypter.Decrypt(enc, rec4) + require.Errorf(t, err, "no key accessible") + }) + + t.Run("Test decrypting python envelope", func(t *testing.T) { + env := `{"protected": "eyJlbmMiOiAieGNoYWNoYTIwcG9seTEzMDVfaWV0ZiIsICJ0eXAiOiAiSldNLzEuMCIsICJhbGciOiAiQXV0aGNyeXB0IiwgInJlY2lwaWVudHMiOiBbeyJlbmNyeXB0ZWRfa2V5IjogIkVhTVl4b3RKYjg4Vmt2ZmxNN1htajdFUzdvVVVSOEJSWWZ1akJGS1FGT3Y4Q2o3c0F2RndVWE5QdWVWanZ0SkEiLCAiaGVhZGVyIjogeyJraWQiOiAiRjdtTnRGMmZyTHVSdTJjTUVqWEJuV2RZY1RaQVhOUDlqRWtwclh4aWFaaTEiLCAic2VuZGVyIjogInJna1lWLUlxTWxlQUNkdE1qYXE4YnpwQXBKLXlRbjdWdzRIUnFZODNJVFozNzJkc0Y5RzV6bTVKMGhyNDVuSzBnS2JUYzRRYk5VZ1NreUExUlpZbEl6WHBwanN5eGdZUkU5ek9IbUFDcF9ldWZzejZ4YUxFOVRxN01KVT0iLCAiaXYiOiAiQ04wZWd4TFM2R19oUThDVXBjZkdZWmxzNjFtMm9YUVQifX1dfQ==", "iv": "Y4osZIg1IWaa1kFb", "ciphertext": "m9otQmcqYHOxZh4XfLbdCNouqnuPz7lGtcL5ga_1PZcPZDrhnGWPyLW2rPN2lRTftyYGPPT3tOlu4GFecZIz4zXI9kdz", "tag": "CoV9tCdrFnBbVe2h-pYyhQ=="}` // nolint: lll + + msg := "Yvgu yrf vy jgbuffi tvjc hgsj fhlusfm hsuf tiw fun s kb si kfuh bssnc" + + recKey := getB58EdKey("F7mNtF2frLuRu2cMEjXBnWdYcTZAXNP9jEkprXxiaZi1", + "2nYsWTQ1ZguQ7G2HYfMWjMNqWagBQfaKB9GLbsFk7Z7tKVBEr2arwpVKDwgLUbaxguUzQuf7o67aWKzgtHmKaypM") + + dummySender, err := randEdKeyPair(rand.Reader) + require.NoError(t, err) + + recCrypter, err := New(*dummySender, []*publicEd25519{recKey.pub}) + require.NoError(t, err) + + msgOut, err := recCrypter.Decrypt([]byte(env), recKey) + require.NoError(t, err) + require.ElementsMatch(t, []byte(msg), msgOut) + }) + + t.Run("Test decrypting python envelope with multiple recipients", func(t *testing.T) { + env := `{"protected": "eyJlbmMiOiAieGNoYWNoYTIwcG9seTEzMDVfaWV0ZiIsICJ0eXAiOiAiSldNLzEuMCIsICJhbGciOiAiQXV0aGNyeXB0IiwgInJlY2lwaWVudHMiOiBbeyJlbmNyeXB0ZWRfa2V5IjogImd4X3NySTljSEtNTEJnaktNOTlybUx3alFZUjJxVTdMOXc0QWo3Z1lTbDJvUTRubE5WN2tZSmJ0bVFlaWVueE8iLCAiaGVhZGVyIjogeyJraWQiOiAiQ2ZGUmluZDh0eGYxOHJmVHl1aE1pZ2t4UVBhbVNUb3hVM2prdW5ldjR5dnUiLCAic2VuZGVyIjogImFWRW03ak5Kajg2Zm9NM0VYaXZjYWpOWlFnN3pGUm0wTnk5ZzdZTzFueUpvblI2bmNVaV9EZWZzWVBHa25KcG1ZbFhuRDIzVU5nLXNBN1lWUnh5WW15aFZBSm5XNWZwdjBuNE5jaFdBTjl5S3pIMTd3NjZQLVV2WjVDcz0iLCAiaXYiOiAieVB0NGhHZVpObWFLN0hMMGtoWjhreFJzQjc3c3BOX2UifX0sIHsiZW5jcnlwdGVkX2tleSI6ICJ3a3RrWjY3VDR4R2NjTW1GZnRIRmNEV2FZMVQxRFQ3YURhMHBPeUpqTHU2REU2UGVKMUhuVXlRWXlOZ2VPR3ExIiwgImhlYWRlciI6IHsia2lkIjogIko1c2hTVlo2QW9DWHFxWWROR2tVdjFDTWZRYWVLRnNGRU4zaFdwNVBLVEN3IiwgInNlbmRlciI6ICJWdEQtakZfZFNDbmVxOUtTcVB0SUtHbHdHb0FzVHB0UkhzMTRYaWhNR0U4LUh4SjU5aVhtSnVLellxTjM2b19ZOWxfYmRFT1pRSjN0R2tRX1BqbTJyQ3VqWkRIbjdDS3Fsd3N4QlNVemYweW43aWliaDFQazJ6R0wyb2M9IiwgIml2IjogIm5acW1CbzBfT2QyTHlXejlHclJJMUlhWlRXUk4zbGVBIn19LCB7ImVuY3J5cHRlZF9rZXkiOiAiUlBsQWtTS1NsdFpGeEFJc1VzbWNiUVVMUTJWWHhRT2kzUEIxelhTbGs3TlBtMkZ2TE9zVDdQSEFHQU5Hem5oNiIsICJoZWFkZXIiOiB7ImtpZCI6ICJCS3ZqbUZFYkMyYjF3YkVycUN4R2syYmdxdkc5dUx3UlU5cWdOS3lINXRURiIsICJzZW5kZXIiOiAiTVhvRXl0NlZULXVFQnFzWEM1SWF1VXdZYXFxakxIYTdWWlF0NGRJX3FBaFZHVWhUTi01c004cXB6TnBnQlpUUHJrazFSMlBnbjlraU4waEpTUXk1T0FmOGdkSE43YXRTVDhUWEtMSHJNdm4wcDcyNUNUd3pZVnZFVnlNPSIsICJpdiI6ICJPb2FTVWgycVdOVk5qWVV6ZnZTNTdCQ1RnY3ZQYVhMeCJ9fSwgeyJlbmNyeXB0ZWRfa2V5IjogImY1cXV2amt1c2l6TmtRcm9HMk51akFsa0NzbllleUF1R1pMWDZmXy1DeG4taUNENjI2akp0aEk4OFBSei1TWWUiLCAiaGVhZGVyIjogeyJraWQiOiAiRWZ3cFR3aFVSU0QzY3lxanNWYlNWU0VMeU4yN250Tlk4V3dhZHNnVUNEOW0iLCAic2VuZGVyIjogImlOMDJNRzllSEpZZmQ3V3pGd1VFeWJBNmFWeU1Ma1JHcXVhYlJGQnJobFU3Q29EMzFHdW5yTWhEWTZETGFJV0FoX2dPMVRLMWtpMzYtTzQ4TlEyZGdOLU1RdS0wZTV5V2dQS1dzV1MtQ2xPbllEQ0RpVkc1VHBJS2dpVT0iLCAiaXYiOiAiZUg0cDZOX0dGNnpzU2trQk5nY0dWN3RRQkxfRl93MS0ifX0sIHsiZW5jcnlwdGVkX2tleSI6ICJqa3FnbHlmUlNWSXZqVnpkZ04wSGN4SGVzMTBoTjE3ckJLejZhcUtlczR3UTRLWGNGYjNpa3pNSmFSWHAwblVSIiwgImhlYWRlciI6IHsia2lkIjogIkFROW5IdExubXVHODFweTY0WUc1Z2VGMnZkNWhRQ0tIaTVNcnFRMUxZQ1hFIiwgInNlbmRlciI6ICJpSXJFOVUyOUVUbTRWa045aFdvYy1UN0dGYjVrdHB4SGtGeWp6d3BLcDJ5MWh2WWQ0NDF0SzdFUXlhTXhHeG9KNklMaWFHNnNpbTF4WS05UHV2Ny03clB4QTFCb3FxMTY0VzJZZU9FRjFwbnBOV2VmYmdTc1dtQUk0QlU9IiwgIml2IjogIm03S2h3THJ1OGtyQ1VXN1BiNTczZWpGblI3Ymlod3lNIn19XX0=", "iv": "1_pOOQhySyaYcVxi", "ciphertext": "CYHrOg1HeNxhUECoRIQRLNAOXwAjagUYf0xLp0Knnj6mEALg8lFbfmoh_oDptJ4El8jVbgDLiBExaEXIxYVnR7DR-hZjxjdbOBQAOAMUYnnvAk0lHJM0KBWlhE0AWrek1JlAfTnq-L6VsCXEqGYHg1uvpBIJicE=", "tag": "l1KfDt-VQIAImCTl7SA2og=="}` // nolint: lll + + msg := "Iiwufh utiweuop fji olioy pio omlim, om kutxrwu gvgbkn kutxr " + + "w srt luhsnehim. Igywenomwe fji omwuie fnomhwuie, fjimwef." + + recKey := getB58EdKey("AQ9nHtLnmuG81py64YG5geF2vd5hQCKHi5MrqQ1LYCXE", + "2YbSVZzSVaim41bWDdsBzamrhXrPFKKEpzXZRmgDuoFJco5VQELRSj1oWFR9aRdaufsdUyw8sozTtZuX8Mzsqboz") + + dummySender, err := randEdKeyPair(rand.Reader) + require.NoError(t, err) + + recCrypter, err := New(*dummySender, []*publicEd25519{recKey.pub}) + require.NoError(t, err) + + msgOut, err := recCrypter.Decrypt([]byte(env), recKey) + require.NoError(t, err) + require.ElementsMatch(t, []byte(msg), msgOut) + }) + + t.Run("Test decrypting python envelope with invalid recipient", func(t *testing.T) { + env := `{"protected": "eyJlbmMiOiAieGNoYWNoYTIwcG9seTEzMDVfaWV0ZiIsICJ0eXAiOiAiSldNLzEuMCIsICJhbGciOiAiQXV0aGNyeXB0IiwgInJlY2lwaWVudHMiOiBbeyJlbmNyeXB0ZWRfa2V5IjogIjdzN0ZTRXR6Sy1vTzdSWmdISklsSTlzX1lVU2xkMUpnRldPeUNhYUdGY1Y0aHBSTWxQbG0wNDBFcUJXRWVwY3oiLCAiaGVhZGVyIjogeyJraWQiOiAiN0RLbk56TWJHRWNYODYxOGp2WWtiNlhQTFR6eXU2YnhSbTh3RnhZb0d3SHEiLCAic2VuZGVyIjogInFLYTRDeXV1OXZOcmJzX1RCLXhQWXI2aFg2cXJZLTM4Vjd4VXdOQjFyd0J1TjVNTUVJYmRERDFvRElhV2o0QUpSYUZDTEVhSzMtakFSZHBsR1UtM2d4TWY2dkpRZWhiZkZhZHNwemdxRE9iWFZDWUJONGxrVXZLZWhvND0iLCAiaXYiOiAiSWFqeVdudFdSMENxS1BYUWJpWWptbWJRWFNNTEp2X1UifX0sIHsiZW5jcnlwdGVkX2tleSI6ICJZa05vVGh2ZUlIcC13NGlrRW1kQU51VHdxTEx1ZjBocVlVbXRJc2c5WlJMd1BKaUZHWVZuTXl1ZktKZWRvcmthIiwgImhlYWRlciI6IHsia2lkIjogIjdDRURlZUpZTnlRUzhyQjdNVHpvUHhWYXFIWm9ZZkQxNUVIVzhaVVN3VnVhIiwgInNlbmRlciI6ICJ3ZEhjc1hDemdTSjhucDRFU0pDcmJ5OWNrNjJaUEFFVjhJRjYwQmotaUhhbXJLRnBKOTJpZVNTaE1JcTdwdTNmQWZQLWo5S3J6ajAwMEV0SXB5cm05SmNrM0QwSnRBcmtYV2VsSzBoUF9ZeDR4Vlc5dW43MWlfdFBXNWM9IiwgIml2IjogIkRlbUlJbHRKaXd5TU1faGhIS29kcTZpQkx4Q1J5Z2Z3In19XX0=", "iv": "BKWHs6z0UHxGddwg", "ciphertext": "YC2eQQPYVjPHj3wIxUXxBj0yXFLuRN5Lc-9WM8hY6TXoekh-ca9-UWbHasikbcxyukTT3e-QiteOilG-6X7e9x4wiQmWn_NFLOLrqoFe669JIbkgvjHYwuQEQkIVfbD-2woSxsMUl9yln5RS-NssI5cEIVH_C1w=", "tag": "M8GPexbguDoZk5L51AvLjA=="}` // nolint: lll + + recKey := getB58EdKey("A3KnccxQu27yWQrSLwA2YFbfoSs4CHo3q6LjvhmpKz9h", + "49Y63zwonNoj2jEhMYE22TDwQCn7RLKMqNeSkSoBBucbAWceJuXXNCACXfpbXD7PHKM13SWaySyDukEakPVn5sWs") + + dummySender, err := randEdKeyPair(rand.Reader) + require.NoError(t, err) + + recCrypter, err := New(*dummySender, []*publicEd25519{recKey.pub}) + require.NoError(t, err) + + _, err = recCrypter.Decrypt([]byte(env), recKey) + require.Errorf(t, err, "no key accessible") + }) +} + +func decryptComponentFailureTest( + t *testing.T, + protectedHeader, + msg string, + sender, recKey *keyPairEd25519, + errString string) { + fullMessage := `{"protected": "` + base64.URLEncoding.EncodeToString([]byte(protectedHeader)) + "\", " + msg + recCrypter, err := New(*sender, []*publicEd25519{recKey.pub}) + require.NoError(t, err) + + _, err = recCrypter.Decrypt([]byte(fullMessage), recKey) + require.EqualError(t, err, errString) +} + +func TestDecryptComponents(t *testing.T) { + recKey := getB58EdKey("Ak528pLhb6DNFrGWY6HjMUjpNV613h2qtAJ47j1FYe8v", + "5pG8rLcp9WqPXQLSyQetPiyTEnLuanjS2TGd7h4DqutY6gNbLD6pnvT3H8nC5K9vEjy1UJdTtwaejf1xqDyhCrzr") + + dummySender, err := randEdKeyPair(rand.Reader) + require.NoError(t, err) + + t.Run("Fail: non-JSON envelope (truncated)", func(t *testing.T) { + msg := `ed": "eyJlbmMiOiAieGNoYWNoYTIwcG9seTEzMDVfaWV0ZiIsICJ0eXAiOiAiSldNLzEuMCIsICJhbGciOiAiQXV0aGNyeXB0IiwgInJlY2lwaWVudHMiOiBbeyJlbmNyeXB0ZWRfa2V5IjogIkRhWkdpbV9XQ3ludFNkemlGZ25RYW5wUWxSX3RWSHpIem5HYlcteWhUWURWZ0d1YzVucjZKNXN2dTdkUWJCZzMiLCAiaGVhZGVyIjogeyJraWQiOiAiQWs1MjhwTGhiNkRORnJHV1k2SGpNVWpwTlY2MTNoMnF0QUo0N2oxRlllOHYiLCAic2VuZGVyIjogIndaNGNDNDJlRE1lTEFwbUp2SkM0SU5idUtJTnpkWlpFQ0dIcFdEZ3NybUJVUlBKTl9iV09rVVYzRTZvT1JONElMQWZfeEV1V2VmUzRiX2dvUnljQ29na1p2VHlTMUhndkJ0eDJZTzFBMnEtYTd0cF9fMDhLeTRxdFNpWT0iLCAiaXYiOiAiQTgxOFdNdmRkUHJaOG1tWXFwMml1dThncW9aWkMySHgifX1dfQ==", "iv": "oDZpVO648Po3UcoW", "ciphertext": "pLrFQ6dND0aB4saHjSklcNTDAvpFPmIvebCis7S6UupzhhPOHwhp6o97_EphsWbwqqHl0HTiT7W9kUqrvd8jcWgx5EATtkx5o3PSyHfsfm9jl0tmKsqu6VG0RML_OokZiFv76ZUZuGMrHKxkCHGytILhlpSwajg=", "tag": "6GigdWnW59aC9Y8jhy76rA=="}` // nolint: lll + + recCrypter, err := New(*dummySender, []*publicEd25519{recKey.pub}) + require.NoError(t, err) + + _, err = recCrypter.Decrypt([]byte(msg), recKey) + require.EqualError(t, err, "invalid character 'e' looking for beginning of value") + }) + + t.Run("Fail: non-base64 protected header", func(t *testing.T) { + msg := `{"protected": "&**^(&^%", "iv": "oDZpVO648Po3UcoW", "ciphertext": "pLrFQ6dND0aB4saHjSklcNTDAvpFPmIvebCis7S6UupzhhPOHwhp6o97_EphsWbwqqHl0HTiT7W9kUqrvd8jcWgx5EATtkx5o3PSyHfsfm9jl0tmKsqu6VG0RML_OokZiFv76ZUZuGMrHKxkCHGytILhlpSwajg=", "tag": "6GigdWnW59aC9Y8jhy76rA=="}` // nolint: lll + + recCrypter, err := New(*dummySender, []*publicEd25519{recKey.pub}) + require.NoError(t, err) + + _, err = recCrypter.Decrypt([]byte(msg), recKey) + require.EqualError(t, err, "illegal base64 data at input byte 0") + }) + + t.Run("Fail: bad 'typ' field", func(t *testing.T) { + decryptComponentFailureTest(t, + `{"enc": "xchacha20poly1305_ietf", "typ": "JSON", "alg": "Authcrypt", "recipients": [{"encrypted_key": "DaZGim_WCyntSdziFgnQanpQlR_tVHzHznGbW-yhTYDVgGuc5nr6J5svu7dQbBg3", "header": {"kid": "Ak528pLhb6DNFrGWY6HjMUjpNV613h2qtAJ47j1FYe8v", "sender": "wZ4cC42eDMeLApmJvJC4INbuKINzdZZECGHpWDgsrmBURPJN_bWOkUV3E6oORN4ILAf_xEuWefS4b_goRycCogkZvTyS1HgvBtx2YO1A2q-a7tp__08Ky4qtSiY=", "iv": "A818WMvddPrZ8mmYqp2iuu8gqoZZC2Hx"}}]}`, // nolint: lll + `"iv": "oDZpVO648Po3UcoW", "ciphertext": "pLrFQ6dND0aB4saHjSklcNTDAvpFPmIvebCis7S6UupzhhPOHwhp6o97_EphsWbwqqHl0HTiT7W9kUqrvd8jcWgx5EATtkx5o3PSyHfsfm9jl0tmKsqu6VG0RML_OokZiFv76ZUZuGMrHKxkCHGytILhlpSwajg=", "tag": "6GigdWnW59aC9Y8jhy76rA=="}`, // nolint: lll + dummySender, recKey, + "message type JSON not supported") + }) + + t.Run("Fail: anoncrypt not supported", func(t *testing.T) { + decryptComponentFailureTest(t, + `{"enc": "xchacha20poly1305_ietf", "typ": "JWM/1.0", "alg": "Anoncrypt", "recipients": [{"encrypted_key": "DaZGim_WCyntSdziFgnQanpQlR_tVHzHznGbW-yhTYDVgGuc5nr6J5svu7dQbBg3", "header": {"kid": "Ak528pLhb6DNFrGWY6HjMUjpNV613h2qtAJ47j1FYe8v", "sender": "wZ4cC42eDMeLApmJvJC4INbuKINzdZZECGHpWDgsrmBURPJN_bWOkUV3E6oORN4ILAf_xEuWefS4b_goRycCogkZvTyS1HgvBtx2YO1A2q-a7tp__08Ky4qtSiY=", "iv": "A818WMvddPrZ8mmYqp2iuu8gqoZZC2Hx"}}]}`, // nolint: lll + `"iv": "oDZpVO648Po3UcoW", "ciphertext": "pLrFQ6dND0aB4saHjSklcNTDAvpFPmIvebCis7S6UupzhhPOHwhp6o97_EphsWbwqqHl0HTiT7W9kUqrvd8jcWgx5EATtkx5o3PSyHfsfm9jl0tmKsqu6VG0RML_OokZiFv76ZUZuGMrHKxkCHGytILhlpSwajg=", "tag": "6GigdWnW59aC9Y8jhy76rA=="}`, //nolint: lll + dummySender, recKey, + "message format Anoncrypt not supported") + }) + + t.Run("Fail: no recipients in header", func(t *testing.T) { + decryptComponentFailureTest(t, + `{"enc": "xchacha20poly1305_ietf", "typ": "JWM/1.0", "alg": "Authcrypt", "recipients": []}`, + `"iv": "oDZpVO648Po3UcoW", "ciphertext": "pLrFQ6dND0aB4saHjSklcNTDAvpFPmIvebCis7S6UupzhhPOHwhp6o97_EphsWbwqqHl0HTiT7W9kUqrvd8jcWgx5EATtkx5o3PSyHfsfm9jl0tmKsqu6VG0RML_OokZiFv76ZUZuGMrHKxkCHGytILhlpSwajg=", "tag": "6GigdWnW59aC9Y8jhy76rA=="}`, // nolint: lll + dummySender, + recKey, + "no key accessible") + }) + + t.Run("Fail: invalid public key", func(t *testing.T) { + rec := getB58EdKey("6ZAQ7QpmR9EqhJdwx1jQsjq6nnpehwVqUbhVxiEiYEV7", // invalid key, won't convert + "5pG8rLcp9WqPXQLSyQetPiyTEnLuanjS2TGd7h4DqutY6gNbLD6pnvT3H8nC5K9vEjy1UJdTtwaejf1xqDyhCrzr") + + decryptComponentFailureTest(t, + `{"enc": "xchacha20poly1305_ietf", "typ": "JWM/1.0", "alg": "Authcrypt", "recipients": [{"encrypted_key": "DaZGim_WCyntSdziFgnQanpQlR_tVHzHznGbW-yhTYDVgGuc5nr6J5svu7dQbBg3", "header": {"kid": "6ZAQ7QpmR9EqhJdwx1jQsjq6nnpehwVqUbhVxiEiYEV7", "sender": "wZ4cC42eDMeLApmJvJC4INbuKINzdZZECGHpWDgsrmBURPJN_bWOkUV3E6oORN4ILAf_xEuWefS4b_goRycCogkZvTyS1HgvBtx2YO1A2q-a7tp__08Ky4qtSiY=", "iv": "A818WMvddPrZ8mmYqp2iuu8gqoZZC2Hx"}}]}`, // nolint: lll + `"iv": "oDZpVO648Po3UcoW", "ciphertext": "pLrFQ6dND0aB4saHjSklcNTDAvpFPmIvebCis7S6UupzhhPOHwhp6o97_EphsWbwqqHl0HTiT7W9kUqrvd8jcWgx5EATtkx5o3PSyHfsfm9jl0tmKsqu6VG0RML_OokZiFv76ZUZuGMrHKxkCHGytILhlpSwajg=", "tag": "6GigdWnW59aC9Y8jhy76rA=="}`, // nolint: lll + dummySender, rec, + "failed to convert public key") + }) + + t.Run("Fail: invalid public key", func(t *testing.T) { + rec := getB58EdKey("57N4aoQKaxUGNeEn3ETnTKgeD1L5Wm3U3Vb8qi3hupLn", // mismatched keypair, won't decrypt + "5pG8rLcp9WqPXQLSyQetPiyTEnLuanjS2TGd7h4DqutY6gNbLD6pnvT3H8nC5K9vEjy1UJdTtwaejf1xqDyhCrzr") + + decryptComponentFailureTest(t, + `{"enc": "xchacha20poly1305_ietf", "typ": "JWM/1.0", "alg": "Authcrypt", "recipients": [{"encrypted_key": "DaZGim_WCyntSdziFgnQanpQlR_tVHzHznGbW-yhTYDVgGuc5nr6J5svu7dQbBg3", "header": {"kid": "57N4aoQKaxUGNeEn3ETnTKgeD1L5Wm3U3Vb8qi3hupLn", "sender": "wZ4cC42eDMeLApmJvJC4INbuKINzdZZECGHpWDgsrmBURPJN_bWOkUV3E6oORN4ILAf_xEuWefS4b_goRycCogkZvTyS1HgvBtx2YO1A2q-a7tp__08Ky4qtSiY=", "iv": "A818WMvddPrZ8mmYqp2iuu8gqoZZC2Hx"}}]}`, // nolint: lll + `"iv": "oDZpVO648Po3UcoW", "ciphertext": "pLrFQ6dND0aB4saHjSklcNTDAvpFPmIvebCis7S6UupzhhPOHwhp6o97_EphsWbwqqHl0HTiT7W9kUqrvd8jcWgx5EATtkx5o3PSyHfsfm9jl0tmKsqu6VG0RML_OokZiFv76ZUZuGMrHKxkCHGytILhlpSwajg=", "tag": "6GigdWnW59aC9Y8jhy76rA=="}`, // nolint: lll + dummySender, rec, + "failed to unpack") + }) + + t.Run("Sender is invalid base64 data", func(t *testing.T) { + decryptComponentFailureTest(t, + `{"enc": "xchacha20poly1305_ietf", "typ": "JWM/1.0", "alg": "Authcrypt", "recipients": [{"encrypted_key": "DaZGim_WCyntSdziFgnQanpQlR_tVHzHznGbW-yhTYDVgGuc5nr6J5svu7dQbBg3", "header": {"kid": "Ak528pLhb6DNFrGWY6HjMUjpNV613h2qtAJ47j1FYe8v", "sender": "*^&", "iv": "A818WMvddPrZ8mmYqp2iuu8gqoZZC2Hx"}}]}`, // nolint: lll + `"iv": "oDZpVO648Po3UcoW", "ciphertext": "pLrFQ6dND0aB4saHjSklcNTDAvpFPmIvebCis7S6UupzhhPOHwhp6o97_EphsWbwqqHl0HTiT7W9kUqrvd8jcWgx5EATtkx5o3PSyHfsfm9jl0tmKsqu6VG0RML_OokZiFv76ZUZuGMrHKxkCHGytILhlpSwajg=", "tag": "6GigdWnW59aC9Y8jhy76rA=="}`, // nolint: lll + dummySender, recKey, + "illegal base64 data at input byte 0") + }) + + t.Run("Sender is invalid public key", func(t *testing.T) { + decryptComponentFailureTest(t, + `{"enc": "xchacha20poly1305_ietf", "typ": "JWM/1.0", "alg": "Authcrypt", "recipients": [{"encrypted_key": "DaZGim_WCyntSdziFgnQanpQlR_tVHzHznGbW-yhTYDVgGuc5nr6J5svu7dQbBg3", "header": {"kid": "Ak528pLhb6DNFrGWY6HjMUjpNV613h2qtAJ47j1FYe8v", "sender": "7ZA_k_bM4FRp6jY_LNzv9pjuOh1NbVlbBA-yTjzsc22HnPKPK8_MKUNU1Rlt0woNUNWLZI4ShBD_th14ULmTjggBI8K4A8efTI4efxv5xTYEemj9uVPvvLKs4Go=", "iv": "A818WMvddPrZ8mmYqp2iuu8gqoZZC2Hx"}}]}`, // nolint: lll + `"iv": "oDZpVO648Po3UcoW", "ciphertext": "pLrFQ6dND0aB4saHjSklcNTDAvpFPmIvebCis7S6UupzhhPOHwhp6o97_EphsWbwqqHl0HTiT7W9kUqrvd8jcWgx5EATtkx5o3PSyHfsfm9jl0tmKsqu6VG0RML_OokZiFv76ZUZuGMrHKxkCHGytILhlpSwajg=", "tag": "6GigdWnW59aC9Y8jhy76rA=="}`, // nolint: lll + dummySender, recKey, + "failed to convert public key") + }) + + t.Run("Message auth fail, protected header has extra whitespace", func(t *testing.T) { + decryptComponentFailureTest(t, + ` {"enc": "xchacha20poly1305_ietf", "typ": "JWM/1.0", "alg": "Authcrypt", "recipients": [{"encrypted_key": "DaZGim_WCyntSdziFgnQanpQlR_tVHzHznGbW-yhTYDVgGuc5nr6J5svu7dQbBg3", "header": {"kid": "Ak528pLhb6DNFrGWY6HjMUjpNV613h2qtAJ47j1FYe8v", "sender": "wZ4cC42eDMeLApmJvJC4INbuKINzdZZECGHpWDgsrmBURPJN_bWOkUV3E6oORN4ILAf_xEuWefS4b_goRycCogkZvTyS1HgvBtx2YO1A2q-a7tp__08Ky4qtSiY=", "iv": "A818WMvddPrZ8mmYqp2iuu8gqoZZC2Hx"}}]}`, // nolint: lll + `"iv": "oDZpVO648Po3UcoW", "ciphertext": "pLrFQ6dND0aB4saHjSklcNTDAvpFPmIvebCis7S6UupzhhPOHwhp6o97_EphsWbwqqHl0HTiT7W9kUqrvd8jcWgx5EATtkx5o3PSyHfsfm9jl0tmKsqu6VG0RML_OokZiFv76ZUZuGMrHKxkCHGytILhlpSwajg=", "tag": "6GigdWnW59aC9Y8jhy76rA=="}`, // nolint: lll + dummySender, recKey, + "chacha20poly1305: message authentication failed") + }) + + t.Run("Nonce is invalid base64 data", func(t *testing.T) { + decryptComponentFailureTest(t, + `{"enc": "xchacha20poly1305_ietf", "typ": "JWM/1.0", "alg": "Authcrypt", "recipients": [{"encrypted_key": "DaZGim_WCyntSdziFgnQanpQlR_tVHzHznGbW-yhTYDVgGuc5nr6J5svu7dQbBg3", "header": {"kid": "Ak528pLhb6DNFrGWY6HjMUjpNV613h2qtAJ47j1FYe8v", "sender": "wZ4cC42eDMeLApmJvJC4INbuKINzdZZECGHpWDgsrmBURPJN_bWOkUV3E6oORN4ILAf_xEuWefS4b_goRycCogkZvTyS1HgvBtx2YO1A2q-a7tp__08Ky4qtSiY=", "iv": "(^_^)"}}]}`, // nolint: lll + `"iv": "oDZpVO648Po3UcoW", "ciphertext": "pLrFQ6dND0aB4saHjSklcNTDAvpFPmIvebCis7S6UupzhhPOHwhp6o97_EphsWbwqqHl0HTiT7W9kUqrvd8jcWgx5EATtkx5o3PSyHfsfm9jl0tmKsqu6VG0RML_OokZiFv76ZUZuGMrHKxkCHGytILhlpSwajg=", "tag": "6GigdWnW59aC9Y8jhy76rA=="}`, // nolint: lll + dummySender, recKey, + "illegal base64 data at input byte 0") + }) + + t.Run("Encrypted CEK is invalid base64 data", func(t *testing.T) { + decryptComponentFailureTest(t, + `{"enc": "xchacha20poly1305_ietf", "typ": "JWM/1.0", "alg": "Authcrypt", "recipients": [{"encrypted_key": "_-", "header": {"kid": "Ak528pLhb6DNFrGWY6HjMUjpNV613h2qtAJ47j1FYe8v", "sender": "wZ4cC42eDMeLApmJvJC4INbuKINzdZZECGHpWDgsrmBURPJN_bWOkUV3E6oORN4ILAf_xEuWefS4b_goRycCogkZvTyS1HgvBtx2YO1A2q-a7tp__08Ky4qtSiY=", "iv": "A818WMvddPrZ8mmYqp2iuu8gqoZZC2Hx"}}]}`, // nolint: lll + `"iv": "oDZpVO648Po3UcoW", "ciphertext": "pLrFQ6dND0aB4saHjSklcNTDAvpFPmIvebCis7S6UupzhhPOHwhp6o97_EphsWbwqqHl0HTiT7W9kUqrvd8jcWgx5EATtkx5o3PSyHfsfm9jl0tmKsqu6VG0RML_OokZiFv76ZUZuGMrHKxkCHGytILhlpSwajg=", "tag": "6GigdWnW59aC9Y8jhy76rA=="}`, // nolint: lll + dummySender, recKey, + "illegal base64 data at input byte 0") + }) + + t.Run("Bad encrypted key cannot be decrypted", func(t *testing.T) { + decryptComponentFailureTest(t, + `{"enc": "xchacha20poly1305_ietf", "typ": "JWM/1.0", "alg": "Authcrypt", "recipients": [{"encrypted_key": "DaZGim_W", "header": {"kid": "Ak528pLhb6DNFrGWY6HjMUjpNV613h2qtAJ47j1FYe8v", "sender": "wZ4cC42eDMeLApmJvJC4INbuKINzdZZECGHpWDgsrmBURPJN_bWOkUV3E6oORN4ILAf_xEuWefS4b_goRycCogkZvTyS1HgvBtx2YO1A2q-a7tp__08Ky4qtSiY=", "iv": "A818WMvddPrZ8mmYqp2iuu8gqoZZC2Hx"}}]}`, // nolint: lll + `"iv": "oDZpVO648Po3UcoW", "ciphertext": "pLrFQ6dND0aB4saHjSklcNTDAvpFPmIvebCis7S6UupzhhPOHwhp6o97_EphsWbwqqHl0HTiT7W9kUqrvd8jcWgx5EATtkx5o3PSyHfsfm9jl0tmKsqu6VG0RML_OokZiFv76ZUZuGMrHKxkCHGytILhlpSwajg=", "tag": "6GigdWnW59aC9Y8jhy76rA=="}`, // nolint: lll + dummySender, recKey, + "failed to decrypt CEK") + }) + + // valid protected header for envelope being used + prot := `{"enc": "xchacha20poly1305_ietf", "typ": "JWM/1.0", "alg": "Authcrypt", "recipients": [{"encrypted_key": "DaZGim_WCyntSdziFgnQanpQlR_tVHzHznGbW-yhTYDVgGuc5nr6J5svu7dQbBg3", "header": {"kid": "Ak528pLhb6DNFrGWY6HjMUjpNV613h2qtAJ47j1FYe8v", "sender": "wZ4cC42eDMeLApmJvJC4INbuKINzdZZECGHpWDgsrmBURPJN_bWOkUV3E6oORN4ILAf_xEuWefS4b_goRycCogkZvTyS1HgvBtx2YO1A2q-a7tp__08Ky4qtSiY=", "iv": "A818WMvddPrZ8mmYqp2iuu8gqoZZC2Hx"}}]}` // nolint: lll + + t.Run("Ciphertext nonce not valid b64 data", func(t *testing.T) { + decryptComponentFailureTest(t, + prot, + `"iv": "!!!!!", "ciphertext": "pLrFQ6dND0aB4saHjSklcNTDAvpFPmIvebCis7S6UupzhhPOHwhp6o97_EphsWbwqqHl0HTiT7W9kUqrvd8jcWgx5EATtkx5o3PSyHfsfm9jl0tmKsqu6VG0RML_OokZiFv76ZUZuGMrHKxkCHGytILhlpSwajg=", "tag": "6GigdWnW59aC9Y8jhy76rA=="}`, // nolint: lll + dummySender, recKey, + "illegal base64 data at input byte 0") + }) + + t.Run("Ciphertext not valid b64 data", func(t *testing.T) { + decryptComponentFailureTest(t, + prot, `"iv": "oDZpVO648Po3UcoW", "ciphertext": "=", "tag": "6GigdWnW59aC9Y8jhy76rA=="}`, + dummySender, recKey, + "illegal base64 data at input byte 0") + }) + + t.Run("Ciphertext tag not valid b64 data", func(t *testing.T) { + decryptComponentFailureTest(t, + prot, + `"iv": "oDZpVO648Po3UcoW", "ciphertext": "pLrFQ6dND0aB4saHjSklcNTDAvpFPmIvebCis7S6UupzhhPOHwhp6o97_EphsWbwqqHl0HTiT7W9kUqrvd8jcWgx5EATtkx5o3PSyHfsfm9jl0tmKsqu6VG0RML_OokZiFv76ZUZuGMrHKxkCHGytILhlpSwajg=", "tag": "123"}`, // nolint: lll + dummySender, recKey, + "illegal base64 data at input byte 0") }) } diff --git a/pkg/didcomm/crypto/legacy/authcrypt/decrypt.go b/pkg/didcomm/crypto/legacy/authcrypt/decrypt.go index 056e37f38b..3c5c0d3274 100644 --- a/pkg/didcomm/crypto/legacy/authcrypt/decrypt.go +++ b/pkg/didcomm/crypto/legacy/authcrypt/decrypt.go @@ -7,16 +7,158 @@ SPDX-License-Identifier: Apache-2.0 package authcrypt import ( + "bytes" + "crypto/ed25519" + "encoding/base64" + "encoding/json" "errors" + "fmt" + "github.com/btcsuite/btcutil/base58" + chacha "golang.org/x/crypto/chacha20poly1305" "golang.org/x/crypto/nacl/box" ) // Decrypt will decode the envelope using the legacy format // Using (X)Chacha20 encryption algorithm and Poly1035 authenticator -func (c *Crypter) Decrypt(envelope []byte, recipientPrivKey *privateEd25519) ([]byte, error) { - // TODO implement legacy auth decrypt https://github.com/hyperledger/aries-framework-go/issues/294 - return nil, errors.New("not implemented") +func (c *Crypter) Decrypt(envelope []byte, recipientKey *keyPairEd25519) ([]byte, error) { + var envelopeData legacyEnvelope + err := json.Unmarshal(envelope, &envelopeData) + if err != nil { + return nil, err + } + + protectedBytes, err := base64.URLEncoding.DecodeString(envelopeData.Protected) + if err != nil { + return nil, err + } + + var protectedData protected + err = json.Unmarshal(protectedBytes, &protectedData) + if err != nil { + return nil, err + } + + if protectedData.Typ != "JWM/1.0" { + return nil, fmt.Errorf("message type %s not supported", protectedData.Typ) + } + + if protectedData.Alg != "Authcrypt" { + // TODO: change this when anoncrypt is introduced + return nil, fmt.Errorf("message format %s not supported", protectedData.Alg) + } + + cek, err := getCEK(protectedData.Recipients, recipientKey) + if err != nil { + return nil, err + } + + return c.decodeCipherText(cek, &envelopeData) +} + +func getCEK(recipients []recipient, recKey *keyPairEd25519) (*[chacha.KeySize]byte, error) { + for _, candidate := range recipients { + header := candidate.Header + pubKey := base58.Decode(header.KID) + + if !bytes.Equal(pubKey, recKey.pub[:]) { + continue + } + + pk, err := publicEd25519toCurve25519(recKey.pub) + if err != nil { + return nil, err + } + sk, err := secretEd25519toCurve25519(recKey.priv) + if err != nil { + return nil, err + } + + sender, err := decodeSender(header.Sender, pk, sk) + if err != nil { + return nil, err + } + + nonceSlice, err := base64.URLEncoding.DecodeString(header.IV) + if err != nil { + return nil, err + } + var nonce [24]byte + copy(nonce[:], nonceSlice) + + encCEK, err := base64.URLEncoding.DecodeString(candidate.EncryptedKey) + if err != nil { + return nil, err + } + + cekSlice, success := box.Open(nil, encCEK, &nonce, (*[CurveKeySize]byte)(sender), (*[CurveKeySize]byte)(sk)) + if !success { + return nil, errors.New("failed to decrypt CEK") + } + + var cek [chacha.KeySize]byte + copy(cek[:], cekSlice) + + return &cek, nil + } + + return nil, errors.New("no key accessible") +} + +func decodeSender(b64Sender string, pk *publicCurve25519, sk *privateCurve25519) (*publicCurve25519, error) { + encSender, err := base64.URLEncoding.DecodeString(b64Sender) + if err != nil { + return nil, err + } + + senderSlice, err := sodiumBoxSealOpen(encSender, pk, sk) + if err != nil { + return nil, err + } + + senderData := base58.Decode(string(senderSlice)) + + var senderEdPub [ed25519.PublicKeySize]byte + copy(senderEdPub[:], senderData) + + sender, err := publicEd25519toCurve25519((*publicEd25519)(&senderEdPub)) + if err != nil { + return nil, err + } + + return sender, nil +} + +// decodeCipherText decodes (from base64) and decrypts the ciphertext using chacha20poly1035 +func (c *Crypter) decodeCipherText(cek *[chacha.KeySize]byte, envelope *legacyEnvelope) ([]byte, error) { + var cipherText, nonce, tag, aad, message []byte + aad = []byte(envelope.Protected) + cipherText, err := base64.URLEncoding.DecodeString(envelope.CipherText) + if err != nil { + return nil, err + } + nonce, err = base64.URLEncoding.DecodeString(envelope.IV) + if err != nil { + return nil, err + } + tag, err = base64.URLEncoding.DecodeString(envelope.Tag) + if err != nil { + return nil, err + } + + chachaCipher, err := chacha.New(cek[:]) + if err != nil { + return nil, err + } + + payload := append(cipherText, tag...) + + message, err = chachaCipher.Open(nil, nonce, payload, aad) + if err != nil { + return nil, err + } + + return message, nil } // Open a box sealed by sodiumBoxSeal diff --git a/pkg/didcomm/crypto/legacy/authcrypt/encrypt.go b/pkg/didcomm/crypto/legacy/authcrypt/encrypt.go index 51dfad32b6..6846ca4cdf 100644 --- a/pkg/didcomm/crypto/legacy/authcrypt/encrypt.go +++ b/pkg/didcomm/crypto/legacy/authcrypt/encrypt.go @@ -71,8 +71,8 @@ func (c *Crypter) Encrypt(payload []byte) ([]byte, error) { } // buildEnvelope builds the Envelope following the legacy format -func (c *Crypter) buildEnvelope(protectedBytes, nonce, cipherText, tag []byte) envelope { - return envelope{ +func (c *Crypter) buildEnvelope(protectedBytes, nonce, cipherText, tag []byte) legacyEnvelope { + return legacyEnvelope{ Protected: base64.URLEncoding.EncodeToString(protectedBytes), IV: base64.URLEncoding.EncodeToString(nonce), CipherText: base64.URLEncoding.EncodeToString(cipherText),