Skip to content
This repository has been archived by the owner on Mar 27, 2024. It is now read-only.

Commit

Permalink
feat: EDV document processor implementation
Browse files Browse the repository at this point in the history
Added a concrete document processor implementation. It can encrypt and decrypt between structured documents and encrypted documents.

Signed-off-by: Derek Trider <[email protected]>
  • Loading branch information
Derek Trider committed Oct 19, 2020
1 parent 440ab57 commit 96acdde
Show file tree
Hide file tree
Showing 4 changed files with 254 additions and 3 deletions.
79 changes: 79 additions & 0 deletions pkg/storage/edv/documentprocessor/documentprocessor.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,91 @@ SPDX-License-Identifier: Apache-2.0
package documentprocessor

import (
"encoding/json"
"fmt"

"github.com/hyperledger/aries-framework-go/pkg/doc/jose"
"github.com/hyperledger/aries-framework-go/pkg/storage/edv"
)

const (
failMarshalStructuredDocument = "failed to marshal structured document into bytes: %w"
failEncryptStructuredDocument = "failed to encrypt structured document into JWE form: %w"
failJWESerialize = "failed to serialize JWE: %w"
failDeserializeJWE = "failed to deserialize JWE: %w"
failDecryptJWE = "failed to decrypt JWE: %w"
failUnmarshalStructuredDocument = "failed to unmarshal structured document: %w"
)

type marshalFunc func(interface{}) ([]byte, error)

// DocumentProcessor represents a type that can encrypt and decrypt between
// Structured Documents and Encrypted Documents.
type DocumentProcessor interface {
Encrypt(*edv.StructuredDocument) (*edv.EncryptedDocument, error)
Decrypt(*edv.EncryptedDocument) (*edv.StructuredDocument, error)
}

// AriesDocumentProcessor uses Aries crypto to encrypt and decrypt between
// Structured Documents and Encrypted Documents.
type AriesDocumentProcessor struct {
jweEncrypter jose.Encrypter
jweDecrypter jose.Decrypter
marshal marshalFunc
}

// New returns a new instance of an AriesDocumentProcessor.
func New(jweEncrypter jose.Encrypter, jweDecrypter jose.Decrypter) *AriesDocumentProcessor {
return &AriesDocumentProcessor{
jweEncrypter: jweEncrypter,
jweDecrypter: jweDecrypter,
marshal: json.Marshal,
}
}

// Encrypt creates a new encrypted document based off of the given structured document.
func (a *AriesDocumentProcessor) Encrypt(structuredDocument *edv.StructuredDocument) (*edv.EncryptedDocument, error) {
structuredDocumentBytes, err := a.marshal(structuredDocument)
if err != nil {
return nil, fmt.Errorf(failMarshalStructuredDocument, err)
}

jwe, err := a.jweEncrypter.Encrypt(structuredDocumentBytes)
if err != nil {
return nil, fmt.Errorf(failEncryptStructuredDocument, err)
}

serializedJWE, err := jwe.FullSerialize(json.Marshal)
if err != nil {
return nil, fmt.Errorf(failJWESerialize, err)
}

encryptedDoc := edv.EncryptedDocument{
ID: structuredDocument.ID,
JWE: []byte(serializedJWE),
}

return &encryptedDoc, nil
}

// Decrypt decrypts the encrypted document into a structured document.
func (a *AriesDocumentProcessor) Decrypt(encryptedDocument *edv.EncryptedDocument) (*edv.StructuredDocument, error) {
encryptedJWE, err := jose.Deserialize(string(encryptedDocument.JWE))
if err != nil {
return nil, fmt.Errorf(failDeserializeJWE, err)
}

structuredDocumentBytes, err := a.jweDecrypter.Decrypt(encryptedJWE)
if err != nil {
return nil, fmt.Errorf(failDecryptJWE, err)
}

var structuredDocument edv.StructuredDocument

err = json.Unmarshal(structuredDocumentBytes, &structuredDocument)
if err != nil {
return nil, fmt.Errorf(failUnmarshalStructuredDocument, err)
}

return &structuredDocument, nil
}
172 changes: 172 additions & 0 deletions pkg/storage/edv/documentprocessor/documentprocessor_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@
package documentprocessor

import (
"bytes"
"encoding/json"
"errors"
"fmt"
"testing"

"github.com/google/tink/go/keyset"
"github.com/stretchr/testify/require"

"github.com/hyperledger/aries-framework-go/pkg/crypto/tinkcrypto/primitive/composite"
"github.com/hyperledger/aries-framework-go/pkg/crypto/tinkcrypto/primitive/composite/ecdhes"
"github.com/hyperledger/aries-framework-go/pkg/crypto/tinkcrypto/primitive/composite/keyio"
"github.com/hyperledger/aries-framework-go/pkg/doc/jose"
"github.com/hyperledger/aries-framework-go/pkg/storage/edv"
)

const (
documentID = "VJYHHJx4C8J9Fsgz7rZqSp"
encryptedDocumentContentPayload = "Gemini"
jweCreatedUsingKeyWeDoNotHave = `{"protected":"eyJhbGciOiJFQ0RILUVTK0EyNTZLVyIsImVuYyI6IkEyNT` +
`ZHQ00iLCJlcGsiOnsidXNlIjoiZW5jIiwia3R5IjoiRUMiLCJjcnYiOiJQLTI1NiIsIngiOiJCNDYzRVYyd0tfYzFF` +
`OFJvMk91MVFmNkJvZGZzcXJmbHJjcXdrMVR5YkZjIiwieSI6Ik5rNzhSRjVwSmUxWTF1T0lPVG4ySXJjWHJnYjVoeE` +
`NKam9RWUotV21KTHMifSwia2lkIjoiIiwidHlwIjoiRURWRW5jcnlwdGVkRG9jdW1lbnQifQ","encrypted_key":` +
`"ty13RppgkBMLYsOfkq4gvdQwgkDfKZeAy33unLLo_1PhfKa4j1SA-A","iv":"O36okwbi1_ZfVzYG","cipherte` +
`xt":"d80J0oVrGnrlEgNEjVU_FMDbB03LVFTuHgMOaqQW7bDJgoYwl_wcGBKnduP7YOas5toNLFL7M6VE7wAA5-IVj` +
`nd5dyLtFtKxBacjNJ0XhhJ6UTSLi3zQG3qIA50fuA","tag":"XgFSOBj96lL3aVP6AhyCmA"}`
)

func TestNew(t *testing.T) {
createDocumentProcessor(t)
}

func TestAriesDocumentProcessor_Encrypt(t *testing.T) {
t.Run("Success", func(t *testing.T) {
documentProcessor := createDocumentProcessor(t)

createEncryptedDocument(t, documentProcessor)
})
t.Run("Fail to marshal structured document", func(t *testing.T) {
documentProcessor := AriesDocumentProcessor{marshal: failingMarshal}
require.NotNil(t, documentProcessor)

encryptedDocument, err := documentProcessor.Encrypt(nil)
require.EqualError(t, err, fmt.Errorf(failMarshalStructuredDocument, errFailingMarshal).Error())
require.Nil(t, encryptedDocument)
})
t.Run("Fail to encrypt structured document", func(t *testing.T) {
documentProcessor := New(&failingEncrypter{}, nil)
require.NotNil(t, documentProcessor)

encryptedDocument, err := documentProcessor.Encrypt(createStructuredDocument())
require.EqualError(t, err, fmt.Errorf(failEncryptStructuredDocument, errFailingEncrypter).Error())
require.Nil(t, encryptedDocument)
})
}

func TestAriesDocumentProcessor_Decrypt(t *testing.T) {
t.Run("Success", func(t *testing.T) {
documentProcessor := createDocumentProcessor(t)

encryptedDocument := createEncryptedDocument(t, documentProcessor)

structuredDocument, err := documentProcessor.Decrypt(encryptedDocument)
require.NoError(t, err)
require.Equal(t, structuredDocument.Content["payload"], encryptedDocumentContentPayload)
})
t.Run("Fail to deserialize encrypted document's JWE", func(t *testing.T) {
documentProcessor := createDocumentProcessor(t)

encryptedDocument := edv.EncryptedDocument{JWE: []byte("Not valid JWE")}

structuredDocument, err := documentProcessor.Decrypt(&encryptedDocument)
require.EqualError(t, err,
fmt.Errorf(failDeserializeJWE, errors.New("invalid compact JWE: it must have five parts")).Error())
require.Nil(t, structuredDocument)
})
t.Run("Fail to decrypt encrypted document - we don't have the key", func(t *testing.T) {
documentProcessor := createDocumentProcessor(t)

encryptedDocumentCreatedUsingKeyWeDoNotHave :=
edv.EncryptedDocument{JWE: []byte(jweCreatedUsingKeyWeDoNotHave)}

structuredDocument, err := documentProcessor.Decrypt(&encryptedDocumentCreatedUsingKeyWeDoNotHave)
require.EqualError(t, err,
fmt.Errorf(failDecryptJWE, errors.New("ecdhes_factory: decryption failed")).Error())
require.Nil(t, structuredDocument)
})
}

func createDocumentProcessor(t *testing.T) DocumentProcessor {
encrypter, decrypter := createEncrypterAndDecrypter(t)

documentProcessor := New(encrypter, decrypter)
require.NotNil(t, documentProcessor)

return documentProcessor
}

func createEncrypterAndDecrypter(t *testing.T) (*jose.JWEEncrypt, *jose.JWEDecrypt) {
keyHandle, err := keyset.NewHandle(ecdhes.ECDHES256KWAES256GCMKeyTemplate())
require.NoError(t, err)

pubKH, err := keyHandle.Public()
require.NoError(t, err)

buf := new(bytes.Buffer)
pubKeyWriter := keyio.NewWriter(buf)

err = pubKH.WriteWithNoSecrets(pubKeyWriter)
require.NoError(t, err)

ecPubKey := new(composite.PublicKey)

err = json.Unmarshal(buf.Bytes(), ecPubKey)
require.NoError(t, err)

encrypter, err := jose.NewJWEEncrypt(jose.A256GCM, "EDVEncryptedDocument", "", nil,
[]*composite.PublicKey{ecPubKey})
require.NoError(t, err)

decrypter := jose.NewJWEDecrypt(nil, keyHandle)

return encrypter, decrypter
}

func createStructuredDocument() *edv.StructuredDocument {
meta := make(map[string]interface{})
meta["created"] = "2020-10-20"

content := make(map[string]interface{})
content["payload"] = encryptedDocumentContentPayload

structuredDocument := edv.StructuredDocument{
ID: documentID,
Meta: meta,
Content: content,
}

return &structuredDocument
}

func createEncryptedDocument(t *testing.T, documentProcessor DocumentProcessor) *edv.EncryptedDocument {
structuredDocument := createStructuredDocument()

encryptedDocument, err := documentProcessor.Encrypt(structuredDocument)
require.NoError(t, err)
require.NotNil(t, encryptedDocument)

return encryptedDocument
}

var errFailingMarshal = errors.New("failingMarshal always fails")

func failingMarshal(interface{}) ([]byte, error) {
return nil, errFailingMarshal
}

type failingEncrypter struct {
}

var errFailingEncrypter = errors.New("failingEncrypter always fails")

func (f *failingEncrypter) EncryptWithAuthData([]byte, []byte) (*jose.JSONWebEncryption, error) {
panic("implement me")
}

func (f *failingEncrypter) Encrypt([]byte) (*jose.JSONWebEncryption, error) {
return nil, errFailingEncrypter
}
2 changes: 1 addition & 1 deletion pkg/storage/edv/formatprovider/formatprovider.go
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ type formatStore struct {

func (s formatStore) Put(k string, v []byte) error {
content := make(map[string]interface{})
content["payload"] = v
content[payloadKey] = v

structuredDoc := edv.StructuredDocument{
ID: k,
Expand Down
4 changes: 2 additions & 2 deletions pkg/storage/edv/models.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ SPDX-License-Identifier: Apache-2.0
package edv

import (
"github.com/hyperledger/aries-framework-go/pkg/doc/jose"
"encoding/json"
)

// StructuredDocument represents a Structured Document
Expand All @@ -24,7 +24,7 @@ type EncryptedDocument struct {
ID string `json:"id"`
Sequence int `json:"sequence"`
IndexedAttributeCollections []IndexedAttributeCollection `json:"indexed,omitempty"`
JWE jose.JSONWebEncryption `json:"jwe"`
JWE json.RawMessage `json:"jwe"`
}

// IndexedAttributeCollection represents a collection of indexed attributes,
Expand Down

0 comments on commit 96acdde

Please sign in to comment.