forked from libapps/libapps-mirror
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathlib_credential_cache.js
More file actions
171 lines (159 loc) · 5.01 KB
/
lib_credential_cache.js
File metadata and controls
171 lines (159 loc) · 5.01 KB
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
// Copyright 2018 The ChromiumOS Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
/**
* @fileoverview A general-purpose cache for user credentials in the form of
* a Uint8Array.
*/
import {lib} from '../../libdot/index.js';
/**
* A general-purpose cache for user credentials in the form of a Uint8Array.
*
* The data in the cache is encrypted at rest using a random Web Crypto AES-CBC
* key.
*
* Security considerations:
*
* - The cache is only used by the current Secure Shell window and
* cleared when the screen locks.
* - Data in the cache is encrypted using a random Web Crypto AES-CBC
* key configured to be non-extractable and a randomly generated IV for
* every encryption operation.
* - Since the Web Crypto spec does not require non-extractable keys to be
* kept in a secure hardware element or protected memory, a leak of
* memory contents should realistically be considered equivalent to a
* full compromise.
* - Malicious servers can abuse the caching behavior for e.g. user passwords
* and smart card PINs if the user has agent forwarding enabled.
*
* @constructor
*/
export function CredentialCache() {
/**
* The underlying cache. Keys are strings and the cache entries consist of the
* encrypted data and the initialization vector (IV) used for the AES
* encryption in CBC mode.
*
* @private {?Object<string, {
* encryptedData: !ArrayBuffer,
* iv: !ArrayBufferView,
* }>}
*/
this.cache_ = null;
/**
* The Web Crypto API AES-CBC CryptoKey that has been used to encrypt the data
* in the cache. The key is randomly generated during initialization or after
* the cache has been cleared.
*
* @private {?webCrypto.CryptoKey}
*/
this.cryptoKey_ = null;
/**
* Set to true if caching is enabled; false if caching is disabled and null if
* the user has not yet made a decision.
*
* @private {?boolean}
*/
this.enabled_ = null;
// Clear the cache on screen lock.
if (globalThis.chrome?.idle) {
chrome.idle.onStateChanged.addListener((state) => {
if (state === 'locked') {
this.clear_();
}
});
}
}
/**
* Initialize the cache and generate a new, non-extractable encryption key.
*
* @return {!Promise.<void>}
* @private
*/
CredentialCache.prototype.init_ = async function() {
this.cache_ = {};
this.cryptoKey_ = /** @type {!webCrypto.CryptoKey} */ (
await globalThis.crypto.subtle.generateKey(
{name: 'AES-CBC', length: 128}, false, ['encrypt', 'decrypt']));
};
/**
* Clear the cache and delete the reference to the encryption key.
*
* @private
*/
CredentialCache.prototype.clear_ = function() {
this.cache_ = null;
this.cryptoKey_ = null;
this.enabled_ = null;
};
/**
* Retrieve the cached data for the given key string.
*
* Note: The data bytes in the returned Uint8Array should be overwritten after
* use.
*
* Note: In order to ensure consistency, upon successful retrieval the cached
* data is deleted and should be added again after its validity has been
* verified.
*
* @param {string} key The key to which the corresponding data should be
* looked up.
* @return {?Promise<?Uint8Array>} The data bytes if the key is present in the
* cache; null otherwise.
*/
CredentialCache.prototype.retrieve = async function(key) {
if (!this.cache_) {
await this.init_();
}
if (key in this.cache_) {
const {encryptedData, iv} = this.cache_[key];
// Remove cache entry to be added again only if data verification succeeds.
delete this.cache_[key];
return new Uint8Array(await globalThis.crypto.subtle.decrypt(
{name: 'AES-CBC', iv}, lib.notNull(this.cryptoKey_), encryptedData));
}
return null;
};
/**
* Store the data in the cache under the given key.
*
* Note: The provided data array is overwritten with zeroes after the data has
* been added to the cache.
*
* @param {string} key The key under which the data should be stored in the
* cache.
* @param {!Uint8Array} data The data bytes to be stored.
* @return {?Promise<void>}
*/
CredentialCache.prototype.store = async function(key, data) {
if (!this.cache_) {
await this.init_();
}
// AES-CBC requires a new, cryptographically random IV for every operation.
const iv = globalThis.crypto.getRandomValues(new Uint8Array(16));
const encryptedData = await globalThis.crypto.subtle.encrypt(
{name: 'AES-CBC', iv}, lib.notNull(this.cryptoKey_), data.buffer);
data.fill(0);
this.cache_[key] = {encryptedData, iv};
};
/**
* Check whether caching is enabled.
*
* @return {?boolean} True if the user enabled caching; false if the user
* disabled caching; null if the user has not made a decision yet.
*/
CredentialCache.prototype.isEnabled = function() {
return this.enabled_;
};
/**
* Enable or disable caching.
*
* Note: Caching can only be enabled or disabled once.
*
* @param {boolean} enable
*/
CredentialCache.prototype.setEnabled = function(enable) {
if (this.enabled_ === null) {
this.enabled_ = enable;
}
};