forked from aws/aws-encryption-sdk-javascript
-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathget_local_cryptographic_materials_cache.ts
141 lines (130 loc) · 5.23 KB
/
get_local_cryptographic_materials_cache.ts
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
// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
import LRU from 'lru-cache'
import {
EncryptionMaterial, // eslint-disable-line no-unused-vars
DecryptionMaterial, // eslint-disable-line no-unused-vars
SupportedAlgorithmSuites, // eslint-disable-line no-unused-vars
needs,
isEncryptionMaterial,
isDecryptionMaterial
} from '@aws-crypto/material-management'
import {
CryptographicMaterialsCache, // eslint-disable-line no-unused-vars
Entry, // eslint-disable-line no-unused-vars
EncryptionMaterialEntry, // eslint-disable-line no-unused-vars
DecryptionMaterialEntry // eslint-disable-line no-unused-vars
} from './cryptographic_materials_cache'
export function getLocalCryptographicMaterialsCache<S extends SupportedAlgorithmSuites> (
capacity: number,
proactiveFrequency: number = 1000 * 60
): CryptographicMaterialsCache<S> {
const cache = new LRU<string, Entry<S>>({
max: capacity,
dispose (_key, value) {
/* Zero out the unencrypted dataKey, when the material is removed from the cache. */
value.response.zeroUnencryptedDataKey()
}
})
/* It is not a guarantee that the last item in the LRU will be the Oldest Item.
* But such degenerative cases are not my concern.
* The LRU will not return things that are too old,
* so all this is just to try and proactively dispose material.
*
* To be clear, as an example say I add 9 items at T=0.
* If the MaxAge is 60 minutes, and at T=59 I add a 10th item.
* Then get each of the other 9 items.
* Now, at T=60, `mayEvictTail` will check the age of the tail
* and not evict it because the item has not aged out.
* If there is no get activity,
* it will take until T=120 before I again begin evicting items.
*/
;(function proactivelyTryAndEvictTail () {
const timeout = setTimeout(() => {
mayEvictTail()
proactivelyTryAndEvictTail()
}, proactiveFrequency)
/* In Node.js the event loop will _only_ exit if there are no outstanding events.
* This means that if I did nothing the event loop would *always* be blocked.
* This is unfortunate and very bad for things like Lambda.
* So, I tell Node.js to not wait for this timer.
* See: https://nodejs.org/api/timers.html#timers_timeout_unref
*/
// @ts-ignore
timeout.unref && timeout.unref()
})()
return {
putEncryptionMaterial (
key: string,
material: EncryptionMaterial<S>,
plaintextLength: number,
maxAge?: number
) {
/* Precondition: putEncryptionMaterial plaintextLength can not be negative. */
needs(plaintextLength >= 0, 'Malformed plaintextLength')
/* Precondition: Only cache EncryptionMaterial. */
needs(isEncryptionMaterial(material), 'Malformed response.')
/* Precondition: Only cache EncryptionMaterial that is cacheSafe. */
needs(material.suite.cacheSafe, 'Can not cache non-cache safe material')
const entry = Object.seal({
response: material,
bytesEncrypted: plaintextLength,
messagesEncrypted: 1,
now: Date.now()
})
cache.set(key, entry, maxAge)
},
putDecryptionMaterial (
key: string,
material: DecryptionMaterial<S>,
maxAge?: number
) {
/* Precondition: Only cache DecryptionMaterial. */
needs(isDecryptionMaterial(material), 'Malformed response.')
/* Precondition: Only cache DecryptionMaterial that is cacheSafe. */
needs(material.suite.cacheSafe, 'Can not cache non-cache safe material')
const entry = Object.seal({
response: material,
bytesEncrypted: 0,
messagesEncrypted: 0,
now: Date.now()
})
cache.set(key, entry, maxAge)
},
getEncryptionMaterial (key: string, plaintextLength: number) {
/* Precondition: plaintextLength can not be negative. */
needs(plaintextLength >= 0, 'Malformed plaintextLength')
const entry = cache.get(key)
/* Check for early return (Postcondition): If this key does not have an EncryptionMaterial, return false. */
if (!entry) return false
/* Postcondition: Only return EncryptionMaterial. */
needs(isEncryptionMaterial(entry.response), 'Malformed response.')
entry.bytesEncrypted += plaintextLength
entry.messagesEncrypted += 1
return <EncryptionMaterialEntry<S>>entry
},
getDecryptionMaterial (key: string) {
const entry = cache.get(key)
/* Check for early return (Postcondition): If this key does not have a DecryptionMaterial, return false. */
if (!entry) return false
/* Postcondition: Only return DecryptionMaterial. */
needs(isDecryptionMaterial(entry.response), 'Malformed response.')
return <DecryptionMaterialEntry<S>>entry
},
del (key: string) {
cache.del(key)
}
}
function mayEvictTail () {
// @ts-ignore
const { tail } = cache.dumpLru()
/* Check for early return (Postcondition) UNTESTED: If there is no tail, then the cache is empty. */
if (!tail) return
/* The underlying Yallist tail Node has a `value`.
* This value is a lru-cache Entry and has a `key`.
*/
const { key } = tail.value
// Peek will evict, but not update the "recently used"-ness of the key.
cache.peek(key)
}
}