forked from hyperledger-archives/aries-framework-go
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathencryptedformatter.go
314 lines (251 loc) · 10.9 KB
/
encryptedformatter.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
/*
Copyright SecureKey Technologies Inc. All Rights Reserved.
SPDX-License-Identifier: Apache-2.0
*/
package edv
import (
"crypto/rand"
"encoding/base64"
"encoding/json"
"errors"
"fmt"
"github.com/btcsuite/btcutil/base58"
"github.com/hyperledger/aries-framework-go/pkg/doc/jose"
spi "github.com/hyperledger/aries-framework-go/spi/storage"
)
// EncryptedFormatterOption allows for configuration of an EncryptedFormatter.
type EncryptedFormatterOption func(opts *EncryptedFormatter)
// WithDeterministicDocumentIDs indicates whether the document IDs produced by this formatter can be
// deterministically derived (using an HMAC function) from the unformatted keys. Having deterministic document IDs
// allows the EDV REST storage provider (and the more general formatted storage provider wrapper in the
// storageutil module) to operate faster. Per the Confidential Storage specification, document IDs are supposed
// to be randomly generated. Other than the randomness aspect, the document IDs produced by this formatter with
// this optimization enabled are still in the correct format: Base58-encoded 128-bit values. This means that they
// should still be valid in any EDV server, since it's impossible for any EDV server to determine whether our IDs
// are random anyway.
func WithDeterministicDocumentIDs() EncryptedFormatterOption {
return func(encryptedFormatter *EncryptedFormatter) {
encryptedFormatter.useDeterministicDocumentIDs = true
}
}
// EncryptedFormatter formats data for use with an Encrypted Data Vault.
type EncryptedFormatter struct {
jweEncrypter jose.Encrypter
jweDecrypter jose.Decrypter
macCrypto *MACCrypto
useDeterministicDocumentIDs bool
}
// NewEncryptedFormatter returns a new instance of an EncryptedFormatter.
func NewEncryptedFormatter(jweEncrypter jose.Encrypter, jweDecrypter jose.Decrypter, macCrypto *MACCrypto,
options ...EncryptedFormatterOption) *EncryptedFormatter {
encryptedFormatter := &EncryptedFormatter{
jweEncrypter: jweEncrypter,
jweDecrypter: jweDecrypter,
macCrypto: macCrypto,
}
for _, option := range options {
option(encryptedFormatter)
}
return encryptedFormatter
}
// Format returns formatted versions of key, value and tags in the following ways:
// For the formatted key (string): If this EncryptedFormatter was initialized with the WithDeterministicDocumentIDs
// option, then the formatted key (document ID) will be generated in a deterministic way that allows it to be
// derived from the unformatted key. Otherwise, the document ID is generated in a random manner.
// For the formatted value ([]byte): This will be a marshalled EDV encrypted document based on the
// unformatted key, value and tags.
// For the formatted tags ([]spi.Tag): The tag names and values are converted to the same format that EDV encrypted
// indexes use.
// and tags turns key into an EDV-compatible document ID, turns tag names and values into the format needed for
// EDV encrypted indexes, and turns key + value + tags into an encrypted document, which is then returned as the
// formatted value from this function.
func (e *EncryptedFormatter) Format(key string, value []byte, tags ...spi.Tag) (string, []byte, []spi.Tag, error) {
return e.format("", key, value, tags...)
}
// Deformat takes formattedValue (which is expected to be a marshalled encrypted document produced by the Format
// function above), and returns the unformatted key, value and tags which are all contained in formattedValue.
// The unformatted key and tags must come from the encrypted document (formatted value) since they cannot be
// cannot be derived from the formatted key and tags, respectively.
func (e *EncryptedFormatter) Deformat(_ string, formattedValue []byte, _ ...spi.Tag) (string, []byte,
[]spi.Tag, error) {
if formattedValue == nil {
return "", nil, nil, errors.New("EDV encrypted formatter requires the formatted value " +
"in order to return the deformatted key and tags")
}
structuredDocument, err := e.getStructuredDocFromEncryptedDoc(formattedValue)
if err != nil {
return "", nil, nil, fmt.Errorf("failed to get structured document from encrypted document bytes: %w", err)
}
return structuredDocument.Content.UnformattedKey, structuredDocument.Content.UnformattedValue,
structuredDocument.Content.UnformattedTags, nil
}
// UsesDeterministicKeyFormatting indicates whether this encrypted formatter will produce deterministic or random
// document IDs. See the WithDeterministicDocumentIDs option near the top of this file for more information.
func (e *EncryptedFormatter) UsesDeterministicKeyFormatting() bool {
return e.useDeterministicDocumentIDs
}
func (e *EncryptedFormatter) format(keyAndTagPrefix, key string, value []byte, tags ...spi.Tag) (string, []byte,
[]spi.Tag, error) {
var documentID string
var err error
if e.useDeterministicDocumentIDs {
documentID, err = e.generateDeterministicDocumentID(keyAndTagPrefix, key)
if err != nil {
return "", nil, nil, fmt.Errorf("failed to format key into an encrypted document ID: %w", err)
}
} else {
documentID, err = generateRandomDocumentID()
if err != nil {
return "", nil, nil, fmt.Errorf("failed to generate EDV-compatible ID: %w", err)
}
}
formattedTags, err := e.formatTags(keyAndTagPrefix, tags)
if err != nil {
return "", nil, nil, fmt.Errorf("failed to format tags: %w", err)
}
var formattedValue []byte
if documentID != "" {
// Since the formatted tag are hashes and can't be reversed, the only way we can retrieve
// the unformatted tags later is to embed them in the stored value.
formattedValue, err = e.formatValue(key, documentID, value, tags, formattedTags)
if err != nil {
return "", nil, nil, fmt.Errorf("failed to format value: %w", err)
}
}
return documentID, formattedValue, formattedTags, nil
}
func (e *EncryptedFormatter) getStructuredDocFromEncryptedDoc(
encryptedDocBytes []byte) (structuredDocument, error) {
var encryptedDocument encryptedDocument
err := json.Unmarshal(encryptedDocBytes, &encryptedDocument)
if err != nil {
return structuredDocument{},
fmt.Errorf("failed to unmarshal value into an encrypted document: %w", err)
}
encryptedJWE, err := jose.Deserialize(string(encryptedDocument.JWE))
if err != nil {
return structuredDocument{}, fmt.Errorf("failed to deserialize JWE: %w", err)
}
structuredDocumentBytes, err := e.jweDecrypter.Decrypt(encryptedJWE)
if err != nil {
return structuredDocument{}, fmt.Errorf("failed to decrypt JWE: %w", err)
}
var structuredDoc structuredDocument
err = json.Unmarshal(structuredDocumentBytes, &structuredDoc)
if err != nil {
return structuredDocument{}, fmt.Errorf("failed to unmarshal structured document: %w", err)
}
return structuredDoc, nil
}
// Generates an encrypted document ID based off of key.
func (e *EncryptedFormatter) generateDeterministicDocumentID(prefix, key string) (string, error) {
if key == "" {
return "", nil
}
keyHash, err := e.macCrypto.ComputeMAC([]byte(prefix + key))
if err != nil {
return "", fmt.Errorf(`failed to compute MAC based on key "%s": %w`, key, err)
}
return base58.Encode(keyHash[0:16]), nil
}
func generateRandomDocumentID() (string, error) {
randomBytes := make([]byte, 16)
_, err := rand.Read(randomBytes)
if err != nil {
return "", fmt.Errorf("failed to generate random bytes: %w", err)
}
return base58.Encode(randomBytes), nil
}
func (e *EncryptedFormatter) formatTags(tagNamePrefix string, tags []spi.Tag) ([]spi.Tag, error) {
formattedTags := make([]spi.Tag, len(tags))
for i, tag := range tags {
formattedTag, err := e.formatTag(tagNamePrefix, tag)
if err != nil {
return nil, fmt.Errorf("failed to format tag: %w", err)
}
formattedTags[i] = formattedTag
}
return formattedTags, nil
}
func (e *EncryptedFormatter) formatTag(tagNamePrefix string, tag spi.Tag) (spi.Tag, error) {
tagNameMAC, err := e.macCrypto.ComputeMAC([]byte(tagNamePrefix + tag.Name))
if err != nil {
return spi.Tag{}, fmt.Errorf(`failed to compute MAC for tag name "%s": %w`, tag.Name, err)
}
formattedTagName := base64.URLEncoding.EncodeToString(tagNameMAC)
var formattedTagValue string
if tag.Value != "" {
tagValueMAC, err := e.macCrypto.ComputeMAC([]byte(tag.Value))
if err != nil {
return spi.Tag{}, fmt.Errorf(`failed to compute MAC for tag value "%s": %w`, tag.Value, err)
}
formattedTagValue = base64.URLEncoding.EncodeToString(tagValueMAC)
}
return spi.Tag{
Name: formattedTagName,
Value: formattedTagValue,
}, nil
}
func (e *EncryptedFormatter) formatValue(key, documentID string, value []byte,
tags, formattedTags []spi.Tag) ([]byte, error) {
var formattedValue []byte
if value != nil {
// Since the formatted key and tags are hashes and can't be reversed, the only way we can retrieve the
// unformatted key and tags later is to embed them in the structured document.
structuredDoc := createStructuredDocument(key, value, tags)
structuredDocumentBytes, err := json.Marshal(structuredDoc)
if err != nil {
return nil, fmt.Errorf(`failed to marshal structured document into bytes: %w`, err)
}
jwe, err := e.jweEncrypter.Encrypt(structuredDocumentBytes)
if err != nil {
return nil, fmt.Errorf(`failed to encrypt structured document bytes: %w`, err)
}
serializedJWE, err := jwe.FullSerialize(json.Marshal)
if err != nil {
return nil, fmt.Errorf(`failed to serialize JWE: %w`, err)
}
indexedAttributeCollections := e.convertToIndexedAttributeCollection(formattedTags)
encryptedDoc := encryptedDocument{
ID: documentID,
IndexedAttributeCollections: indexedAttributeCollections,
JWE: []byte(serializedJWE),
}
encryptedDocumentBytes, err := json.Marshal(encryptedDoc)
if err != nil {
return nil, fmt.Errorf("failed to marshal encrypted document into bytes: %w", err)
}
formattedValue = encryptedDocumentBytes
}
return formattedValue, nil
}
func createStructuredDocument(key string, value []byte, tags []spi.Tag) structuredDocument {
structuredDocumentContent := content{
UnformattedKey: key,
UnformattedValue: value,
}
if len(tags) != 0 {
structuredDocumentContent.UnformattedTags = tags
}
// In the spec, Structured Documents have IDs, but they don't really seem to serve
// any purpose - at least not for us.
// We will omit it for now. https://github.com/decentralized-identity/confidential-storage/issues/163
return structuredDocument{
Content: structuredDocumentContent,
}
}
func (e *EncryptedFormatter) convertToIndexedAttributeCollection(
formattedTags []spi.Tag) []indexedAttributeCollection {
indexedAttributes := make([]indexedAttribute, len(formattedTags))
for i, formattedTag := range formattedTags {
indexedAttributes[i] = indexedAttribute{
Name: formattedTag.Name,
Value: formattedTag.Value,
}
}
indexedAttrCollection := indexedAttributeCollection{
HMAC: idTypePair{},
IndexedAttributes: indexedAttributes,
}
return []indexedAttributeCollection{indexedAttrCollection}
}